I/O多路复用之select

来源:互联网 发布:老外中国快递知乎 编辑:程序博客网 时间:2024/05/22 04:26

I/O多路复用之select

在I/O编程中,在调用读写函数如write/read、send/recv、sendto/recvfrom(主要用于UDP编程)时,系统默认为阻塞模式,即当没有东西读或没有东西写时,程序发送阻塞,直到满足读或写时,进程才会被唤醒继续运行。这样,在C/S架构中就无法实现服务器的并发运行。

解决这样的问题有以下思路:

1、采用多进程:在主进程中循环监听网络连接,当有网络连接请求时,主进程创建子进程完成网络连接请求,实现服务器端的并发。

2、采用多线程:在程序中循环监听网络连接,当有网络连接请求时,创建一个线程完成网络连接请求,实现服务器端的并发。

3、I/O多路复用:I/O多路复用采用系统内核缓冲I/O数据,当某个I/O准备好后,系统通知应用程序该I/O可读或可写,这样应用程序可以马上完成相应的I/O操作,而不需要等待系统完成相应I/O操作,从而应用程序不必因等待I/O操作而阻塞,由此实现服务器端的并发。

I/O多路复用:等待多个文件描述符的多个事件发生,如果有发生,即进行I/O操作,其大致步骤如下。

1、管理等待对象及事件

2、把管理的对象及事件传递给内核

3、等其中的对象的事件发生(select、poll、epoll)

4、轮询是谁是什么事件发生

5、完成事件(read/write、recv/send、...)

A、管理对象

fd_set对象的集合名; //创建对象集合

/*从指定集合中添加删除指定文件描述符*/

voidFD_CLR(int fd, fd_set *set);

/*向指定集合中添加一个文件描述符*/

voidFD_SET(int fd, fd_set *set);

/*清空集合*/

voidFD_ZERO(fd_set *set);

 

B、通知内核及等待事件的发生

intselect(int nfds, fd_set *readfds, fd_set *writefds,

                  fd_set *exceptfds, structtimeval *timeout);

/*

 * 功能:通知内核要等的对象及事件,并等待发生

 * 参数:

   int nfds - 等待的最大文件描述符+1

   fd_set *readfds - 读集合(集合放在不同的参数位置表示不同的事件)

   fd_set *writefds- 写集合

   fd_set *exceptfds - 异常集合

   struct timeval *timeout:超时集合

         时间值0 :非阻塞

         合理的时间值 : 时间到时不满足即超时

         -1 : 阻塞(-1的补码即是最大的整数,等待时间最长即为阻塞)

返回值:

   -1:失败

   0 :超时

   >0: 有事件发生

 

C、轮询是谁是什么发生

for(inti = 最小的文件描述符; i < nfds; i++){

   //测试是谁是什么事情

   /*测试指定的文件描述符的指定事件是否*/

   //int FD_ISSET(int fd, fd_set *set);

   if(FD_ISSET(int fd, fd_set *set)){

      D、做事件

      read/write();

   }

}

利用select()构建一个模拟并发服务器(实现多连接):

#include <stdio.h>#include <string.h>#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <sys/select.h>#define MAX 1024int init_server(const char *ipstr, unsigned short port, int backlog){int s = socket(AF_INET, SOCK_STREAM, 0);if(0 > s){perror("socket");return -1;}struct sockaddr_in addr = {.sin_family = AF_INET,.sin_port   = htons(port),.sin_addr = {.s_addr = (NULL == ipstr ? INADDR_ANY : inet_addr(ipstr)),},};memset(addr.sin_zero, 0, sizeof(addr.sin_zero));socklen_t len = sizeof(addr);if(0 > bind(s, (struct sockaddr *)&addr, len)){perror("bind");return -1;}if(0 > listen(s, backlog)){perror("listen");return -1;}return s;}int main(int argc,char *argv[]){unsigned short port = 9999;char *ipstr = NULL;if(3 == num){ipstr = arg[1];port  = atoi(arg[2]);}if(2 == num){port  = atoi(arg[1]);}int s = init_server(ipstr, 9999, 1);if(0 > s){return -1;}fd_set oldset, newset;FD_ZERO(&newset);     //清空对象集合FD_SET(s, &newset);     //添加一个文件描述符到对象集合int maxfd = s+1;        //最大文件描述符while(1){ /*  select函数每执行一次后,对象集合会被清空,使用对象集合oldset进行监听,newset更新对象集合  */oldset = newset;     int ret = select(maxfd, &oldset, NULL, NULL, NULL);//通知内核等待if(0 > ret){                //失败perror("select");break;}else if(0 == ret){          //超时continue;}if(FD_ISSET(s, &oldset)){ ///测试指定的文件描述符的指定事件是否发生,这里就是检测是否有网络连接请求。struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));socklen_t len = sizeof(addr);int rws = accept(s, (struct sockaddr*)&addr, &len);if(0 > rws){perror("accept");return -1;}printf("a nen comming [%s:%u] \n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));FD_SET(rws, &newset); //有连接时把新的文件描述符加到对象集合中if(maxfd == rws){      //更新文件描述符的最大值maxfd = rws+1;}}for(int i = s+1; i < maxfd; i++){if(FD_ISSET(i, &oldset)){char buf[MAX];memset(buf, 0, MAX);int len = read(i, buf, MAX-1);if(0 >= len){printf("read [%d] fail .\n", i);close(i);FD_CLR(i, &newset);if(maxfd == (i+1)){maxfd--;}}else{printf("sock[%d], RECV[%dbytes]:%s\n", i, len, buf);}}}}close(s);}


0 0
原创粉丝点击