最近在搞点网络编程的小玩意儿,具体来说就是写个客户端去连服务器,碰到了个挺有意思的情况,代码跑起来有时候就连不上,返回个错误,但有时候又莫名其妙好了。这给我整得有点懵。
遇到问题
就是那个connect
函数,按理说成功返回0,失败返回-1。我这边,经常拿到-1,第一反应就是,完了,连接失败。要么是服务器没开,要么是网络不通,要么就是我地址端口写错了。
但我查来查去,服务器好好的,网络也没问题,地址端口更是核对了好几遍,没毛病。这就奇怪了。
排查过程
没办法,只能看看到底是啥错误。我就去检查那个errno
,就是那个全局变量,专门存错误码的。一看,错误码是115,或者说,错误名是EINPROGRESS。
EINPROGRESS?这是啥意思?“正在进行中”?连接操作还在进行中,没立刻完成?我当时就纳闷了,连接要么成功要么失败,咋还有个中间状态?
我又去翻了翻资料,结合我代码的写法,发现问题出在哪了。我用的是非阻塞的socket。对于非阻塞的socket来说,调用connect
时,它不会傻等连接建立成功,而是马上返回。如果连接不能立刻建立(这很常见,网络传输需要时间嘛),它就会返回-1,同时把errno
设置成EINPROGRESS,告诉你:“哥们儿,我正在努力连接,还没搞定,你等会儿再来看看情况。”
更有意思的是,我去看服务器那边的日志,发现有时候我客户端报这个错的时候,服务器那边居然显示连接已经建立成功了(就是那个ESTABLISHED状态)。这说明连接过程确实已经启动,只是客户端这边还没得到最终确认。
怎么解决的
搞明白了原因,解决起来就好办了。既然它告诉我“正在进行中”,那我不能把它当成失败处理。
我试了几种法子:
- 简单粗暴重试? 有时候再调一次
connect
好像能行,但感觉不靠谱,而且第二次可能还是EINPROGRESS,或者遇到其他问题。 - 改成阻塞模式? 这倒是能解决EINPROGRESS问题,
connect
会一直等到连接成功或失败才返回。但我的程序设计就是需要非阻塞,不能因为这个就改回去,不然整个程序逻辑都得变。 - 正确处理非阻塞逻辑: 这才是正道。既然
connect
发起了连接,返回了EINPROGRESS,那我接下来就得用别的方法来检查这个连接是不是真的完成了。
我的做法是,用了select
(或者poll
/epoll
也行,看你用哪个方便)来监听这个socket描述符。当select
告诉我这个socket变得“可写”的时候,就表示连接建立成功了。还得再用getsockopt
检查一下socket上有没有错误(比如SO_ERROR选项),确保万无一失。如果select
超时了还没可写,或者getsockopt
检查到了错误,那才是真的连接失败。
总结
这个EINPROGRESS不是啥洪水猛兽,它就是非阻塞socket在connect
时的一个正常状态反馈。告诉你后台正在忙活,需要你后续再确认一下结果。
这回折腾让我明白了,用非阻塞模式就得有处理这种“中间状态”的准备。不能简单地把返回-1都当成连接失败。得看errno
,如果是EINPROGRESS,就得有耐心,用select
之类的机制去等最终结果。
这下心里踏实了,程序跑起来也稳定多了。记录一下,免得下次再踩坑。