前一章节客户端同时处理两个输入:标准输入和TCP套接字,然而问题在于客户端阻塞于fgets调用期,服务器进程被杀死后,服务器tcp虽然可以正确发送一个fin,但进程正阻塞于标准输入,它无法看到eof,直到从套接字读为止.这样的进程需要一种预先告诉内核的能力,一旦内核发现进程指定的一个或者多个I/O就绪,它就通知进程,这就叫做I/O复用


select函数

  1. #include <sys/select.h>
  2. #include<sys/time.h>
  3. int select(int nfds, fd_set *readfds, fd_set *writefds,
  4. fd_set *exceptfds, struct timeval *timeout);
timeout参数:告知内核等待所指定的描述符集合的任意一个可以花多少时间
  1. struct timeval{
  2. long tv_sec;
  3. long tv_usec;
  4. };
参数有以下可能:(1) 永远等下去.有一个以上描述符准备好I/O才返回
(2)等待固定一段时间,如果这段时间没I/O准备好.那就直接返回
(3)根本不等待,检查描述符后立刻返回,这称为轮询(polling)
中间三个参数readset,writeset,exceptset指定我们要让内核测试读,写,异常的描述符
异常的一种情况只是某个套接字的带外数据的到达

select使用一个整数数组,其中每个整数的每一位对应一个描述符...其实现隐藏在fd_set跟四个宏
  1. void FD_ZERO(fd_set*fdset);
  2. void FD_SET(int fd,fd_set*fdset);
  3. void FD_CLR(int fd,fd_set *fdset);
  4. int FD_ISSET(int fd,fd_set *fdset);
  1. fd_set rset;
  2. FD_ZERO(&rset);
  3. FD_SET(1,&rset);
  4. FD_SET(4,&rset);
  5. FD_SET(5,&rset);
select中间三个参数.如果对某一个条件不感兴趣那就设置为null.nfds的值等于最大描述符值+1,描述符0一直到nfds-1都被测试,该函数返回时,结果将指示哪些描述符就绪,我们使用d_isset来表示该描述符是否就绪,描述符集内任何与未就绪描述符对应的位返回时均会被置为0,因此,每次调用select函数.我们都要把所关心的描述符集的位设置为1,此函数返回时已就绪总数.如果定时器到时了,那么返回0,出错返回-1(比如被中断打断)

描述符就绪条件:
(1)如果套接字接收缓冲区的数据大于等于套接字接收缓冲区低水位标记的大小(就是最低值,可用SO_REVLOWAT改变),这样对套接字进行读操作不会阻塞并且将会返回一个大于0的值(返回准备好读入的数据)
(2)该连接的读半部关闭(接收FIN的TCP).对这样的套接字的读操作不会阻塞并且返回0(返回EOF)
(3)该套接字是监听套接字并且已完成队列不为空,
(4)一个套接字有错误待处理.对这样的套接字的读操作将不会阻塞返回-1.同时errno将设置为错误条件



套接字可写条件
(1)该套接字发送缓冲区的可用空间字节大于等于套接字发送缓冲区低水位的当前大小
(2)该连接写操作关闭,继续写将产生SIGPIPE信号
(3)使用非阻塞的connect套接字已连接.或者失败
(4)其上有一个套接字错误待处理.对这样的套接字的写操作将不阻塞返回-1.
(3)一个套接字存在带外数据,有异常条件待处理

如果键入eof后直接return那么可能在发送的服务器的数据还在路上或者返回给客户端的数据还在路上,标准eof不意味着完成了套接字的读入
 
当客户端使用eof关闭读时,将使用shutdown半关闭,接着继续select阻塞.直到服务端也发送fin.客户端套接字触发可读.读到0.表示服务端也开始关闭了.
客户端代码:
  1. #include "unp.h"
  2. void
  3. str_cli(FILE *fp, int sockfd)
  4. {
  5. int maxfdp1, stdineof;
  6. fd_set rset;
  7. char buf[MAXLINE];
  8. int n;
  9. stdineof = 0;
  10. FD_ZERO(&rset);
  11. for ( ; ; ) {
  12. if (stdineof == 0)
  13. FD_SET(fileno(fp), &rset);
  14. FD_SET(sockfd, &rset);
  15. maxfdp1 = max(fileno(fp), sockfd) + 1;
  16. Select(maxfdp1, &rset, NULL, NULL, NULL);
  17. if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
  18. if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
  19. if (stdineof == 1)
  20. return; /* normal termination */
  21. else
  22. err_quit("str_cli: server terminated prematurely");
  23. }
  24. Write(fileno(stdout), buf, n);
  25. }
  26. if (FD_ISSET(fileno(fp), &rset)) {
  27. if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {
  28. stdineof = 1;
  29. Shutdown(sockfd, SHUT_WR);
  30. FD_CLR(fileno(fp), &rset);
  31. continue;
  32. }
  33.     Writen(sockfd, buf, n);
  34. }
  35. }
  36. }
  37. int main(int argc,char *argv[])
  38. {
  39. if(argc<3)
  40. err_quit("please input IP Address : Port");
  41. int connfd=Socket(AF_INET,SOCK_STREAM,0);
  42. struct sockaddr_in servaddr;
  43. servaddr.sin_family=AF_INET;
  44. servaddr.sin_port=htons(atoi(argv[2]));
  45. Inet_pton(AF_INET,argv[1],&servaddr.sin_addr);
  46. Connect(connfd,(SA*)&servaddr,sizeof(servaddr));
  47. str_cli(stdin,connfd);
  48. return 0;
  49. }
服务端代码:
  1. #include "unp.h"
  2. static int create_sock(char *ip)
  3. {
  4. int listenfd;
  5. listenfd=Socket(AF_INET,SOCK_STREAM,0);
  6. struct sockaddr_in servaddr;
  7. servaddr.sin_family=AF_INET;
  8. servaddr.sin_port=htons(atoi(ip));
  9. Inet_pton(AF_INET,"0.0.0.0",&servaddr.sin_addr);
  10. Bind(listenfd,(SA*)&servaddr,sizeof(servaddr));
  11. Listen(listenfd,20);
  12. return listenfd;
  13. }
  14. static void deal_cli(int listenfd)
  15. {
  16. int connfd,i;
  17. fd_set allset,rset;//allset保存的整个描述符集。但并不会直接select.每次select都会使得清0.很不方便。于是当要select的时候把它付给rset
  18. FD_ZERO(&allset);
  19. FD_ZERO(&rset);
  20. char buf[1024];//从客户端读取的缓冲区
  21. int ret=0;
  22. int maxfd=listenfd;//select第一个参数
  23. int allfd[MAXLINE];//连接数组
  24. for(i=0;i<MAXLINE;++i){
  25. allfd[i]=-1;
  26. }
  27. int nready; //select I/O已经就绪
  28. int max=0;//表示已完成连接数组中最大的队列
  29. FD_SET(listenfd,&allset);//将监听连接加入allset集合中
  30. for(;;){
  31. rset=allset;//更新
  32. nready=Select(maxfd+1,&rset,NULL,NULL,NULL);
  33. if(FD_ISSET(listenfd,&rset)){
  34. connfd=Accept(listenfd,NULL,NULL); //从已连接套接字拿取一个出来
  35. for(i=0;i<MAXLINE;++i){ //遍历整个已连接数组。找到alifd[i]等于-1(表示该位可用)
  36. if(allfd[i]<0){
  37. allfd[i]=connfd;
  38. break;
  39. }
  40. }
  41. FD_SET(connfd,&allset);
  42. if(maxfd<connfd) //每次都需要比较maxfd跟新来的描述符值大小
  43. maxfd=connfd;
  44. if(max<i)//还要更新最后可连接数组最后的那个已连接索引
  45. max=i;
  46. if(--nready<=0) //如果该连接已经处理完成。那就
  47. continue;
  48. }
  49. for(i=0;i<max+1;++i){ //遍历以此处理所有已完成连接
  50. if(allfd[i]!=-1)//跳过
  51. if(FD_ISSET(allfd[i],&rset)){
  52. ret=read(allfd[i],buf,1024);
  53. if(ret==0){
  54. close(allfd[i]);
  55. FD_CLR(allfd[i],&allset);
  56. allfd[i]=-1;
  57. }else if(ret<0)
  58. {
  59. perror("error");
  60. }else
  61. Writen(allfd[i],buf,ret);
  62. }
  63. if(--nready<=0) //如果处理最后一个连接。继续返回到select中去
  64. continue;
  65. }
  66. }
  67. }
  68. int main(int argc,char *argv[])
  69. {
  70. if(argc<2){
  71. err_quit("please input ip aiddress\n");
  72. }
  73. int listenfd=create_sock(argv[1]);//创建一个套接字
  74. deal_cli(listenfd);//开始处理细节
  75. return 0;
  76. }

poll函数
  1. #include <poll.h>
  2. int poll(struct pollfd *fds, nfds_t nfds, int timeout);
第一个参数是指向一个结构数组第一个元素的指针,每个数组元素都是一个pollfd结构图,用来指定厕所某个给定描述符fd的条件
  1. struct pollfd {
  2. int fd; /* file descriptor */
  3. short events; /* requested events */
  4. short revents; /* returned events */
  5. };

要测试的条件由events成员指定,函数在相应的revents成员返回该描述符状态
 poll识别三类数据:普通,优先级带和高优先级
相对于TCP和UDP套接字而言,以下条件引起poll返回特定的revent
(1)所有正规的TCP数据和所有UDP数据都被认为是普通数据
(2)TCP的带外数据被认为是优先级带数据
(3)当TCP连接的读半部关闭时,也被认为是普通数据,随后的读操作返回0
(4)TCP连接存在错误可以被认为是错误也可以认为是普通数据,无论哪种情况随后的读操作都将返回-1,并且把error设置为合适的值。可用来接收到RST或者发生超时等条件。
(5) 在监听套接字上有新的连接可用,被视为普通数据,也可以认为是优先级数据,大多数被视为普通数据
(6)非阻塞式connect的完成被认为使得套接字可写

结构数组中元素的个数是由nfds参数所指定
timeout参数指定poll函数返回前等待多长时间,它是一个指定应等待毫秒数的正值
  INFTIM 永远等待    0 立即返回     >0 等待指定数据的毫秒数

发生错误时,poll的返回值为-1,若定时器到时之前没有任何描述符就绪,则返回0,否则返回就绪描述符个数,也就是revents成员值为非0的个数
如果我们不再关心某个描述符的属性,那就把对应的pollfd的fd设置为一个负数

  1. /* include fig01 */
  2. #include "unp.h"
  3. //#include <limits.h> /* for OPEN_MAX */
  4. int
  5. main(int argc, char **argv)
  6. {
  7. int i, maxi, listenfd, connfd, sockfd;
  8. int nready;
  9. ssize_t n;
  10. char buf[MAXLINE];
  11. socklen_t clilen;
  12. long OPEN_MAX = sysconf(_SC_OPEN_MAX);
  13. struct pollfd client[OPEN_MAX];
  14. struct sockaddr_in cliaddr, servaddr;
  15. listenfd = Socket(AF_INET, SOCK_STREAM, 0);
  16. bzero(&servaddr, sizeof(servaddr));
  17. servaddr.sin_family = AF_INET;
  18. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  19. servaddr.sin_port = htons(SERV_PORT);
  20. Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
  21. Listen(listenfd, LISTENQ);
  22. client[0].fd = listenfd;
  23. client[0].events = POLLRDNORM;
  24. for (i = 1; i < OPEN_MAX; i++)
  25. client[i].fd = -1; /* -1 indicates available entry */
  26. maxi = 0; /* max index into client[] array */
  27. /* end fig01 */
  28. /* include fig02 */
  29. for ( ; ; ) {
  30. nready = Poll(client, maxi+1, INFTIM);
  31. if (client[0].revents & POLLRDNORM) { /* new client connection */
  32. clilen = sizeof(cliaddr);
  33. connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
  34. #ifdef NOTDEF
  35. printf("new client: %s\n", Sock_ntop((SA *) &cliaddr, clilen));
  36. #endif
  37. for (i = 1; i < OPEN_MAX; i++)
  38. if (client[i].fd < 0) {
  39. client[i].fd = connfd; /* save descriptor */
  40. break;
  41. }
  42. if (i == OPEN_MAX)
  43. err_quit("too many clients");
  44. client[i].events = POLLRDNORM;
  45. if (i > maxi)
  46. maxi = i; /* max index in client[] array */
  47. if (--nready <= 0)
  48. continue; /* no more readable descriptors */
  49. }
  50. for (i = 1; i <= maxi; i++) { /* check all clients for data */
  51. if ( (sockfd = client[i].fd) < 0)
  52. continue;
  53. if (client[i].revents & (POLLRDNORM | POLLERR)) {
  54. if ( (n = read(sockfd, buf, MAXLINE)) < 0) {
  55. if (errno == ECONNRESET) {
  56. /*4connection reset by client */
  57. #ifdef NOTDEF
  58. printf("client[%d] aborted connection\n", i);
  59. #endif
  60. Close(sockfd);
  61. client[i].fd = -1;
  62. } else
  63. err_sys("read error");
  64. } else if (n == 0) {
  65. /*4connection closed by client */
  66. #ifdef NOTDEF
  67. printf("client[%d] closed connection\n", i);
  68. #endif
  69. Close(sockfd);
  70. client[i].fd = -1;
  71. } else
  72. Writen(sockfd, buf, n);
  73. if (--nready <= 0)
  74. break; /* no more readable descriptors */
  75. }
  76. }
  77. }
  78. }
  79. /* end fig02 */















第6章 I/O多路复用的更多相关文章

  1. LPC43xx SGPIO Experimentation

    LPC4350 SGPIO Experimentation The NXP LPC43xx microcontrollers have an interesting, programmable ser ...

  2. 《Linux/UNIX系统编程手册》第63章 IO多路复用、信号驱动IO以及epoll

    关键词:fasync_helper.kill_async.sigsuspend.sigaction.fcntl.F_SETOWN_EX.F_SETSIG.select().poll().poll_wa ...

  3. Netty源码分析第1章(Netty启动流程)---->第4节: 注册多路复用

    Netty源码分析第一章:Netty启动流程   第四节:注册多路复用 回顾下以上的小节, 我们知道了channel的的创建和初始化过程, 那么channel是如何注册到selector中的呢?我们继 ...

  4. 第15章 高并发服务器编程(2)_I/O多路复用

    3. I/O多路复用:select函数 3.1 I/O多路复用简介 (1)通信领域的时分多路复用 (2)I/O多路复用(I/O multiplexing) ①同一线程,通过“拨开关”方式,来同时处理多 ...

  5. 【面试】一篇文章帮你彻底搞清楚“I/O多路复用”和“异步I/O”的前世今生

    曾经的VIP服务 在网络的初期,网民很少,服务器完全无压力,那时的技术也没有现在先进,通常用一个线程来全程跟踪处理一个请求.因为这样最简单. 其实代码实现大家都知道,就是服务器上有个ServerSoc ...

  6. 深入理解计算机操作系统——12章:多进程,IO多路复用

    三种并行的应用程序: 1. 基于进程的并发编程: 2. 基于IO多路复用的并发: 3. 基于线程的并发编程: 12.1 基于进程的并发编程 进程的优劣: (1)进程间共享文件表,但不共享用户地址空间, ...

  7. 一篇文章帮你彻底搞清楚“I/O多路复用”和“异步I/O”的前世今生

    在网络的初期,网民很少,服务器完全无压力,那时的技术也没有现在先进,通常用一个线程来全程跟踪处理一个请求.因为这样最简单. 其实代码实现大家都知道,就是服务器上有个ServerSocket在某个端口监 ...

  8. Python(七)Socket编程、IO多路复用、SocketServer

    本章内容: Socket IO多路复用(select) SocketServer 模块(ThreadingTCPServer源码剖析) Socket socket通常也称作"套接字" ...

  9. 【翻译】XV6-DRAFT as of September 3,2014 第0章 操作系统接口

    操作系统接口 操作系统的任务是让多个程序共享计算机(资源),并且提供一系列基于计算机硬件的但更有用的服务.操作系统管理并且把底层的硬件抽象出来,举例来说,一个文字处理软件(例如word)不需要关心计算 ...

随机推荐

  1. cnpm 莫名奇妙bug 莫名奇妙的痛

    cnpm 莫名奇妙bug 莫名奇妙的痛 最近想搭建react@v16 和 react-router@v4,搭建过程打算用vue脚手架webpack模板那套配置方法(webpack3). 由于我之前安装 ...

  2. sql where in字符串问题

    在pycharm中执行 select * from value in(1,2); 会提醒: No statement found under the caret. Execute all statem ...

  3. 学习笔记(四): Representation:Feature Engineering/Qualities of Good Features/Cleaning Data/Feature Sets

    目录 Representation Feature Engineering Mapping Raw Data to Features Mapping numeric values Mapping ca ...

  4. matplotlib绘图(一)

    绘制这折现图 导入响应的包 import numpy as npimport pandas as pdfrom pandas import Series,DataFrame%matplotlib in ...

  5. Linux 命令、配置文件及操作

    Linux 命令.配置文件及操作 命令 命令 参数 说明 A alias.unalias 命令别名 B C cat 查看文件内容 cd 切换目录 chown 修改拥有着 chgrp 修改所属组 chm ...

  6. 10GNU C语言函数调用

    6. C 函数调用机制概述 ​ 在 Linux 内核程序 boot/head.s 执行完基本初始化操作之后,就会跳转区执行 init/main.c 程序.那么 head.s 程序时如何把执行控制转交给 ...

  7. 【Windows7注册码】

    [文章转载自 http://www.win7zhijia.cn/jiaocheng/win7_19324.html] 一.神Key: KH2J9-PC326-T44D4-39H6V-TVPBY TFP ...

  8. Applied Nonparametric Statistics-lec4

    Ref: https://onlinecourses.science.psu.edu/stat464/print/book/export/html/5 Two sample test 直接使用R的t- ...

  9. Seven Puzzle Aizu - 0121 (搜索)

    7 パズル ime limit1000 ms Memory limit131072 kB OSLinux Source3rd PC Koshien, Final 7 パズルは 8 つの正方形のカードと ...

  10. 树状数组:CDOJ1583-曜酱的心意(树状数组心得)

    曜酱的心意 Time Limit: 3000/1000MS (Java/Others) Memory Limit: 131072/131072KB (Java/Others) Description ...