87-非阻塞 connect

来源:互联网 发布:白银理财投资实时数据 编辑:程序博客网 时间:2024/05/18 02:45

非阻塞i/o 上调用 connect 比非阻塞 i/o 上调用 read/write 要麻烦一点,一方面 connect 函数不能像 read/write 那样反复调用,它只能调用一次;另一方面,connect 函数返回错误,并不代表连接建立不成功。

1. 非阻塞 connect

对于 TCP 协议,在非阻塞 i/o 上调用 connect,意味着 connect 会发送 SYN 段给服务器:

  • 如果在 connect 返回时,收到了服务器的 ACK,则 connect 返回 0,意味着连接建立成功,这通常只会发生在本机连接上。
  • 如果在 connect 返回时,未收到服务器的 ACK,则 connect 返回 -1,同时 errno 置为 EINPROGRESS,这个错误表示“正在进行……”.
  • 如果 connect 返回错误,errno 不是 EINPROGRESS 可以立即判断连接建立失败。

1.1 如何判断连接成功或失败?

对于 TCP 连接:

  • 连接建立成功:套接字描述符可写
  • 连接建立失败:套接字描述符可读可写

根据上面两条规则,我们可以使用 select 来处理这两种情况。事先建立读写集合,然后使用 select 监听。

但是,反过来根据套接字描述符可读可写来判断连接成功是不可行的。换句话说,下面这样做是不对的

  • 如果可写不可读:连接建立成功(可以这样判断)
  • 如果可写可读:连接建立失败(不可以这样判断)

原因很简单,可写可读,并不一定就是失败,也许是对端发来数据了呢?所以,得使用另外一种办法来判断——使用套接字选项 SO_ERROR. 这也是 SO_ERROR 极少能派上用场的地方之一。

只要套接字描述符变得可读或可写,直接使用 getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) 取得套接字的状态。

注意:不同版本实现 getsockopt 行为有差异。如果真的产生错误:

  • Berkeley 实现让 getsockopt 返回 0,同时 error 中保存错误码(linux 也是这样的)
  • Solaris 实现让 getsockopt 返回 -1,同时 errno 中保存错误码(不是 error 参数)

1.2 伪代码

int nbioConnect(int sockfd, sockaddr *addr, socklent_t addrlen, int nsec) {  int ret, error;  socklent_t len;  fd_set rfds, wfds;  setNonblock(sockfd, 1); // 设置为非阻塞  error = 0;  // 发起连接  ret = connect(sockfd, addr, addrlen);  if (ret < 0) {    if (errno != EINPROGRESS) {      // 立即返回错误。      close(sockfd);      return -1;    }  }  else if (ret == 0) {    // 这种一般出现在本机连接上    setNonblock(sockfd, 0); // 重新设置为阻塞    return 0;  }  rfds = {sockfd};  wfds = {sockfd};  tv.tv_sec = nsec;  tv.tv_usec = 0;  // 在连接建立成功或失败前,或者超时时间未到,select 是不会返回的。  ret = select(sockfd + 1, &rfds, &wfds, NULL, nsec ? &tv : NULL);  if (ret < 0) {    // 小概率事件    close(sockfd);    return -1;  }  else if (ret == 0) {    // 超时    errno == ETIMEDOUT;    close(sockfd);    return -1;  }  // 如果执行到这里了,说明连接已经建立成功或者失败,也就是说套接字描述符一定是可写或可读的或两者兼有。  if (FD_ISSET(sockfd, &rfds) || FD_ISSET(sockfd, &wfds)) {    // 这个 if 判断显的多余    len = sizeof(error);    ret = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);    if (ret < 0) {      // Solaris 版本可能会发生这种情况      close(sockfd);      return -1;    }  }  if (error) {    // Linux 版本如果错误会执行到这里    errno = error;    close(sockfd);    return -1;  }  setNonblock(sockfd, 0);   return 0;}

2. 实验

这一次,只是将上篇写的时间获取客户端中的 connect 函数修改为了 nbioConnect 函数,并添加了一个超时参数进行控制。

  • 程序路径

本文使用的程序托管在 gitos 上:http://git.oschina.net/ivan_allen/unp

如果你已经 clone 过这个代码了,请使用 git pull 更新一下。本节程序所使用的程序路径是 unp/program/nonblockio/nbiotimecli.

  • 实验结果


这里写图片描述
图1 设置超时时间为 10 s,结果超时


这里写图片描述
图2 连接成功

3. 总结

  • 掌握非阻塞 i/o 上调用 connect 函数
  • 知道如何判断非阻塞 connect 函数调用成功还是失败
0 0
原创粉丝点击