【整理】Socket编程之非阻塞connect(一)


非阻塞 connect:
html

      在 TCP socket 被设置为非阻塞的状况下调用 connect ,若没有当即返回成功,则会返回 -1 以及 errno =  EINPROGRESS 的 错误,其表示链接操做正在进行中,可是还没有完成,与此同时 TCP 三次握手操做会同时进行。在这以后,咱们能够经过调用 select 来检查这个连接是否创建成功。

非阻塞 connect 的三种用途:
  • 能够在 TCP 三次握手的同时作一些其它的处理。connect 操做须要一个往返时间才能完成,从几个毫秒(局域网)到几百毫秒或几秒(广域网)。在这段时间内咱们可能有一些其余的处理想要同时执行; 
  • 能够用这种技术同时创建多个链接。在 Web 浏览器中很广泛; 
  • 因为咱们使用 select 来等待链接的完成,所以咱们能够给 select 设置一个时间限制,从而缩短 connect 的超时时间。在大多数实现中,connect 的超时时间在 75 秒到几分钟 之间(linux 内核中对 connect 的超时限制是 75 秒)。有时候应用程序想要一个更短的超时时间,使用非阻塞 connect 就是一种方法。 

非阻塞 connect 听起来虽然简单,可是仍然有一些细节问题要处理:
1.即便套接字是非阻塞的,若是链接的服务器在同一台主机上,那么在调用 connect 创建链接时,链接一般会当即创建成功。咱们必须处理这种状况;
2.源自 Berkeley 的实现有两条与 select 和非阻塞 I/O 相关的规则:
    A) 当链接创建成功时,套接口描述符变成 可写 (链接创建时,写缓冲区空闲,因此可写)
    B) 当链接创建出错时,套接口描述符变成 既可读又可写 (因为有未决的错误,从而可读又可写)

注意:当一个套接口出错时,它会被 select 调用标记为既可读又可写。
非阻塞 connect 有这么多好处,可是处理非阻塞 connect 时会遇到不少【可移植性问题】。

处理非阻塞 connect 的步骤:
第一步,建立 socket,返回套接字描述符;
第二步,调用 fcntl 或 ioctlsocket 把套接口描述符设置成非阻塞;
第三步,调用 connect 开始创建链接;
第四步,判断链接是否成功创建:
    A) 若是 connect 返回 0 ,表示链接成功(服务器和客户端在同一台机器上时就有可能发生这种状况);
    B) 调用 select 来断定链接创建的是否成功;
       若是 select 返回 0 ,则表示在 select 的超时时间内未能成功创建链接;咱们须要返回超时错误给用户,同时关闭链接,以防止 TCP 三次握手继续进行下去;
       若是 select 返回大于 0 的值,则说明检测到可读或可写或异常的套接字描述符存在;此时咱们能够经过调用 getsockopt 来检测集合中的套接口上是否存在待处理的错误,若是链接创建是成功的,则经过 getsockopt(sockfd,SOL_SOCKET,SO_ERROR,(char *)&error,&len) 获取的 error 值将是 0 ,若是创建链接时遇到错误,则 error 的值是链接错误所对应的 errno 值,好比 ECONNREFUSED,ETIMEDOUT 等。

=============
      “读取套接口上的错误”是遇到的【第一个可移植性问题】:若是出现问题,getsockopt 源自 Berkeley 的实现是返回 0 ,等待处理的错误在变量 errno 中返回;可是 Solaris 会让 getsockopt 返回 -1 ,errno 置为待处理的错误。咱们对这两种状况都要处理。

      这样,在处理非阻塞 connect 时,在不一样的套接口实现的平台中存在的移植性问题。首先,有可能在调用 select 以前,链接就已经创建成功,并且对方的数据已经到来。在这种状况下,链接成功时套接口将既可读又可写,这和链接失败时是同样的。这个时候咱们还得经过 getsockopt 来读取错误值。这是【第二个可移植性问题】。
=============

移植性问题总结
  1. 对于出错的套接口描述符,getsockopt 的返回值源自 Berkeley 的实现是返回 0 ,待处理的错误值存储在 errno 中;而源自 Solaris 的实现是返回 -1 ,待处理的错误存储在 errno 中。(套接口描述符出错时调用 getsockopt 的返回值不可移植) 
  2. 有可能在调用 select 以前,链接就已经创建成功,并且对方的数据已经到来,在这种状况下,套接口描述符是既可读又可写,这与套接口描述符出错时是同样的。(怎样判断链接是否创建成功的条件不可移植) 

这样的话,在咱们判断链接是否创建成功的条件不惟一时,咱们能够有如下的方法来解决这个问题:
  1. 调用获取对端 socket 地址的 getpeername 代替 getsockopt 。若是调用 getpeername 失败,getpeername 返回 ENOTCONN ,表示链接创建失败,以后咱们必须再以 SO_ERROR 调用 getsockopt 获得套接口描述符上的待处理错误; 
  2. 调用 read ,读取长度为 0 字节的数据。若是链接创建失败,则 read 会返回 -1 ,且相应的 errno 指明了链接失败的缘由;若是链接创建成功,read 应该返回 0 。 
  3. 再调用一次 connect 。它应该失败,若是错误 errno 是 EISCONN ,就表示套接口已经创建,并且第一次链接是成功的;不然,链接就是失败的。 

被中断的 connect
       若是在一个阻塞式套接口上调用 connect ,在 TCP 的三次握手操做完成以前被中断了,好比说被捕获的信号中断,将会发生什么呢?假定 connect 不会自动重启,它将返回 EINTR 。那么这个时候,咱们就不能再调用 connect 等待链接创建完成了,若是再次调用 connect 来等待链接创建完成的话,connect 将会返回错误值 EADDRINUSE 。在这种状况下,应该作的是调用 select ,就像在非阻塞式 connect 中所作的同样。而后 select 在链接创建成功(使套接口描述符可写)或链接创建失败(使套接口描述符既可读又可写)时返回。

===================


非阻塞socket调用connect, epoll和select检查链接状况示例 linux

相关文章
相关标签/搜索