本文共 3988 字,大约阅读时间需要 13 分钟。
多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想是,不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件。
1.select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数
2.解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力总结(面试考点!!!):
select函数传入3个自定义的需要内核监控的文件描述符集合(位图),死循环,由内核依次遍历所有的文件描述符集合,判断是否有可读、可写、异常事件。遍历完,如果有事件,返回事件个数,并传出3个文件描述符集合(修改后的位图)。如果没有事件响应,则select阻塞一定时间后继续轮询。 特点: 1.单个进程能够让内核监控的文件描述符的数量最多为1024个。 2.每次调用select都要把3个fd集合从用户态拷贝到内核态。 3.内核采用轮询方式遍历所有的fd,需要监控的文件描述符越多,效率越低。 4.select是LT水平触发,应用程序如果没有完成对一个已就绪的文件描述符进行IO操作,那么下次select调用还会将这些文件描述符通知应用程序。#include/* According to earlier standards */#include #include #include int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态readfds: 监控有读数据到达文件描述符集合,传入传出参数,传入的是需要监控的文件描述符集合,传出的是实际有读数据到达的文件描述符集合writefds: 监控写数据到达文件描述符集合,传入传出参数exceptfds: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数timeout: 定时阻塞监控时间,3种情况 1.NULL,永远等下去 2.设置timeval,等待固定时间 3.设置timeval里时间均为0,检查描述字后立即返回,轮询struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */};
void FD_CLR(int fd, fd_set *set); //把文件描述符集合里fd清0int FD_ISSET(int fd, fd_set *set); //测试文件描述符集合里fd是否置1void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0
例子(感觉还是很好理解的):
#include#include #include #include #include #include #include "wrap.h"#define SERV_PORT 6666int main(int argc, char *argv[]){ int i, j, n, maxi; int nready, client[FD_SETSIZE]; /* 自定义数组client, 防止遍历1024个文件描述符 FD_SETSIZE默认为1024 */ int maxfd, listenfd, connfd, sockfd; char buf[BUFSIZ], str[INET_ADDRSTRLEN]; /* #define INET_ADDRSTRLEN 16 */ struct sockaddr_in clie_addr, serv_addr; socklen_t clie_addr_len; fd_set rset, allset; /* rset 读事件文件描述符集合 allset用来暂存 */ listenfd = Socket(AF_INET, SOCK_STREAM, 0); int opt = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family= AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port= htons(SERV_PORT); Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); Listen(listenfd, 128); maxfd = listenfd; /* 起初 listenfd 即为最大文件描述符 */ maxi = 0; /* 将来用作client[]的下标, 初始值指向0个元素之前下标位置 */ client[0] = listenfd; for (i = 1; i < FD_SETSIZE; i++) client[i] = -1; /* 用-1初始化client[] */ FD_ZERO(&allset); FD_SET(listenfd, &allset); /* 构造select监控文件描述符集 */ while (1) { rset = allset; /* 每次循环时都从新设置select监控信号集 */ nready = select(maxfd+1, &rset, NULL, NULL, NULL); //传入的reset是自己设置的需要监控的文件描述符集,传出的reset是有读数据到达的文件描述符集合 if (nready < 0) perr_exit("select error"); if (FD_ISSET(listenfd, &rset)) { /* 说明有新的客户端链接请求 */ clie_addr_len = sizeof(clie_addr); connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len); /* Accept 不会阻塞 */ printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)), ntohs(clie_addr.sin_port)); for (i = 0; i < FD_SETSIZE; i++) if (client[i] < 0) { /* 找client[]中没有使用的位置 */ client[i] = connfd; /* 保存accept返回的文件描述符到client[]里 */ break; } if (i == FD_SETSIZE) { /* 达到select能监控的文件个数上限 1024 */ fputs("too many clients\n", stderr); exit(1); } FD_SET(connfd, &allset); /* 向监控文件描述符集合allset添加新的文件描述符connfd */ if (connfd > maxfd) maxfd = connfd; /* 刷新maxfd,select第一个参数需要 */ if (i > maxi) maxi = i; /* 刷新maxi,保证maxi存的总是client[]最后一个元素下标 */ if (--nready == 0) continue; } for (i = 0; i <= maxi; i++) { if ((sockfd = client[i]) == -1) continue; if (FD_ISSET(sockfd, &rset)) /* 检测哪个clients 有数据就绪 */ { if ((n = Read(sockfd, buf, sizeof(buf))) == 0) /* 当client关闭链接时,服务器端也关闭对应链接 */ { Close(sockfd); FD_CLR(sockfd, &allset); /* 解除select对此文件描述符的监控 */ client[i] = -1; } else if (n > 0) { for (j = 0; j < n; j++) buf[j] = toupper(buf[j]); Write(sockfd, buf, n); Write(STDOUT_FILENO, buf, n); } if (--nready == 0) break; /* 跳出for, 但还在while中 */ } } } Close(listenfd); return 0;}
转载地址:http://uxmt.baihongyu.com/