首页 > 其他分享 >select系统调用(实现I/O复用)

select系统调用(实现I/O复用)

时间:2024-09-18 22:48:49浏览次数:3  
标签:调用 socket 复用 描述符 set fd FD include select

API

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

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

文件描述符集合fd_set

是一个用于管理文件描述符集合的结构体。select调用返回时,内核将修改fd_set通知应用程序哪些文件描述符已就绪。

typedef struct {
    unsigned long fds_bits[FD_SETSIZE / (8 * sizeof(unsigned long))];
} fd_set;
fds_bits

一个数组,用于存储文件描述符的位图,每个文件描述符状态(可读、可写、异常)在位图中用一位表示。这样可以使用少量的存储空间来表示多个文件描述符的状态。

sizeof(unsigned long)返回unsigned long类型在当前系统上的字节大小。通常在 32 位系统上为 4 字节,而在 64 位系统上为 8 字节。

因为1字节有8 位,所以8*sizeof(unsigned long)表示一个unsigned long能表示的位数。

FD_SETSIZE

一个常量,定义了fd_set能容纳的最大文件描述符的数量,这限制了select能同时处理的文件描述符总量。它的值通常是1024,但可以在不同的实现中有所不同。

例子

假设FD_SETSIZE是1024,且unsigned long是 8 字节,则8*sizeof(unsigned long)=64,1024/64=16,这意味着fd_set中需要16个unsigned long来表示 1024 个文件描述符的状态。

常用的宏

位操作比较繁琐,可以用宏访问fd_set结构体中的位。

// FD_ZERO: 初始化 fd_set 结构,将指定的集合 set 清空。
FD_ZERO(fd_set *set);

// FD_SET: 将文件描述符 fd 添加到 fd_set 结构 set 中。
FD_SET(int fd, fd_set *set);

// FD_CLR: 从 fd_set 结构 set 中移除文件描述符 fd。
FD_CLR(int fd, fd_set *set);

// FD_ISSET: 检查文件描述符 fd 是否在 fd_set 结构 set 中。
FD_ISSET(int fd, fd_set *set);

参数

nfds

监视的文件描述符数量,通常是最高文件描述符的值加一。例如,如果你监视的文件描述符是 0, 1, 和 2,则nfds应为 3。

readfds

指向一个fd_set结构的指针,用于指定需要监视可读事件的文件描述符集合。

writefds

指向一个fd_set结构的指针,用于指定需要监视可写事件的文件描述符集合。

exceptfds

指向一个fd_set结构的指针,用于指定需要监视异常条件的文件描述符集合。异常条件通常包括紧急数据等。

timeout

一个timeval结构的指针,用于设置select函数的超时时间。采用指针参数,是因为内核将修改它以告诉程序select等待了多久。如果设置为NULL,则表示无限等待;如果指定了时间,则select将在超时后返回。

struct timeval {
    long tv_sec;    // 秒数
    long v_usec;    // 微秒数
};

返回值

成功

返回就绪(可读、可写和异常)的文件描述符数量。

超时

在超时时间内没有任何文件描述符就绪,返回0。

失败

返回-1,并设置errno为EINTR,可以通过perror函数输出错误信息。

文件操作符就绪条件

socket可读条件

接收到数据

对端发送了数据,数据会被放入socket的接收缓冲区。一旦接收缓冲区中有数据可供读取,socket 的状态会变为可读,可以调用读取函数来获取数据。

对端关闭连接

对端调用了关闭操作,本端socket仍然可读,但读取的数据量为 0,表示连接已经正常关闭,而没有任何剩余数据可供读取。这是一个正常的情况,通常可以通过检查返回值来判断连接状态,进而进行相应的清理或处理。

接收新的连接

监听socket上有新的连接请求到达时,监听socket被标记为可读,可以调用accept函数来接收这个连接。

错误条件

socket上可能会发生错误,例如,连接被重置、超时、网络不可达等情况都可能导致socket状态不正常。当socket发生错误时,通常会将错误信息存储在一个内部状态中,使用getsockopt来读取和清除该错误。

socket可写条件

发送缓冲区可用

当发送缓冲区有可用字节,socket 会被标记为可写。

关闭连接

当对一个已关闭写通道的socket执行写操作时,通常会触发一个SIGPIPE信号。这种情况发生在尝试写入数据到一个已经关闭的连接时。

非阻塞模式

socket使用非阻塞connect连接成功或超时后,socket可写。

错误条件

socket上可能会发生错误,使用getsockopt来读取和清除该错误。

socket异常条件

接收带外数据

带外数据是一种特殊的传输方式,用于发送紧急数据。带外数据通常用于需要立即处理的重要信息,例如中断信号或控制信息。带外数据的接收被视为异常条件,需特别处理。

处理带外数据

server.cpp

#include <libgen.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>

int main(int argc, char* argv[]) {
    // 检查参数数量
    if (argc < 3) {
        printf("usage: %s ip_address, port number\n", basename(argv[0]));
        return -1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);

    int ret = 0;

    // 初始化地址结构
    struct sockaddr_in address = {0};
    address.sin_family = AF_INET;
    if (inet_pton(AF_INET, ip, &address.sin_addr) <= 0) {
        perror("Invalid address");
        return -1;
    }
    address.sin_port = htons(port);

    // 创建监听套接字
    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenfd >= 0);

    // 绑定地址
    ret = bind(listenfd, (struct sockaddr*)&address, sizeof(struct sockaddr_in));
    assert(ret != -1);

    // 开始监听
    ret = listen(listenfd, 5);
    assert(ret != -1);
    
    struct sockaddr_in client_address = {0};
    socklen_t client_addrlength = sizeof(client_address);

    // 接受客户端连接
    int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength);
    if (connfd < 0) {
        printf("errno is: %d\n", errno);
        close(listenfd);
        return -1;  // 添加返回以结束程序
    }

    char buffer[1024];
    fd_set read_fds;
    fd_set exception_fds;
    FD_ZERO(&read_fds);
    FD_ZERO(&exception_fds);

    // 主循环,处理接收到的数据
    while (1) {
        memset(buffer, '\0', sizeof(buffer));

        FD_SET(connfd, &read_fds);
        FD_SET(connfd, &exception_fds);

        ret = select(connfd + 1, &read_fds, NULL, &exception_fds, NULL);
        if (ret < 0) {
            printf("selection failure\n");
            break;
        }
        
        // 处理正常数据
        if (FD_ISSET(connfd, &read_fds)) {
            ret = recv(connfd, buffer, sizeof(buffer) - 1, 0);
            if (ret <= 0) {
                break;  // 处理关闭连接
            }
            printf("get %d bytes of normal data: %s\n", ret, buffer);
        } 
        // 处理紧急数据
        else if (FD_ISSET(connfd, &exception_fds)) {
            ret = recv(connfd, buffer, sizeof(buffer) - 1, MSG_OOB);
            if (ret <= 0) {
                break;  // 处理关闭连接
            }
            printf("get %d bytes of oob data: %s\n", ret, buffer);
        }
    }
    close(connfd);
    close(listenfd);
    return 0;
}

client.cpp

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 2222
#define SERVER_IP "192.168.32.162"

int main() {
    int sockfd;
    struct sockaddr_in server_addr;

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("Socket creation failed");
        return 1;
    }

    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);

    // 连接到服务器
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Connection failed");
        close(sockfd);
        return 1;
    }

    // 发送普通数据
    const char *msg = "Hello, server!";
    send(sockfd, msg, strlen(msg), 0);

    // 发送带外数据
    const char *urgent_msg = "Urgent data!";
    send(sockfd, urgent_msg, strlen(urgent_msg), MSG_OOB);

    // 关闭套接字
    close(sockfd);
    return 0;
}

运行结果

先运行server.cpp,再运行client.cpp,结果如下。

推荐一下

0voice · GitHub

标签:调用,socket,复用,描述符,set,fd,FD,include,select
From: https://blog.csdn.net/Wendy_robot/article/details/142332345

相关文章

  • 大项目函数调用详解
    os.path.relpath是什么os.path.relpath是Python中os.path模块的一个函数,用于获取两个路径之间的相对路径。作用:os.path.relpath(path,start)会返回从start目录到path目录的相对路径。如果不指定start,则默认从当前工作目录计算。path:目标路径,表示你想获取相对路径......
  • C++基于select和epoll的TCP服务器
    select版本服务器#include<arpa/inet.h>#include<stdlib.h>#include<stdio.h>#include<string.h>#include<unistd.h>#include<sys/socket.h>#include<string>#include<pthread.h>#include<sys/select.h>......
  • Pyhton调用R语言rpy2包概要
    随着深度学习、大数据和AI的发展,Python的热度持续上升,引发了关于选择Python还是R的讨论。作为数据分析工具,两者各有优缺点。在特定领域,如生态学,R仍被广泛应用,而Python则更多用于日常办公自动化,如批量处理文档和Excel。由于数据处理占用了我们大量时间,很多人希望数据分析操作能够集......
  • MySQL数据库select语句详细用法三(子查询及其select练习)
    SELECT*FROMstudent2WHEREage> (SELECTageFROMstudent2WHERENAME='欧阳丹丹')首先解释一下括号中的代码,意思是在查询student2中的name为欧阳丹丹的人的名字,然后解释一下整个语句的意思:在括号中查询出来的字段中再次进行查询在student2中age大于name为欧阳丹丹的......
  • 进一步认识系统调用write()和read()
    简介write函数ssize_twrite(intfd,constvoid*buf,size_tcount);fd:文件描述符,表示要写入的文件或设备buf:指向要写入的数据的缓冲区count:要写入的字节数返回值:成功时返回写入的字节数;失败时返回-1read函数ssize_tread(intfd,void*buf,size_tcount);fd:文......
  • 菜鸟笔记之PWN入门(1.1.2)C程序调用过程与函数栈变化(32位 vs 64位)(Intel)
    本文使用Intel的32位为例子进行举例。64位本质上和32位类似,主要区别在于函数参数的调用方式,文章结尾会简要提及。重新回顾一下栈pop和push指令//将0x50的压入栈push0x50//将esp指向的数据放入指定的寄存器中pop寄存器名字比如:popeax执行之后eax的值就变成了0x50......
  • 鹏哥C语言42---函数调用相关练习
    #define_CRT_SECURE_NO_WARNINGS#include<stdio.h>//------------------------------------打印1000-2000年之间的闰年---------------------------------------------------//闰年的判断规则有两个//1.能被4整除,但是不能被100整除//2.能被400整除也是闰年/*intmain(......
  • Java调用Apache commons-text求解字符串相似性
    前言    在之前的一篇漂亮国的全球的基地博客中,我们曾经对漂亮国的全球基地进行了一些梳理。博文中使用的数据来源,重点是参考以为博主分享的KML的数据,同时针对其国内的基地部署信息,我们从互联网百科的数据中搜寻到一些。其实拿到这两份数据的时候,是存在一些问题的,比如,KML的......
  • Spring Cloud全解析:服务调用之自定义Feign的配置
    自定义Feign的配置Feign的默认配置类是FeignClientsConfiguration,其内部定义了Feign默认使用的编码器、解码器、契约、重试机制等@Bean@ConditionalOnMissingBeanpublicDecoderfeignDecoder(){//解码器,将字节数组反序列化为方法返回值类型的对象,默认只支持反序列化为St......
  • C#里方法的怎么编写XML文档注释说明,用于调用时参数提示等
    一.什么是xml的注释?答:XML注解是一种用于描述XML文档结构和元素内容的标记语言。它是通过在XML文档中使用特殊的标记来定义文档结构和元素属性的。XML注解通常用于数据编码和数据交换的应用程序之间,以确保数据的一致性和互操作性。XML注解具有良好的可扩展性和可读性,因此它通......