关于非阻塞connnect的总结

在面试题中,看到有关于阻塞connect和非阻塞connect的区别;

显然,我们可以从阻塞和非阻塞的意思来回答,既然是阻塞,那么执行connect的操作会一直阻塞到连接超时或者连接成功才会返回相应的信息,而非阻塞connect则不管是否连接成功,都会立即返回信息。

但是有几个点需要注意:

  1. 阻塞超时以后应该如何进行接下来的操作;
  2. 在非阻塞连接失败以后,接下来应该如何操作;

先来看第一个问题,个人认为应该继续的进行connect,可以通过设定一个时间范围,超过这个时间范围以后则继续的connect;再看第二个问题,由于conncect是非阻塞的,不会一直等待到成功或者超时的结果,这样我们就无法知道它是否已经成功连接上了,那么我们可以通过select去轮询它,每隔一段时间就去检查服务器套接口,询问服务器的套接口是否存在可读或者可写或者是错误信息,为什么这么做呢?因为如果connect连接上了的时候,select则会监听到存在请求到达的事件,那么它就会返回一个可写的消息,我们就通过这么一个返回值来决定我们是否还要继续connect,但是如果返回0,说明select在指定的事件内并没监听到任何可读或者可写的事件,那么我们则返回超时错误,并且断掉握手连接。

但是,这样的解决方案中会存在几个问题:

  1. 我们之前已经提到过了,如果存在错误的信息,那么就会返回一个可读并且可写的信息,应该如何分辨成功还是错误;

如果存在可写的消息,则继续通过getsocketopt中的返回值判断是否存在错误,接着通过错误参数去判断是什么错误信息,其错误信息可能是连接超时,连接错误等等,根据错误信息决定接下来的操作;如果此时其返回为0,显然这个时候没有错误,再进行接下里的操作;

但是,但是网上上面的操作方案说这么做依然会存在这么一个移植性的问题,说是Berkeley的实现中getsocketopt如果有错误的话返回的是0,但是我上网查了一下,并不是这样的,难道是我查的姿势不对?具体网址是这里。除此之外,还有存在另外一个问题,就是在select之前,已经建立了连接,并且此时对方的数据要发送过来,那么此时再进行select,那么它就会返回同样的可读并且可写的消息,跟错误信息返回的是一样的,那么我们之前定义其已经连接的方法就不再唯一。如果我们想要获取准确唯一的获取当前是否已经连接,可以通过下面的办法:

  1. 先通过getpeername获取远程协议地址,如果之前已经连接了,那么它可以返回,但是如果是报错信息,那么显然返回错误,然后再通过getsockopt获取相应的错误信息;
  2. 通过read读取0字节的数据,如果是存在数据,那么可以读取,但是如果是错误信息,因为之前并没有建立连接,所以它会返回相应的错误信息;
  3. 再次的connect,如果之前已经建立了连接,那么它的返回就是EISCONN,但是如果是错误信息,那么就会返回失败信息。

我们可以通过Redis的代码去了解如何实现非阻塞connect。

通过函数fcntl设置非阻塞或者阻塞

  1. static int redisSetBlocking(redisContext *c, int fd, int blocking) {
  2. int flags;
  3. /* Set the socket nonblocking.
  4. * Note that fcntl(2) for F_GETFL and F_SETFL can't be
  5. * interrupted by a signal. */
  6. if ((flags = fcntl(fd, F_GETFL)) == -1) {
  7. __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
  8. close(fd);
  9. return REDIS_ERR;
  10. }
  11. if (blocking)
  12. flags &= ~O_NONBLOCK;
  13. else
  14. flags |= O_NONBLOCK;
  15. if (fcntl(fd, F_SETFL, flags) == -1) {
  16. __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
  17. close(fd);
  18. return REDIS_ERR;
  19. }
  20. return REDIS_OK;
  21. }

核心函数,有一点不太明白,为什么redis上下文是非阻塞的情况下则不进行任何的操作,而如果是阻塞的,才用IO多路复用poll进行检测??

  1. int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct timeval *timeout) {
  2. int s, rv;
  3. char _port[6]; /* strlen("65535"); */
  4. struct addrinfo hints, *servinfo, *p;
  5. int blocking = (c->flags & REDIS_BLOCK); // 保存redis上下文是否是阻塞
  6. snprintf(_port, 6, "%d", port);
  7. memset(&hints,0,sizeof(hints));
  8. hints.ai_family = AF_INET;
  9. hints.ai_socktype = SOCK_STREAM;
  10. /* Try with IPv6 if no IPv4 address was found. We do it in this order since
  11. * in a Redis client you can't afford to test if you have IPv6 connectivity
  12. * as this would add latency to every connect. Otherwise a more sensible
  13. * route could be: Use IPv6 if both addresses are available and there is IPv6
  14. * connectivity. */
  15. if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
  16. hints.ai_family = AF_INET6;
  17. if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
  18. __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
  19. return REDIS_ERR;
  20. }
  21. }
  22. for (p = servinfo; p != NULL; p = p->ai_next) {
  23. if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
  24. continue;
  25. // 设置成非阻塞
  26. if (redisSetBlocking(c,s,0) != REDIS_OK)
  27. goto error;
  28. // 如果连接发成错误信息,返回-1,成功返回0
  29. if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
  30. if (errno == EHOSTUNREACH) { // 远程地址无法到达
  31. close(s);
  32. continue;
  33. } else if (errno == EINPROGRESS && !blocking) {
  34. /* This is ok. */
  35. // 如果之前套接字是非阻塞,并且返回正在连接则连接建立已经启动,但是还没完成
  36. // 这种情况发生在redis环境是非阻塞的状态下
  37. } else {
  38. // 如果redis上下文是阻塞的,在设置成非阻塞后进行接下来的IO多路复用操作
  39. if (redisContextWaitReady(c,s,timeout) != REDIS_OK)
  40. goto error;
  41. }
  42. }
  43. // 如果之前是阻塞则设置回阻塞
  44. if (blocking && redisSetBlocking(c,s,1) != REDIS_OK)
  45. goto error;
  46. // 设置没有延迟
  47. if (redisSetTcpNoDelay(c,s) != REDIS_OK)
  48. goto error;
  49. c->fd = s;
  50. c->flags |= REDIS_CONNECTED; // 设置状态为已连接
  51. rv = REDIS_OK;
  52. goto end;
  53. }
  54. if (p == NULL) {
  55. char buf[128];
  56. snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno));
  57. __redisSetError(c,REDIS_ERR_OTHER,buf);
  58. goto error;
  59. }
  60. error:
  61. rv = REDIS_ERR;
  62. end:
  63. freeaddrinfo(servinfo);
  64. return rv; // Need to return REDIS_OK if alright
  65. }

使用poll进行检测,和select的作用是一样的,Redis可能考虑到单个进程打开的套接字限制才改用的poll:

  1. static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *timeout) {
  2. struct pollfd wfd[1];
  3. long msec;
  4. msec = -1;
  5. wfd[0].fd = fd;
  6. wfd[0].events = POLLOUT;
  7. /* Only use timeout when not NULL. */
  8. if (timeout != NULL) {
  9. if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
  10. close(fd);
  11. return REDIS_ERR;
  12. }
  13. msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000);
  14. if (msec < 0 || msec > INT_MAX) {
  15. msec = INT_MAX;
  16. }
  17. }
  18. if (errno == EINPROGRESS) {
  19. int res;
  20. if ((res = poll(wfd, 1, msec)) == -1) {
  21. __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
  22. close(fd);
  23. return REDIS_ERR;
  24. } else if (res == 0) {
  25. errno = ETIMEDOUT;
  26. __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
  27. close(fd);
  28. return REDIS_ERR;
  29. }
  30. if (redisCheckSocketError(c, fd) != REDIS_OK)
  31. return REDIS_ERR;
  32. return REDIS_OK;
  33. }
  34. __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
  35. close(fd);
  36. return REDIS_ERR;
  37. }

参考

  1. Socket编程之非阻塞connect
  2. Redis github

关于非阻塞connnect的看法的更多相关文章

  1. Verilog中的阻塞与非阻塞

    这篇文档值得阅读 按说阻塞与非阻塞是Verilog中最基本的东西,也是老生常谈.但是最近看到很多程序里用到阻塞语句竟然不是很明白,说到底是从来没有自己仔细分析过.当然一般情况程序中也是推荐用非阻塞的. ...

  2. 简述linux同步与异步、阻塞与非阻塞概念以及五种IO模型

    1.概念剖析 相信很多从事linux后台开发工作的都接触过同步&异步.阻塞&非阻塞这样的概念,也相信都曾经产生过误解,比如认为同步就是阻塞.异步就是非阻塞,下面我们先剖析下这几个概念分 ...

  3. 非阻塞/异步(epoll) openssl

    前段时间在自己的异步网络框架handy中添加openssl的支持,当时在网络上搜索了半天也没有找到很好的例子,后来自己慢慢的摸索,耗费不少时间,终于搞定.因此把相关的资料整理一下,并给出简单的例子,让 ...

  4. 同步与异步 & 阻塞与非阻塞

    在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式: 一.同步 所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用 ...

  5. 网络IO之阻塞、非阻塞、同步、异步总结

    网络IO之阻塞.非阻塞.同步.异步总结 1.前言 在网络编程中,阻塞.非阻塞.同步.异步经常被提到.unix网络编程第一卷第六章专门讨论五种不同的IO模型,Stevens讲的非常详细,我记得去年看第一 ...

  6. (转)NIO与AIO,同步/异步,阻塞/非阻塞

    原文地址: http://www.cnblogs.com/enjoy-ourselves/p/3793771.html 1.flip(),compact(),与clear()的使用 flip()内部实 ...

  7. Linux下的串口编程及非阻塞模式

    本篇介绍了如何在linux系统下向串口发送数据.包括read的阻塞和非阻塞.以及select方法. 打开串口 在Linux系统下,打开串口是通过使用标准的文件打开函数操作的. #include < ...

  8. python学习笔记-(十四)I/O多路复用 阻塞、非阻塞、同步、异步

    1. 概念说明 1.1 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方).操作系统的核心是内核,独立于普通的应用程序,可 ...

  9. tornado 异步调用系统命令和非阻塞线程池

    项目中异步调用 ping 和 nmap 实现对目标 ip 和所在网关的探测 Subprocess.STREAM 不用担心进程返回数据过大造成的死锁, Subprocess.PIPE 会有这个问题. i ...

随机推荐

  1. PHP编码规范建议学习

    ###php编码规范 -------* sql过长 ```$sql = <<<SQLSELECT delivery_idFROM d_testWHERE delivery_idIN ...

  2. 小tips:用java模拟小球做抛物线运动

    这几天刚刚学习了java线程,然后跟着书做了几个关于线程的练习,其中有一个练习题是小球动起来.这个相信很简单,只要运用线程就轻松能够实现.然后看到了它的一个课后思考题,怎样让小球做个抛物线运动,这点我 ...

  3. Oracle CDC简介及异步在线日志CDC部署示例

    摘要 最近由于工作需要,花时间研究了一下Oracle CDC功能和LogMiner工具,希望能找到一种稳定.高效的技术来实现Oracle增量数据抽取功能.以下是个人的部分学习总结和部署实践. 1. O ...

  4. css因Mime类型不匹配而被忽略,怎么解决

    问题:在火狐.谷歌都可以正常显示出来,在别人的IE浏览器上也可以正常显示出来,但是在自己的ie浏览器就完全不能加载的熬样式了 控制台报告 SEC7113: CSS 因 Mime 类型不匹配而被忽略 答 ...

  5. 小K的H5之旅-HTML5与CSS3部分新属性浅见

    一.HTML部分 1.HTML5新特点 向下兼容.用户至上.化繁为简.无插件范式.访问通用性.引入语义.引入原生媒体支持.引入可编程内容 2.HTML5标签语法 可以省略的元素:空元素语法的元素{br ...

  6. 使用 libdvm.so 内部函数dvm* 加载 dex

    首先要清楚,odex只是对代码段(我将dex文件与elf文件类比,大家都将执行文件分成不同的段)作优化,而其它用于类反射信息的段都应用原来的dex,所以odex文件内部还包含了一个dex. 打开一个d ...

  7. 提升单元测试体验的利器--Mockito使用总结

    为神马要使用Mockito? 在编写单元测试的时候,为了尽可能的保证隔离性,我们时常需要对某些不容易构造或者不容易获取或者对外部环境有依赖的对象,用一个虚拟的对象来创建以便于测试.假设你正在开发的的代 ...

  8. 用js获取页面颜色值怎么比较?

    一般情况下,我们通过十六进制的方式设置页面颜色值 如#64e164 但当我们通过js获取这个dom颜色值的时候,返回的值却可能不是十六进制的,所以比较的时候需要分浏览器进行 在火狐和谷歌浏览器中,返回 ...

  9. spring管理配置文件的工厂类--PropertiesFactoryBean

    使用这个工厂的配置,可以很方便的获取配置文件中的属性.具体使用如下; 对于属性配置,一般采用的是键值对的形式,如: key=value 属性配置文件一般使用的是XXX.properties,当然有时候 ...

  10. openvpn实现内网 映射到 外网

    openvpn实现内网 映射到 外网 场景介绍: 机器介绍 本地一台Ubuntu服务器A , 处于内网中 , 无外网IP 外网一台Ubuntu服务器B , 外网地址139.199.4.205 目标 : ...