LinuxI/O多路复用转接服务器——select模型实现

select函数

select系统调用的用途是:在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常等事件。

函数原型

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,检查描述字后立即返回,轮询

返回值:

  成功:返回就绪(可读、可写和异常)文件描述符的总数。如果在超时时间内没有任何文件描述符就绪,select将返回0。

  失败:返回-1并设置errno。如果在select等待期间,程序接收到信号,则select立即返回-1,并设置errno为EINTR。

fd_set结构体

上面select函数中需要用到两个fd_set形参,这个结构体到底做什么用的呢?

  fd_set其实这是一个数组的宏定义,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(socket、文件、管道、设备等)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪个句柄可读。

位操作函数

系统提供了FD_SET, FD_CLR, FD_ISSET, FD_ZERO进行操作,声明如下:

#include <sys/select.h>
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

select实现实现I/O多路复用服务器

实现流程

在这里插入图片描述

程序实现

服务端程序

#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <signal.h>
#include "wrap.h"
using namespace std;
//定义服务端端口号
#define SERVER_PORT  9527


int main (int argc ,char*argv[])
{
    int i,n;
    int client[FD_SETSIZE];//自定义数组,大小为1024
    int nready=0;//保存select函数返回值
    int maxfd=0;//记录最大的文件描述符
    int lfd=0;//用于监听的套接字
    int cfd=0;//用于通信的套接字
    int sockfd=0;
    int maxi;//用于检索客户端文件描述符的下标

    char buf[BUFSIZ];

    //创建套接字
    lfd=Socket(AF_INET,SOCK_STREAM,0);
    //创建地址结构
    struct sockaddr_in server_addr,client_addr;
    socklen_t client_addr_len;
  
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    //初始化
    //memset(&server_addr,0,sizeof(server_addr));//将地址结构清零
    bzero(&server_addr,sizeof(server_addr));

    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(SERVER_PORT);
    server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    //绑定地址结构
    Bind(lfd,(struct sockaddr*)&server_addr,sizeof(server_addr));
    //设置监听
    Listen(lfd,128);
   
    //定义文件描述符集合   读事件文件描述符集合  allset用来存储
    fd_set rset;
    //存放所有可以被监控的文件描述符
    fd_set allset;
    //刚开始只有一个用于连接的文件描述符,故最大的即为lfd
    maxfd=lfd;

    //用于检索客户端文件描述符的下标
    maxi=-1;
    for(i=0;i<FD_SETSIZE;i++)
    {
        //初始化client数组
        client[i]=-1;
    }

    //把文件描述符集合里所有位清0
    FD_ZERO(&allset);
    //把文件描述符集合里lfd位置1
    FD_SET(lfd,&allset);
    //需要循环设置监听
    while(1)
    {
        rset=allset;
        //调用select函数监听文件描述符对应事件
        nready=select(maxfd+1,&rset,NULL,NULL,NULL);
        //检查是否成功返回
        if(nready<0)
        {
            sys_err("select error");
        }
        //判断监听套接字是否在文件描述符集合里
        //如返回为真则说明有新的客户端进行连接请求
        if(FD_ISSET(lfd,&rset))
        {
            client_addr_len=sizeof(client_addr);
            //调用accept函数接收客户端请求
            cfd=Accept(lfd,(struct sockaddr*)&client_addr,&client_addr_len);
            //将返回的文件描述符存入client数组
            for(i=0;i<FD_SETSIZE;i++)
            {
                if(client[i])
                {
                    client[i]=cfd;
                    break;
                }
            }
            //达到监听上限报错  1024
            if(i==FD_SETSIZE)
            {
                cout<<"too many clients"<<endl;
                exit(1);
            }

            //将返回的文件描述符添加到文件描述符集合中
            FD_SET(cfd,&allset);
            //获取最大文件描述符
            if(maxfd<cfd)
                maxfd=cfd;
            //保证maxi存的为数组的最后一个元素下标
            if(i>maxi)
            {
                maxi=i;
            }
            //判断是否只有lfd事件,若为真则只有lfd事件,后续代码不需要执行
            if(0==--nready)
                continue;
        }
            //循环检测哪个客户端数据就绪
        for(i=0;i<maxi;i++)
        {
            if((sockfd=client[i])<0)
            {
                continue;
            }
            //判断文件描述符是否在集合里
            if(FD_ISSET(sockfd,&rset))
            {
                //调用read函数读取数据
                n =Read(sockfd,buf,sizeof(buf));
                //判断是否读到结尾,若是就将该文件描述符关闭,并从文件描述符集合中清除
                if(n==0)
                {
                    Close(sockfd);
                    FD_CLR(sockfd,&allset);
                    client[i]=-1;
                }
                //没有读到结尾,则对读取到的数据完成大小写转换
                else if(n>0)
                {
                    for(int j=0;j<n;j++)
                    {
                        //大小写转换
                        buf[j]=toupper(buf[j]);
                    }
                //写回buf
                Write(i,buf,n);
                //写到屏幕输出
                Write(STDOUT_FILENO, buf, n); 
                }
            }
        }
    }
    Close(lfd);
    return 0;
}

客户端程序

多进程客户端程序

运行结果

服务端:
在这里插入图片描述
客户端:
在这里插入图片描述
在这里插入图片描述

select使用优缺点

优点:

  1. 跨平台使用

缺点:

  1. select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个。
  2. 解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力。
  3. 检测满足条件的文件描述符,编码难度增加。

select、poll、epoll对比分析

select、poll、epoll对比分析

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值