LwIP socket移除select()后send()报连接进行中错误咨询
问题背景
维护搭载LwIP协议栈的嵌入式系统时,涉及TCP客户端逻辑的整理后代码如下:
iobSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); connectRC = connect(socket, &serverAddress, sizeof(struct sockaddr)); FD_SET(socket, &fdset); selectRC = select((socket) + 1, NULL, &fdset, NULL, &tv); sendRC = send(iobSocket, dout, strlen(dout), 0);
实际测试发现:保留select()调用时代码可正常运行;移除select()后执行send()会触发错误,errno被置为119,对应错误信息为Connection already in progress(连接已在进行中),该错误码未在send()的官方手册页中记载。待解答的两个核心问题为:
- 此处
select()调用的必要性是什么 - 移除
select()后出现该未文档化错误的具体原因是什么
具体解答
首先明确前提:从现象判断,工程中创建的socket为非阻塞模式,这是嵌入式LwIP工程中非常常见的配置,避免socket操作阻塞整个应用线程。
1. select()调用的必要性
阻塞模式下调用connect(),函数会一直阻塞到TCP三次握手完成、连接进入ESTABLISHED状态才会返回,后续可以直接调用send()发送数据。
但非阻塞模式下的connect()行为完全不同:调用后会立刻返回,不会等待三次握手完成,此时三次握手流程在协议栈后台异步执行。此时socket还没进入可发送状态,直接发数据必然失败。
代码中的select()监听socket的可写事件,核心作用就是等待非阻塞connect完成:当三次握手成功、socket具备发送条件时,select()会返回可写事件,此时再调用send()才能正常把数据提交到协议栈发送缓冲区。这是非阻塞TCP客户端连接流程的标准必要步骤,不是冗余代码。
注:贴出的代码存在一处变量名笔误,connect()和select()中传入的socket变量名是socket,而socket()返回值存到了iobSocket,后续send()也用的iobSocket,这是代码整理时的疏漏,不影响问题逻辑判断。
2. 移除select()后触发119错误的原因
首先纠正认知:错误码119对应的是LwIP中定义的EINPROGRESS,这个错误本来就是非阻塞connect()的正常返回值,表示连接正在后台进行,并不是send()本身产生的错误。
移除select()后,代码在connect()返回后立刻调用send(),此时三次握手还没完成,socket还处于连接中的状态。LwIP作为轻量级协议栈,没有像完整POSIX系统那样对不同阶段的错误做转换封装:在处理send()请求时,协议栈首先检查socket连接状态,发现连接尚未建立,就直接把connect阶段留存的EINPROGRESS错误码透传给应用层,所以会看到send()返回了一个不在它官方手册里的错误——本质是跳过了等待连接完成的步骤,对处于连接过程中的socket执行了发送操作。
对比完整POSIX系统的实现:大部分桌面/服务器系统的socket层在这种场景下会返回ENOTCONN错误,因此查通用的send()手册找不到119这个错误码,属于LwIP轻量实现下的特殊行为。
额外提醒:非阻塞connect流程中,仅靠select()返回可写不能100%判定连接成功,select()返回后还需要调用getsockopt()读取socket层的错误码,确认连接没有失败,再执行后续发送操作,避免异常场景下的逻辑错误。
内容的提问来源于stack exchange,提问作者mzimmers

