select函数、I/O复用、并发服务器

select函数、I/O复用、并发服务器

select函数原型

c 复制代码
extern int select (int __nfds, fd_set *__restrict __readfds,
		   fd_set *__restrict __writefds,
		   fd_set *__restrict __exceptfds,
		   struct timeval *__restrict __timeout);

参数介绍:

  1. int __nfds:
  • 这是需要监视的文件描述符集合中最大的文件描述符值加1。因为文件描述符是从0开始分配的,所以如果你有文件描述符3,那么你实际上是在监视0, 1, 2, 和3这四个文件描述符(尽管可能其中一些并没有被使用)。因此,这个参数应该设置为4(即最大文件描述符+1)。
    操作fe_set集合统一的宏来处理:
    • FD_SET(fd, &set): 将文件描述符fd添加到set集合中。
    • FD_CLR(fd, &set): 从set集合中删除文件描述符fd。
    • FD_ISSET(fd, &set): 检查fd是否在set集合中。
    • FD_ZERO(&set): 清空set集合。
  1. fd_set *__restrict __readfds:
  • 这是一个指向文件描述符集合的指针,该集合中的文件描述符被监视以查看它们是否可读。如果不需要监视任何文件描述符是否可读,这个参数可以设置为NULL。
  1. fd_set *__restrict __writefds:
  • 类似于 __readfds,这个参数指向一个文件描述符集合,集合中的文件描述符被监视以查看它们是否可写。如果不需要监视任何文件描述符是否可写,这个参数可以设置为NULL。
  1. fd_set *__restrict __exceptfds:
  • 这也是一个指向文件描述符集合的指针,集合中的文件描述符被监视以查看是否有异常条件发生(比如带外数据到达)。如果不需要监视任何文件描述符的异常条件,这个参数可以设置为NULL。
  1. struct timeval *__restrict __timeout:
  • 这是一个指向 timeval 结构体的指针,该结构体指定了 select 函数等待的最长时间。如果设置为NULL,select 将无限期地等待,直到某个文件描述符准备好。
  • timeval 结构体通常包含两个成员:tv_sec(秒)和 tv_usec(微秒),用于指定时间间隔。
  1. 返回值:
  • select 函数返回准备好的文件描述符的总数(即可读、可写或有异常条件的文件描述符总数)。
  • 如果返回-1,则表示发生了错误(此时应该检查 errno 来确定错误的具体原因)。
  • 如果返回0,这表示超时。

可以直接看使用实例:可以使用任意客户端代码进行测试

c 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/select.h>

#define LISTEN_NUM 10

void error_handing(char *message)
{
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}
int main(int argc, char const *argv[])
{
    //步骤1:创建套接字
    int server_sock;
    server_sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
    if (server_sock == -1)
    {
        error_handing("socket() error");
    }
    
    //步骤2:绑定监听端口
    struct sockaddr_in server_addr;
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(atoi("3333"));
    if (bind(server_sock,(struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
    {
        error_handing("bind() error");
    }

    //步骤3:开启监听
    if (listen(server_sock,LISTEN_NUM) == -1)
    {
        error_handing("listen() error");
    }

    //步骤4:做select调用之前的准备

    //步骤4.1:初始化sock 集合
    fd_set fd_reads,fd_copy;//经过select函数之后,需要比较fd_set中的位是否改变,所以这里总是需要两个fd_set变量
    FD_ZERO(&fd_reads);
    
    //步骤4.2:将server_sock加入到集合中
    FD_SET(server_sock,&fd_reads);

    //步骤4.3:初始化最大值
    int fd_max = server_sock;//定义最大的socket
    while (1)
    {
        fd_copy = fd_reads;

        //步骤4.4:设置超时时间
        struct timeval time_out;
        time_out.tv_sec = 5;
        
        //步骤5:调用select函数,讨论其返回值
        int activity_sockets = select(fd_max + 1,&fd_copy,NULL,NULL,&time_out);

        if (activity_sockets == -1)//发生错误,退出
        {
            error_handing("select() error");
        }
        else if (activity_sockets == 0)//超时
        {
            //选择继续运行
            printf("Time out.\n");
            continue;
        }

        for (size_t i = 0; i < fd_max + 1; i++)//比较是否对应的事件位是否改变
        {
            if (FD_ISSET(i,&fd_copy))//说明i事件活动了
            {   
                if(i == server_sock)//说明有新的客户端接入
                {
                    printf("New client connected...\n");
                    struct sockaddr client_addr;
                    int sizeof_client_addr = sizeof(client_addr);
                    int client_sock = accept(server_sock,&client_addr,&sizeof_client_addr);

                    //将客户端的socket加入到总的fd_set中,就可以使用select查询处理
                    FD_SET(client_sock,&fd_reads);
                    
                    //更新最大值,因为select监听的sockt是从0~fd_max  
                    fd_max = (client_sock > fd_max) ? client_sock : fd_max;
                }
                else//说明有客户端数据进来
                {
                    const int message_size = 30;
                    char message[message_size];

                    int read_size;
                    read_size = read(i,message,message_size);

                    if (read_size == 0)//说明客户端请求关闭连接
                    {
                        printf("Close client...\n");
                        FD_CLR(i,&fd_reads);//踢出监控集合
                        close(i);//再关闭连接
                    }
                    else//收到数据,选择给它回发过去
                    {
                        message[read_size] = '\0';
                        printf("Receive message is %s.\n",message);
                        write(i,message,read_size);
                    }
                }
            } 
        }  
    }

    close(server_sock);
    return 0;
}
相关推荐
m0_6299583414 分钟前
TCP编程-socket(套接字)编程实战1
网络·网络协议·tcp/ip
C++忠实粉丝34 分钟前
Linux系统基础-多线程超详细讲解(5)_单例模式与线程池
linux·运维·服务器·c++·算法·单例模式·职场和发展
zhuyan10842 分钟前
【VMware】使用笔记
服务器
华纳云IDC服务商1 小时前
CentOS系统中查看内网端口映射的多种方法
linux·运维·centos
EasyCVR2 小时前
EHOME视频平台EasyCVR萤石设备视频接入平台视频诊断技术可以识别哪些视频质量问题?
服务器·人工智能·计算机视觉·音视频·1024程序员节
中云DDoS CC防护蔡蔡2 小时前
棋牌游戏防ddos攻击,高防IP好用吗?
运维·服务器·游戏·网络安全·ddos
gengjianchun2 小时前
clickhouse 安装配置
服务器·网络·clickhouse
光芒再现dev2 小时前
CentOS—OpenEulerOS系统联网指南
linux·运维·centos
蓝莓星冰乐2 小时前
Linux入门(2)
linux·运维·服务器