26-LINUX--I/O复用-select

一.I/O复用概述

/O复用使得多个程序能够同时监听多个文件描述符,对提高程序的性能有很大帮助。以下情况适用于I/O复用技术:
◼ TCP 服务器同时要处理监听套接字和连接套接字。
◼ 服务器要同时处理 TCP 请求和 UDP 请求。
◼ 程序要同时处理多个套接字。
◼ 客户端程序要同时处理用户输入和网络连接。
◼ 服务器要同时监听多个端口
需要指出的是,I/O 复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当
多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依处理其中的每一
个文件描述符,这使得服务器看起来好像是串行工作的。如果要提高并发处理的能力,可以
配合使用多线程或多进程等编程方法

二.select机制

1.select接口介绍

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

 #include <sys/select.h>

 int select(int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct ti
meval *timeout);
 /*
 select 成功时返回就绪(可读、可写和异常)文件描述符的总数。如果在超时时间内
没有任何文件描述符就绪,select 将返回 0。select 失败是返回-1.如果在 select 等待
期间,程序接收到信号,则 select 立即返回-1,并设置 errno 为 EINTR。

 maxfd 参数指定的被监听的文件描述符的总数。它通常被设置为 select 监听的所
有文件描述符中的最大值+1
 readfds、writefds 和 exceptfds 参数分别指向可读、可写和异常等事件对应的文件
描述符集合。应用程序调用 select 函数时,通过这 3 个参数传入自己感兴趣的文件
描述符。select 返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪
fd_set 结构如下:
 #define __FD_SETSIZE 1024
 typedef long int __fd_mask;
 #define __NFDBITS (8 * (int) sizeof (__fd_mask))
 typedef struct
 {
 #ifdef __USE_XOPEN
 __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
 # define __FDS_BITS(set) ((set)->fds_bits)
 #else
 __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
 # define __FDS_BITS(set) ((set)->__fds_bits)
 #endif
 } fd_set;
 通过下列宏可以访问 fd_set 结构中的位:
 FD_ZERO(fd_set *fdset); // 清除 fdset 的所有位
 FD_SET(int fd, fd_set *fdset); // 设置 fdset 的位 fd

 FD_CLR(int fd, fd_set *fdset); // 清除 fdset 的位 fd
 int FD_ISSET(int fd, fd_set *fdset);// 测试 fdset 的位 fd 是否被设置
 timeout 参数用来设置 select 函数的超时时间。它是一个 timeval 结构类型的指
 针,采用指针参数是因为内核将修改它以告诉应用程序 select 等待了多久。timeval
 结构的定义如下:
 struct timeval
 {
 long tv_sec; //秒数
 long tv_usec; // 微秒数
 };//struct timeval tv = {5,0};
 如果给 timeout 的两个成员都是 0,则 select 将立即返回。如果 timeout 传递
NULL,则 select 将一直阻塞,直到某个文件描述符就绪
 */

2.设计思路图解

3.测试代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/select.h>
#include<time.h>

#define STDIN 0
int main()
{
        int fd = STDIN;//键盘
        fd_set fdset;//集合,收集描述符

        while(1)//因为不止检测一次
        {
                FD_ZERO(&fdset);//清空集合,每个位置0:FD_ZERO
                FD_SET(fd,&fdset);//将描述符fd添加到集合fdset

                struct timeval tv = {5,0};//超时时间

                int n = select(fd+1,&fdset,NULL,NULL,&tv);//可能阻塞
                if(n ==-1)//select执行失败
                {
                        printf("select err\n");
                }
                else if(n==0)//超市,没有找到可用事件描述符
                {
                        printf("tme out\n");
                }
                else
                {
                        if(FD_ISSET(fd,&fdset))
                        {
                                char buff[128]={0};
                                int num = read(fd,buff,127);
                                printf("num=%d,buff=%s\n",num,buff);
                        }
                }

        }
}
~                                                                                                                                                                                            
~                                                                                                                                                                                            
~                         

4.tcp通过select实现并发连接

SER.C

#include<stdio.h>      // 标准输入输出库
#include<stdlib.h>     // 标准库,提供一些函数如malloc, free, rand等
#include<string.h>    // 字符串操作库
#include<unistd.h>    // UNIX标准函数库
#include<sys/select.h>// 选择库,提供select函数
#include<time.h>      // 时间库
#include<sys/socket.h>// 套接字库
#include<arpa/inet.h> // 提供inet_addr等函数
#include<netinet/in.h>// 提供一些网络相关的宏

#define MAXFD 10       // 定义最大文件描述符数量

// 初始化socket函数
int socket_init();

// 初始化文件描述符数组
void fds_init(int fds[]){
    for(int i=0; i<MAXFD; i++){
        fds[i] = -1; // 将所有文件描述符初始化为-1,表示未被使用
    }
}

// 将新的文件描述符添加到数组中
void fds_add(int fds[], int fd){
    for(int i=0; i<MAXFD; i++){
        if(fds[i] == -1){ // 找到数组中第一个未使用的文件描述符位置
            fds[i] = fd;  // 添加文件描述符
            break;        // 退出循环
        }
    }
}

// 从未使用的文件描述符数组中删除指定的文件描述符
void fds_del(int fds[], int fd){
    for(int i=0; i<MAXFD; i++){
        if(fds[i] == fd){ // 找到要删除的文件描述符
            fds[i] = -1;   // 将其设置为-1,表示未使用
            break;         // 退出循环
        }
    }
}

// 接受客户端连接请求并添加到文件描述符数组
void accept_client(int sockfd, int fds[]){
    int c = accept(sockfd, NULL, NULL); // 接受连接
    if(c < 0){
        return; // 如果返回-1,表示出错
    }
    printf("accept c = %d\n", c);
    fds_add(fds, c); // 添加到文件描述符数组
}

// 接收客户端发送的数据
void recv_date(int c, int fds[]){
    char buff[128] = {0}; // 创建缓冲区
    int n = recv(c, buff, 127, 0); // 接收数据
    if(n < 0){
        printf("cli close\n");
        close(c);          // 如果接收失败,关闭连接
        fds_del(fds, c);   // 从数组中删除该文件描述符
        return;
    }
    if(n == 0){
        printf("time out(%d)\n", n); // 如果超时
    }

    printf("buff(c=%d)=%s\n", c, buff); // 打印接收到的数据
    send(c, "ok", 2, 0);             // 发送确认消息
}

// 主函数
int main(){
    int sockfd = socket_init(); // 初始化socket
    if(sockfd == -1){
        exit(1); // 如果初始化失败,退出程序
    }

    int fds[MAXFD]; // 文件描述符数组
    fds_init(fds);  // 初始化数组
    fds_add(fds, sockfd); // 将监听的socket添加到数组

    fd_set fdset; // 创建文件描述符集合
    while(1){ // 无限循环等待事件
        FD_ZERO(&fdset); // 清空文件描述符集合
        int maxfd = -1; // 存储最大的文件描述符

        // 遍历文件描述符数组,将所有文件描述符添加到集合中
        for(int i=0; i<MAXFD; i++){
            if(fds[i] == -1){
                continue; // 如果文件描述符未使用,跳过
            }
            FD_SET(fds[i], &fdset); // 添加到集合
            if(fds[i] > maxfd){ // 更新最大文件描述符
                maxfd = fds[i];
            }
        }
        struct timeval tv = {5,0}; // 设置超时时间

        // 使用select等待直到有文件描述符准备好IO操作或超时
        int n = select(maxfd+1, &fdset, NULL, NULL, &tv);
        if(n == -1){
            printf("select err\n"); // 错误
        } else if(n == 0){
            printf("time out\n"); // 超时
        } else{
            // 遍历文件描述符数组,检查哪些文件描述符准备好了IO操作
            for(int i=0; i<MAXFD; i++){
                if(fds[i] == -1){
                    continue; // 如果文件描述符未使用,跳过
                }
                if(FD_ISSET(fds[i], &fdset)){ // 检查文件描述符是否被设置
                    if(fds[i] == sockfd){ // 如果是监听的socket
                        accept_client(sockfd, fds); // 接受新的连接
                    } else{ // 如果是已连接的客户端
                        recv_date(fds[i], fds); // 接收数据
                    }
                }
            }
        }
    }
}

// 创建socket并绑定到端口
int socket_init(){
    int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建socket
    if(sockfd == -1){
        return -1; // 创建失败返回-1
    }
    struct sockaddr_in saddr; // 服务器地址结构
    memset(&saddr, 0, sizeof(saddr)); // 清零
    saddr.sin_family = AF_INET; // 地址族
    saddr.sin_port = htons(6000); // 端口
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // IP地址

    int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)); // 绑定
    if(res == -1){
        printf("bind err\n");
        return -1; // 绑定失败返回-1
    }
    if(listen(sockfd, 5) == -1){ // 开始监听,设置队列长度为5
        return -1; // 监听失败返回-1
    }
    return sockfd; // 返回socket文件描述符
}

CLI.C

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int main()
{
        int sockfd = socket(AF_INET,SOCK_STREAM,0);
        if(sockfd == -1)
        {
                exit(1);
        }
        struct sockaddr_in saddr;//代表服务器的端口
        memset(&saddr,0,sizeof(saddr));
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(6000);
        saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

        int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
        if(res == -1)
        {
                printf("connct err\n");
                exit(1);
        }

        while(1)
        {
                printf("input: ");
                char buff[128]={0};
                fgets(buff,128,stdin);
                if(strncmp(buff,"end",3)==0)
                {
                        break;
                }
                send(sockfd,buff,strlen(buff)-1,0);
                memset(buff,0,128);
                recv(sockfd,buff,127,0);
                printf("buff=%s\n",buff);
        }
        close(sockfd);
        exit(0);
}
相关推荐
C++忠实粉丝25 分钟前
Linux环境基础开发工具使用(2)
linux·运维·服务器
康熙38bdc1 小时前
Linux 环境变量
linux·运维·服务器
存储服务专家StorageExpert1 小时前
DELL SC compellent存储的四种访问方式
运维·服务器·存储维护·emc存储
hakesashou2 小时前
python如何比较字符串
linux·开发语言·python
Ljubim.te2 小时前
Linux基于CentOS学习【进程状态】【进程优先级】【调度与切换】【进程挂起】【进程饥饿】
linux·学习·centos
cooldream20092 小时前
Linux性能调优技巧
linux
大G哥2 小时前
记一次K8S 环境应用nginx stable-alpine 解析内部域名失败排查思路
运维·nginx·云原生·容器·kubernetes
长天一色2 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
醉颜凉2 小时前
银河麒麟桌面操作系统修改默认Shell为Bash
运维·服务器·开发语言·bash·kylin·国产化·银河麒麟操作系统
QMCY_jason3 小时前
Ubuntu 安装RUST
linux·ubuntu·rust