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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【JAVA |数组】数组定义与使用、常见的Arrays类介绍

目录 一、前言 二、数组的创建和初始化 三、数组的使用 四、数组是引用类型 1.JVM的内存分配 2.与引用类型变量 3.null 五、二维数组 六、Java中Arrays类的常用方法 1. Arrays.fill ->填充数组 2. Arrays.sort ->数组排序 3. Arrays.toString ->数组打印 …

数据中台:企业数字化转型的驱动力量_光点科技

在当今数字化快速发展的时代&#xff0c;企业正积极寻求转型升级的新路径。在这个过程中&#xff0c;数据中台以其独特的功能和价值&#xff0c;逐渐成为了企业数字化转型的关键驱动力。本文将深入探讨数据中台的角色、架构及其在企业中的应用&#xff0c;以期为企业的数字化转…

十个数据安全最佳实践:保护数据的简单方法

在德迅云安全将介绍数据安全的主要原则&#xff0c;并了解适用于大多数行业的 10 种数据安全最佳实践&#xff0c;以及云端安全检测的重要性。 数据威胁和维护数据安全的好处 什么是数据安全&#xff1f; 数据安全是旨在保护组织敏感资产的流程和工具的组合。有价值的数据在…

多核DSP并行计算跨平台通信解决方案

并行计算的核心是计算节点以及节点间的通信与协调机制。OpenMP虽然给开发者提供了极易上手的增量式开发方式&#xff0c;但是OpenMP在与复杂架构的MCSDK结合后&#xff0c;工具与代码产生了大量不可调试的黑盒子&#xff0c;更是决定了它不能用于关键任务领域&#xff0c;如军工…

C语言(指针)1

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸各位能阅读我的文章&#xff0c;诚请评论指点&#xff0c;关注收藏&#xff0c;欢迎欢迎~~ &#x1f4a5;个人主页&#xff1a;小羊在奋斗 &#x1f4a5;所属专栏&#xff1a;C语言 本系列文章为个人学习笔记&#x…

用python写个控制MicroSIP自动拨号和定时呼叫功能(可用在小型酒店叫醒服务)

首先直接上结果吧&#xff0c;MicroSIP 助手&#xff0c;控制MicroSIP自动拨号&#xff0c;定时呼叫的非常实用小工具&#xff01; 在使用MicroSIP 助手之前&#xff0c;我们需要了解MicroSIP是什么&#xff0c;MicroSIP是一个SIP拨号软件&#xff0c;支持注册任意SIP平台实现拨…

Linux学习笔记:信号

信号 在Linux中什么是信号信号的产生方式硬件产生的信号软件产生的信号异常产生的信号 进程对信号的处理信号的保存信号方法更改函数signal信号处理的更改恢复默认信号忽略 信号的管理信号集 sigset_t对信号集的操作 信号的捕捉过程 在Linux中什么是信号 在 Linux 系统中&…

Python中tkinter编程入门1

1 tkinter库简介 tkinter是Python的标准库&#xff0c;用来进行GUI&#xff08;Graphical User Interface&#xff0c;图形用户界面&#xff09;编程。 2 导入tkinter库 tkinter是Python默认的GUI库&#xff0c;因此&#xff0c;IDLE中已经包含了该库&#xff0c;使用时无需…

在uniapp中如何安装axios并解决跨域问题

目录 1、安装axios 2、导入 3、使用&#xff08;发请求&#xff09; 2.解决跨域问题 1.为什么要解决跨域问题&#xff1f; 2.前端如何解决跨域问题&#xff1f; 1、安装axios npm install axios 2、导入 在main.js中导入使用 import axios from axios; // 创建一个名…

男士内裤什么品牌质量好?男士内裤选购指南攻略分享

有很多小伙伴认为男士内裤只是穿在里面的&#xff0c;只要能穿就不讲究了。但实际上选择一些质量不好的男士内裤会让穿着舒适性十分不佳&#xff0c;同时还会因为不具备抗菌效果而滋生细菌&#xff0c;导致出现健康问题。 最近我也是深入研究了一番关于男士内裤&#xff0c;今天…

旺店通·企业奇门与金蝶云星空对接集成订单查询打通销售订单新增

旺店通企业奇门与金蝶云星空对接集成订单查询打通销售订单新增 对接源平台:旺店通企业奇门 慧策最先以旺店通ERP切入商家核心管理痛点——订单管理&#xff0c;之后围绕电商经营管理中的核心管理诉求&#xff0c;先后布局流量获取、会员管理、仓库管理等其他重要经营模块。慧策…

该怎么发外贸开发信才能瞄准大客户?

1.要知道80%的业务源自于大客户&#xff0c;要合理利用自己的时间。其实我自己发邮件一直都是粗发模式&#xff0c;效果也还可以&#xff0c;主要是因为我的客户都是展会上的&#xff0c;所以拒收和失败率会很低&#xff0c;而且客户意向度一直很高&#xff0c;但是花费的时间精…

泉州晋江厦门拉货最便宜的7个方式,建议收藏

众所周知&#xff0c;搬家、拉货的时间长、距离长&#xff0c;运费也比较贵。面对不菲的费用&#xff0c;很多人会比较谨慎&#xff0c;先网上搜搬家攻略&#xff0c;一番对比以后&#xff0c;找到最便宜的运输方式。那怎么运输最便宜最放心呢&#xff1f; 方式一&#xff1a;找…

[MDK] 介绍STM32使用C和C++混合编程的方法

目录 [MDK] 介绍STM32使用C和C混合编程的方法前言业务场景步骤1基础工程步骤2写代码步骤3添加cpp文件步骤4配置与编译上机现象后记 [MDK] 介绍STM32使用C和C混合编程的方法 前言 搞单片机编程大多数还是使用MDK编程&#xff0c;自己对MDK这个软件也比较熟悉&#xff0c;在网络…

【RAG 论文】Dense X 检索:将“命题”作为检索粒度

论文&#xff1a;Dense X Retrieval: What Retrieval Granularity Should We Use? ⭐⭐⭐⭐ Code: github.com/ct123098/factoid-wiki 文章目录 一、论文速读二、命题&#xff08;Proposition&#xff09;三、FactoidWiki四、实验及分析4.1 Passage Retrieval 任务4.2 Open-Do…

前端css中径向渐变(radial-gradient)的使用

前端css中径向渐变的使用 一、前言二、主要内容说明&#xff08;一&#xff09;、径向渐变的形状1.椭圆形渐变&#xff08;ellipse&#xff09;&#xff0c;源码12.源码1运行效果3.圆形渐变&#xff08;circle&#xff09;&#xff0c;源码24.源码2运行效果 &#xff08;二&…

遇到螺纹连接过程中的软连接,怎么办?——SunTorque智能扭矩系统

智能扭矩系统-智能拧紧系统-扭矩自动控制系统-SunTorque 在螺纹连接过程中遇到软连接时&#xff0c;首先需要明确软连接的概念及其特点。软连接通常指的是在螺栓拧紧过程中&#xff0c;由于紧固件与被连接件之间的材料、表面状况或装配工艺等因素&#xff0c;导致拧紧力矩不能…

分布式关系型数据库管理系统 OceanBase 安装和配置教程

&#x1f30a; 分布式关系型数据库管理系统 OceanBase 安装和配置教程 &#x1f680; 博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝…

Kubernetes核心概念基本操作

1.1 Namespace命名空间 1.1.1 Namespace核心概念 Kubernetes 的 Namespace&#xff08;命名空间&#xff09;是一种用于创建逻辑隔离分区的机制&#xff0c;它的主要作用是用来实现多套环境的资源隔&#xff0c;它允许用户在同一个物理集群中模拟出多个虚拟集群的效果。以下是…

APP广告变现:自刷的秘密与规则

在移动互联网时代&#xff0c;广告已成为众多APP盈利的主要方式之一。对于开发者和运营者而言&#xff0c;如何通过广告变现提高收益是他们必须关注的问题。然而&#xff0c;在众多的变现方法中&#xff0c;“自刷广告”这一概念可能让一些人感到迷惑。实际上&#xff0c;只要在…