当前位置:   article > 正文

Socket通信java.io.IOException: Broken pipe问题分析和解决

ioexception: broken pipe

场景

android程序开发中,Java层与JNI层使用socket进行通信:

java层提供服务

  1. @Override
  2. public void run() {
  3. //.............省略一万行
  4. while(true){
  5. //.............省略一万行
  6. try {
  7. //等待客户端请求
  8. logi(TAG, "run: Ready to accept client socket output...");
  9. OutputStream stream = acceptClientSocketOutput();
  10. if (stream == null) {
  11. logw(TAG, "run: continue for output is null.");
  12. continue;
  13. }
  14. //创建返回写入数据,可能消耗时间比较长
  15. byte[] data = createWriteData();
  16. //执行代理写入数据
  17. boolean res = write(stream, data);
  18. if (!res) {
  19. logw(TAG, "doProxy: Failed to write port data:" + data);
  20. //睡眠1秒,避免快速执行循环
  21. doThreadSleep(1000); // 潜在问题隐患
  22. } else {
  23. logw(TAG, "doProxy: Written port: " + portStr);
  24. }
  25. } catch (Throwable e) {
  26. logi(TAG, "run: proxy failed: " + e);
  27. } finally {
  28. closeClientSocket();
  29. }
  30. }
  31. //.............省略一万行
  32. }
  33. /**
  34. * 向文件输出流写入数据
  35. * <p>默认值写入端口号,子类可以复写此函数写入其他数据
  36. * @param stream
  37. * @param data 数据
  38. * @throws IOException
  39. */
  40. protected boolean write(OutputStream stream, byte[] data) {
  41. if (stream == null) {
  42. logw(TAG, "write: stream is null");
  43. return false;
  44. }
  45. if (data == null) {
  46. logw(TAG, "write: data is null");
  47. return false;
  48. }
  49. try {
  50. logi(TAG, "write data: " + Arrays.toString(data));
  51. stream.write(data);
  52. stream.flush();
  53. return true;
  54. } catch (IOException e) {
  55. logw(TAG, "write failed: " + e);
  56. return false;
  57. }
  58. }

JNI的C语言层为client端:

  1. //client获取代理数据
  2. int get_proxy_data(void){
  3. int sockfd;
  4. struct sockaddr_un servaddr;
  5. socklen_t servaddr_len=sizeof(struct sockaddr_un);
  6. char *path = "com.hulk.sockettest";
  7. sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
  8. socket_make_sockaddr_un(path, &servaddr, &servaddr_len);
  9. logi("get_proxy_data:connect:sockfd:%d\n",sockfd);
  10. int f = make_async_connect(sockfd, (struct sockaddr *) &servaddr, servaddr_len);
  11. if(f==-1){
  12. logi("get_proxy_data: failed connect f:%d, error:%s\n", f, strerror(errno));
  13. close(sockfd);
  14. return -1;
  15. }
  16. char buf[4096];
  17. memset(buf, 0x00, sizeof(buf));
  18. socket_set_timeout(sockfd, 1);
  19. int r=0;
  20. int count=0;
  21. again:
  22. logi("get_proxy_data:read:sockfd:%d\n",sockfd);
  23. r = read(sockfd, buf, sizeof(buf));
  24. if(errno==EAGAIN && r<=0){
  25. //读取数据失败:睡眠10毫秒,重试2
  26. usleep(10*1000);
  27. count++;
  28. logi("get_proxy_data:read:sockfd:%d, try_again_count:%d\n",sockfd, count);
  29. if(count==3){
  30. logi("get_proxy_data: Failed to read proxy port, close sockfd:%d\n", sockfd);
  31. close(sockfd);
  32. return -1;
  33. }
  34. goto again;
  35. }
  36. logi("get_proxy_data: close sockfd:%d\n", sockfd);
  37. close(sockfd);
  38. if(r<=0){
  39. return -1;
  40. }
  41. int res = atoi(buf);
  42. return res;
  43. }

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端的请求,更快写入数据,确保中间不要断档。

代码修改如下

  1. @Override
  2. public void run() {
  3. //.............省略一万行
  4. while(true){
  5. //.............省略一万行
  6. try {
  7. //等待客户端请求
  8. logi(TAG, "run: Ready to accept client socket output...");
  9. OutputStream stream = acceptClientSocketOutput();
  10. if (stream == null) {
  11. logw(TAG, "run: continue for output is null.");
  12. continue;
  13. }
  14. //创建数据:createWriteData()中优先使用缓存数据,确保时间不能超过8毫秒
  15. byte[] data = createWriteData();
  16. //执行代理写入数据
  17. boolean res = write(stream, data);
  18. if (!res) {
  19. logw(TAG, "doProxy: Failed to write port data:" + data);
  20. } else {
  21. logw(TAG, "doProxy: Written port: " + portStr);
  22. }
  23. } catch (Throwable e) {
  24. logi(TAG, "run: proxy failed: " + e);
  25. } finally {
  26. closeClientSocket();
  27. }
  28. }
  29. //.............省略一万行
  30. }
  31. /**
  32. * 向文件输出流写入数据
  33. * <p>默认值写入端口号,子类可以复写此函数写入其他数据
  34. * @param stream
  35. * @param data 数据
  36. * @throws IOException
  37. */
  38. protected boolean write(OutputStream stream, byte[] data) {
  39. if (stream == null) {
  40. logw(TAG, "write: stream is null");
  41. return false;
  42. }
  43. if (data == null) {
  44. logw(TAG, "write: data is null");
  45. return false;
  46. }
  47. try {
  48. logi(TAG, "write data: " + Arrays.toString(data));
  49. stream.write(data);
  50. stream.flush();
  51. return true;
  52. } catch (IOException e) {
  53. logw(TAG, "write failed: " + e);
  54. return false;
  55. }
  56. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/561934
推荐阅读
相关标签
  

闽ICP备14008679号