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_SETSIZEtypedef 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还是1int  FD_ISSET(int fd, fd_set *set);// 将文件描述符fd添加到set集合中 == 将fd对应的标志位设置为1void 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 8192int 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) {// 初始化bufmemset(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://xiahunao.cn/news/3018896.html

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

相关文章

算法学习:数组 vs 链表

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 &#x1f3af; 引言&#x1f6e0;️ 内存基础什么是内存❓内存的工作原理 &#x1f3af; &#x1f4e6; 数组&#xff08;Array&#xff09;&#x1f4d6; 什么是数组&#x1f300; 数组的存储&#x1f4dd; 示例代码&#…

运放的同相与反相放大

反相放大器 同相端接地&#xff0c;电压为 0&#xff0c;反相端和同相端虚短&#xff0c;因此也是 0 V 的电压&#xff0c;同时由于虚断&#xff0c;几乎没有电流注入&#xff0c;所以R 1 和R 2 相当于串联&#xff0c;电阻上的电流相等 因此可以求出输入输出关系式为 V o u t…

excel如何将多列数据转换为一列?

这个数据整理借用数据透视表也可以做到&#xff1a; 1.先将数据源的表头补齐&#xff0c;“姓名” 2.点击插入选项卡&#xff0c;数据透视表&#xff0c;在弹出对话框中&#xff0c;数据透视位置选择 现有工作表&#xff0c;&#xff08;实际使用时新建也没有问题&#xff09;…

免费分享一套微信小程序在线订餐(点餐)配送系统(SpringBoot+Vue),帅呆了~~

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序在线订餐(点餐)配送系统(SpringBootVue)&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序在线订餐(点餐)配送系统(SpringBootVue) Java毕业设计_哔哩哔哩_bilibili【免费】微信小程序在…

每日OJ题_贪心算法三⑥_力扣738. 单调递增的数字

目录 力扣738. 单调递增的数字 解析代码 力扣738. 单调递增的数字 738. 单调递增的数字 难度 中等 当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。 给定一个整数 n &#xff0c;返回 小于或等于 n 的最大数字&#xff0…

集成学习案例-幸福感预测

集成学习案例一 &#xff08;幸福感预测&#xff09; 背景介绍 此案例是一个数据挖掘类型的比赛——幸福感预测的baseline。比赛的数据使用的是官方的《中国综合社会调查&#xff08;CGSS&#xff09;》文件中的调查结果中的数据&#xff0c;其共包含有139个维度的特征&#xf…

哪款充电宝质量和口碑比较好?适合入手充电宝有哪些?

充电宝这么好用的移动电源就不用我说了吧&#xff0c;平时不出门还好&#xff0c;家里有插座可以充电&#xff0c;但是但凡出门了&#xff0c;手机电量一般是不能够支撑到&#xff0c;像我这种手机重度使用者&#xff0c;出门在外手机快没电了我就非常焦虑了&#xff0c;有一款…

五一 大项目

Docker 中的 Nginx 服务为什么要启用 HTTPS 一安装容器 1 安装docker-20.10.17 2 安装所需的依赖 sudo yum install -y yum-utils device-mapper-persistent-data lvm23 添加Docker官方仓库 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos…

Python pypdf库:PDF文档处理的利器

更多Python学习内容&#xff1a;ipengtao.com PDF&#xff08;Portable Document Format&#xff09;是一种常见的文档格式&#xff0c;广泛应用于各种场景&#xff0c;包括文档编辑、电子书籍、报告等。Python作为一种强大的编程语言&#xff0c;在处理PDF文档方面也有着丰富的…

怎样通过PHP语言实现远程控制一路开关

怎样通过PHP语言实现远程控制一路开关呢&#xff1f; 本文描述了使用PHP语言调用HTTP接口&#xff0c;实现控制一路开关&#xff0c;一路开关可控制一路照明、排风扇等电器。 可选用产品&#xff1a;可根据实际场景需求&#xff0c;选择对应的规格 序号设备名称厂商1智能WiFi…

【JavaWeb】网上蛋糕项目商城-我的订单,退出功能

概念 上一文中&#xff0c;我们实现了注册&#xff0c;登录&#xff0c;提交订单以及修改个人信息等功能。本文在登录的状态下&#xff0c;实现订单列表以及退出登录功能等。 我的订单 在head.jsp头部页面中&#xff0c;当用户处于登录状态&#xff0c;则会显示“我的订单”…

面试分享——订单超30分钟未支付自动取消用什么实现?如何使用Redis实现延迟队列?

目录 1.订单超时未支付自动取消&#xff0c;这个你用什么方案实现&#xff1f; 2.如何使用Redis实现延迟队列 2.1实验步骤 2.2实现生产可用的延迟队列还需关注什么 3.总结 电商场景中的问题向来很受面试官的青睐&#xff0c;因为业务场景大家都相对更熟悉&#xff0c;相关…

Java | Leetcode Java题解之第75题颜色分类

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

Pytorch入门—Tensors张量的学习

Tensors张量的学习 张量是一种特殊的数据结构&#xff0c;与数组和矩阵非常相似。在PyTorch中&#xff0c;我们使用张量来编码模型的输入和输出&#xff0c;以及模型的参数。 张量类似于NumPy的ndarrays&#xff0c;只是张量可以在GPU或其他硬件加速器上运行。事实上&#xf…

Flink 部署模式

目录 概述 部署模式 会话模式&#xff08;Session Mode&#xff09; 单作业模式(Per-Job Mode) 应用模式(Application Mode) 运行模式&#xff08;资源管理模式&#xff09; Standalone运行模式 会话模式部署 应用模式部署 Yarn运行模式 会话模式部署 单作业模式部…

LeetCode70:爬楼梯

题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 解题思想 1.确定dp数组以及下标的含义 dp[i]&#xff1a; 爬到第i层楼梯&#xff0c;有dp[i]种方法 2.确定递推公式 从dp[i]的定义可以…

Git系列:git merge 使用技巧

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

基于springboot实现体育馆管理系统项目【项目源码+论文说明】

基于springboot实现体育馆管理系统演示 摘要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本体育馆管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理…

2024高安全个人密码本程序源码,贴身密码管家-随机密码备忘录二代密码

项目概述&#xff1a; 在这个网络高度发展的时代&#xff0c;每个人都需要上网&#xff0c;而上网就不可避免地需要使用账号和密码。 在众多账号的情况下&#xff0c;你是否还在为复杂难记的密码感到烦恼&#xff1f;现在只需要记录一次&#xff0c; 就可以随时查看你的密码…

代码随想录算法训练营第二十天:二叉树成长

代码随想录算法训练营第二十天&#xff1a;二叉树成长 110.平衡二叉树 力扣题目链接(opens new window) 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a;一个二叉树每个节点 的左右两个子树的高度差的绝…