I/O多路复用(select,poll)函数与回射服务器实现
I/O多路复用
在网络编程中,不得不提的一个概念就是I/O多路复用机制,采用I/O多路复用机制,使得内核一旦发现进程指定的一个或多个I/O条件就绪,就通知进程进行处理,能够同时处理多个描述符。
目前Linux上的I/O多路复用机制主要有三种select,poll,epoll,因为select与poll比较相似,因此这篇文章先讲select和poll函数。
接下来分别从函数、结构、注意点分别进行说明:
CSDN博客地址:https://blog.csdn.net/aa123248135/article/details/96729425
select函数
select的异步阻塞I/O模型
- 应用进程向内核进行系统调用,由于内核中接受缓冲区没有可读内容,因此返回EAGEIN错误;
- 应用进程阻塞与select调用,等到多个套接字中的任意一个变为可读;
- 当内核的接收缓冲区中由数据读入时,内核向进程返回可读条件,应用程序再次进行系统调用,内核收到调用后,将内核接受缓冲区的数据复制到应用缓冲区;
- 内核向应用进程返回数据复制成功消息。
select的函数及结构
函数结构
int select(int maxfdp1, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval *timeout); 返回:若有就绪描述符则为其数目,超时则为0,若出错则为-1
函数参数及含义:
1.maxfdp1参数指定待测试的描述符个数,它的值是带测试的最大描述符加1。(在中限定了select的最大描述符个数,一般为1024,poll和epoll可以说是没有文件描述符的限制)
2.readfds,writefds,exceptfds指定我们要让内核测试读、写、异常的文件描述符;(fd_set是select特有的的数据结构,可以简单的理解为按位标记具柄的队列),select在处理fd_set是常用的四个宏如下所示
FD_ZERO(int fd, fd_set* fds) /*clear all bits in fdset*/ FD_SET(int fd, fd_set* fds) /*turn on the bit for fd in fdset*/ FD_ISSET(int fd, fd_set* fds) /*turn off the bit for fd in fdset*/ FD_CLR(int fd, fd_set* fds) /*is the bit for fd in fdset?*/
3.timeout参数内核等待的时间,timeval的结构如下,这个参数可以有以下三种情况
struct timeval{ long tv_sec; /*seconds*/ long tv_usec; /*microseconds*/ }
- 永远等待(阻塞):当timeout为空指针时,仅当文件描述符准备好I/O才返回,否则将一直阻塞;
- 等待固定时间:当timeout设置为具体的时间,且不为0,当文件描述符准备好I/O,且不超过设定的时间;
- 不等待(非阻塞):timeout为0,检查文件描述符后理解返回,非阻塞轮询模式。
select的tcp回射服务器程序
/**server端 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/select.h> #include <sys/time.h> #include <netinet/in.h> #include <sys/socket.h> #include <poll.h> #include <unistd.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #define IPADDRESS "127.0.0.1" #define PORT 8787 #define MAXLINE 1024 #define LISTENQ 5 #define OPEN_MAX 1000 #define INFTIM -1 //函数声明 //创建套接字并进行绑定 static int socket_bind(const char* ip,int port); //IO多路复用select static void do_select(int listenfd); //处理多个连接 static void handle_connection(int* connfds, fd_set &allset, fd_set &rset, int num); int main(int argc,char *argv[]) { int listenfd,connfd,sockfd; struct sockaddr_in cliaddr; socklen_t cliaddrlen; listenfd = socket_bind(IPADDRESS,PORT); listen(listenfd,LISTENQ); do_select(listenfd); return 0; } static int socket_bind(const char* ip,int port) { int listenfd; struct sockaddr_in servaddr; listenfd = socket(AF_INET,SOCK_STREAM,0); if (listenfd == -1){ perror("socket error:"); exit(1); } bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET,ip,&servaddr.sin_addr); servaddr.sin_port = htons(port); if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1){ perror("bind error: "); exit(1); } return listenfd; } static void do_select(int listenfd) { int connfd,sockfd; struct sockaddr_in cliaddr; socklen_t cliaddrlen; int maxfd, maxi, i, nready, client[FD_SETSIZE]; fd_set rset, allset; //添加监听描述符 //初始化客户连接描述符 maxfd = listenfd; maxi = -1; for(int i=0;i<FD_SETSIZE;++i) client[i] = -1; FD_ZERO(&allset); FD_SET(listenfd, &allset); //循环处理 for ( ; ; ) { rset = allset; //获取可用描述符的个数 nready = select(maxfd+1, &rset, NULL, NULL, NULL); if (nready == -1){ perror("select error:"); exit(1); } //测试监听描述符是否准备好 if (FD_ISSET(listenfd, &rset)){ cliaddrlen = sizeof(cliaddr); //接受新的连接 if ((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen)) == -1){ if (errno == EINTR) continue; else{ perror("accept error:"); exit(1); } } fprintf(stdout,"accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port); FD_SET(connfd, &allset); if(connfd > maxfd) maxfd = connfd; //将新的连接描述符添加到数组中 //这个是找到最小的一个没有用的文件描述符 for (i = 1;i < FD_SETSIZE;i++){ if (client[i] < 0){ client[i] = connfd; break; } } if (i == FD_SETSIZE){ fprintf(stderr,"too many clients.\n"); exit(1); } //记录客户连接套接字的个数 maxi = (i > maxi ? i : maxi); if (--nready <= 0) continue; } //处理客户连接 handle_connection(client, allset, rset, maxi); } } static void handle_connection(int* connfds,fd_set &allset ,fd_set &rset, int num) { int i, n; char buf[MAXLINE]; memset(buf, 0, MAXLINE); for (i = 1; i <= num; i++) { if (connfds[i] < 0) continue; //测试客户描述符是否准备好 if (FD_ISSET(connfds[i], &rset)) { //接收客户端发送的信息 n = read(connfds[i], buf, MAXLINE); /*n == 0 就是说明子机与母机断开连接了,子机在那边不输入,直接敲回车,这边也是可以收到回车符的*/ if (n == 0) { close(connfds[i]); FD_CLR(connfds[i], &allset); char buf2[20] = {"client closed"}; write(STDOUT_FILENO, buf2, strlen(buf2)); continue; } // printf("read msg is: "); write(STDOUT_FILENO, buf, n); //向客户端发送buf write(connfds[i], buf, n); } } }
/**client端 */ #include <netinet/in.h> #include <sys/socket.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <poll.h> #include <time.h> #include <unistd.h> #include <sys/types.h> #include<arpa/inet.h> #define MAXLINE 1024 #define IPADDRESS "127.0.0.1" #define SERV_PORT 8787 #define max(a,b) (a > b) ? a : b static void handle_connection(int sockfd); int main(int argc,char *argv[]) { int sockfd; struct sockaddr_in servaddr; sockfd = socket(AF_INET,SOCK_STREAM,0); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr); connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)); //处理连接描述符 handle_connection(sockfd); return 0; } static void handle_connection(int sockfd) { char sendline[MAXLINE],recvline[MAXLINE]; int maxfdp,stdineof; struct pollfd pfds[2]; int n; //添加连接描述符 pfds[0].fd = sockfd; pfds[0].events = POLLIN; //添加标准输入描述符 pfds[1].fd = STDIN_FILENO; pfds[1].events = POLLIN; for (; ;){ poll(pfds,2,-1); if (pfds[0].revents & POLLIN){ n = read(sockfd,recvline,MAXLINE); if (n == 0){ fprintf(stderr,"client: server is closed.\n"); close(sockfd); } write(STDOUT_FILENO,recvline,n); } //测试标准输入是否准备好 if (pfds[1].revents & POLLIN){ n = read(STDIN_FILENO,sendline,MAXLINE); if (n == 0) { shutdown(sockfd,SHUT_WR); continue; } write(sockfd,sendline,n); } } }
poll函数
poll函数的异步阻塞I/O模型与select的类似。但是poll与select不同的是,poll 不是为每个条件构造一个描述符集,而是构造一个 pollfd 结构体数组,每个数组元素指定一个描述符标号及其所关心的条件。
poll的函数及结构
函数结构
#include /* poll函数 * fds指向监控pollfd的首指针 * nfds是poll监听的最大文件描述符的个数,使用时传入最大文件描述符+1 * timeout设置超时模式 * 返回值:0表示没有套接字发生变化 n(n>0)表示有n个套接字有变化 -1有错误 */ int poll(struct pollfd* fds, unsigned long nfds, int timeout); 返回:若有就绪描述符则为其数目,超时则为0,若出错则为-1
函数参数及含义:
1.fds参数是指向一个pollfd结构的第一个元素的指针。其中每个元素都为polled结构,用于定于及返回poll感兴趣的文件描述符。
struct pollfd{ int fd; /*descriptor to check*/ short events; /*events of interest on fd*/ short revents; /*events that occurred on fd*/ };
其中events和reverts标志是系统定义的一些常值,常用的如下:
2.nfds参数指定pllfd中参数的个数;
3.timeout指定poll等待的时间,为一个毫秒级的正数;
poll的tcp回射服务器程序
/* server端 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/select.h> #include <sys/time.h> #include <netinet/in.h> #include <sys/socket.h> #include <poll.h> #include <unistd.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #define IPADDRESS "127.0.0.1" #define PORT 8787 #define MAXLINE 1024 #define LISTENQ 5 #define OPEN_MAX 1000 #define INFTIM -1 //函数声明 //创建套接字并进行绑定 static int socket_bind(const char* ip,int port); //IO多路复用poll static void do_poll(int listenfd); //处理多个连接 static void handle_connection(struct pollfd *connfds, int num, int nready); int main(int argc,char *argv[]) { int listenfd,connfd,sockfd; struct sockaddr_in cliaddr; socklen_t cliaddrlen; listenfd = socket_bind(IPADDRESS,PORT); listen(listenfd,LISTENQ); do_poll(listenfd); return 0; } static int socket_bind(const char* ip,int port) { int listenfd; struct sockaddr_in servaddr; listenfd = socket(AF_INET,SOCK_STREAM,0); if (listenfd == -1){ perror("socket error:"); exit(1); } bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET,ip,&servaddr.sin_addr); servaddr.sin_port = htons(port); if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1){ perror("bind error: "); exit(1); } return listenfd; } static void do_poll(int listenfd) { int connfd,sockfd; struct sockaddr_in cliaddr; socklen_t cliaddrlen; struct pollfd clientfds[OPEN_MAX]; int maxi; int i; int nready; //添加监听描述符 clientfds[0].fd = listenfd; clientfds[0].events = POLLIN; //初始化客户连接描述符 for (i = 1;i < OPEN_MAX;i++) clientfds[i].fd = -1; maxi = 0; //循环处理 for ( ; ; ){ //获取可用描述符的个数 nready = poll(clientfds,maxi+1,INFTIM); if (nready == -1){ perror("poll error:"); exit(1); } //测试监听描述符是否准备好 if (clientfds[0].revents & POLLIN){ cliaddrlen = sizeof(cliaddr); //接受新的连接 if ((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen)) == -1){ if (errno == EINTR) continue; else{ perror("accept error:"); exit(1); } } fprintf(stdout,"accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port); //将新的连接描述符添加到数组中 //这个是找到最小的一个没有用的文件描述符 for (i = 1;i < OPEN_MAX;i++){ if (clientfds[i].fd < 0){ clientfds[i].fd = connfd; break; } } if (i == OPEN_MAX){ fprintf(stderr,"too many clients.\n"); exit(1); } //将新的描述符添加到读描述符集合中 clientfds[i].events = POLLIN; //记录客户连接套接字的个数 maxi = (i > maxi ? i : maxi); if (--nready <= 0) continue; } //处理客户连接 handle_connection(clientfds, maxi, nready); } } static void handle_connection(struct pollfd *connfds, int num, int nready) { int i,n; char buf[MAXLINE]; memset(buf,0,MAXLINE); for (i = 1;i <= num;i++){ if (connfds[i].fd < 0) continue; //测试客户描述符是否准备好 if (connfds[i].revents & POLLIN){ //接收客户端发送的信息 n = read(connfds[i].fd,buf,MAXLINE); /*n == 0 就是说明子机与母机断开连接了,子机在那边不输入,直接敲回车,这边也是可以收到回车符的*/ if (n == 0){ close(connfds[i].fd); connfds[i].fd = -1; char buf2[1024] = {"client closed"}; write(STDOUT_FILENO, buf2, strlen(buf2)); continue; } // printf("read msg is: "); write(STDOUT_FILENO,buf,n); //向客户端发送buf write(connfds[i].fd,buf,n); if(--nready <= 0) break; } } }
/**client端 */ #include <netinet/in.h> #include <sys/socket.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <poll.h> #include <time.h> #include <unistd.h> #include <sys/types.h> #include<arpa/inet.h> #define MAXLINE 1024 #define IPADDRESS "127.0.0.1" #define SERV_PORT 8787 #define max(a,b) (a > b) ? a : b static void handle_connection(int sockfd); int main(int argc,char *argv[]) { int sockfd; struct sockaddr_in servaddr; sockfd = socket(AF_INET,SOCK_STREAM,0); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr); connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)); //处理连接描述符 handle_connection(sockfd); return 0; } static void handle_connection(int sockfd) { char sendline[MAXLINE],recvline[MAXLINE]; int maxfdp,stdineof; struct pollfd pfds[2]; int n; //添加连接描述符 pfds[0].fd = sockfd; pfds[0].events = POLLIN; //添加标准输入描述符 pfds[1].fd = STDIN_FILENO; pfds[1].events = POLLIN; for (; ;){ poll(pfds,2,-1); if (pfds[0].revents & POLLIN){ n = read(sockfd,recvline,MAXLINE); if (n == 0){ fprintf(stderr,"client: server is closed.\n"); close(sockfd); } write(STDOUT_FILENO,recvline,n); } //测试标准输入是否准备好 if (pfds[1].revents & POLLIN){ n = read(STDIN_FILENO,sendline,MAXLINE); if (n == 0) { shutdown(sockfd,SHUT_WR); continue; } write(sockfd,sendline,n); } } }#C/C++##笔记#