5、select多路IO转接

高并发服务器

多线程/进程服务器实现思路:
server lfd cfd1 cfd2 client1 client2

但是这样做所有客户端直接连服务器占用资源太多,所以有以下另一种方式。
server select lfd client1 client2 client3

​ 使用select来管理客户端。

​ 当客户端有需要连接请求时,才会向select申请。此时select向sever报备,sever调用accept函数
监管read/write client select lfd server 产生cfd

select也称为,响应模式---多路IO转接

1、select参数简介:

cpp 复制代码
int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struvt timeval *time out);
/*
nfds:监听的所有文件描述符当中最大的文件描述符的编号 + 1
在select的内部有一个循环,循环的次数就是文件描述符的上限
fd_set *readfds, fd_set *writefds, fd_set *exceptfds,均是传入传出参数
exceptfds时异常事件
*/

在文件描述符表中,当程序启动时,默认被占用

0 启动被占用
1 启动被占用
2 启动被占用
3 lfd
4 cfd1
5 cfd2
6 cfd3

readfds,writefds:

​ 当客户端发送请求时,lfd需要读这个请求。同理,有时也会给客户端写数据。 fd_set中的set的意思是集合。这两个是传入传出参数,比如writefds有三个{4,5,6},但是如果在监听时只有两个满足写事件,其就会传出{4,5}。

​ select的返回值是指在监听的时候满足时间的个数。

​ 传入和传出的参数可以理解为一个位图:

传入:监控写文件描述符{3,5,6}

0 1 2 3 4 5 6 7 8
0 0 0 1 0 1 1 0 0

传出:监控写文件描述符{5,6}

0 1 2 3 4 5 6 7 8
0 0 0 0 0 1 1 0 0

2、select相关参数分析

cpp 复制代码
void FD_ZERO(fd_set *set); //全部置0
void FD_SET(int fd, fd_set *set);//将待监听的文件描述字加入
void FD_CLR(int fd, fd_set *set);//清楚某一个文件描述符
void DS_ISSET(int fd, fd_set *set);//添加文件描述符
int FD_ISSET(int fd, fd_set *set);//判断一个文件描述字是否在监听集合中
cpp 复制代码
int select(int nfds, fd set *readfds, fd set *writefds, fd_set *exceptfds, struct timeval *timecut);
/*
nds:监听的所有文件描述符中,最大文件描述符+1
传入、传出参数
readfds:读文件描述符监听集合。
writefds,写文件描述符监听集合。传入、传出参数nulL
exceptfds,异常文件描述符监听集合NULL
timeout:
>0:设置监听超时日长
NUII:阻赛监听
0:非阻害监听,轮询
返回值:
>0:所有监听集合 (3个)中, 满对应事件的点数。0:
0:没有满足监听条件的文件描述符0
-1:errno
*/

3、select实现多路IO转接思路

思路伪代码:

cpp 复制代码
lfd = socket();
bind();
listen();
fd_set allset;
fd_set rset;//创建读集合
FD_ZERO(&allrset);//将读监听集合清空
FD_SET(lfd,&allrset);//将lfd添加到读集合当中
while(1)
{
    rset = allset;
	ret = select(lfd+1,&rset,NULL,NULL,NULL);//监听文件描述符集合对应的事件
	if(ret > 0)//有监听的描述满足对应事件
	{
    	if(FD_ISSET(lfd,&rset))//1在,0不在,说明建立完结完成
        {
            cfd = accept();
            FD_SET(cdf,&allset);
        }
        for(i=lfd+1; i < 最大文件描述符;i++)
        {
            FD_ISSET(i,&rset); //有read、write事件
            read();
            toUpper();
            write();  
        }
	}
}

代码:

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<ctype.h>

#define SERV_PORE 8086

int main(int argc, char *argv[])
{
    int i,j,n,nready;
    int cfd;
    int maxfd = 0;
    char buf[BUFSIZ];

    struct sockaddr_in clie_addr,serv_addr;
    socklen_t clie_addr_len;
    int lfd = socket(AF_INET,SOCK_STREAM,0);
    int opt = 1;
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    bzero(&serv_addr,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORE);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
    listen(lfd,128);

    fd_set rset,allset;
    maxfd = lfd;
    FD_ZERO(&allset);
    FD_SET(lfd,&allset);
    while(1)
    {
        rset = allset;
        nready = select(maxfd+1,&rset,NULL,NULL,NULL);
        if(FD_ISSET(lfd,&rset))
        {
            clie_addr_len = sizeof(clie_addr);
            cfd = accept(lfd,(struct sockaddr *)&clie_addr,&clie_addr_len);
            FD_SET(cfd,&allset);//必须设定在拷贝集合当中
            if(maxfd < cfd)
                maxfd = cfd;
            if(0 == --nready)//必须有这一项,保证当只有lfd是会跳过后面的for循环
                continue;
        }
        for(i = lfd+1; i<= maxfd; i++)
        {
            if(FD_ISSET(i,&rset))
            {
                if((n = read(i,buf,sizeof(buf))) == 0)
                {
                    close(i);
                    FD_CLR(i,&allset);
                }
                else if(n > 0)
                {
                    for(j = 0; j < n; j++)
                    {
                        buf[j] = toupper(buf[j]);
                    }
                    write(i,buf,n);
                }
            }
        }
    }
}

4、select优缺点

  1. select监听是使用轮询实现的
  2. 监听受文件描述符限制,最大为1024
  3. 检测满足条件的cfd,只能自己添加业务逻辑,提高了编码难度
  4. 优点:跨平台
相关推荐
小金的学习笔记2 小时前
如何在本地和服务器新建mysql用户和密码
运维·服务器·mysql
EasyNVR2 小时前
EasyRTC智能硬件:实时畅联、沉浸互动、消音护航
运维·服务器·网络·安全·音视频·webrtc·p2p
风口上的猪20153 小时前
thingboard告警信息格式美化
java·服务器·前端
xing.yu.CTF5 小时前
Web入侵实战分析-常见web攻击类应急处置实验2
运维·服务器·windows·web安全·apache·php漏洞·phpstudy后门漏洞
小池先生5 小时前
阿里云子账号管理ECS权限配置全指南
服务器·阿里云·云计算
sekaii7 小时前
ReDistribution plan细节
linux·服务器·数据库
一小路一8 小时前
Go Web 开发基础:从入门到实战
服务器·前端·后端·面试·golang
小金的学习笔记10 小时前
如何在本地和服务器新建Redis用户和密码
服务器·数据库·redis
EasyNVR10 小时前
EasyRTC:全平台支持与自研算法驱动的智能音视频通讯解决方案
运维·服务器·小程序·音视频·webrtc·p2p·智能硬件
红豆和绿豆11 小时前
springboot项目如何部署到tomcat中
服务器·spring boot·tomcat