Linux C++ Socket 套接字、select、poll、epoll 实例

news2024/9/23 15:31:14

文章目录

  • 1. 概述
  • 2. TCP 网络编程实例
    • 2.1 服务器端
    • 2.2 客户端
    • 2.3 运行截图
  • 3. I/O 模型
    • 3.1 阻塞式I/O模型
    • 3.2 非阻塞I/O模型
    • 3.3 I/O 复用模型
    • 3.4 信号驱动式I/O
    • 3.5 异步I/O模型
  • 4. I/O复用之 select
    • 4.1 select 函数描述
    • 4.2 服务端代码
    • 4.3 客户端代码
    • 4.4 运行截图
  • 5. I/O复用之 poll
    • 5.1 poll 函数描述
    • 5.2 服务端代码
    • 5.3 客户端代码
    • 5.4 运行截图
  • 6. I/O复用之 epoll
    • 6.1 常用函数
    • 6.2 服务端代码
  • 参考文献

1. 概述

  • 网络编程, 就是编写程序, 使两台联网的电脑可以交换数据,
  • 套接字是网络数据传输用的软件设备, 用来连接网络的工具
  • 在 linux中 socket被认为是文件中的一种, 在网络数据传输过程中, 使用文件I/O的相关函数
  • 套接字常用网络协议: TCP、UDP

套接字进行网络连接流程, 如下图:

服务器端:

  1. 创建服务器套接字 socket()
  2. 绑定端口 bind()
  3. 监听端口 listen()
  4. 接受客户端请求 accept()
  5. 读取客户端请求的数据 read()
  6. 返回客户端要响应的数据 write()
  7. 关闭与客户端的连接 close()
  8. 关闭服务器套接字 close()

客户端:

  1. 创建客户端套接字 socket()
  2. 连接服务端 connect()
  3. 请求服务端数据, 发送操作数和操作符到服务器 write()
  4. 从服务器读取操作结果 read()
  5. 关闭客户端套接字 close()

流程图如下, 具体代码示例可以看下面的 2. TCP 网络编程实例在这里插入图片描述

2. TCP 网络编程实例

2.1 服务器端

开放的端口号 6666:

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

// 定义缓冲区大小和操作数大小
#define BUF_SIZE 1024
#define OPSZ 4

// 错误处理函数
void error_handling(char *message);

// 计算函数,根据操作数和操作符进行计算
int calculate(int opnum, int opnds[], char oprator);

int main(int argc, char *argv[]) {
    int serv_sock, clnt_sock;
    char opinfo[BUF_SIZE]; // 用于接收客户端发送的操作数和操作符
    int result, opnd_cnt, i;
    int recv_cnt, recv_len;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;

    // 创建服务器套接字
    serv_sock = socket(PF_INET,SOCK_STREAM, 0);
    if (serv_sock == -1)
        error_handling(
            "socket() error"
        );

    // 初始化服务器地址结构体
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = 6666; // 设置端口号为 6666

    // 绑定端口
    if (bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1)
        error_handling(
            "bind() error"
        );

    // 监听端口
    if (listen(serv_sock, 5) == -1)
        error_handling(
            "listen() error"
        );

    clnt_adr_sz = sizeof(clnt_adr); // 设置客户端地址结构体的大小

    // 接受多个客户端连接并进行计算
    for (i = 0; i < 5; i++) {
        opnd_cnt = 0;
        clnt_sock = accept(serv_sock, (struct sockaddr *) &clnt_adr, &clnt_adr_sz); // 接受客户端连接
        read(clnt_sock, &opnd_cnt, 1); // 读取操作数个数
        recv_len = 0;
        // 循环接收操作数,直到接收完所有操作数
        while ((opnd_cnt * OPSZ + 1) > recv_len) {
            recv_cnt = read(clnt_sock, &opinfo[recv_len], BUF_SIZE - 1); // 从客户端接收数据
            recv_len += recv_cnt;
            // 计算结果并发送回客户端
            result = calculate(opnd_cnt, (int *) opinfo, opinfo[recv_len - 1]);
            write(clnt_sock, (char *) &result, sizeof(result));
            close(clnt_sock); // 关闭与客户端的连接
        }
    }
    close(serv_sock); // 关闭服务器套接字

    return 0;
}

// 计算函数,根据操作数和操作符返回计算结果
int calculate(int opnum, int opnds[], char op) {
    int result = opnds[0], i;
    switch (op) { // 根据操作符进行不同的计算
        case '+':
            for (i = 1; i < opnum; i++) result += opnds[i];
            break;
        case '-':
            for (i = 1; i < opnum; i++) result -= opnds[i];
            break;
        case '*':
            for (i = 1; i < opnum; i++) result *= opnds[i];
            break;
    }
    return result;
}


// 错误处理函数,在遇到错误时打印消息并退出程序
void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

2.2 客户端

#include <stdio.h>          // 标准输入输出
#include <stdlib.h>         // 系统函数库
#include <string.h>         // 字符串处理
#include <unistd.h>         // UNIX标准函数
#include <arpa/inet.h>      // Internet网络协议
#include <sys/socket.h>     // Socket编程相关

// 定义缓冲区大小、结果大小和操作数字节大小
#define BUF_SIZE 1024
#define RLT_SIZE 4
#define OPSZ 4

// 错误处理函数
void error_handling(char *message);

int main(int argc, char *argv[]) {
    int sock; // Socket描述符
    char opmsg[BUF_SIZE]; // 用于存储操作数和操作符的消息
    int result, opnd_cnt, i; // 结果、操作数个数和循环变量
    struct sockaddr_in serv_adr; // 服务端地址结构体

    // 创建Socket
    sock = socket(PF_INET,SOCK_STREAM, 0);
    if (sock == -1)
        error_handling(
            "socket() error"
        );

    // 初始化服务端地址
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = 6666;

    // 连接服务端
    if (connect(sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1)
        error_handling(
            "connect() error!"
        );
    else
        puts("Connected ...........");

    // 获取并存储操作数个数
    fputs("Operand count: ",stdout);
    scanf("%d", &opnd_cnt);
    opmsg[0] = (char) opnd_cnt;

    // 循环读取操作数
    for (i = 0; i < opnd_cnt; i++) {
        printf("Operand %d:", i + 1);
        scanf("%d", (int *) &opmsg[i * OPSZ + 1]);
    }

    // 暂停,等待用户输入操作符
    fgetc(stdin);
    fputs("Operator: ",stdout);
    scanf("%c", &opmsg[opnd_cnt * OPSZ + 1]);

    // 发送操作数和操作符到服务器
    write(sock, opmsg, opnd_cnt * OPSZ + 2);

    // 从服务器读取操作结果
    read(sock, &result, RLT_SIZE);

    // 打印操作结果
    printf("Operation result: %d \n", result);

    // 关闭Socket
    close(sock);

    return 0;
}

// 错误处理函数:输出错误信息并退出程序
void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

2.3 运行截图

在这里插入图片描述

3. I/O 模型

一个入门科普文章

3.1 阻塞式I/O模型

2. TCP 网络编程实例 就是一个阻塞式的模型, acceptread 都是阻塞的, 当 accept 到新连接, 或者 read 到数据程序才往下走

为了提高服务端处理能力, 一个客户端连接一个线程处理

不能一个线程处理多个客户端, 某个客户端会阻塞这个线程处理其他客户端
在这里插入图片描述

3.2 非阻塞I/O模型

不停的轮询, 看看有没有accept 到新连接, 没有连接不阻塞等待, 继续去看看已经建立的连接有没有read到客户端的新数据, read到新数据处理, read不到不处理

为了提高服务端处理能力, 可以一个客户端连接一个线程处理, 线程不停的轮询自己要处理的客户端

也可以一个线程处理多个客户端, 相较于上面的阻塞I/O模型, 非阻塞不至于某个客户端阻塞这个线程处理其他客户端
在这里插入图片描述

3.3 I/O 复用模型

可以调用 select/poll/epoll , 阻塞在select/poll/epoll, select/poll/epoll 监听多个客户端连接事件或写入的数据, 然后这些事件可再有多个线程分一分处理掉
在这里插入图片描述

3.4 信号驱动式I/O

让内核在描述符就绪时发送SINIO信号 通知我们

Linux 网络编程的5种IO模型:信号驱动IO模型 接受SINIO信号, 直接 recvfrom , 然后 sentTo 了, 不用 , 遍历socket
在这里插入图片描述

3.5 异步I/O模型

告诉内核启动某个操作, 并且把数据copy到用户缓冲区再通知我们, 和信号驱动的区别是, 信号驱动内核通知我们去I/O, 异步I/O, 通知我们I/O完成了, 送到了

[C++ 网络协议] 异步通知I/O模型

在这里插入图片描述

4. I/O复用之 select

4.1 select 函数描述

int      select(int maxfdpl, fd_set *readSet, fd_set *writeSet,
    fd_set *exceptSet, struct timeval *timeout)
  • 最大描述符+1
  • fd_set *readSet 读事件描述符
  • fd_set *writeSet 写事件描述符
  • fd_set *exceptSet 异常事件描述符
  • struct timeval *timeout 等待超时时间

fd_set 里数组初始化1024, 网上都说 select 最多监听 1024 个连接
在这里插入图片描述

  • FD_SET: 将套接字添加到文件描述符集合中
  • FD_ZERO: 用来清空文件描述符集合
  • FD_CLR: 从套接字描述符集合中清除指定的套接字描述符
  • FD_ISSET: 检查文件描述符集合中是否包含文件描述符

4.2 服务端代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>

// 定义缓冲区大小
#define BUF_SIZE 100

// 错误处理函数
void error_handling(char *message);

int main(int argc, char *argv[]) {
    int serv_sock, clnt_sock; // 服务端和客户端套接字
    struct sockaddr_in serv_adr, clnt_adr; // 服务端和客户端地址结构体
    struct timeval timeout; // 超时时间结构体
    fd_set reads, cpy_reads; // 文件描述符集合,用于select函数

    socklen_t adr_sz; // 地址大小
    int fd_max, str_len, fd_num, i; // 各种变量初始化
    char buf[BUF_SIZE]; // 用于读取和写入数据的缓冲区

    // 创建套接字
    serv_sock = socket(PF_INET,SOCK_STREAM, 0);

    // 初始化服务端地址结构体
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = 6666;

    // 绑定端口
    if (bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1)
        error_handling(
            "bind() error"
        );

    // 监听端口
    if (listen(serv_sock, 5) == -1)
        error_handling(
            "listen() error"
        );

    // 用来清空文件描述符集合read
    FD_ZERO(&reads);
    // 将服务器套接字serv_sock添加到文件描述符集合reads中
    FD_SET(serv_sock, &reads);
    fd_max = serv_sock;

    // 无限循环,等待客户端连接和处理数据
    while (1) {
        cpy_reads = reads;
        timeout.tv_sec = 5;
        timeout.tv_usec = 5000;

        // 使用select函数等待事件发生
        if ((fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout)) == -1)
            break;

        // 没有客户端连接
        if (fd_num == 0) {
            continue;
        }

        // 遍历文件描述符集合,处理连接和数据传输
        for (i = 0; i < fd_max + 1; i++) {
            // 检查文件描述符集合cpy_reads中是否包含文件描述符i
            if (FD_ISSET(i, &cpy_reads)) {
                if (i == serv_sock) {
                    // 处理新连接
                    adr_sz = sizeof(clnt_adr);
                    clnt_sock = accept(serv_sock, (struct sockaddr *) &clnt_adr, &adr_sz);
                    FD_SET(clnt_sock, &reads);
                    if (fd_max < clnt_sock)
                        fd_max = clnt_sock;
                    printf("connected client: %d \n", clnt_sock);
                } else {
                    // 处理数据传输
                    str_len = read(i, buf, BUF_SIZE);
                    if (str_len == 0) {
                        // 从reads套接字描述符集合中清除指定的套接字描述符i
                        FD_CLR(i, &reads);
                        close(i);
                        printf("connected client: %d \n", i);
                    } else {
                        // 向客户端写入数据
                        write(i, buf, str_len);
                    }
                }
            }
        }
    }
    // 关闭服务端套接字
    close(serv_sock);
    return 0;
}

// 错误处理函数
void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

4.3 客户端代码

#include <stdio.h>          // 标准输入输出头文件
#include <stdlib.h>         // 通用实用程序头文件
#include <arpa/inet.h>      // IPv4 Internet地址转换头文件
#include <sys/socket.h>     // 用于创建和操作套接字的头文件
#include <string.h>         // 字符串操作头文件
#include <unistd.h>         // 提供一些与操作系统交互的函数
#include <sys/types.h>      // 定义各种数据类型的头文件
#include <sys/time.h>       // 时间处理头文件
#include <sys/select.h>     // 用于文件描述符选择的头文件

#define BUF_SIZE 1024      // 定义缓冲区大小

// 错误处理函数
// 参数: buf - 存储错误信息的字符数组
void error_handling(char *buf);

int main(int argc, char *argv[]){ // 程序入口
    int sock;                     // 套接字变量
    char message[BUF_SIZE];       // 用于存储消息的缓冲区
    int str_len;                  // 存储字符串长度的变量
    struct sockaddr_in serv_adr;  // 用于存储服务器地址信息的结构体

    // 创建TCP套接字
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock==-1)
    {
        error_handling("socket create error"); // 若创建失败,调用错误处理函数
    }

    // 初始化服务器地址结构体
    memset(&serv_adr, 0,sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = 6666;

    // 尝试连接到服务器
    if (connect(sock,(struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
    {
        error_handling("connect error"); // 若连接失败,调用错误处理函数
    }else{
        puts("connected......"); // 连接成功,输出提示信息
    }

    // 进入消息收发循环
    while (1)
    {
        fputs("input message (q to quit): ", stdout); // 提示用户输入消息
        fgets(message, BUF_SIZE, stdin); // 从标准输入读取消息
        if (!strcmp(message,"q\n")||!strcmp(message,"Q\n") )
        {
            break; // 如果用户输入的是 'q' 或 'Q',则退出循环
        }
        write(sock, message, strlen(message)); // 将消息写入套接字,发送给服务器
        str_len=read(sock, message, BUF_SIZE-1); // 从套接字读取消息,存储到message中
        message[str_len] = 0; // 在消息末尾添加null字符,以结束字符串
        printf("Message from server: %s", message); // 输出服务器返回的消息

    }
    close(sock); // 关闭套接字

    return 0; // 程序正常结束,返回0
}

// 错误处理函数
// 参数: buf - 存储错误信息的字符数组
// 说明: 该函数将错误信息输出到标准错误流,并退出程序
void error_handling(char* buf){
    fputs(buf, stderr); // 将错误信息输出到标准错误流
    fputc('\n', stderr); // 输出换行符
    exit(1); // 退出程序,返回码为1,表示异常结束
}

4.4 运行截图

在这里插入图片描述

5. I/O复用之 poll

5.1 poll 函数描述

poll 提供的功能和 selcet 差不多, poll 可以自己声明最大长度, 还不用自己去 FD_SET、FD_ISSET 维护状态

int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);

struct pollfd {
	int     fd;
	short   events;
	short   revents;
};
  • fdarray : pollfd数组
  • nfds : fd 数量
  • timeout : 超时时间

5.2 服务端代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/poll.h>

using namespace std;

int main() {
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);

    sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = 6666;

    ::bind(listenfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    listen(listenfd, 10);

    /*
        poll只是对select函数传入的参数的一个封装,底层实现还是select,好处就是需要传入的参数少了
    */
    //pollfd数组, 一个元素为一个 描述符以及相对应的事件。元素的个数为可处理socket的最大个数,突破了select的1024的限制
    pollfd pfds[2048] = {0};
    pfds[listenfd].fd = listenfd;
    pfds[listenfd].events = POLLIN; //可读
    //pfds.revents //revents为poll返回的事件

    int maxfd = listenfd;

    while (1) {
        int nready = poll(pfds, maxfd + 1, -1);

        //监听socket有可读事件
        if (pfds[listenfd].revents & POLLIN) {
            sockaddr_in clientAddr;
            socklen_t len = sizeof(clientAddr);
            int clientfd = accept(pfds[listenfd].fd, (sockaddr*)&clientAddr, &len);
            printf("accept==>> clientfd:%d %s:%d\n", clientfd, inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
            if (-1 == clientfd) {
                continue;
            }

            //请注意这里的clientfd越界问题,这里就不做处理了
            //需要注意的是,如果有客户端关闭连接了,再次有新的客户端连接进来时,accept返回的是没有使用的最小的描述符
            //比如现在有4,5,6客户端建立了连接,4和5客户端断开了,再次有新的客户端建立连接后,accept会给分配4描述符,这点内核还是很人性化的。
            pfds[clientfd].fd = clientfd;
            pfds[clientfd].events = POLLIN;

            maxfd = clientfd;
        }

        for (int i = listenfd + 1; i <= maxfd; i++) {
            if (pfds[i].revents & POLLIN) {
                char buf[128] = {0};
                int count = recv(pfds[i].fd, buf, sizeof(buf), 0);
                if (count == 0) {
                    printf("close\n");

                    close(pfds[i].fd);

                    pfds[i].fd = -1;
                    pfds[i].events = 0;

                    continue;
                }
                printf("clientfd:%d, count:%d, buf:%s\n", pfds[i].fd, count, buf);
                send(pfds[i].fd, buf, count, 0);
            }
        }
    }

	close(listenfd);
    return 0;
}

5.3 客户端代码

同 4.3

5.4 运行截图

在这里插入图片描述

6. I/O复用之 epoll

epoll 比 select、poll 性能都好, 获得就绪的文件描述符, select、poll O(n) 复杂度, epoll 是 O(1)

select,poll是基于轮询实现的,将fd_set从用户空间复制到内核空间,然后让内核空间以poll机制来进行轮询,一旦有其中一个fd对应的设备活跃了,那么就把整个fd_set返回给客户端(复制到用户空间),再由客户端来轮询每个fd的,找出发生了IO事件的fd

epoll是基于事件驱动实现的,加入一个新的fd,会调用epoll_ctr函数为该fd注册一个回调函数,然后将该fd结点注册到内核中的epoll红黑树中,当IO事件发生时,就会调用回调函数,将该fd结点放到就绪链表中,epoll_wait函数实际上就是从这个就绪链表中获取这些fd。

epoll很详细的文章

6.1 常用函数

poll操作过程需要三个接口,分别如下:

int epoll_create(int size)//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  1. int epoll_create(int size); 创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大,这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值,参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。 当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
  2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 函数是对指定描述符fd执行op操作。
  • epfd:是epoll_create()的返回值。
  • op:表示op操作,用三个宏来表示:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。
  • fd:是需要监听的fd(文件描述符)
  • epoll_event:是告诉内核需要监听什么事,struct epoll_event结构如下:
struct epoll_event {
  __uint32_t events;  /* Epoll events */
  epoll_data_t data;  /* User data variable */
};

//events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

  1. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 等待epfd上的io事件,最多返回maxevents个事件。 参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

6.2 服务端代码

我用的MAC电脑, 不支持 epoll, 下面的代码没运行过:

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/select.h>
#include <sys/epoll.h>

#define BUF_SIZE 100
#define EPOLL_SIZE 50

void error_handling(char *buf);
/**
 * 主程序:创建一个使用epoll模型的服务器,监听客户端连接,并处理客户端请求。
 *
 * @param argc 命令行参数个数
 * @param argv 命令行参数数组
 * @return 总是返回0,表示程序正常结束
 */
int main(int argc, char *argv[]) {
    // 定义服务器套接字和客户端套接字
    int serv_sock, clnt_sock;
    // 定义服务器地址结构体和客户端地址结构体
    struct sockaddr_in serv_adr, clnt_adr;
    // 定义地址大小变量
    socklen_t adr_sz;
    // 定义字符串长度和循环索引
    int str_len, i;
    // 定义缓冲区,用于读取和写入数据
    char buf[BUF_SIZE];

    // 定义epoll事件结构体数组和epoll文件描述符
    struct epoll_event *ep_events;
    struct epoll_event event;
    // 定义epoll等待事件个数变量
    int epfd, event_cnt;

    // 检查命令行参数是否正确
    if (argc != 2) {
        printf("usage error\n");
        exit(1);
    }
    // 创建服务器套接字
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    // 初始化服务器地址结构体
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = 6666;

    // 绑定服务器套接字到指定地址和端口
    if (bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1) {
        error_handling("bind error");
    }

    // 监听服务器套接字
    if (listen(serv_sock, 5) == -1) {
        error_handling("listen error");
    }

    // 创建epoll实例
    epfd = epoll_create(EPOLL_SIZE);
    // 分配存储epoll事件的内存
    ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);

    // 将服务器套接字添加到epoll监控列表中
    event.events = EPOLLIN;
    event.data.fd = serv_sock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

    // 主循环,等待和处理客户端连接和请求
    while (1) {
        // 等待epoll事件
        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
        if (event_cnt == -1) {
            puts("epoll wait error\n");
            break;
        }
        // 遍历所有触发的epoll事件
        for (i = 0; i < event_cnt; i++) {
            // 处理服务器套接字上的事件,即新的客户端连接
            if (ep_events[i].data.fd == serv_sock) {
                adr_sz = sizeof(clnt_adr);
                clnt_sock = accept(serv_sock, (struct sockaddr *) &clnt_adr, &adr_sz);
                // 将新连接的客户端套接字添加到epoll监控列表中
                event.events = EPOLLIN;
                event.data.fd = clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
                printf("connnected client: %d\n", clnt_sock);
            } else {
                // 处理客户端套接字上的事件,即客户端发送的数据
                str_len = read(ep_events[i].data.fd, buf,BUF_SIZE);
                if (str_len == 0) {
                    // 如果客户端关闭了连接,则从epoll监控列表中移除,并关闭套接字
                    epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
                    close(ep_events[i].data.fd);
                    printf("closed client: %d \n", ep_events[i].data.fd);
                } else {
                    // 如果客户端发送了数据,则将数据回传给客户端
                    write(ep_events[i].data.fd, buf, str_len);
                }
            }
        }
    }
    // 关闭服务器套接字和epoll文件描述符
    close(serv_sock);
    close(epfd);
    return 0;
}

/**
 * 错误处理函数:输出错误信息并退出程序。
 *
 * @param buf 包含错误信息的字符串
 */
void error_handling(char *buf) {
    fputs(buf, stderr);
    fputc('\n', stderr);
    exit(1);
}

参考文献

  • UNIX 网络编程 卷1: 套接字联网API
  • TCP/IP网络编程 尹圣雨 著 金国哲 译
  • Linux IO模式及 select、poll、epoll详解
  • 浅谈select,poll和epoll的区别

在这里插入图片描述

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

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

相关文章

虚拟海外仓用什么系统最好?5个步骤帮你选出适合自己仓库的WMS系统

面对国际市场越来越大的仓储需求&#xff0c;虚拟海外仓的受众还是非常广泛的。不过很多经营虚拟海外仓的企业往往都会陷入管理混乱&#xff0c;低效的怪圈。 要想突破这个经营的瓶颈&#xff0c;快速发展企业&#xff0c;选择一个适合自己的海外仓WMS系统是个不错的选择。 1…

Java中Stack的使用详解

Stack是一种运算受限的线性表&#xff0c;其特点在于仅允许在表的一端&#xff08;即表尾&#xff09;进行插入和删除操作。这一端被称为栈顶&#xff0c;而相对的另一端则称为栈底。向一个栈插入新元素的操作称为进栈或入栈&#xff0c;它将新元素放到栈顶元素的上面&#xff…

昂达固态硬盘数据恢复方法:全面解析与操作指南

在数字化时代&#xff0c;数据已经成为我们生活和工作中不可或缺的一部分。而固态硬盘&#xff08;SSD&#xff09;由于其读写速度快、抗震性强等优点&#xff0c;慢慢取代了传统的机械硬盘&#xff0c;成为我们存储数据的主要选择。然而&#xff0c;即便再先进的存储设备&…

如何远程访问Redis?

远程访问Redis是一种常见的需求&#xff0c;特别是在分布式系统或跨地域网络中。通过远程访问&#xff0c;我们可以轻松地对远程的Redis数据库进行操作和管理。 天联保障数据安全 对于远程访问Redis的安全性问题&#xff0c;我们可以借助天联来保障数据的安全。天联是一种基于…

Oracle-修改用户名

1、项目背景 需要将导入一份最新的用户数据在tbl用户上&#xff0c;但需要将原来的tbl用户数据保留并能实现两个用户的比对。 2、解决思路 思路一&#xff1a;1&#xff09;新建用户tbl_feng,导入数据&#xff1b;2&#xff09;将两个用户换名称 3&#xff09;比对 思路二&…

STM32实现HAL库LED点灯

目录 一、相关环境 STM32CubeMX 介绍 安装下载 相关配置完善 尝试使用该软件&#xff0c;点亮LED灯 项目创建 代码示例 二、任务实现 1、使用HAL库方式完成LED灯的周期闪烁 要求1 项目创建 代码示例 要求2 项目创建 代码示例 2、使用仿真软件分析仪功能观察…

VS2022配合Qt与boost.asio实现一个TCP异步通信系统远程操作mysql数据库

上一篇博客我们通过boost.asio搭建了一个简单的异步服务器&#xff0c;但是那是基于命令行的&#xff0c;所有用起来还是相当枯燥的&#xff0c;这次我们配合Qt实现一个简陋的前端页面来控制后端mysql数据库中的表&#xff0c;实现添加密钥的功能(本次博客使用的boost版本是1.8…

为什么大部分新手做抖音小店赚不到钱?

大家好&#xff0c;我是喷火龙。 今天来给大家聊聊&#xff0c;为什么大部分新手做抖店赚不到钱&#xff1f; 不知道大家想过这个问题没有&#xff0c;可能有些人把赚不到钱的原因归结于市场、或者平台、又或者运营技术以及做店经验。 但我觉得这些都不是重点&#xff0c;重…

关于RAG(检索增强生成)的一些知识

写在前面 最近一直在看AI相关的文章&#xff0c;不出意外的话&#xff0c;后续几篇应该都是关于这方面的。希望能和大家一起了解这方面的技术。 什么是RAG 检索增强生成 (RAG,全称Retrieval-Augmented Generation) 是一种利用从外部来源获取的事实来提高生成式 AI 模型的准确…

视频监控管理平台LntonCVS安防监控平台实现接入监控视频资源的视频汇聚方案

随着各行业数字化转型的不断推进&#xff0c;视频监控技术在行业内的安防应用及管理支撑日益增多。然而&#xff0c;由于前期规划不清晰、管理不到位等问题&#xff0c;视频监管系统普遍存在以下问题&#xff1a; 1. 各部门单位在视频平台建设中以所属领域为单位&#xff0c;导…

Intellij IDEA创建springboot 3.2.5 项目

1、idea file -> new ->Project 点击 2、创建项目配置 完成配置-> 点击Next 3、选择对应jar 包 -> 点击Create 4、删除 .mvn、mvnw、mvnw.cmd

宝塔Nginx设置图片访问跨域

宝塔Nginx设置图片访问跨域 针对某一个站点设置 找到对应的站点点击 “设置” 增加对应header设置 代码&#xff1a; location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)${#允许跨域add_header Access-Control-Allow-Origin *;add_header Access-Control-Allow-Headers X-Requeste…

数字图像处理系列 | 非线性滤波 (4)

非线性滤波就不能做卷积了 文章目录 前言. 去除噪声如果使用线性滤波 1. 中值滤波2. 重新思考一下 -- 高斯平滑滤波高斯平滑哪里不好用呢&#xff1f;but&#xff0c;我们是不是可以结合高斯平滑滤波的优点和非线性滤波的优点&#xff0c;来做这个去噪呢&#xff1f; 3. Bilat…

融资融券保证金比例,融资融券最低利率4.0%

融资融券保证金比例是指投资者交付的保证金与融资、融券交易金额的比例&#xff0c;用于控制投资者初始资金的放大倍数。这个比例分为融资保证金比例和融券保证金比例。 融资融券保证金比例的计算 1. 融资保证金比例是指投资者融资买入证券时交付的保证金与融资交易金额的比例…

Rust之函数式语言特性:迭代器和闭包(一):概述

开发环境 Windows 11Rust 1.78.0 VS Code 1.89.1 项目工程 这次创建了新的工程minigrep. 函数式语言特性:迭代器和闭包 Rust的设计从许多现有语言和技术中获得了灵感&#xff0c;其中一个重要影响是函数式编程。函数式编程通常包括通过在参数中传递函数、从其他函数返回函数、…

线程池(详细)

Java中的线程池是运用场景最多的并发框架&#xff0c;几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中&#xff0c;合理地使用线程池能够带来3个好处。 第一&#xff1a;降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 第二&…

2024年电工杯数学建模竞赛思路资料汇总贴

下文包含&#xff1a;2024电工杯&#xff08;电工杯数学建模竞赛&#xff09;思路解析、电工杯参赛时间及规则信息说明、好用的数模技巧及如何备战数学建模竞赛 C君将会第一时间发布选题建议、所有题目的思路解析、相关代码、参考文献、参考论文等多项资料&#xff0c;帮助大家…

前端怎么使用svg格式的图片

目录 第一步&#xff0c;找到图标 第二步&#xff0c;使用 第一种&#xff0c;SVG下载 第二种&#xff0c;粘贴SVG代码 第一步&#xff0c;找到图标 以阿里巴巴矢量图标库为例&#xff0c;随便找一个图标&#xff0c;如下图 第二步&#xff0c;使用 第一种&#xff0c;SV…

Matlab 2023b学习笔记1——界面认识

下载安装好Matlab后&#xff0c;可以看到如下界面&#xff1a; 可以看到&#xff0c;这时只有命令行窗口。我们在上方工具栏中选择“布局”—— “默认”&#xff0c;即可看到左右两边多出来了“当前文件夹”与“工作区”两栏。 一、当前文件夹界面 这个界面显示的是当前目录下…

什么是独特摆动交易策略?fpmarkets1分钟讲清楚

摆动交易策略想必各位投资者都已经接触过了&#xff0c;但是什么是独特摆动交易策略&#xff1f;各位投资者知道吗&#xff1f;其实很简单&#xff0c;这是一种基于斐波纳契工具的独特摆动交易策略。下面fpmarkets1分钟讲清楚&#xff0c;趋势总会经历调整&#xff0c;而这些调…