5种IO模型分别如下:

1、阻塞IO模型

当上层应用app1调用recv系统调用时,如果对等方没有发送数据(缓冲区没有数据),上层app1将阻塞(默认行为,被linux内核阻塞)。

当对等方发送了数据,linux内核recv端缓冲区有数据后,内核会把数据copy给用户空间。然后上层应用app1解除阻塞,执行下一步操作。

2、非阻塞IO模型

上层应用程序app2将套接字设置成非阻塞模式。

上层应用程序app2轮询调用recv函数,接收数据,若缓冲区没有数据,上层程序app2不会阻塞,recv返回值-1,错误码是EWOULDBLOCK。

上层应用程序不断轮询有没有数据到来,会造成上层应用忙等待,大量的消耗CPU时间,很少直接用,应用范围小,一般和select IO复用配合使用。

阻塞IO和非阻塞IO是两个极端,一个是没有数据就死等,另一个极端是轮询。

有没有这样一种机制去管理n个文件描述符,当文件描述符状态发生变化了,会通知应用程序呢?这就是IO复用技术,也叫作多路复用。

3、IO复用模型

上层应用程序app3调用select机制(该机制由linux内核支持,避免了app3忙等待),进行轮询文件描述符的状态变化。

当select管理的文件描述符没有数据(或者状态没有变化时),上层应用程序app3也会阻塞。

好处是select会管理多个文件描述符。

select可以看成一个管理者,用select来管理多个IO。

一旦检测到一个IO或者多个IO有我们感兴趣的事件发生,select函数将返回,返回值为检测到的事件个数,继而可以利用select相关api函数,具体操作事件。

select函数可以设置等待时间,避免了上层应用程序app3长期僵死。

和阻塞IO模型相比,select IO复用模型相当于提前阻塞了,等到有数据到来时再调用recv就不会发生阻塞。

4、信号驱动模型:

上层应用程序app4建立SIGIO信号处理程序,当缓冲区有数据到来时,内核会发送信号告诉上层应用程序app4。

上层应用程序app4接收到信号后,调用recv函数,因缓冲区有数据,recv函数一般不会阻塞。

这种模型用的也比较少,属于典型的“拉模式”,即上层应用程序app4需要调用recv函数把数据拉进来。

应用程序接收到信号有一个时间延迟,处理这个信号可能还有一个时间延迟,如果这些延迟比较长,内核空间数据有可能发生变化。存在隐患。因此,这种IO模型用的不多。

5、异步IO模型

上层应用app5调用aio_read函数,同时提交一个应用层的缓冲区buf,调用完毕后不会阻塞,上层应用程序app5可以进行其他任务。

当TCP IP协议缓冲区有数据时,linux内核主动把内核缓冲区中的数据copy到用户空间,然后再给上层应用app5发送信号,告诉app5数据有了,赶快处理吧。

这是典型的推模式。

效率最高的一种形式,上层应用app5有异步处理的能力(在linux内核的支持下,言外之意:处理其他任务的同时,也可支持IO通讯)。

上层应用app5也可以在干别的活儿的同时接收数据。与信号驱动IO模型相比,上层应用程序app5不需要调用recv函数。

这种IO模型效率非常高,是高性能、高并发服务器常用的模型。

IO复用模型和异步IO模型是两种常用的模型。

重要概念:

阻塞IO:

  数据没有准备好,读操作就会阻塞。

  数据不能立即被收时,写操作就会阻塞。

  打开文件时阻塞,直到某些条件发生。

非阻塞IO:

  立即返回,并用错误值来表示当前的状态。

指定非阻塞方式:

  打开时指定O_NONBLOCK标志。

  使用fcntl打开或者关闭非阻塞方式

网络编程时,使用非阻塞,用轮询方式发送

使用多线程可以避免使用非阻塞IO,但是同步开销大

多路IO:

  当程序需要同时从两个输入读数据时

  使用多进程、多线程,同步复杂,进程线程开销

  使用非阻塞IO,交替轮询

  通过信号使用异步IO,无法判断哪个IO完成

  多路IO:把关心的IO放入一个列表,调用多路函数

  多路IO函数阻塞,直到有一个数据准备好后返回

  返回后告诉调用者哪个描述符准备好了

IO复用模型分析:

  select实现说明:

    调用select时通过参数告诉内核用户感兴趣的IO描述符

    关心的IO状态:输入、输出或者错误

    调用者等待时间

    返回之后内核告诉调用者多个描述符准备好了

    哪些描述符发生了变化

    调用返回后对准备好的描述符调用读写操作

    不关心的描述符集传NULL

select函数原型如下:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

监视readfs来查看是否read的时候会被阻塞,注意:即使到了end-of-file,fd也是可读的

监视writefds看写的时候会不会阻塞

监视excepted是否出现了异常,主要用来读取OOB数据,异常并不是指出错

注意当一个套接口出错时,它会变得既可读又可写

如果有了状态改变,会将其他fd清零,只有那些发生改变了的fd保持置位,以用来指示set中的哪一个改变了状态

参数n是所有set里的所有fd里面,具有最大值的那个fd的值加1

以下四个宏用来对fd_set进行操作:

void FD_CLR(int fd, fd_set *set); :把一个描述符从集合内移除
int FD_ISSET(int fd, fd_set *set) :测试某个描述符是否在集合内
void FD_SET(int fd, fd_set *set) : 把一个描述符加入集合

void FD_ZERO(fd_set *set) :清空描述符集合

每个描述符在集合中只占一位,如下所示:

下面我们将客户端程序改成select形式,select用来监视标准输入描述符和套接字,服务器端程序还是用普通的多进程模型。只给出客户端程序如下:

  1. #include <sys/types.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <stdio.h>
  5. #include <string.h>
  6. #include <errno.h>
  7. #include <signal.h>
  8. #include <arpa/inet.h>
  9. #include <sys/socket.h>
  10. #include <netinet/in.h>
  11. #include <sys/socket.h>
  12. #include <netinet/ip.h> /* superset of previous */
  13.  
  14. void handler(int num)
  15. {
  16. printf("signal num : %d\n", num);
  17. }
  18.  
  19. ssize_t readn(int fd, void *buf, size_t count)
  20. {
  21. size_t nleft = count;
  22. ssize_t nread;
  23.  
  24. char *bufp = (char*)buf;
  25.  
  26. while(nleft > )
  27. {
  28. if( (nread = read(fd, bufp, nleft)) < )
  29. {
  30. if(errno == EINTR)
  31. {
  32. continue;
  33. }
  34.  
  35. return -;
  36. }
  37. else if(nread == )
  38. {
  39. return count - nleft;
  40. }
  41.  
  42. bufp += nread;
  43. nleft -= nread;
  44. }
  45.  
  46. return count;
  47. }
  48.  
  49. ssize_t writen(int fd, const void *buf, size_t count)
  50. {
  51. size_t nleft = count;
  52. ssize_t nwritten;
  53.  
  54. char *bufp = (char*)buf;
  55.  
  56. while(nleft > )
  57. {
  58. if( (nwritten = write(fd, bufp, nleft)) < )
  59. {
  60. if(errno == EINTR)
  61. {
  62. continue;
  63. }
  64.  
  65. return -;
  66. }
  67. else if(nwritten == )
  68. {
  69. continue;
  70. }
  71.  
  72. bufp += nwritten;
  73. nleft -= nwritten;
  74. }
  75.  
  76. return count;
  77. }
  78.  
  79. ssize_t recv_peek(int sockfd, void *buf, size_t len)
  80. {
  81. while()
  82. {
  83. int ret = recv(sockfd, buf, len, MSG_PEEK);
  84. if(ret == - && errno == EINTR)
  85. continue;
  86. return ret;
  87. }
  88. }
  89.  
  90. ssize_t readline(int sockfd, void *buf, size_t maxline)
  91. {
  92. int ret;
  93. int nread;
  94. char *bufp = (char*)buf;
  95. int nleft = maxline;
  96.  
  97. while()
  98. {
  99. ret = recv_peek(sockfd, bufp, nleft);
  100. if(ret < )
  101. {
  102. return ret;
  103. }
  104. else if(ret == )
  105. {
  106. return ret;
  107. }
  108.  
  109. nread = ret;
  110. int i;
  111. for(i = ; i < nread; i++)
  112. {
  113. if(bufp[i] == '\n')
  114. {
  115. ret = readn(sockfd, bufp, i+);
  116. if(ret != i+)
  117. {
  118. perror("readn error");
  119. exit();
  120. }
  121.  
  122. return ret;
  123. }
  124. }
  125.  
  126. if(nread > nleft)
  127. {
  128. perror("FAILURE");
  129. exit();
  130. }
  131.  
  132. nleft -= nread;
  133. ret = readn(sockfd, bufp, nread);
  134. if(ret != nread)
  135. {
  136. perror("readn error");
  137. exit();
  138. }
  139. bufp += nread;
  140. }
  141.  
  142. return -;
  143. }
  144.  
  145. void echo_cli(int sock)
  146. {
  147. fd_set rset;
  148. FD_ZERO(&rset);
  149.  
  150. int nready;
  151. int maxfd;
  152. int fd_stdin = fileno(stdin);
  153. if(fd_stdin > sock)
  154. maxfd = fd_stdin;
  155. else
  156. maxfd = sock;
  157.  
  158. char sendbuf[] = {};
  159. char recvbuf[] = {};
  160.  
  161. int stdineof = ;
  162.  
  163. while()
  164. {
  165. if (stdineof == )
  166. FD_SET(fd_stdin, &rset);
  167. FD_SET(sock, &rset);
  168.  
  169. nready = select(maxfd + , &rset, NULL, NULL, NULL);
  170.  
  171. if(nready == -)
  172. {
  173. perror("select error");
  174. exit();
  175. }
  176.  
  177. if(nready == )
  178. {
  179. continue;
  180. }
  181.  
  182. if(FD_ISSET(sock, &rset))
  183. {
  184. int ret = readline(sock, recvbuf, sizeof(recvbuf));
  185. if(ret == -)
  186. {
  187. perror("readline error");
  188. exit();
  189. }
  190. else if(ret == )
  191. {
  192. printf("server closed\n");
  193. break;
  194. }
  195.  
  196. fputs(recvbuf, stdout);
  197. memset(recvbuf, , sizeof(recvbuf));
  198. }
  199.  
  200. if(FD_ISSET(fd_stdin, &rset))
  201. {
  202. if(fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
  203. {
  204. printf("ctrl+d result fgets return\n");
  205. stdineof = ;
  206. close(sock);
  207. }
  208. else
  209. {
  210. writen(sock, sendbuf, strlen(sendbuf));
  211. memset(sendbuf, , sizeof(sendbuf));
  212. }
  213. }
  214.  
  215. }
  216. }
  217.  
  218. int main()
  219. {
  220. int sockfd;
  221.  
  222. signal(SIGPIPE, handler);
  223.  
  224. sockfd = socket(AF_INET, SOCK_STREAM, );
  225.  
  226. struct sockaddr_in addr;
  227. addr.sin_family = AF_INET;
  228. addr.sin_port = htons();
  229. inet_aton("192.168.31.128", &addr.sin_addr);
  230. //addr.sin_addr.s_addr = inet_addr("192.168.31.128");
  231.  
  232. if( connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == - )
  233. {
  234. perror("connect error");
  235. exit();
  236. }
  237.  
  238. struct sockaddr_in localaddr;
  239. socklen_t addrlen = sizeof(localaddr);
  240. if(getsockname(sockfd, (struct sockaddr*)&localaddr, &addrlen) < )
  241. {
  242. perror("getsockname error");
  243. exit();
  244. }
  245.  
  246. printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port));
  247.  
  248. echo_cli(sockfd);
  249.  
  250. return ;
  251. }

核心函数为echo_cli,其中调用了select函数,167、168将两个描述符加入到监视器中。fgets发生错误或者读到文件末尾时会返回NULL。读标准输入时ctrl+d代表文件末尾。

启动服务器和客户端,执行结果如下:

connect连接时,会阻塞进程,在公网上默认会等待75秒超时,但是一般应用不想等这么久,因此,需要优化一下。

网络模型中常用的操作:

  1. #ifndef _SCK_UTIL_H_
  2. #define _SCK_UTIL_H_
  3.  
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <fcntl.h>
  7. #include <sys/select.h>
  8. #include <sys/time.h>
  9. #include <sys/socket.h>
  10. #include <netinet/in.h>
  11. #include <arpa/inet.h>
  12. #include <netdb.h>
  13.  
  14. #include <stdio.h>
  15. #include <errno.h>
  16. #include <string.h>
  17. #include <stdlib.h>
  18.  
  19. #define ERR_EXIT(m) \
  20. do \
  21. { \
  22. perror(m); \
  23. exit(EXIT_FAILURE); \
  24. } \
  25. while ()
  26.  
  27. void activate_nonblock(int fd);
  28. void deactivate_nonblock(int fd);
  29.  
  30. int read_timeout(int fd, unsigned int wait_seconds);
  31. int write_timeout(int fd, unsigned int wait_seconds);
  32. int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds);
  33. int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds);
  34.  
  35. ssize_t readn(int fd, void *buf, size_t count);
  36. ssize_t writen(int fd, const void *buf, size_t count);
  37. ssize_t recv_peek(int sockfd, void *buf, size_t len);
  38. ssize_t readline(int sockfd, void *buf, size_t maxline);
  39.  
  40. #endif /* _SYS_UTIL_H_ */
  1. #include "sckutil.h"
  2.  
  3. /* read函数的调用方法
  4. int ret;
  5. ret = read_timeout(fd, 5);
  6. if (ret == 0)
  7. {
  8. read(fd, ...);
  9. }
  10. else if (ret == -1 && errno == ETIMEDOUT)
  11. {
  12. timeout....
  13. }
  14. else
  15. {
  16. ERR_EXIT("read_timeout");
  17. }
  18. */
  19.  
  20. /**
  21. * read_timeout - 读超时检测函数,不含读操作
  22. * @fd: 文件描述符
  23. * @wait_seconds: 等待超时秒数,如果为0表示不检测超时
  24. * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
  25. */
  26. int read_timeout(int fd, unsigned int wait_seconds)
  27. {
  28. int ret = ;
  29. if (wait_seconds > )
  30. {
  31. fd_set read_fdset;
  32. struct timeval timeout;
  33.  
  34. FD_ZERO(&read_fdset);
  35. FD_SET(fd, &read_fdset);
  36.  
  37. timeout.tv_sec = wait_seconds;
  38. timeout.tv_usec = ;
  39.  
  40. //select返回值三态
  41. //1 若timeout时间到(超时),没有检测到读事件 ret返回=0
  42. //2 若ret返回<0 && errno == EINTR 说明select的过程中被别的信号中断(可中断睡眠原理)
  43. //2-1 若返回-1,select出错
  44. //3 若ret返回值>0 表示有read事件发生,返回事件发生的个数
  45.  
  46. do
  47. {
  48. ret = select(fd + , &read_fdset, NULL, NULL, &timeout);
  49. } while (ret < && errno == EINTR);
  50.  
  51. if (ret == )
  52. {
  53. ret = -;
  54. errno = ETIMEDOUT;
  55. }
  56. else if (ret == )
  57. ret = ;
  58. }
  59.  
  60. return ret;
  61. }
  62.  
  63. /**
  64. * write_timeout - 写超时检测函数,不含写操作
  65. * @fd: 文件描述符
  66. * @wait_seconds: 等待超时秒数,如果为0表示不检测超时
  67. * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
  68. */
  69. int write_timeout(int fd, unsigned int wait_seconds)
  70. {
  71. int ret = ;
  72. if (wait_seconds > )
  73. {
  74. fd_set write_fdset;
  75. struct timeval timeout;
  76.  
  77. FD_ZERO(&write_fdset);
  78. FD_SET(fd, &write_fdset);
  79.  
  80. timeout.tv_sec = wait_seconds;
  81. timeout.tv_usec = ;
  82. do
  83. {
  84. ret = select(fd + , NULL, &write_fdset, NULL, &timeout);
  85. } while (ret < && errno == EINTR);
  86.  
  87. if (ret == )
  88. {
  89. ret = -;
  90. errno = ETIMEDOUT;
  91. }
  92. else if (ret == )
  93. ret = ;
  94. }
  95.  
  96. return ret;
  97. }
  98.  
  99. /**
  100. * accept_timeout - 带超时的accept
  101. * @fd: 套接字
  102. * @addr: 输出参数,返回对方地址
  103. * @wait_seconds: 等待超时秒数,如果为0表示正常模式
  104. * 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT
  105. */
  106. int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
  107. {
  108. int ret;
  109. socklen_t addrlen = sizeof(struct sockaddr_in);
  110.  
  111. if (wait_seconds > )
  112. {
  113. fd_set accept_fdset;
  114. struct timeval timeout;
  115. FD_ZERO(&accept_fdset);
  116. FD_SET(fd, &accept_fdset);
  117. timeout.tv_sec = wait_seconds;
  118. timeout.tv_usec = ;
  119. do
  120. {
  121. ret = select(fd + , &accept_fdset, NULL, NULL, &timeout);
  122. } while (ret < && errno == EINTR);
  123. if (ret == -)
  124. return -;
  125. else if (ret == )
  126. {
  127. errno = ETIMEDOUT;
  128. return -;
  129. }
  130. }
  131.  
  132. //一但检测出 有select事件发生,表示对等方完成了三次握手,客户端有新连接建立
  133. //此时再调用accept将不会堵塞
  134. if (addr != NULL)
  135. ret = accept(fd, (struct sockaddr*)addr, &addrlen); //返回已连接套接字
  136. else
  137. ret = accept(fd, NULL, NULL);
  138. if (ret == -)
  139. ERR_EXIT("accept");
  140.  
  141. return ret;
  142. }
  143.  
  144. /**
  145. * activate_noblock - 设置I/O为非阻塞模式
  146. * @fd: 文件描符符
  147. */
  148. void activate_nonblock(int fd)
  149. {
  150. int ret;
  151. int flags = fcntl(fd, F_GETFL);
  152. if (flags == -)
  153. ERR_EXIT("fcntl");
  154.  
  155. flags |= O_NONBLOCK;
  156. ret = fcntl(fd, F_SETFL, flags);
  157. if (ret == -)
  158. ERR_EXIT("fcntl");
  159. }
  160.  
  161. /**
  162. * deactivate_nonblock - 设置I/O为阻塞模式
  163. * @fd: 文件描符符
  164. */
  165. void deactivate_nonblock(int fd)
  166. {
  167. int ret;
  168. int flags = fcntl(fd, F_GETFL);
  169. if (flags == -)
  170. ERR_EXIT("fcntl");
  171.  
  172. flags &= ~O_NONBLOCK;
  173. ret = fcntl(fd, F_SETFL, flags);
  174. if (ret == -)
  175. ERR_EXIT("fcntl");
  176. }
  177.  
  178. /**
  179. * connect_timeout - connect
  180. * @fd: 套接字
  181. * @addr: 要连接的对方地址
  182. * @wait_seconds: 等待超时秒数,如果为0表示正常模式
  183. * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
  184. */
  185. int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
  186. {
  187. int ret;
  188. socklen_t addrlen = sizeof(struct sockaddr_in);
  189.  
  190. if (wait_seconds > )
  191. activate_nonblock(fd);
  192.  
  193. ret = connect(fd, (struct sockaddr*)addr, addrlen);
  194. if (ret < && errno == EINPROGRESS)
  195. {
  196. //printf("11111111111111111111\n");
  197. fd_set connect_fdset;
  198. struct timeval timeout;
  199. FD_ZERO(&connect_fdset);
  200. FD_SET(fd, &connect_fdset);
  201. timeout.tv_sec = wait_seconds;
  202. timeout.tv_usec = ;
  203. do
  204. {
  205. // 一但连接建立,则套接字就可写 所以connect_fdset放在了写集合中
  206. ret = select(fd + , NULL, &connect_fdset, NULL, &timeout);
  207. } while (ret < && errno == EINTR);
  208. if (ret == )
  209. {
  210. ret = -;
  211. errno = ETIMEDOUT;
  212. }
  213. else if (ret < )
  214. return -;
  215. else if (ret == )
  216. {
  217. //printf("22222222222222222\n");
  218. /* ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/
  219. /* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */
  220. int err;
  221. socklen_t socklen = sizeof(err);
  222. int sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);
  223. if (sockoptret == -)
  224. {
  225. return -;
  226. }
  227. if (err == )
  228. {
  229. //printf("3333333333333\n");
  230. ret = ;
  231. }
  232. else
  233. {
  234. //printf("4444444444444444:%d\n", err);
  235. errno = err;
  236. ret = -;
  237. }
  238. }
  239. }
  240. if (wait_seconds > )
  241. {
  242. deactivate_nonblock(fd);
  243. }
  244. return ret;
  245. }
  246.  
  247. /**
  248. * readn - 读取固定字节数
  249. * @fd: 文件描述符
  250. * @buf: 接收缓冲区
  251. * @count: 要读取的字节数
  252. * 成功返回count,失败返回-1,读到EOF返回<count
  253. */
  254. ssize_t readn(int fd, void *buf, size_t count)
  255. {
  256. size_t nleft = count;
  257. ssize_t nread;
  258. char *bufp = (char*)buf;
  259.  
  260. while (nleft > )
  261. {
  262. if ((nread = read(fd, bufp, nleft)) < )
  263. {
  264. if (errno == EINTR)
  265. continue;
  266. return -;
  267. }
  268. else if (nread == )
  269. return count - nleft;
  270.  
  271. bufp += nread;
  272. nleft -= nread;
  273. }
  274.  
  275. return count;
  276. }
  277.  
  278. /**
  279. * writen - 发送固定字节数
  280. * @fd: 文件描述符
  281. * @buf: 发送缓冲区
  282. * @count: 要读取的字节数
  283. * 成功返回count,失败返回-1
  284. */
  285. ssize_t writen(int fd, const void *buf, size_t count)
  286. {
  287. size_t nleft = count;
  288. ssize_t nwritten;
  289. char *bufp = (char*)buf;
  290.  
  291. while (nleft > )
  292. {
  293. if ((nwritten = write(fd, bufp, nleft)) < )
  294. {
  295. if (errno == EINTR)
  296. continue;
  297. return -;
  298. }
  299. else if (nwritten == )
  300. continue;
  301.  
  302. bufp += nwritten;
  303. nleft -= nwritten;
  304. }
  305.  
  306. return count;
  307. }
  308.  
  309. /**
  310. * recv_peek - 仅仅查看套接字缓冲区数据,但不移除数据
  311. * @sockfd: 套接字
  312. * @buf: 接收缓冲区
  313. * @len: 长度
  314. * 成功返回>=0,失败返回-1
  315. */
  316. ssize_t recv_peek(int sockfd, void *buf, size_t len)
  317. {
  318. while ()
  319. {
  320. int ret = recv(sockfd, buf, len, MSG_PEEK);
  321. if (ret == - && errno == EINTR)
  322. continue;
  323. return ret;
  324. }
  325. }
  326.  
  327. /**
  328. * readline - 按行读取数据
  329. * @sockfd: 套接字
  330. * @buf: 接收缓冲区
  331. * @maxline: 每行最大长度
  332. * 成功返回>=0,失败返回-1
  333. */
  334. ssize_t readline(int sockfd, void *buf, size_t maxline)
  335. {
  336. int ret;
  337. int nread;
  338. char *bufp = buf;
  339. int nleft = maxline;
  340. while ()
  341. {
  342. ret = recv_peek(sockfd, bufp, nleft);
  343. if (ret < )
  344. return ret;
  345. else if (ret == )
  346. return ret;
  347.  
  348. nread = ret;
  349. int i;
  350. for (i=; i<nread; i++)
  351. {
  352. if (bufp[i] == '\n')
  353. {
  354. ret = readn(sockfd, bufp, i+);
  355. if (ret != i+)
  356. exit(EXIT_FAILURE);
  357.  
  358. return ret;
  359. }
  360. }
  361.  
  362. if (nread > nleft)
  363. exit(EXIT_FAILURE);
  364.  
  365. nleft -= nread;
  366. ret = readn(sockfd, bufp, nread);
  367. if (ret != nread)
  368. exit(EXIT_FAILURE);
  369.  
  370. bufp += nread;
  371. }
  372.  
  373. return -;
  374. }

上述带超时的函数,具有一定的健壮性,将IO设置为阻塞或者非阻塞,主要是给connect_timeout函数用。先将fd设置为非阻塞,这样connect就可以立即返回,如果返回时fd还没有连接好,即errno == EINPROGRESS,则调用select去监控这个fd,要么指定时间内fd可用,要么超时。虽然connect立即返回了,但是三次握手内核还是会做的。fd可读可有两种可能,一种是连接建立了,真正可读了,另一种是发生错误了,这时候fd也可读了。因此,

我们需要调用getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen)来获取错误码,根据错误码进行进一步的判断。

connect_timeout总结如下图:

优化网络应用就是优化连接,可以通过在客户端建立连接池提供优化。TCP IP三次握手很浪费时间。

select三个应用场景:

1、用select封装超时(connect、accept、read、write)

2、用select优化客户端(stdin、confd)

3、用select优化服务器(用一个单进程去支持多个客户端)

select是一个管理机制,管理了多个IO,用单进程轮询的方式去查询n个IO是否发生了变化。

7.3 5种IO模型与IO复用的更多相关文章

  1. IO模型--阻塞IO,非阻塞IO,IO多路复用,异步IO

    IO模型介绍: * blocking IO 阻塞IO * nonblocking IO 非阻塞IO * IO multiplexing IO多路复用 * signal driven IO 信号驱动IO ...

  2. 多路复用 阻塞/非阻塞IO模型 网络IO两个阶段

    1.网络IO的两个阶段 waitdata copydata send 先经历:copydata阶段 recv 先经历:waitdata阶段 再经历 copydata阶段 2.阻塞的IO模型 之前写的都 ...

  3. 并发编程 - IO模型 - 1.io模型/2.阻塞io/3.非阻塞io/4.多路复用io

    1.io模型提交任务得方式: 同步:提交完任务,等结果,执行下一个任务 异步:提交完,接着执行,异步 + 回调 异步不等结果,提交完任务,任务执行完后,会自动触发回调函数同步不等于阻塞: 阻塞:遇到i ...

  4. IO模型之IO多路复用 异步IO select poll epoll 的用法

    IO 模型之 多路复用 IO 多路复用IO IO multiplexing 这个词可能有点陌生,但是如果我说 select/epoll ,大概就都能明白了.有些地方也称这种IO方式为 事件驱动IO ( ...

  5. Linux下5种IO模型的小结

    概述 接触网络编程,我们时常会与各种与IO相关的概念打交道:同步(Synchronous).异步(ASynchronous).阻塞(blocking)和非阻塞(non-blocking).关于概念的区 ...

  6. 聊聊 Linux 中的五种 IO 模型

    本文转载自: http://mp.weixin.qq.com/s?__biz=MzAxODI5ODMwOA==&mid=2666538919&idx=1&sn=6013c451 ...

  7. 5种IO模型

    Unix下可用的5种I/O模型分别是: 阻塞IO 非阻塞IO IO复用(select和poll) 信号驱动式IO(SIGIO) 异步IO(POSIX的aio系列函数)   阻塞式I/O模型:      ...

  8. Linux 下的五种 IO 模型

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

  9. [转载] Linux五种IO模型

      转载:http://blog.csdn.net/jay900323/article/details/18141217     Linux五种IO模型性能分析   目录(?)[-] 概念理解 Lin ...

随机推荐

  1. python学习——大文件分割与合并

    在平常的生活中,我们会遇到下面这样的情况: 你下载了一个比较大型的游戏(假设有10G),现在想跟你的同学一起玩,你需要把这个游戏拷贝给他. 然后现在有一个问题是文件太大(我们不考虑你有移动硬盘什么的情 ...

  2. IDEA配置GIT

    注:此方法可用于配置gitlab也可用于配置github 1.在github中创建一个账号:https://github.com/join?source=header-home 2.下载并安装git: ...

  3. BeautifulSoup中的select方法

    在写css时,标签名不加任何修饰,类名前加点,id名前加 #,我们可以用类似的方法来筛选元素,用到的方法是soup.select(),返回类型是list. (1).通过标签名查找 print(soup ...

  4. SetCommMask

    SetCommMask           用途:设置串口通信事件   原型:BOOL SetCommMask(HANDLE hFile, //标识通信端口的句柄   DWORD dwEvtMask ...

  5. 字符集(编码)转换_Linux

    ZC: 来自 我的项目 czgj 1.代码: #include <stdio.h> #include <iconv.h> #include <string.h> / ...

  6. HDU 6106 Classes

    Classes 思路:a中包含的元素:只参加a的,只参加a且b的,只参加a且c的,只参加a且b且c的: b中包含的元素:只参加b的,只参加a且b的,只参加b且c的,只参加a且b且c的: c中包含的元素 ...

  7. Python 爬虫-图片的爬取

    2017-07-25 22:49:21 import requests import os url = 'https://wallpapers.wallhaven.cc/wallpapers/full ...

  8. SQLServer创建用户、数据库、表、约束、存储过程、视图

    --创建登录账户和数据库用户 ' exec sp_grantdbaccess 'sysAdmin','aa' --给数据库用户赋权限 grant select,update,insert,delete ...

  9. spring boot 2.0+ 错误页面配置

    如果访问了错误的路径,或者后台报错 如果没有一个统一的页面! 或者说页面上展示一堆报错信息,既影响美观,又对用户不友好! 那么如何配置? 定义 ErrorPageConfig,配置错误状态与对应访问路 ...

  10. R—读取数据(导入csv,txt,excel文件)

    导入CSV.TXT文件 read.table函数:read.table函数以数据框的格式读入数据,所以适合读取混合模式的数据,但是要求每列的数据数据类型相同. read.table读取数据非常方便,通 ...