UNIX下C语言编程与实践58-UNIX TCP 连接处理:accept 函数与新套接字创建

本文中TCP套接字通信章节,聚焦UNIX系统中accept函数的核心功能、工作原理与实践应用,结合文档中的AcceptSock函数实例与多客户端处理方案,详细解析TCP连接接收流程、新套接字特性及常见问题解决方法,为UNIX TCP服务器开发提供理论与实践指导。

一、accept 函数的核心定义与作用

在UNIX TCP通信模型中,accept函数是服务器端接收客户端连接请求的"关键入口"。根据文档中TCP套接字的通信流程,服务器端需先通过socket()创建侦听套接字、bind()绑定地址端口、listen()设置侦听状态,最终通过accept()函数从"已完成连接队列"中取出连接请求,创建用于与客户端通信的新套接字。

1.1 函数原型与头文件

文档中明确了accept函数的原型及依赖头文件,该函数属于UNIX系统调用,定义在<sys/socket.h>中,需配合<sys/types.h>使用:

复制代码
#include <sys/types.h>
#include <sys/socket.h>

int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

1.2 核心功能解析

accept函数的核心功能可概括为"接收连接、创建新套接字、获取客户端地址",具体包括三个层面:

  • 接收连接请求 :从服务器侦听套接字(s)对应的"已完成连接队列"中,取出一个已通过TCP三次握手的连接请求;若队列为空,函数会根据套接字模式(阻塞/非阻塞)决定是否阻塞等待。
  • 创建新套接字 :为取出的连接请求创建一个新的套接字描述符,该新套接字与侦听套接字(s)属性一致(如协议族、套接字类型、协议),但仅用于与当前客户端的专属通信,原侦听套接字仍继续侦听其他连接请求。
  • 获取客户端地址 :若addr参数非NULL,函数会将客户端的协议地址信息(如IPv4地址、端口号)存入addr指向的结构中,同时通过addrlen参数返回地址结构的实际长度。

文档关键结论 :文档中强调,accept函数返回的新套接字是"通信套接字",而传入的s是"侦听套接字"------二者分工明确,侦听套接字仅负责接收连接,通信套接字仅负责与特定客户端的数据交互,避免功能混淆。

1.3 返回值说明

accept函数的返回值是区分操作成功与否的关键,文档中对返回值的定义如下:

  • 成功 :返回非负整数,即新创建的通信套接字描述符(后续通过send()recv()与客户端通信需使用该描述符);
  • 失败 :返回-1,并设置errno以指示错误原因,常见错误包括EBADF(侦听套接字无效)、EAGAIN(非阻塞模式下队列为空)、EINVAL(套接字未设置为侦听状态)等。

二、accept 函数的参数详解

文档中对accept函数的三个参数(saddraddrlen)的作用与使用细节有明确说明,每个参数的正确设置直接影响连接接收与客户端地址获取的正确性,以下是参数的详细解析:

参数名 类型 核心作用 使用注意事项(基于文档)
s int 服务器端的侦听套接字描述符,需先通过socket()创建、bind()绑定、listen()设置侦听状态。 1. 必须是有效的套接字描述符(非负数),否则返回EBADF; 2. 必须已调用listen()设置为侦听状态,否则返回EINVAL; 3. 必须是TCP流套接字(SOCK_STREAM),UDP数据报套接字调用accept会失败。
addr struct sockaddr * 输出参数,用于存储客户端的协议地址信息(如IPv4的struct sockaddr_in、IPv6的struct sockaddr_in6)。 1. 若为NULL,表示不需要获取客户端地址,函数仅创建新套接字; 2. 实际使用时需强制类型转换(如将struct sockaddr_in *转为struct sockaddr *),因为struct sockaddr是通用地址结构; 3. 需确保该指针指向的内存空间已分配,避免野指针导致内存错误。
addrlen socklen_t * 输入输出参数:输入时指定addr指向的地址结构大小;输出时返回客户端地址的实际长度(可能小于输入的结构大小)。 1. 必须是指向socklen_t类型变量的指针,不能直接传入值,否则返回EINVAL; 2. 输入前需初始化该变量为地址结构的大小(如sizeof(struct sockaddr_in)); 3. 输出后可通过该值判断客户端地址的实际长度(如IPv4地址长度固定为sizeof(struct sockaddr_in))。

2.1 客户端地址获取实例(基于文档)

文档中AcceptSock函数的实例展示了如何通过addraddrlen参数获取客户端地址,以下是简化后的代码示例(以IPv4为例):

复制代码
#include <netinet/in.h>
#include <arpa/inet.h>

// 封装accept函数,获取客户端地址
int AcceptSock(int *pSock, int nSock) {
    struct sockaddr_in client_addr; // IPv4客户端地址结构
    socklen_t addr_len = sizeof(client_addr); // 输入:地址结构大小
    char client_ip[INET_ADDRSTRLEN]; // 存储客户端IP字符串

    // 调用accept,获取新套接字并存储客户端地址
    *pSock = accept(nSock, (struct sockaddr*)&client_addr, &addr_len);
    if (*pSock < 0) {
        perror("accept failed");
        return -1;
    }

    // 输出:从addr_len获取实际地址长度(IPv4中通常等于sizeof(client_addr))
    printf("客户端地址长度:%d 字节\n", addr_len);

    // 将客户端IP从网络字节序转为字符串(inet_ntoa函数)
    inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
    // 客户端端口从网络字节序转为主机字节序(ntohs函数)
    printf("客户端连接:IP=%s, Port=%d\n", client_ip, ntohs(client_addr.sin_port));

    return 0;
}

上述代码中,通过struct sockaddr_in存储客户端IPv4地址,inet_ntop()将网络字节序的IP地址转为字符串,ntohs()将网络字节序的端口号转为主机字节序,这与文档中"字节序转换"的实践要求一致。

三、accept 函数的工作原理与新套接字特性

文档中从TCP连接队列与套接字分工两个维度,详细解释了accept函数的工作原理,以及新创建通信套接字的核心特性,这是理解TCP服务器并发处理的基础。

3.1 工作原理:基于TCP连接队列

TCP服务器调用listen()后,会创建两个队列:"未完成连接队列"(SYN队列)和"已完成连接队列"(Accept队列),accept函数的工作流程围绕这两个队列展开,文档中的流程描述如下:

TCP连接队列与accept函数交互流程
  1. 客户端发送SYN报文,请求建立连接,服务器将该连接放入"未完成连接队列"(SYN队列),并回复SYN+ACK报文;

  2. 客户端回复ACK报文,完成三次握手,服务器将该连接从"未完成连接队列"移至"已完成连接队列"(Accept队列);

  3. 服务器调用accept函数,从"已完成连接队列"头部取出一个连接请求;

  4. 为取出的连接创建新的通信套接字,将客户端地址存入addr(若非NULL),返回新套接字描述符;

  5. 若"已完成连接队列"为空:

  • 若侦听套接字为阻塞模式 (默认):accept函数阻塞,直到队列中有新连接;
  • 若侦听套接字为非阻塞模式accept函数立即返回-1errno设为EAGAINEWOULDBLOCK

文档关键细节 :文档中提到,listen()函数的backlog参数用于限制"已完成连接队列"的最大长度(默认5或10),若队列已满,新的连接请求会被服务器忽略(客户端会重发SYN报文),因此需根据并发量合理设置backlog值。

3.2 新套接字的核心特性

文档中明确了accept函数创建的新套接字(通信套接字)与原侦听套接字的区别与联系,核心特性如下:

特性维度 侦听套接字(s 通信套接字(accept返回值)
核心功能 接收客户端连接请求(仅负责"接客") 与特定客户端进行数据交互(仅负责"服务")
生命周期 服务器启动时创建,关闭时销毁(长期存在) 客户端连接建立时创建,连接关闭时销毁(短期存在)
属性继承 原始属性(协议族、类型、协议、端口绑定) 继承侦听套接字的协议族、类型、协议,但不继承端口绑定(与客户端建立专属连接)
可调用函数 仅支持accept()close()等少数函数,不能调用send()recv() 支持send()recv()shutdown()close()等数据交互函数
并发处理 一个服务器通常只有一个侦听套接字 一个客户端连接对应一个通信套接字,支持多客户端并发(需多进程/多线程/I/O多路复用)

例如,文档中的TCP服务器实例(tcp1.c)中,nSock是侦听套接字,通过AcceptSock(&nSock1, nSock)获取的nSock1是通信套接字,后续通过recv(nSock1, buf, ...)接收客户端数据,这正是基于两种套接字的功能分工。

四、accept 函数的阻塞与非阻塞模式

文档中提到,accept函数的阻塞行为由侦听套接字的模式(阻塞/非阻塞)决定,默认情况下侦听套接字为阻塞模式,accept会阻塞等待连接;若需实现非阻塞接收连接(如并发处理多个客户端),需通过fcntl函数修改套接字模式,以下是详细解析:

4.1 阻塞模式(默认)

当侦听套接字为阻塞模式时,accept函数的行为如下:

  • 若"已完成连接队列"非空:立即取出一个连接,创建新套接字并返回;
  • 若"已完成连接队列"为空:函数阻塞,直到队列中有新连接到达(或被信号中断);
  • 被信号中断:若阻塞过程中收到可捕获的信号(如SIGINTSIGCHLD),函数会返回-1errno设为EINTR,此时需重新调用accept(文档中建议通过循环重试处理该错误)。

阻塞模式下处理EINTR错误(基于文档)

复制代码
int accept_blocking(int listen_fd) {
    int conn_fd;
    struct sockaddr_in client_addr;
    socklen_t addr_len = sizeof(client_addr);

    // 循环调用accept,处理EINTR错误(被信号中断)
    while (1) {
        conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &addr_len);
        if (conn_fd < 0) {
            if (errno == EINTR) {
                // 被信号中断,重试accept
                perror("accept interrupted by signal, retrying...");
                continue;
            } else {
                // 其他错误,返回失败
                perror("accept failed");
                return -1;
            }
        }
        // 成功获取连接,返回通信套接字
        return conn_fd;
    }
}

4.2 非阻塞模式(手动设置)

文档中AcceptSock函数的实例展示了如何通过fcntl函数将侦听套接字设为非阻塞模式,实现非阻塞接收连接,核心是设置O_NONBLOCK标志:

(1)设置非阻塞模式的步骤
复制代码
#include <fcntl.h>

// 将套接字设为非阻塞模式
int set_nonblocking(int sock_fd) {
    int flags;

    // 1. 获取套接字当前的标志
    flags = fcntl(sock_fd, F_GETFL, 0);
    if (flags < 0) {
        perror("fcntl F_GETFL failed");
        return -1;
    }

    // 2. 添加O_NONBLOCK标志,设置为非阻塞模式
    if (fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK) < 0) {
        perror("fcntl F_SETFL failed");
        return -1;
    }

    return 0;
}
(2)非阻塞模式下accept的行为

侦听套接字设为非阻塞模式后,accept函数的行为如下:

  • 若"已完成连接队列"非空:立即取出一个连接,创建新套接字并返回;
  • 若"已完成连接队列"为空:函数立即返回-1errno设为EAGAINEWOULDBLOCK(不同系统可能不同,文档中建议同时判断这两个值);
  • 适用场景:非阻塞模式通常与select()poll()epoll()(Linux)结合使用,实现I/O多路复用,同时监控多个套接字(如侦听套接字+多个通信套接字)的状态变化。
(3)非阻塞模式下的错误处理(基于文档)
复制代码
int accept_nonblocking(int listen_fd) {
    int conn_fd;
    struct sockaddr_in client_addr;
    socklen_t addr_len = sizeof(client_addr);

    conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &addr_len);
    if (conn_fd < 0) {
        // 区分"暂时无连接"和"真正错误"
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            // 暂时无连接,非错误,返回0提示重试
            printf("no connection available now, try again later\n");
            return 0;
        } else if (errno == EINTR) {
            // 被信号中断,返回0提示重试
            printf("accept interrupted by signal, try again later\n");
            return 0;
        } else {
            // 其他错误,返回-1
            perror("accept failed");
            return -1;
        }
    }

    // 成功获取连接,打印客户端信息
    char client_ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
    printf("new client: IP=%s, Port=%d\n", client_ip, ntohs(client_addr.sin_port));
    return conn_fd;
}

五、accept 函数的常见错误与解决方法

文档中通过tcp1.c等实例,总结了accept函数调用过程中常见的错误类型及排查方向,结合UNIX系统编程实践,以下是典型错误的分析与解决方法:

错误现象 errno值 产生原因(基于文档) 解决方法
accept返回-1,提示"无效文件描述符" EBADF 1. 传入的侦听套接字描述符(s)无效(如为负数、已关闭); 2. 套接字描述符被意外修改(如变量被覆盖)。 1. 检查socket()的返回值,确保侦听套接字创建成功; 2. 调用accept前,确认套接字未被close()关闭; 3. 排查代码中是否有意外修改套接字描述符变量的逻辑。
accept返回-1,提示"无效参数" EINVAL 1. 套接字未调用listen()设置为侦听状态; 2. addrlen参数非指针(如直接传入值); 3. 套接字类型不支持accept(如UDP数据报套接字)。 1. 确保调用accept前已执行listen(s, backlog); 2. addrlen必须传入指向socklen_t变量的指针(如&addr_len); 3. 确认套接字类型为SOCK_STREAM(TCP),UDP套接字无需调用accept
非阻塞模式下accept返回-1,提示"暂时无可用连接" EAGAIN/EWOULDBLOCK 非阻塞模式下,"已完成连接队列"为空,无新连接可接收。 1. 结合I/O多路复用(如select()),先监控侦听套接字的"读事件"(有连接时触发),再调用accept; 2. 通过循环重试(需控制重试频率,避免CPU空转); 3. 若无需实时性,可短暂休眠(如usleep(1000))后重试。
阻塞模式下accept返回-1,提示"被信号中断" EINTR 阻塞等待过程中,进程收到可捕获的信号(如SIGCHLD子进程退出、SIGINTCtrl+C中断)。 1. 在accept外层添加循环,若errno == EINTR则重新调用accept; 2. 若无需处理信号,可通过signal(sig, SIG_IGN)忽略特定信号(如忽略SIGCHLD); 3. 使用sigaction函数设置信号处理,并添加SA_RESTART标志,使被中断的系统调用自动重启(部分系统支持)。
客户端地址获取错误(如IP为0.0.0.0、端口为0) 无(accept返回成功,但addr内容异常) 1. addrlen参数未初始化,传入了随机值; 2. addr指针指向的内存空间不足,导致地址信息被截断; 3. 未进行字节序转换(如直接使用client_addr.sin_port,未调用ntohs())。 1. 调用accept前,必须初始化addrlen为地址结构的大小(如addr_len = sizeof(struct sockaddr_in)); 2. 确保addr指向的内存空间大于等于地址结构的大小; 3. 获取IP和端口时,使用inet_ntop()转换IP、ntohs()转换端口,确保字节序正确。

文档中的典型错误案例 :文档中的tcp1.c实例曾出现"accept返回-1,errno=9(EBADF)"的错误,经排查发现是CreateSock函数中错误关闭了侦听套接字,导致后续accept调用时传入的套接字描述符无效。这提示开发者:调用accept前必须严格检查侦听套接字的有效性。

六、多客户端连接处理与新套接字管理

文档中指出,单个accept调用只能处理一个客户端连接,要实现多客户端并发,需结合多进程、多线程或I/O多路复用技术;同时,新创建的通信套接字需及时管理,避免资源泄漏,以下是详细方案:

6.1 多客户端连接处理方案(基于文档)

文档中重点介绍了三种多客户端处理方案,分别适用于不同的并发场景:

(1)多进程方案:父进程侦听,子进程处理

核心逻辑:父进程创建侦听套接字并循环调用accept,每接收一个连接就fork()创建子进程,由子进程通过新套接字与客户端通信,父进程关闭新套接字并继续侦听。文档中exec1.cfork-exec模型为此方案的基础,具体实现如下:

复制代码
#include <unistd.h>
#include <sys/wait.h>

void multi_process_server(int listen_fd) {
    int conn_fd;
    struct sockaddr_in client_addr;
    socklen_t addr_len = sizeof(client_addr);

    while (1) {
        // 父进程接收连接
        conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &addr_len);
        if (conn_fd < 0) {
            perror("accept failed");
            continue;
        }

        // 创建子进程
        pid_t pid = fork();
        if (pid < 0) {
            perror("fork failed");
            close(conn_fd); // 失败时关闭新套接字,避免泄漏
            continue;
        } else if (pid == 0) {
            // 子进程:处理客户端通信,关闭侦听套接字(子进程无需侦听)
            close(listen_fd);
            char buf[1024];
            ssize_t n;

            // 接收客户端数据
            n = recv(conn_fd, buf, sizeof(buf)-1, 0);
            if (n > 0) {
                buf[n] = '\0';
                printf("child process %d: received data: %s\n", getpid(), buf);
                // 回复客户端
                send(conn_fd, "Hello from server", 16, 0);
            }

            // 通信结束,关闭新套接字
            close(conn_fd);
            exit(0);
        } else {
            // 父进程:关闭新套接字(子进程已复制该套接字),继续侦听
            close(conn_fd);
            // 回收子进程资源,避免僵死进程
            waitpid(-1, NULL, WNOHANG);
        }
    }
}

优点:实现简单,各子进程独立运行,互不影响;缺点:进程创建开销大,内存占用高,不适用于高并发(如万级客户端)。

(2)多线程方案:主线程侦听,子线程处理

核心逻辑:主线程创建侦听套接字并循环调用accept,每接收一个连接就创建子线程,由子线程通过新套接字与客户端通信,主线程继续侦听。文档中虽未直接提供多线程实例,但基于UNIX线程模型,实现如下:

复制代码
#include <pthread.h>

// 线程处理函数的参数:新套接字描述符
typedef struct {
    int conn_fd;
    struct sockaddr_in client_addr;
} ThreadArgs;

// 子线程:处理客户端通信
void *handle_client(void *arg) {
    ThreadArgs *args = (ThreadArgs*)arg;
    int conn_fd = args->conn_fd;
    struct sockaddr_in client_addr = args->client_addr;
    free(args); // 释放参数内存

    // 分离线程,避免主线程等待
    pthread_detach(pthread_self());

    char client_ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
    printf("thread %ld: new client: IP=%s, Port=%d\n", 
           pthread_self(), client_ip, ntohs(client_addr.sin_port));

    // 与客户端通信
    char buf[1024];
    ssize_t n = recv(conn_fd, buf, sizeof(buf)-1, 0);
    if (n > 0) {
        buf[n] = '\0';
        printf("thread %ld: received: %s\n", pthread_self(), buf);
        send(conn_fd, "Server received", 14, 0);
    }

    // 通信结束,关闭新套接字
    close(conn_fd);
    return NULL;
}

// 主线程:侦听并创建子线程
void multi_thread_server(int listen_fd) {
    int conn_fd;
    struct sockaddr_in client_addr;
    socklen_t addr_len = sizeof(client_addr);

    while (1) {
        conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &addr_len);
        if (conn_fd < 0) {
            perror("accept failed");
            continue;
        }

        // 为线程处理函数分配参数
        ThreadArgs *args = malloc(sizeof(ThreadArgs));
        args->conn_fd = conn_fd;
        args->client_addr = client_addr;

        // 创建子线程
        pthread_t tid;
        if (pthread_create(&tid, NULL, handle_client, args) != 0) {
            perror("pthread_create failed");
            free(args);
            close(conn_fd);
            continue;
        }
    }
}

优点:线程创建开销比进程小,内存占用低,并发性能优于多进程;缺点:需注意线程安全(如共享变量需加锁),线程数量过多时仍会消耗大量资源。

(3)I/O多路复用方案:select/poll/epoll

核心逻辑:通过select()poll()epoll()(Linux)监控多个套接字(侦听套接字+多个通信套接字)的"读事件",当侦听套接字有新连接时调用accept,当通信套接字有数据时调用recv(),单进程/单线程即可处理多客户端。文档中timeout3.cselect实例为此方案的基础,具体实现如下(以select为例):

复制代码
#include <sys/select.h>
#include <limits.h>

#define MAX_CLIENT 1024 // 最大客户端数量

void io_multiplex_server(int listen_fd) {
    int max_fd = listen_fd;
    fd_set read_fds;
    int client_fds[MAX_CLIENT] = {0}; // 存储所有通信套接字
    int i;

    while (1) {
        // 初始化文件描述符集合
        FD_ZERO(&read_fds);
        FD_SET(listen_fd, &read_fds); // 添加侦听套接字

        // 添加所有通信套接字到集合
        for (i = 0; i < MAX_CLIENT; i++) {
            if (client_fds[i] > 0) {
                FD_SET(client_fds[i], &read_fds);
                if (client_fds[i] > max_fd) {
                    max_fd = client_fds[i]; // 更新最大文件描述符
                }
            }
        }

        // 等待事件(超时时间设为NULL,永久阻塞)
        int ret = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
        if (ret < 0) {
            perror("select failed");
            continue;
        }

        // 检查侦听套接字:是否有新连接
        if (FD_ISSET(listen_fd, &read_fds)) {
            struct sockaddr_in client_addr;
            socklen_t addr_len = sizeof(client_addr);
            int conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &addr_len);
            if (conn_fd < 0) {
                perror("accept failed");
                continue;
            }

            // 将新套接字加入client_fds
            for (i = 0; i < MAX_CLIENT; i++) {
                if (client_fds[i] == 0) {
                    client_fds[i] = conn_fd;
                    printf("new client: conn_fd=%d\n", conn_fd);
                    break;
                }
            }
            if (i == MAX_CLIENT) {
                // 客户端数量达到上限,关闭新套接字
                printf("max client reached, close conn_fd=%d\n", conn_fd);
                close(conn_fd);
            }
        }

        // 检查通信套接字:是否有数据可读
        for (i = 0; i < MAX_CLIENT; i++) {
            int conn_fd = client_fds[i];
            if (conn_fd > 0 && FD_ISSET(conn_fd, &read_fds)) {
                char buf[1024];
                ssize_t n = recv(conn_fd, buf, sizeof(buf)-1, 0);
                if (n > 0) {
                    // 接收数据成功
                    buf[n] = '\0';
                    printf("conn_fd=%d: received: %s\n", conn_fd, buf);
                    send(conn_fd, "Server ok", 9, 0);
                } else if (n == 0) {
                    // 客户端关闭连接
                    printf("conn_fd=%d: client closed\n", conn_fd);
                    close(conn_fd);
                    client_fds[i] = 0; // 清空套接字
                } else {
                    // 接收错误
                    perror("recv failed");
                    close(conn_fd);
                    client_fds[i] = 0;
                }
            }
        }
    }
}

优点:单进程/单线程处理多客户端,资源消耗极低,适用于高并发(如万级/十万级客户端);缺点:实现较复杂,需手动管理套接字集合,select有最大文件描述符限制(默认1024),可通过epoll(Linux)或kqueue(BSD)突破限制。

6.2 新套接字的管理:避免资源泄漏

文档中多次强调,新创建的通信套接字必须及时关闭,否则会导致文件描述符泄漏(每个进程的文件描述符数量有上限,默认1024),最终导致无法创建新套接字或文件。以下是新套接字管理的核心原则:

  • 通信结束后关闭 :当客户端断开连接(recv()返回0)或通信完成后,必须调用close(conn_fd)关闭新套接字;若需半关闭(仅关闭发送或接收通道),可使用shutdown(conn_fd, SHUT_WR)shutdown(conn_fd, SHUT_RD),但最终仍需close()释放资源。
  • 错误处理时关闭 :若recv()send()等函数调用失败(返回-1),需先关闭新套接字,再处理错误,避免套接字资源泄漏。例如,多进程方案中,fork()失败时需关闭conn_fd
  • 进程/线程退出时关闭 :若子进程/子线程异常退出(如收到SIGSEGV信号),需确保在退出前关闭新套接字,可通过注册信号处理函数(如signal(SIGTERM, handle_exit)),在处理函数中关闭套接字。
  • 监控文件描述符数量 :通过ulimit -n查看进程的最大文件描述符限制,通过lsof -p 查看进程当前打开的文件描述符,及时发现泄漏问题。

文档中的资源泄漏案例 :文档中szomb1.c的僵死进程案例提示,若子进程未关闭新套接字就退出,父进程又未及时回收子进程资源,会导致新套接字长期占用,最终引发资源泄漏。因此,新套接字的关闭与进程资源回收需同步处理。

七、总结

accept函数是UNIX TCP服务器接收客户端连接的核心入口,其核心价值在于"分离侦听与通信功能"------通过创建新套接字,使侦听套接字可持续接收新连接,通信套接字专注于与特定客户端的数据交互,为多客户端并发提供基础。

在实践中,需重点掌握:

  • accept函数的参数设置,尤其是addraddrlen的正确使用,确保能准确获取客户端地址;
  • 阻塞与非阻塞模式的区别,根据并发需求选择合适的模式,非阻塞模式需结合I/O多路复用提升性能;
  • 常见错误的排查方法,尤其是EINTR(信号中断)和EAGAIN(非阻塞无连接)的处理;
  • 多客户端处理方案的选择(多进程/多线程/I/O多路复用),以及新套接字的及时关闭,避免资源泄漏。

掌握accept函数的使用与新套接字管理,是构建稳定、高效的UNIX TCP服务器的关键,也是深入理解UNIX网络编程模型的基础。

相关推荐
菜择贰9 分钟前
计算机网络课设
网络·计算机网络·智能路由器
代码游侠9 分钟前
学习笔记——ESP8266 WiFi模块
服务器·c语言·开发语言·数据结构·算法
浅安的邂逅26 分钟前
ubuntu 18.04及以上版本配置静态IP方法
linux·运维·网络·ubuntu·ip设置
阿巴~阿巴~28 分钟前
从钓鱼到高性能服务器:深入解析操作系统五大 I/O 模型
运维·服务器·网络·系统调用·五种i/o模型
sunfove10 小时前
光网络的立交桥:光开关 (Optical Switch) 原理与主流技术解析
网络
Felven12 小时前
A. Helmets in Night Light
c语言
Kevin Wang72713 小时前
欧拉系统服务部署注意事项
网络·windows
min18112345613 小时前
深度伪造内容的检测与溯源技术
大数据·网络·人工智能
汤愈韬13 小时前
Full Cone Nat
网络·网络协议·网络安全·security·huawei
zbtlink14 小时前
现在还需要带电池的路由器吗?是用来干嘛的?
网络·智能路由器