select实现多路IO转接
Posted milaiko
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了select实现多路IO转接相关的知识,希望对你有一定的参考价值。
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);
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);
参数介绍
nfds
最大文件描述符+1, – 需要测试文件描述符的个数
readfds
需要观测的文件描述符集合, 观测其中文件描述符有没有read的事件发生,如果没有,就会在这个文件描述符集合删去(位图设为0)
writefd
和readfds
类似,观测其中文件描述符有没有write的事件发生,如果没有,就会在这个文件描述符集合删去(位图设为0)
exceptfds
和上面类似,观测其中文件描述符有没有异常的事件发生,如果没有,就会在这个文件描述符集合删去(位图设为0)
timeval
是用来告知内核等待所指定的描述符的任何一个就绪可花多长时间。
- NULL 说明内核会一直等下去
- 固定时间 设置好timeval结果的秒和微秒
- 0 说明内核会一直询问(根本不等待),也就是轮询
其中FD_CLR
FD_ISSET
, FD_SET
和 FD_ZERO
分别表示
- 从set里面clear某个文件描述符, 设置某个文件描述符为0
- 判断set里面有没有某个文件描述符
- 在set里面设置某个文件描述符为1
- 将set集合全部清零
返回值
该函数返回值表示跨所有描述符集的已就绪的总位数,如果在任何文件描述符就绪之前定时器到时, 则返回0;如果出错返回-1。
用select实现服务器IO转接
服务器常规步骤
- 先使用socket创建监听套接字
- 设置setsockopt实现端口复用
- 设置服务器结构体 sockaddr_in ,传入参数时强转为sockaddr
- 使用Bind绑定服务器地址结构
- 开始监听
设置fdset
我们现在只对读和异常这两个事件进行监视
- 定义
fd_set
rset
,exception_set
, 并使用FD_ZERO
函数将这两个fdset清零 - 将connfd放入对应的文件描述符集合中
rset
和excepiton_set
- 使用select监听用户感兴趣的文件描述符(connfd )的可读事件和异常事件
- 利用
FD_ISSET
来对可读事件进行处理
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<ctype.h>
#include<pthread.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include"wrap.h"
const int PORT = 8888;
int main(int argc, char* argv[])
int i,j,n,maxi;
int nready, client[FD_SETSIZE]; //FD-SERSIZE是1024,防止遍历1024个文件描述符
int maxfd, listenfd, connfd, sockfd;
char buf[BUFSIZ], str[INET_ADDRSTRLEN];
struct sockaddr_in cli_addr, serv_addr;
socklen_t cli_addr_len;
listenfd = Socket(AF_INET, SOCK_STREAM, 0); //创建监听套接字
// 端口复用
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
//设置服务器地址结构体
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
//将服务器地址和套接字绑定
Bind(listenfd,(struct sockaddr*)&serv_addr, sizeof(serv_addr));
//开始监听
Listen(listenfd, 128);
printf("Accpeting connetion");
// maxfd 是最后一个(最大一个)文件描述符, maxi是对应的下标
maxfd = listenfd;
maxi = -1;
for(i = 0;i<FD_SETSIZE;i++) //初始化client数组
client[i] = -1;
fd_set rset, allset; //rset:读事件文件描述符集合, allset用来暂存
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
while(1)
rset = allset; //rset 一开始是用来监视listenfd套接字的, 备份
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if(nready < 0)
perr_exit("select error");
if(FD_ISSET(listenfd, &rset)) //if listenfd had data, start response, listenfd满足监听的读事件
cli_addr_len = sizeof(cli_addr);
connfd = Accept(listenfd, (struct sockaddr*)&cli_addr, &cli_addr_len); //建立连接(不会阻塞, 因为已经有了就绪事件)
printf("received from %s at port %d\\n",
inet_ntop(AF_INET, &cli_addr.sin_addr, str, sizeof(str)),
ntohs(cli_addr.sin_port));
for(i=0;i<FD_SETSIZE;i++)
if(client[i] < 0) //找client没有使用过的位置,感觉逻辑有问题,client数组默认值小于0?--- 所以要在前面把它设为-1
client[i] = connfd; //保存accept返回的文件描述符到client[]里面
break;
if(i == FD_SETSIZE) //i 到达文件描述符上限 1024
fputs("too many clients\\n", stderr);
exit(1);
// 将connfd放进测试set里
FD_SET(connfd, &allset);
if(connfd > maxfd) //修改maxfd
maxfd = connfd;
if(i > maxi) //maxi用来存放最后一个文件描述符的下标
maxi = i;
if(--nready == 0)
continue;
// 检测哪个文件描述符 有数据就绪
for(i=0;i<=maxi;i++) //这里必须设为小于等于,不然第一个创建的套接字下标为0,这个循环将不被执行,也就没法执行读写操作了。
if((sockfd = client[i]) < 0)
continue;
if(FD_ISSET(sockfd, &rset)) //检查就绪
if((n = Read(sockfd, buf, sizeof(buf))) == 0) //检查到客户端关闭连接。
Close(sockfd);
FD_CLR(sockfd, &allset);
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;
Close(listenfd);
return 0;
总结
使用select写的服务器和普通的多进程服务器有什么区别?
- 多进程服务器
- 使用IO转接的服务器
步骤二:当客户端连接后,有读写就绪事件时, select通知server调用accept去创建cfd套接字。
select的缺点?
受监听上限文件描述符限制,最大1024个,当出现了5000多个客户端需要使用多线程
检测满足条件的fd, 自己添加业务逻辑提高效率, 增加了编码难度。否则就要轮询。
优点:
跨平台。win、linux、macos都支持select的多路IO转接
以上是关于select实现多路IO转接的主要内容,如果未能解决你的问题,请参考以下文章