多路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 <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
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清0
int FD_ISSET(int fd, fd_set *set); //测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0
例子(感觉还是很好理解的):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
#include "wrap.h"
#define SERV_PORT 6666
int 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;
}