Android开发中阻塞队列take()调用后Socket无法发送数据问题
编辑说明:我已找到问题原因(见下方回答)
我正在开发一款Android应用,计划通过TCP协议向运行中的termux实例发送数据。在Termux环境中,执行命令nc -l localhost 8080启动netcat监听入站TCP连接,配置其将收到的数据打印输出到stdout。
Android应用内实现了一个独立工作线程,负责从阻塞队列读取字符串数据,写入到已连接netcat监听地址的Socket中,相关实现代码如下:
runnable = () -> { Socket socket = null; OutputStream socketOutStream = null; while (running) { try { if (null==socket) { socket = new Socket(); socket.setTcpNoDelay(true); socket.setKeepAlive(true); socket.connect(new InetSocketAddress("127.0.0.1", 8080), 2000); socketOutStream = socket.getOutputStream(); socketOutStream.write("Hello, Server!".getBytes()); // [1] 发送成功 socketOutStream.flush(); } String message = queue.take(); socketOutStream.write(message.getBytes()); // [2] 发送失败 socketOutStream.flush(); socketOutStream.close(); Log.i(TAG, "We wrote 'Button Clicked!' to the socket I think."); // running=false; } catch (IOException e) { Log.e(TAG, e.toString()); socket = null; } catch (InterruptedException e) { Log.e(TAG, e.toString()); Log.i(TAG, "Exiting socket sending loop."); running = false; } } }; Thread thread = new Thread(runnable); thread.start();
排查过程中发现两个现象:
- 标记
[1]处发送的初始数据可被Termux端的netcat正常接收并展示,但标记[2]处发送的后续数据始终无法被接收,且从阻塞队列取出的待发消息均非空。 - 若将Socket实例化、连接的逻辑移动到
queue.take()返回后执行,adb日志会输出SocketTimeoutException异常。
核心疑问:Socket连接相关操作放在queue.take()执行前或后,为什么会产生运行差异,导致数据发送失败的根因是什么。
直接原因
你在发送完[2]位置的消息后,直接执行了socketOutStream.close(),这个操作会直接关停对应的TCP连接。而默认参数启动的netcat逻辑是:只要对端断开连接,自己就立刻打印完已收到的数据、退出进程,不再监听端口。
为什么[1]的消息能正常接收
[1]的消息发完之后你没有关闭流,TCP连接还保持存活状态,netcat进程正常运行,自然能收到这段数据。发完[1]之后代码阻塞在queue.take()等待队列里的新消息,这段时间连接处于空闲状态,netcat不会主动断开连接。
等你往队列里塞入消息,take()返回,代码执行[2]位置的写入、flush——这一步数据其实已经写入到系统TCP发送缓冲区了,但你紧接着调用close():
- close()会触发TCP四次挥手,向netcat发送FIN包通知连接断开
- netcat收到FIN包后直接退出进程,8080端口的监听随即停止
- 下一轮循环判断socket为null,尝试重连8080端口,但此时端口已经没有进程监听,自然连接失败。
连接逻辑挪到take()之后报超时的原因
默认参数下nc -l只会处理第一个成功建立的TCP连接,第一个连接断开后netcat就会直接退出,不会等待接收后续连接。
你把建连逻辑放到take()之后,线程启动后会先阻塞在take()等待消息,如果之前调试已经触发过连接断开、导致netcat退出,等你拿到消息再去连接8080的时候,端口根本没有进程在监听,2秒连接超时时间到了自然抛出SocketTimeoutException。
修复方案
- 如果打算用长连接批量发送多条消息,不要发完单条消息就关闭流和Socket,等整个发送逻辑彻底不再需要连接时再释放资源。
- 如果业务逻辑就是每次发消息都新建短连接,那Termux端启动nc的时候要加
-k参数,让它在连接断开后也保持端口监听:nc -l -k localhost 8080。 - 补全异常分支的资源关闭逻辑,避免异常场景下Socket和流没有被正确释放,造成资源泄漏。
内容的提问来源于stack exchange,提问作者Dion Bridger

