多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想是,不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件。
可以理解为老板(服务器)聘一个小助手(select)帮助老板接听电话,有数据请求老板再做处理
多路IO转接: 响应模式 ==> 有数据来我才服务
select
文件描述符集合函数:
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
//void FD_ZERO(fd_set *set); 清空一个文件描述符集合
fd_set rset;
FD_ZERO(&rset); 类似于信号屏蔽字
//void FD_SET(int fd, fd_set *set); 将监听的文件描述符,添加到监听集合中
FD_SET(3, &rset);
FD_SET(4, &rset);
FD_SET(5, &rset);
//void FD_CLR(int fd, fd_set *set); 将一个文件描述符从监听集合中移除
//int FD_ISSET(int fd, fd_set *set); 判断一个文件描述符是否再监听集合中
select函数
#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: >0:设置超时时长
=0:非监听阻塞,忙轮询
NULL:阻塞监听
返回值:
>0: 所有监听集合中满足对应事件的总数
0: 没有满足监听条件的文件描述符
<0: 异常,设置errno
思路分析:
lfd = socket(); //创建监听套接字
bind(); //绑定地址结构
listen(); //设置监听上限
fd_set rset, allset; //创建rset
FD_ZERO(&allset); //清空rset
FD_SET(lfd, &allset); //将lfd添加到读集合中
while(1) {
rset = allset; //allset可以看作是一个容器
ret = select(lfd+1, &rset, NULL, NULL, NULL);
if(ret > 0) {
if(FD_INSET(lfd, &rset)) {
cfd = accept();
FD_SET(cfd, &allset);
}
}
}
for(i = lfd+1, i <= maxfd, i++) {
FD_ISSET(i, &rset);
/*进行数据交换*/
}
代码实现:
#include "wrap.h"
#define SER_PORT 9998
int main(int argc, char *argv[])
{
int lfd;
int cfd;
socklen_t cli_len;
char cli_IP[BUFSIZ];
char buf[BUFSIZ];
struct sockaddr_in ser_addr, cli_addr;
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(SER_PORT);
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = Socket(AF_INET, SOCK_STREAM, 0);
Bind(lfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));
Listen(lfd, 128);
int ret, i, j, n;
int maxfd= 0;
maxfd = lfd;
fd_set rset, allset;
FD_ZERO(&allset);
FD_SET(lfd, &allset);
while(1) {
rset = allset;
ret = select(maxfd+1, &rset, NULL, NULL, NULL);
if(ret < 0)
sys_exit("select error");
if(FD_ISSET(lfd, &rset)) {
cli_len = sizeof(cli_addr);
cfd = Accept(lfd, (struct sockaddr *)&cli_addr, &cli_len);
FD_SET(cfd, &allset);
if(maxfd < cfd)
maxfd = cfd;
if(ret == 1) //select return 1 and it is lfd
continue;
}
for(i = lfd+1; i < maxfd+1; i++) {
if(FD_ISSET(i, &rset)) { //first for continue
n = read(i, buf, sizeof(buf));
if(n == 0) {
close(i);
FD_CLR(i, &allset);
} else if(n == -1) {
sys_exit("read error");
}
for(j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
write(i, buf, n);
write(STDOUT_FILENO, buf, n);
}
}
}
return 0;
}
select
缺点:监听上限受文件描述符上限影响
检测满足条件的fd,自己添加业务逻辑提高效率但是提高了编码难度
优点:跨平台
突破1024文件描述符限制方法:
cat /proc/sys/fs/file-max ==> 当前计算机所能打开的最大的文件个数。受硬件影响
ulimit -a ==> 当前用户下的进程,默认打开文件描述符个数
修改: sudo vi /etc/security/limits.conf
* soft nofile 20000
* hard nofile 30000
hard的值要大于soft