Linux网络编程(三)IO复用一 select系统调用

news2024/12/27 10:03:27

I/O复用使得程序能同时监听多个文件描述符。在以下场景中需要使用到IO复用技术:

  • 客户端程序要同时处理多个socket,非阻塞connect技术
  • 客户端程序要同时处理用户输入和网络连接,聊天室程序
  • TCP服务器要同时处理监听socket和连接socket
  • 服务器要同时处理TCP请求和UDP请求,回射服务器
  • 服务器要同时监听多个端口,或者处理多种服务,xinetd服务器

需要指出的是,I/O复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依次处理其中的每一个文件描述符,这使得服务器程序看起来像是串行工作的。如果要实现并发,只能使用多进程或多线程等编程手段。

一、select系统调用

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

select是跨平台的,支持Linux,Max,Windows。

1.1、select API

#include <sys/select.h>

int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
  • nfds参数指定被监听的文件描述符的总数。它通常被设置为select监听的所有文件描述符中的最大值加1,因为文件描述符是从0开始计数的。

  • readfdswritefdsexceptfds 参数分别指向可读、可写和异常等事件对应的文件描述符集合。这3个参数是fd_set结构指针类型。fd_set结构体的定义如下:

    •   #include <typesizes.h>
        #define __FD_SETSIZE 1024
        #include <sys/select.h>
        #define FD_SETSIZE __FD_SETSIZE
        
        typedef long int__fd_mask;
        
        #undef __NFDBITS
        #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结构体仅包含一个整型数组,该数组的每个元素的每一位(bit)标记一个文件描述符。fd_set能容纳的文件描述符数量由FD_SETSIZE指定,这就限制了select能同时处理的文件描述符的总量。下面的函数提供了位操作。

    •   #include <sys/select.h>
        // 将文件描述符fd从set集合中删除 == 将fd对应的标志位设置为0        
        void FD_CLR(int fd, fd_set *set);
        // 判断文件描述符fd是否在set集合中 == 读一下fd对应的标志位到底是0还是1
        int  FD_ISSET(int fd, fd_set *set);
        // 将文件描述符fd添加到set集合中 == 将fd对应的标志位设置为1
        void FD_SET(int fd, fd_set *set);
        // 将set集合中, 所有文件文件描述符对应的标志位设置为0, 集合中没有添加任何文件描述符
        void FD_ZERO(fd_set *set);
      
  • timeout参数用来设置select函数的超时时间。它是一个timeval结构类型的指针,采用指针参数是因为内核将修改它以告诉应用程序select等待了多久。如果给timeout变量的tv_sec成员和tv_usec成员都传递0,则select将立即返回。如果给timeout传递NULL,则select将一直阻塞,直到某个文件描述符就绪。

    •   struct timeval {
            long tv_sec;/*秒数*/
            long tv_usec;/*微秒数*/
        };
      
  • select成功时返回就绪(可读、可写和异常)文件描述符的总数。如果在超时时间内没有任何文件描述符就绪,select将返回0。select失败时返回-1并设置errno。如果在select等待期间,程序接收到信号,则select立即返回-1,并设置errno为EINTR。

1.2、文件描述符就绪条件

下列情况下socket可读:

  • socket内核接收缓存区中的字节数大于或等于其低水位标记SO_RCVLOWAT。此时我们可以无阻塞地读该socket,并且读操作返回的字节数大于0。
  • socket通信的对方关闭连接。此时对该socket的读操作将返回0。
  • socket上有新的连接请求。
  • socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。

下列情况socket可写:

  • socket内核发送缓存区中的可用字节数大于或等于其低水位标记SO_SNDLOWAT。此时我们可以无阻塞地写该socket,并且写操作返回的字节数大于0。
  • socket的写操作被关闭。对写操作被关闭的socket执行写操作将触发一个SIGPIPE信号。
  • socket使用非阻塞connect连接成功或者失败(超时)之后。
  • socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。

1.3、带外数据

socket上接收到普通数据和带外数据都将使select返回,但socket处于不同的就绪状态:前者处于可读状态,后者处于异常状态。

同时接收普通数据和带外数据:

服务端:

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>

#define PORT 8080  
#define BUFFER_SIZE 8192

int main(int argc,char* argv[]) {
    char buf[BUFFER_SIZE];  
    ssize_t bytes_read;  
    
    int server_socket, client_socket;  
    struct sockaddr_in server_addr, client_addr;  
    socklen_t addr_len = sizeof(struct sockaddr_in);  
    // 创建socket  
    server_socket = socket(AF_INET, SOCK_STREAM, 0);  
    if (server_socket == -1) {  
        perror("socket creation failed");  
        exit(EXIT_FAILURE);  
    }  
    
    // 命名socket  
    memset(&server_addr, 0, addr_len);  
    server_addr.sin_family = AF_INET;  
    server_addr.sin_port = htons(PORT);  
    server_addr.sin_addr.s_addr = INADDR_ANY;  
  
    if (bind(server_socket, (struct sockaddr *) &server_addr, addr_len) == -1) {  
        perror("bind failed");  
        close(server_socket);  
        exit(EXIT_FAILURE);  
    }  
  
    // 监听socket  
    if (listen(server_socket, 5) == -1) {  
        perror("listen failed");  
        close(server_socket);  
        exit(EXIT_FAILURE);  
    }  

    printf("Server is listening on port %d...\n", PORT);  
	
    // 连接
	client_socket = accept(server_socket, (struct sockaddr*) &client_addr,&addr_len);
	if (client_socket<0) {
        printf("errno is:%d\n",errno);
        close(server_socket);
    }

    fd_set read_fds;             // 监听可读事件
    fd_set exception_fds;        // 监听异常事件
    FD_ZERO(&read_fds);          // 初始化
    FD_ZERO(&exception_fds);     // 初始化

    while(1) {
		// 初始化buf
		memset(buf,'\0',sizeof(buf));

    	// 每次调用select前都要重新在read_fds和exception_fds中设置文件描述符client_socket,因为事件发生之后,文件描述符集合将被内核修改
        FD_SET(client_socket, &read_fds);
        FD_SET(client_socket, &exception_fds);
        FD_SET(server_socket, &read_fds);
        FD_SET(server_socket, &exception_fds);

    	int ret = select(client_socket+1,&read_fds,NULL,&exception_fds,NULL);
        
        if(ret<0) {
            printf("selection failure\n");
            break;
        }

    	/* 对于可读事件,采用普通的recv函数读取数据 */
        if(FD_ISSET(client_socket, &read_fds)) {
   			ret = recv(client_socket,buf,sizeof(buf)-1,0);
            if(ret<=0) break;
    		printf("get %d bytes of normal data:%s",ret,buf);
	    }

        /* 对于异常事件,采用带MSG_OOB标志的recv函数读取带外数据 */
        else if(FD_ISSET(client_socket, &exception_fds)) {
        	ret = recv(client_socket, buf, sizeof(buf)-1, MSG_OOB);
        	if(ret<=0) break;
        	printf("get %d bytes of oob data:%s",ret,buf);
        }
    }
    close(server_socket);
    close(client_socket);
    return 0;
}

客户端:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <arpa/inet.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
  
#define SERVER_IP "127.0.0.1"  
#define SERVER_PORT 8080  
#define BUFFER_SIZE 1024  
  
int main() {  
    int client_socket;  
    struct sockaddr_in server_addr;  
    char buffer[BUFFER_SIZE];  
    ssize_t bytes_read;  
  
    // 创建socket  
    client_socket = socket(AF_INET, SOCK_STREAM, 0);  
    if (client_socket == -1) {  
        perror("socket creation failed");  
        exit(EXIT_FAILURE);  
    }  
  
    // 设置服务器地址信息  
    memset(&server_addr, 0, sizeof(server_addr));  
    server_addr.sin_family = AF_INET;  
    server_addr.sin_port = htons(SERVER_PORT);  
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {  
        perror("invalid server address");  
        close(client_socket);  
        exit(EXIT_FAILURE);  
    }  
  
    // 连接到服务器  
    if (connect(client_socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) == -1) {  
        perror("connection failed");  
        close(client_socket);  
        exit(EXIT_FAILURE);  
    }  
  
    printf("Connected to server\n");  
  
    // 发送消息给服务器  
    const char *message1 = "Hello, this is client!\n";
    send(client_socket, message1, strlen(message1), 0);  

    sleep(1);

    // 发送带外数据
    const char *message2 = "Hello, this is client and data is oob!\n";
    send(client_socket, message2, strlen(message2), MSG_OOB);  

    // 关闭连接
    close(client_socket);

}

仿真结果

image-20240507210227770

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1651737.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

美国站群服务器的CN2线路在国际互联网通信中的优势?

美国站群服务器的CN2线路在国际互联网通信中的优势? CN2线路&#xff0c;或称中国电信国际二类线路&#xff0c;是中国电信在全球范围内建设的高速骨干网络。这条线路通过海底光缆系统将中国与全球连接起来&#xff0c;为用户提供高速、低延迟的网络服务。CN2线路在国际互联网…

抖音小店是什么?为什么要去做呢?这几点原因告诉你真相!

大家好&#xff0c;我是电商小V 抖音小店就是抖音平台进军电商行业的踏板&#xff0c;也是抖音内的电商购物业务&#xff0c;咱们就可以理解成可以在抖音平台上面卖货&#xff0c;和淘宝&#xff0c;多多店铺&#xff0c;线下超市都是一个性质的&#xff0c;但是运营的模式不同…

C++ | Leetcode C++题解之第75题颜色分类

题目&#xff1a; 题解&#xff1a; class Solution { public:void sortColors(vector<int>& nums) {int n nums.size();int p0 0, p2 n - 1;for (int i 0; i < p2; i) {while (i < p2 && nums[i] 2) {swap(nums[i], nums[p2]);--p2;}if (nums[i…

AI助力制造行业探索创新路径

近期&#xff0c;著名科技作家凯文凯利&#xff08;K.K.&#xff09;来到中国&#xff0c;发表了一场演讲,给广大听众带来了深刻的启示。他在演讲中强调了人工智能&#xff08;AI&#xff09;对全球经济的重大影响&#xff0c;并提出了AI发展的多个观点&#xff1a; AI的多样性…

KAN: Kolmogorov–Arnold Networks

KAN: Kolmogorov–Arnold Networks 论文链接&#xff1a;https://arxiv.org/abs/2404.19756 代码链接&#xff1a;https://github.com/KindXiaoming/pyKAN 项目链接&#xff1a;https://kindxiaoming.github.io/pyKAN/intro.html Abstract 受Kolmogorov-Arnold表示定理的启…

20240508每日后端---聊聊内存溢出报警自动导出堆文件

1-Xms 内存溢出阈值 -Xmx -Xx:printGcDetail 2-xx:heapdumponoutofmemoryError 3-xx:heapdumppath文件输出路径

如何搭建PHP平台圈子是圈子小程序源码一个系统的软件搭建平台促进交流圈子经济运营模式-APP小程序H5前后端源码交付,一次购买,终生使用。

独立自主的专属APP 可上架至Appstore和各家安卓应用市场 接入现有APP 通过标准接口&#xff0c;快速接入企业已有业务APP 微信平台完美兼容 公众号h5、小程序、企业微信均得以支持 版块、分类结构 版块二级分类、版块内帖子主题分类 频道功能 跨版块、自定义选择条件、…

实验11:静态路由和默认路由故障排除(课内实验)

1、实验目的及要求&#xff1a; 掌握静态路由和默认路由故障排除的过程&#xff0c;在基于IPv4和IPv6双协议栈的网络中能够查找相关的配置问题&#xff0c;完成网络故障的分析和排除&#xff0c;进行相关网络联通性的测试。 2、实验设备&#xff1a; 路由器3台、二层交换机3台、…

用AI评估AI,上交大新款大模型部分任务超越GPT-4,模型数据都开源

评估大模型对齐表现最高效的方式是&#xff1f; 在生成式AI趋势里&#xff0c;让大模型回答和人类价值&#xff08;意图&#xff09;一致非常重要&#xff0c;也就是业内常说的对齐&#xff08;Alignment&#xff09;。 “让大模型自己上。” 这是上海交通大学生成式人工智能…

SpringBoot项目配置HTTPS接口的安全访问

参考&#xff1a;https://blog.csdn.net/weixin_45355769/article/details/131727935 安装好openssl后&#xff0c; 创建 D:\certificate CA文件夹下包含&#xff1a; index.txt OpenSSL在创建自签证书时会向该文件里写下索引database.txt OpenSSL会模拟数据库将一些敏感信息…

Flask-大体了解介绍

初识Flask Flask是使用 Python编写的Web微框架。Web框架可以让我们不用关心底层的请求响应处理&#xff0c;更方便高效地编写Web程序。 Flask主要有两个依赖&#xff0c;一个是WSGI&#xff08;Web Server Gateway Interface&#xff0c;Web服务器网关接口&#xff09;工具集…

森林消防新利器:高扬程水泵的革新与应用/恒峰智慧科技

随着全球气候变化的加剧&#xff0c;森林火灾的频发已成为威胁生态安全的重要问题。在森林消防工作中&#xff0c;高效、快速的水源供给设备显得尤为重要。近年来&#xff0c;高扬程水泵的广泛应用&#xff0c;为森林消防工作带来了新的希望与突破。 一、高扬程水泵的技术优势 …

聚观早报 | 比亚迪首款新能源皮卡;苹果M4芯片有望登场

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 5月8日消息 比亚迪首款新能源皮卡 苹果M4芯片有望登场 红旗首款手机外观揭晓 一加13设计细节曝光 长城汽车4月销…

5本书带你走进大厂的云原生世界

大家好&#xff0c;我是王有志&#xff0c;一个分享硬核 Java 技术的金融摸鱼侠&#xff0c;欢迎大家加入 Java 人自己的交流群“共同富裕的 Java 人”。 今天和大家分享的主题是&#xff1a;大厂的云原生架构设计。公众号内回复关键字&#xff1a;【20240508】&#xff0c;即…

AI智能分析赋能EasyCVR视频汇聚平台,为安全生产监管提供保障

一、背景需求 为提升公共及生产安全监管&#xff0c;深入贯彻落实中央关于智慧城市、数字乡村的部署要求&#xff0c;视频设备融合管理已成为视频治理的必然趋势。针对当前部分地区在视频监控系统建设中存在的问题&#xff0c;如重点地区视频监控系统建设零散、视频监控数据孤…

行业新应用:电机驱动将成为机器人的动力核心

电机已经遍布当今社会人们生活的方方面面&#xff0c;不仅应用范围越来越广&#xff0c;更新换代的速度也日益加快。按照工作电源分类&#xff0c;可以将它划分为直流电机和交流电机两大类型。直流电机中&#xff0c;按照线圈类型分类&#xff0c;又可以分为有铁芯的电机、空心…

在Unity中制作和使用图集

文章目录 使用Unity内置Sprite Packer使用图集NGUI图集制作&#xff08;如果使用NGUI&#xff09;TextMeshPro中文支持 应用案例&#xff1a;在Unity中创建一个使用图集的UI界面场景设定步骤概览1. 准备UI元素2. 创建Sprite Atlas3. 使用图集中的Sprite4. 调整与布局5. 动态加载…

Chromium编译指南2024 Windows11篇-获取 Chromium 的源代码(五)

前言 在《Chromium编译指南2024&#xff08;四&#xff09;》中&#xff0c;我们完成了Git 的初始化配置。 现在&#xff0c;我们将进一步讨论如何获取 Chromium 的源代码&#xff0c;并准备构建所需的文件。 1. 获取Chromium的源代码 在合适的位置准备一个文件夹&#xff…

QSplitter分裂器的使用方法

1.QSplitter介绍 QSplitter是Qt框架提供的一个基础窗口控件类&#xff0c;主要用于分割窗口&#xff0c;使用户能够通过拖动分隔条来调节子窗口的大小。 2.QSplitter的添加方法 &#xff08;1&#xff09;通过Qt Creator的界面设计工具添加&#xff1b; &#xff08;2&#xf…

提升滞销商品处理效能,精细化库存管理的关键要素

一、明确滞销商品的概念 1. 什么是滞销商品 滞销商品是指在一定期限内&#xff0c;其销售量大大低于预期或市场需求的商品。具体来说&#xff0c;这些商品可能因为款式不新颖、功能落后、价格不合理、过时、质量不佳或其他因素而不受消费者欢迎&#xff0c;导致销售速度极慢或…