我正在 Android 中制作一个 VPN 应用程序,以下似乎效果很好:
public class MyVpnService extends VpnService {
private ParcelFileDescriptor lt;
public int onStartCommand(Intent intent,
int flags,
int startId) {
VpnService.Builder builder = new VpnService.Builder();
builder=builder.addAddress("192.168.2.2", 0)
.addDnsServer("192.168.2.1")
.setBlocking(true);
lt = builder.establish();
if(lt==null) {
Log.i(logTag,"We are not prepared");
return START_STICKY;
}
new Thread() {
@Override
public void run() {
FileInputStream in = new FileInputStream(lt.getFileDescriptor());
byte[] buffer = new byte[2000];
for (;;) {
int len;
try {
len = in.read(buffer);
} catch (IOException e) {
Log.i(logTag, "Got exception " + e);
break;
}
if (len <= 0) {
Log.i(logTag, "No more packets; exits");
break;
}
Log.i(logTag, "Got a packet with length " + len);
}
try {
lt.close();
} catch(IOException e) {
Log.i(logTag,"Exception when closing fd - likely it was closed already "+e);
}
}
}.start();
return START_STICKY;
}
// ... other methods omitted...
}
现在,我想用本机代码进行 VPN 处理。所以我试图用这样的东西替换线程:
new Thread() {
@Override
public void run() {
int fd = lt.getFd();
Jni.doVPN(fd);
try {
lt.close();
} catch(IOException e) {
Log.i(logTag,"Exception when closing fd - likely it was closed already "+e);
}
}
}.start();
JNI 代码如下所示:
#include "unistd.h"
#include <android/log.h>
JNIEXPORT void JNICALL Java_com_example_Jni_doVPN(JNIEnv *env, jclass cls, jint fd) {
char buf[2000];
for(;;) {
int n=read(fd,&buf,sizeof(buf));
__android_log_print(ANDROID_LOG_VERBOSE, "foo", "Got packet with length %i",n);
if(n<=0) {
break;
}
}
}
这似乎也有效。
但是:如果我从 Java 中关闭文件描述符,例如:
lt.close()
然后在纯 Java 代码中,读取调用立即抛出一个看起来合理的 InterruptedIOException。
但在本机代码中,read 调用通常似乎需要很长时间才能报告错误——它只是一直阻塞。此外,如果我通过单击 Android UI 关闭 VPN 并要求 Android 忘记 VPN(这会触发对 VpnService.OnRevoke 的调用),那么本机读取调用似乎会永远阻塞。我怀疑 read 调用会阻塞,直到返回与错误不同的东西,然后再返回错误。这将解释这两个观察结果。
关于如何解决这个问题或发生了什么的任何想法?我真的不希望从 Java 代码中读取文件描述符。