场景
android程序开发中,Java层与JNI层使用socket进行通信:
java层提供服务
@Override
public void run() {
//.............省略一万行
while(true){
//.............省略一万行
try {
//等待客户端请求
logi(TAG, "run: Ready to accept client socket output...");
OutputStream stream = acceptClientSocketOutput();
if (stream == null) {
logw(TAG, "run: continue for output is null.");
continue;
}
//创建返回写入数据,可能消耗时间比较长
byte[] data = createWriteData();
//执行代理写入数据
boolean res = write(stream, data);
if (!res) {
logw(TAG, "doProxy: Failed to write port data:" + data);
//睡眠1秒,避免快速执行循环
doThreadSleep(1000); // 潜在问题隐患
} else {
logw(TAG, "doProxy: Written port: " + portStr);
}
} catch (Throwable e) {
logi(TAG, "run: proxy failed: " + e);
} finally {
closeClientSocket();
}
}
//.............省略一万行
}
/**
* 向文件输出流写入数据
* <p>默认值写入端口号,子类可以复写此函数写入其他数据
* @param stream
* @param data 数据
* @throws IOException
*/
protected boolean write(OutputStream stream, byte[] data) {
if (stream == null) {
logw(TAG, "write: stream is null");
return false;
}
if (data == null) {
logw(TAG, "write: data is null");
return false;
}
try {
logi(TAG, "write data: " + Arrays.toString(data));
stream.write(data);
stream.flush();
return true;
} catch (IOException e) {
logw(TAG, "write failed: " + e);
return false;
}
}
JNI的C语言层为client端:
//client获取代理数据
int get_proxy_data(void){
int sockfd;
struct sockaddr_un servaddr;
socklen_t servaddr_len=sizeof(struct sockaddr_un);
char *path = "com.hulk.sockettest";
sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
socket_make_sockaddr_un(path, &servaddr, &servaddr_len);
logi("get_proxy_data:connect:sockfd:%d\n",sockfd);
int f = make_async_connect(sockfd, (struct sockaddr *) &servaddr, servaddr_len);
if(f==-1){
logi("get_proxy_data: failed connect f:%d, error:%s\n", f, strerror(errno));
close(sockfd);
return -1;
}
char buf[4096];
memset(buf, 0x00, sizeof(buf));
socket_set_timeout(sockfd, 1);
int r=0;
int count=0;
again:
logi("get_proxy_data:read:sockfd:%d\n",sockfd);
r = read(sockfd, buf, sizeof(buf));
if(errno==EAGAIN && r<=0){
//读取数据失败:睡眠10毫秒,重试2次
usleep(10*1000);
count++;
logi("get_proxy_data:read:sockfd:%d, try_again_count:%d\n",sockfd, count);
if(count==3){
logi("get_proxy_data: Failed to read proxy port, close sockfd:%d\n", sockfd);
close(sockfd);
return -1;
}
goto again;
}
logi("get_proxy_data: close sockfd:%d\n", sockfd);
close(sockfd);
if(r<=0){
return -1;
}
int res = atoi(buf);
return res;
}
1. 问题现象
android跨进程使用socket通信过程中某些设备出现: java.io.IOException: Broken pipe, 只在少数设备上出现该问题,一旦出现就必须杀掉进程才能恢复。
socket通信的错误异常:
05-13 10:17:26.348 31837 32158 I ProxyThread:write data: [48, 0, 49 ...... 59, 0]
05-13 10:17:26.349 31837 32158 W ProxyThread:write failed: java.io.IOException: Broken pipe
2. 原因分析
按照常理: Java层出现 java.io.IOException: Broken pipe, 直接原因就是对方client端的socket已经关闭(close),Server端不知道被关闭了,还在继续往已经被关闭socket fd的output中写数据;
socket常识:
对方socket已经close后,存在一下两种场景: 读 和 写
此时第一次进行读/写会返回"RST"信号,抛出异常:java.net.SocketException: (Connection reset或者 Connect reset by peer:Socket write error)
再一次write:再次写入就抛出“ java.io.IOException: Broken pipe”。具体可以参考如下描述
Connection reset by peer的常见原因及解决办法 - 云+社区 - 腾讯云
3. 逻辑原因分析
知道了Broken pipe的原因,在对上面的代码进行逻辑分析,找出“ java.io.IOException: Broken pipe”的原因
1. Client端(C层)的read数据失偶尔败是可预料的,所以写了重试机制,最多读3次,每次睡眠时间为10毫秒。 Java层代理时间最长不能超过30毫秒,否则,C层就关闭了socket fd,此时Java层好网里面写入数据,就会出现“Broken pipe”;
2. Server端(Java层)创建写入数据的时间可能比较长(超过30毫秒),且在一次失败后睡眠了1秒钟,彩灯带下一个循环的对方socket请求,此时对方C层的请求socket早就被close,继续写入数据一定是“Broken pipe”,因为两边的读和写不同步,始终错位1秒钟,导致代理一直失败,杀掉进程才能恢复。
4. 解决问题
综上所述,原因定位在两方面:
1. 创建写入数据的时间较长,必须使用缓存机制,一定要确保每次创建数据的水煎在10毫秒以内,最好是8毫秒以内。这个问题可以使用缓存解决,或者使用异步线程去完成,避免排队堵塞。
2。 去掉每次代理异常之后的睡眠代码,不进行睡眠,马上进入下一次循环,及时响应Client端的请求,更快写入数据,确保中间不要断档。
代码修改如下
@Override
public void run() {
//.............省略一万行
while(true){
//.............省略一万行
try {
//等待客户端请求
logi(TAG, "run: Ready to accept client socket output...");
OutputStream stream = acceptClientSocketOutput();
if (stream == null) {
logw(TAG, "run: continue for output is null.");
continue;
}
//创建数据:createWriteData()中优先使用缓存数据,确保时间不能超过8毫秒
byte[] data = createWriteData();
//执行代理写入数据
boolean res = write(stream, data);
if (!res) {
logw(TAG, "doProxy: Failed to write port data:" + data);
} else {
logw(TAG, "doProxy: Written port: " + portStr);
}
} catch (Throwable e) {
logi(TAG, "run: proxy failed: " + e);
} finally {
closeClientSocket();
}
}
//.............省略一万行
}
/**
* 向文件输出流写入数据
* <p>默认值写入端口号,子类可以复写此函数写入其他数据
* @param stream
* @param data 数据
* @throws IOException
*/
protected boolean write(OutputStream stream, byte[] data) {
if (stream == null) {
logw(TAG, "write: stream is null");
return false;
}
if (data == null) {
logw(TAG, "write: data is null");
return false;
}
try {
logi(TAG, "write data: " + Arrays.toString(data));
stream.write(data);
stream.flush();
return true;
} catch (IOException e) {
logw(TAG, "write failed: " + e);
return false;
}
}