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

35 篇文章 1 订阅
6 篇文章 0 订阅
本文分析了Android程序中Java层与JNI层通过Socket通信时出现的'Brokenpipe'异常问题。问题出现在Java层写入数据时间过长及异常处理时的睡眠机制,导致与C层客户端的同步错位。解决方案包括优化数据创建速度,使用缓存,并移除异常后的睡眠代码,以提高响应速度和同步性。
摘要由CSDN通过智能技术生成

场景

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;
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值