Unix C语言编写基于IO多路复用的小型并发服务器

jopen 9年前

本博文主要介绍了如何用Unix C语言编写基于IO多路复用的小型并发服务器,即如何同时处理网络连接请求和标准输入交互。

背景介绍

如果服务器要同时处理网络上的套接字连接请求和本地的标准输入命令请求,那么如果我们使用accept来接受连接请求,则无法处理标准输入请求;类似地,如果在read中等待一个输入请求,则无法处理网络连接的请求。

所谓I/O多路复用机制,就是说通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但 select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而还有一种情况是异步IO,异步I /O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

使用select实现IO多路复用

我们可以使用select函数来实现等待一组描述符准备好读。select函数处理类型为fd_set的集合,也叫做描述符集合。逻辑上,我们可以将描述符集合看成一个大小为n的位向量,每个位对应一个描述符。select函数是一个阻塞函数,即只有等到读集合中至少有一个描述符可以读时,就不会阻塞,开始处理请求了。

代码如下:

#include "csapp.h"     //此程序是使用基于 IO多路复用的并发服务器     void echo(int connfd)  {      int n;      char buf[MAXLINE];      rio_t rio;             rio_readinitb(&rio,connfd);      //带缓冲的读取函数      while((n=rio_readlineb(&rio,buf,MAXLINE))>0) {          //向连接符写入内容          printf("server received %d bytes \n",n);          rio_writen(connfd,buf,n);      }  }        /*command是作为键盘输入时执行的驱动动作*/  void command(void) {      char buf[MAXLINE];      printf("you input just now!\n");      //从标准输入中读取输入到buf中存储      if(!fgets(buf,MAXLINE,stdin))          exit(0);      //输出buf中的数据      printf("%s",buf);  }     //主程序  int main(int argc,char **argv)  {         //监听符,连接符,端口号      int listenfd,connfd,port;      //套接字地址结构的大小      socklen_t clientlen=sizeof(struct sockaddr_in);      //新建套接字地址结构      struct sockaddr_in clientaddr;      //fd_set为描述符集合,此处定义了两个read_set,ready_set描述符集合,分别是读集合/准备好集合      fd_set read_set,ready_set;             //如果运行时参数小于2,则提示错误      if(argc!=2) {          fprintf(stderr,"usage :%s <port>\n",argv[0]);          exit(0);      }         //将第二个参数转化为整型端口号,args to integer      port=atoi(argv[1]);         //打开端口号,返回监听描述符      listenfd=open_listenfd(port);             //清空读集合      FD_ZERO(&read_set);             //将标准输入加到读集合      FD_SET(STDIN_FILENO,&read_set);         //将监听描述符加到读集合      FD_SET(listenfd,&read_set);         //服务器监听处理主程序      while(1) {             //将读集合赋值给准备好集合          ready_set=read_set;             //select函数会要求内核挂起进程,等待一个或多个IO事件发生后,才将控制返回给应用程序,就像在下面的示例一样             select(listenfd+1,&ready_set,NULL,NULL,NULL);             //有IO事件后,将判断是来自从键盘上键入命令还是从客户端发来的请求,分别给出不同的回应          if(FD_ISSET(STDIN_FILENO,&ready_set))              command();          if(FD_ISSET(listenfd,&ready_set)) {              connfd=accept(listenfd,(SA *)&clientaddr,&clientlen);              printf("client connected!");              //向连接符回送数据              echo(connfd);              //关闭连接符,释放资源              close(connfd);          }      }  }

测试部分

zzw@zzw-ThinkPad-Edge-E430c:~$ telnet localhost 9999  Trying 127.0.0.1...  Connected to localhost.  Escape character is '^]'.  hello  hello

zzw@zzw-ThinkPad-Edge-E430c:~/doc_main/CProgram/Concurrency$ ./select.o 9999  client connected!server received 7 bytes

最后强调一下,这里是同步的IO问题,并非异步。



来自:http://my.oschina.net/zzw922cn/blog/493723