【IO模型与并发服务器】

阻塞IO与非阻塞IO

阻塞IO

非阻塞IO

IO多路复用

select函数

cs 复制代码
//头文件:
#include <sys/socket.h> 
#include <sys/types.h>
//函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds,
                                      fd_set *exceptfds, struct timeval  *timeout);

poll函数

cs 复制代码
//头文件:
#include <sys/socket.h> #include <sys/types.h>
//函数原型:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数详解

参数名 类型 作用说明
fds struct pollfd* 指向 pollfd 结构体数组的指针,存储待监控的 FD 及关注的事件(输入 / 输出结合)。
nfds nfds_t 需监控的 pollfd 结构体数量(即数组有效长度),nfds_t 是无符号整数类型(通常为 unsigned int)。
timeout int 超时时间(单位:毫秒),控制 poll() 的阻塞行为: - timeout > 0:阻塞 timeout 毫秒后返回(超时返回 0); - timeout = 0:不阻塞,立即返回(轮询模式); - timeout = -1:无限阻塞,直到至少一个 FD 发生事件或被信号中断。

返回值:成功返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0,失败返回-1(并重置错误码)

pollfd 结构体

cs 复制代码
struct pollfd {
    int    fd;       // 待监控的文件描述符(FD)
    short  events;   // 用户关注的事件类型(输入参数,由用户设置)
    short  revents;  // 内核返回的实际发生的事件(输出参数,由内核填充)
};

fd:待监控的文件描述符

  • 作用 :指定需要 poll() 监控的文件描述符(如 socket、管道、标准输入等)。
  • 特殊值
    • fd = -1poll()忽略该结构体 (不监控任何事件),且对应的 eventsrevents 会被忽略。
    • 用途:动态管理监控列表时(如移除某个 FD),无需修改数组长度,只需将 fd 设为 -1 即可。

2. events:用户关注的事件(输入参数)

用户通过设置 events 的二进制位,指定需要监控的事件类型。常见事件常量定义在 <poll.h> 中,可通过按位或(|) 组合多个事件。

事件常量 取值(十六进制) 含义说明
POLLIN 0x001 普通数据 / 优先数据可读(如 socket 收到客户端数据、标准输入有输入)。
POLLPRI 0x002 紧急数据可读(如 socket 的带外数据 OOB)。
POLLOUT 0x004 普通数据 / 优先数据可写(如 socket 可发送数据,无阻塞)。
POLLERR 0x008 对应 FD 发生错误 (无需用户设置,内核自动返回至 revents)。
POLLHUP 0x010 对应 FD 发生挂起(如 peer 关闭连接,管道写端关闭)(内核自动返回)。
POLLNVAL 0x020 对应 fd 无效(如 FD 未打开、已关闭)(内核自动返回)。
POLLRDNORM 0x040 普通数据 可读(与 POLLIN 功能重叠,部分系统兼容用)。
POLLWRNORM 0x080 普通数据 可写(与 POLLOUT 功能重叠,部分系统兼容用)。
POLLRDBAND 0x100 优先带数据 可读(如 TCP 带外数据,与 POLLPRI 关联)。
POLLWRBAND 0x200 优先带数据可写。

3. revents:内核返回的实际事件(输出参数)

  • 作用poll() 调用成功后,内核会根据 FD 的实际状态,在 revents 中填充发生的事件(二进制位)。
  • 关键特性
    • 只读性 :用户无需设置 revents,仅需在 poll() 返回后读取其值。
    • 自动包含错误事件 :即使用户未在 events 中设置 POLLERRPOLLHUPPOLLNVAL,内核若检测到这些事件,也会自动将其写入 revents
    • events 的关联revents 中的事件是 events 中用户关注事件的子集或超集(超集源于错误事件的自动添加)。

多线程并发服务器

服务器模型

多线程并发服务器原理

服务端

cs 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
 
#define PORT 8888
#define SERVER_IP "192.168.136.130"
#define BUFFER_SIZE 128
 
//自定义结构体 -> 封装客户端连接信息,用于在线程间传递
typedef struct MSG{
    int acceptfd;//客户端套接字描述符
    struct sockaddr_in clientaddr;//客户端地址信息
}msg_t;
 
//创建线程处理函数
void *deal_read_write(void *arg){
    msg_t msg = *(msg_t*)arg;
    char buf[BUFFER_SIZE] = {0}; 
    int nbytes = 0;
    printf("客户端【%s:%d】连接到了服务器\n",inet_ntoa(msg.clientaddr.sin_addr),ntohs(msg.clientaddr.sin_port));
 
    //客户端数据接收与发送
    while(1){
        nbytes = recv(msg.acceptfd, buf, BUFFER_SIZE, 0);
        if(nbytes == -1){
            perror("读取客户端消息失败:");
        }else if(nbytes == 0){
            printf("客户端【%s:%d】断开连接\n",inet_ntoa(msg.clientaddr.sin_addr),ntohs(msg.clientaddr.sin_port));
            break;
        }
 
        printf("客户端【%s:%d】发来数据:%s\n",inet_ntoa(msg.clientaddr.sin_addr),ntohs(msg.clientaddr.sin_port),buf);
     
        //回显数据给客户端
        strcat(buf, "--服务器");
        if(send(msg.acceptfd, buf, BUFFER_SIZE, 0) == -1){
            perror("回显数据失败:");
        }
    }
    close(msg.acceptfd);
    pthread_exit(NULL);
}
 
int main(){
    //创建套接字-配置服务器地址结构体
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd == -1){
        perror("创建套接字失败:");
 
        return -1;
    }
 
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
    socklen_t serveraddr_len = sizeof(server_addr);
 
    //绑定、监听、处理客户端的连接
    if(bind(sockfd, (struct sockaddr *)&server_addr, serveraddr_len) == -1){
        perror("绑定失败:");
        close(sockfd);
        return -1;
    }
 
    if(listen(sockfd, 5) == -1){
        perror("监听失败:");
        close(sockfd);
        return -1;
    }
 
    struct sockaddr_in client_addr;
    socklen_t clientaddr_len = sizeof(client_addr);
 
    int acceptfd = 0;
    pthread_t pthread_id = 0;
    msg_t msg;
    int ret = 0;
    //主线程:接受连接并创建子线程
    while(1){
        if((acceptfd = accept(sockfd, (struct sockaddr *)&client_addr, &clientaddr_len)) == -1){
            perror("接受连接失败:");
            return -1;
        }
 
        msg.acceptfd = acceptfd;
        msg.clientaddr = client_addr;
 
        //创建子线程
        ret = pthread_create(&pthread_id, NULL, deal_read_write, &msg);
        if(ret == -1){
            printf("错误码:%d,错误信息:%s\n",ret,strerror(ret));
            close(sockfd);
            return -1;
        }
 
        ret = pthread_detach(pthread_id);
 
        if(ret == -1){
            printf("错误码:%d,错误信息:%s\n",ret,strerror(ret));
            close(sockfd);
            return -1;
        }
    }
 
    close(sockfd);
    return 0;
}

多进程并发服务器

cs 复制代码
#include <stdio.h>          // 标准输入输出库(打印日志、错误信息)
#include <sys/types.h>      // 系统数据类型定义(pid_t、socket相关类型等)
#include <sys/socket.h>     // socket核心库(创建、绑定、监听、收发等接口)
#include <netinet/ip.h>     // IPv4协议相关定义(补充sockaddr_in结构体支持)
#include <netinet/in.h>     // 网络地址结构体定义(sockaddr_in、字节序转换函数)
#include <string.h>         // 字符串处理库(memset、strcat等)
#include <stdlib.h>         // 标准库(exit函数等)
#include <arpa/inet.h>      // IP地址转换库(inet_ntoa:网络字节序IP转字符串)
#include <unistd.h>         // 系统调用库(close、fork、kill、getpid等)
#include <signal.h>         // 信号处理库(signal、sig_func信号回调)
#include <sys/wait.h>       // 进程等待库(wait函数:回收子进程资源)

// 服务器监听端口号(自定义,需确保未被占用,范围1024-65535)
#define PORT 8888
// 服务器绑定的IP地址(根据实际网卡配置修改,需与客户端在同一网段)
#define SERVER_IP "192.168.26.128"
// 数据收发缓冲区大小(单次最大传输128字节,避免缓冲区溢出)
#define BUFFER_SIZE 128

/**
 * @brief 信号处理函数:专门回收子进程资源,避免僵尸进程
 * @param signum 接收到的信号编号(此处固定为SIGUSR1)
 * 核心逻辑:wait(NULL) 会阻塞等待任意子进程终止,回收其PCB资源
 * 避免子进程退出后成为僵尸进程(占用系统进程表资源)
 */
void sig_func(int signum){
    // wait(NULL):不关心子进程退出状态,仅回收资源;若没有子进程可回收,会立即返回
    wait(NULL);
}

int main(){
    // 服务器监听套接字描述符(用于接受客户端连接请求,不直接收发数据)
    int sockfd = 0;
    // 客户端连接套接字描述符(每个客户端对应一个,用于与该客户端收发数据)
    int acceptfd = 0;
    // 进程ID(fork返回值:父进程返回子进程PID,子进程返回0,失败返回-1)
    pid_t pid = 0;
    // 函数返回值临时变量(用于判断系统调用是否成功)
    int ret = 0;

    /**
     * 1. 创建TCP套接字(监听用)
     * 参数说明:
     * AF_INET:使用IPv4协议族
     * SOCK_STREAM:流式套接字(对应TCP协议,可靠、面向连接)
     * 0:默认协议(由系统自动匹配TCP协议,无需手动指定)
     * 返回值:成功返回非负套接字描述符,失败返回-1
     */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd == -1){
        perror("套接字创建失败:");  // 打印系统错误原因(如权限不足、资源耗尽)
        return -1;                  // 套接字创建失败,直接退出程序
    }

    // 服务器地址结构体:存储服务器的IP、端口、协议族等信息
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));  // 清空结构体(避免随机垃圾值影响)
    server_addr.sin_family = AF_INET;              // 协议族:IPv4
    server_addr.sin_port = htons(PORT);             // 端口号:htons将主机字节序转为网络字节序(大端)
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);  // IP地址:inet_addr将字符串IP转为网络字节序
    socklen_t serveraddr_len = sizeof(server_addr);  // 地址结构体长度(传给bind函数)

    /**
     * 2. 绑定套接字:将监听套接字与服务器IP+端口绑定(确定服务地址)
     * 若绑定失败(如IP错误、端口已被占用),需关闭套接字避免资源泄漏
     */
    if(bind(sockfd, (struct sockaddr*)&server_addr, serveraddr_len) == -1){
        perror("绑定失败:");
        close(sockfd);  // 释放已创建的套接字资源
        return -1;      // 绑定失败,退出程序
    }

    /**
     * 3. 监听连接:将套接字转为监听状态,等待客户端发起连接
     * 参数说明:
     * 第二个参数5:监听队列大小(最大同时等待连接的客户端数,超过则拒绝)
     */
    if(listen(sockfd, 5) == -1){
        perror("监听失败:");
        close(sockfd);  // 释放套接字资源
        return -1;      // 监听失败,退出程序
    }

    // 客户端地址结构体:存储连接成功的客户端IP、端口信息
    struct sockaddr_in client_addr;
    socklen_t clientaddr_len = sizeof(client_addr);  // 客户端地址结构体长度

    /**
     * 4. 注册信号处理函数:绑定SIGUSR1信号与sig_func回调
     * 作用:子进程退出前发送SIGUSR1信号,触发sig_func回收子进程资源
     */
    signal(SIGUSR1, sig_func);

    printf("服务器启动成功!监听IP:%s,端口:%d\n", SERVER_IP, PORT);

    /**
     * 主循环:持续接受客户端连接(服务器核心逻辑)
     * 循环特性:accept是阻塞函数,无客户端连接时会一直阻塞等待
     */
    while(1){
        /**
         * 5. 接受客户端连接:
         * 参数说明:
         * sockfd:监听套接字
         * &client_addr:输出参数,存储连接客户端的IP和端口
         * &clientaddr_len:输入输出参数,传入结构体长度,返回实际使用长度
         * 返回值:成功返回客户端套接字描述符(acceptfd),失败返回-1
         */
        acceptfd = accept(sockfd, (struct sockaddr*)&client_addr, &clientaddr_len);
        if(acceptfd == -1){
            perror("接受连接失败:");
            close(sockfd);  // 接受连接失败,关闭监听套接字
            break;          // 退出主循环,程序终止
        }

        /**
         * 6. 创建子进程:用fork创建子进程处理当前客户端通信
         * 原因:父进程继续监听新连接,子进程专注于单个客户端的数据收发(并发处理)
         */
        pid = fork();
        if(pid == -1){  // fork失败(如系统进程数达到上限)
            perror("创建子进程失败:");
            close(acceptfd);  // 关闭当前客户端套接字(避免资源泄漏)
            break;            // 退出主循环,程序终止
        }else if(pid == 0){  // 子进程逻辑(pid=0表示当前是子进程)
            // 子进程专属:与客户端进行数据收发
            char buf[BUFFER_SIZE] = {0};  // 数据收发缓冲区(初始化清空,避免脏数据)
            int nbytes = 0;               // 实际接收的字节数

            // 打印客户端连接信息:inet_ntoa转网络字节序IP为字符串,ntohs转网络字节序端口为主机字节序
            printf("客户端【%s:%d】连接到服务器\n", 
                   inet_ntoa(client_addr.sin_addr),  // 客户端IP
                   ntohs(client_addr.sin_port));     // 客户端端口

            /**
             * 7. 接收客户端数据:
             * 参数说明:
             * acceptfd:客户端套接字
             * buf:接收缓冲区
             * BUFFER_SIZE:缓冲区最大长度
             * 0:默认阻塞模式(无数据时子进程阻塞等待)
             * 返回值:nbytes>0表示实际接收字节数;=0表示客户端断开;=-1表示接收失败
             */
            nbytes = recv(acceptfd, buf, BUFFER_SIZE, 0);
            if(nbytes == -1){  // 接收数据失败(如网络异常)
                perror("接收失败:");
            }else if(nbytes == 0){  // 客户端主动断开连接(调用close)
                printf("客户端【%s:%d】断开连接\n",
                       inet_ntoa(client_addr.sin_addr),
                       ntohs(client_addr.sin_port));
                break;  // 退出子进程内部逻辑,准备关闭资源
            }

            // 打印客户端发送的数据(nbytes确保只打印实际接收的内容)
            printf("客户端【%s:%d】发来数据:%s\n",
                   inet_ntoa(client_addr.sin_addr),
                   ntohs(client_addr.sin_port),
                   buf);
             
            // 拼接回显标记:在客户端数据后添加"--来自服务端",告知客户端数据已处理
            strcat(buf, "--来自服务端");

            /**
             * 8. 回显数据给客户端:将处理后的数据发送回客户端
             */
            if(send(acceptfd, buf, BUFFER_SIZE, 0) == -1){
                perror("回显失败:");
            }

            close(acceptfd);  // 子进程通信完成,关闭客户端套接字(释放资源)
            kill(getpid(), SIGUSR1);  // 向自身发送SIGUSR1信号,触发父进程回收资源
            exit(0);  // 子进程正常退出(必须调用,否则子进程会继续执行主循环)
        }else if(pid > 0){  // 父进程逻辑(pid>0表示当前是父进程,pid为子进程ID)
            // 父进程无需与客户端通信,关闭客户端套接字(避免文件描述符泄漏)
            // 注:子进程会复制acceptfd,父进程关闭不影响子进程使用
            close(acceptfd);
        }
    }

    close(sockfd);  // 程序退出前,关闭服务器监听套接字(释放资源)
    return 0;
}

IO多路复用并发服务器

服务端

cs 复制代码
#include <stdio.h>          // 标准输入输出库(打印日志、错误信息)
#include <stdlib.h>         // 标准库(无直接使用,保留兼容)
#include <sys/types.h>      // 系统数据类型定义(socket、fd相关类型)
#include <string.h>         // 字符串处理库(memset清空缓冲区/结构体)
#include <sys/socket.h>     // socket核心库(创建、绑定、监听、收发、select等接口)
#include <arpa/inet.h>      // IP地址转换库(inet_ntoa:网络字节序IP转字符串)
#include <netinet/ip.h>     // IPv4协议补充定义(支持sockaddr_in结构体)
#include <netinet/in.h>     // 网络地址结构体(sockaddr_in)+ 字节序转换函数(htons/ntohs)
#include <unistd.h>         // 系统调用库(close关闭套接字)
#include <sys/time.h>       // select机制依赖(fd_set、select函数声明)

// 服务器监听端口号(1024-65535区间,需确保未被占用)
#define PORT 8888
// 服务器绑定IP(根据实际网卡配置修改,与客户端需同一网段;0.0.0.0可监听所有网卡)
#define SERVER_IP "192.168.26.128"
// 数据收发缓冲区大小(单次最大传输128字节,避免缓冲区溢出)
#define BUFFER_SIZE 128

int main(){
    // 监听套接字描述符:专门用于接受客户端连接请求,不直接收发数据
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(listen_fd == -1){  // socket创建失败(如权限不足、系统资源耗尽)
        perror("创建套接字失败:");  // 打印系统级错误原因
        return -1;                  // 初始化失败,直接退出程序
    }

    // 服务器地址结构体:存储服务器的IP、端口、协议族等核心信息
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));  // 清空结构体(避免随机垃圾值干扰)
    server_addr.sin_family = AF_INET;              // 协议族:IPv4
    server_addr.sin_port = htons(PORT);             // 端口号:htons将主机字节序转为网络字节序(大端)
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);  // IP地址:inet_addr将字符串IP转为网络字节序
    socklen_t serveraddr_len = sizeof(server_addr);  // 地址结构体长度(传给bind函数的必需参数)

    // 绑定套接字:将监听套接字与服务器IP+端口绑定(确定服务的网络地址)
    int ret = bind(listen_fd, (struct sockaddr*)&server_addr, serveraddr_len);
    if(ret == -1){  // 绑定失败(如IP错误、端口已被占用、无绑定权限)
        perror("绑定失败:");
        close(listen_fd);  // 释放已创建的监听套接字资源(避免泄漏)
        return -1;          // 绑定失败,退出程序
    }

    // 监听连接:将套接字转为"监听状态",等待客户端发起TCP连接请求
    // 第二个参数10:监听队列大小(最大同时等待连接的客户端数,超过则拒绝新连接)
    if(listen(listen_fd, 10) == -1){
        perror("监听失败:");
        close(listen_fd);  // 释放套接字资源
        return -1;          // 监听失败,退出程序
    }

    /**
     * select机制核心:文件描述符集合(fd_set)管理
     * 为什么需要两个集合?
     * - readfd_save_set:保存所有需要监控的fd(监听fd + 所有客户端连接fd),避免每次select后丢失
     * - readfd_modify_set:每次select调用的临时集合(select会修改该集合,只保留有事件的fd)
     */
    fd_set readfd_save_set, readfd_modify_set;
    FD_ZERO(&readfd_save_set);  // 初始化集合:清空所有fd位(默认所有fd都未被监控)
    FD_SET(listen_fd, &readfd_save_set);  // 将监听套接字加入监控集合(监控"新连接请求"事件)

    int max_fd = listen_fd;  // 记录当前最大的文件描述符(select的第一个参数必需,需动态更新)
    char buf[BUFFER_SIZE] = {0};  // 数据收发缓冲区(初始化清空,避免脏数据残留)

    printf("开启小型的select并发服务器\n");
    printf("服务器监听成功!IP:%s,端口:%d\n", SERVER_IP, PORT);

    // 主循环:持续监控并处理事件(select并发的核心循环)
    while(1){
        // 关键:每次select前,将临时集合重置为保存集合(因为select会修改临时集合)
        readfd_modify_set = readfd_save_set;

        /**
         * select函数:多路复用核心接口,监控多个fd的"读事件"
         * 参数说明:
         * 1. max_fd + 1:监控的fd范围(fd从0开始,需包含最大fd,故+1)
         * 2. &readfd_modify_set:监控"读事件"的fd集合(有数据可读/新连接请求)
         * 3. NULL:不监控"写事件"(此处仅需收发,无需主动监控写就绪)
         * 4. NULL:不监控"异常事件"(简化逻辑,默认忽略)
         * 5. NULL:阻塞模式(无任何事件时,select会一直阻塞,不占用CPU资源)
         * 返回值:>0表示有事件的fd数量;-1表示失败;0表示超时(此处无超时)
         */
        int fds = select(max_fd + 1, &readfd_modify_set, NULL, NULL, NULL);
        if(fds == -1){  // select调用失败(如被信号中断)
            perror("select失败:");
            continue;  // 不退出循环,继续监控(容错处理)
        }

        // 客户端地址结构体:存储"新连接客户端"的IP和端口信息(仅在accept时使用)
        struct sockaddr_in client_addr;
        socklen_t clientaddr_len = sizeof(client_addr);

        /**
         * 遍历所有可能的fd,判断哪个fd有事件发生
         * 为什么从3开始?0=标准输入、1=标准输出、2=标准错误,均不监控
         * 遍历范围:3 ~ max_fd(覆盖所有已加入监控的fd)
         */
        int event_fd = 3;
        for(; event_fd < max_fd + 1; event_fd++){
            // FD_ISSET:判断当前fd是否在"有事件的集合"中(核心事件判断宏)
            if(FD_ISSET(event_fd, &readfd_modify_set)){
                // 情况1:监听套接字有事件 → 有新客户端连接请求
                if(event_fd == listen_fd){
                    // 接受新连接:生成"客户端连接套接字"(acceptfd),专门与该客户端收发数据
                    int acceptfd = accept(event_fd, (struct sockaddr*)&client_addr, &clientaddr_len);
                    if(acceptfd == -1){  // 接受连接失败(如系统资源不足)
                        perror("accept失败:");
                        continue;  // 忽略该失败连接,继续监控其他fd
                    }

                    // 将新客户端的连接fd加入"保存集合"(后续监控其读事件:是否发数据)
                    FD_SET(acceptfd, &readfd_save_set);
                    // 更新max_fd:确保select能监控到最新的fd(必须维护,否则新fd无法被监控)
                    max_fd = (max_fd > acceptfd) ? max_fd : acceptfd;

                    // 打印客户端连接信息(inet_ntoa转IP,ntohs转端口字节序)
                    printf("客户端【%s:%d】连接服务器\n",
                           inet_ntoa(client_addr.sin_addr),  // 客户端IP(网络字节序→字符串)
                           ntohs(client_addr.sin_port));     // 客户端端口(网络字节序→主机字节序)
                }
                // 情况2:客户端连接fd有事件 → 客户端发送数据/断开连接
                else{
                    memset(buf, 0, sizeof(buf));  // 清空缓冲区(避免上次数据残留)
                    // 接收客户端数据:从客户端连接fd中读取数据
                    int nbytes = recv(event_fd, buf, BUFFER_SIZE, 0);
                    
                    if(nbytes == -1){  // 接收数据失败(如网络异常、客户端强制断开)
                        perror("获取客户端信息失败:");
                        continue;  // 忽略该错误,继续监控其他客户端
                    }
                    // nbytes == 0:客户端主动调用close断开连接(TCP四次挥手完成)
                    else if(nbytes == 0){
                        printf("客户端【%s:%d】断开连接\n",
                               inet_ntoa(client_addr.sin_addr),
                               ntohs(client_addr.sin_port));
                        FD_CLR(event_fd, &readfd_save_set);  // 从监控集合中移除该fd(不再监控)
                        close(event_fd);                      // 关闭连接套接字(释放资源,避免泄漏)
                        continue;  // 跳过后续逻辑,处理下一个fd
                    }

                    // 正常接收:打印客户端发送的数据
                    printf("客户端【%s:%d】发来数据:%s\n",
                           inet_ntoa(client_addr.sin_addr),
                           ntohs(client_addr.sin_port),
                           buf);

                    // 数据回显:将接收的数据原封不动发送回客户端(验证通信正常)
                    nbytes = send(event_fd, buf, BUFFER_SIZE, 0);
                    if(nbytes == -1){  // 回显失败(如客户端已断开)
                        perror("回显失败:");
                        continue;
                    }
                }
            }
        }
    }

    // 理论上不会执行到这里(主循环是死循环),仅作资源释放冗余处理
    close(listen_fd);
    return 0;
}
相关推荐
FOREVER-Q2 小时前
Windows 下 Docker Desktop 快速入门与镜像管理
运维·服务器·windows·docker·容器
地球没有花2 小时前
gitlab cicd首次操作
运维·git·ci/cd·gitlab
武子康2 小时前
Java-172 Neo4j 访问方式实战:嵌入式 vs 服务器(含 Java 示例与踩坑)
java·服务器·数据库·sql·spring·nosql·neo4j
864记忆2 小时前
Qt Network 模块中的函数详解
开发语言·网络·qt
864记忆2 小时前
Qt Sql 模块中的函数详解
开发语言·网络·qt
adnyting2 小时前
【Linux日新月异(五)】CentOS 7防火墙深度解析:firewalld全面指南
linux·运维·centos
IT瑞先生2 小时前
Docker容器使用手册——入门篇(上)
运维·docker·容器
东方隐侠安全团队-千里2 小时前
第13节 93年高能所被入侵,开启中国网络安全发展进程
网络·安全·web安全
liebe1*12 小时前
第五章 防火墙设备互联
网络·防火墙