高并发服务器-多路IO转接-select
多路IO转接select
概述
多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想是,不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件。
文章目录
- 多路IO转接select
- 概述
- select
- 相关函数
- 辅助函数
- select 优缺点
- 练习
select
-
select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数
-
解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在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: 监控的文件描述符集里最大文件描述符加1readfds: 监控有读数据到达文件描述符集合,传入传出参数writefds: 监控写数据到达文件描述符集合,传入传出参数 NULLexceptfds: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数 NULLtimeout: 定时阻塞监控时间,3种情况 //超时处理。1.NULL,永远等下去2.设置timeval, > 0 等待固定时间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
辅助函数
fd_set set;
// 清空
void FD_ZERO(fd_set *set);
// 将 fd 从 set集合中清除出去。
void FD_CLR(int fd, fd_set *set);
// 将 fd 添加到监听结合 set中
void FD_SET(int fd, fd_set *set);
// 判断 fd 是否在集合set中
int FD_ISSET(int fd, fd_set *set);返回:在:1. 不在:0// 举例:
fd_set rset; // 创建 read事件监听集合
FD_ZERO(&rset); // 清空 读集合
FD_SET(lfd, &rset); // 将 lfd 添加到读集合
FD_SET(cfd1, &rset); // 将 cfd1 添加到读集合
FD_SET(cfd3, &rset); // 将 cfd3 添加到读集合
while (1) {nready = select(cfd3+1, &rset, NULL, NULL, NULL);
}
select 优缺点
-
缺点:
- select 返回值不能描述,具体满足对应事件的描述符是哪一个!
- 需要使用 遍历方法,找寻那个满足事件的 fd。
- 在一个进程中,最多只支持 1024 客户端链接请求。
-
优点:
- 跨平台!win、Linux、MacOs、Unix、类Unix、mips …
-
特性:select 实现多路IO转接并发服务器的性能,并不比 poll、epoll 低。
练习
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>#define SRV_PORT 10086
#define BUF_SIZE 1024
int test01()
{int ret = -1;int lfd = -1;int cfd = -1;int maxfd = 0;char buf[BUF_SIZE];//初始化服务器地址struct sockaddr_in srv_addr;memset(&srv_addr, 0, sizeof(srv_addr));srv_addr.sin_family = AF_INET;srv_addr.sin_port = htons(SRV_PORT);srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);//用于接收客户端地址struct sockaddr_in clt_addr;socklen_t clt_addr_len = sizeof(clt_addr);fd_set rset; // 读集合fd_set allset;//监听集合lfd = socket(AF_INET, SOCK_STREAM, 0);if(-1 == lfd){perror("socket");}bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));listen(lfd, 128);maxfd = lfd;//清空监听集合FD_ZERO(&allset);//将lfd 添加到集合中FD_SET(lfd, &allset);while(1){//rset = allset;ret = select(maxfd+1, &rset, NULL, NULL, NULL); //监听读集合if(ret < 0){perror("select");}//判断lfd 是否在传出的读集合中if(FD_ISSET(lfd, &rset)){//满足都事件cfd = accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len);FD_SET(cfd, &allset);//将通信的cfd 添加到监听集合中if(maxfd < cfd) //更新maxfd{maxfd = cfd;}if(ret == 1) //只有一个fd满足读事件,并且已经处理完成{continue;}}//处理数据通信for(int i = lfd + 1; i < maxfd + 1; i ++){//判断读集合中的 cfdif(FD_ISSET(i, &rset)){ret = read(i, buf, BUF_SIZE);if(0 == ret){ close(i);FD_CLR(i, &allset); //移除已关闭的cfd}for(int j = 0; j < ret; ++j){buf[j] = toupper(buf[j]);}write(i, buf, ret);//输出到屏幕write(STDOUT_FILENO, buf, ret);}}}close(lfd);return 0;
}