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网络编程模型的基础。

相关推荐
迎風吹頭髮6 小时前
UNIX下C语言编程与实践53-UNIX 共享内存控制:shmctl 函数与共享内存管理
服务器·c语言·unix
猫头虎7 小时前
如何查看局域网内IP冲突问题?如何查看局域网IP环绕问题?arp -a命令如何使用?
网络·python·网络协议·tcp/ip·开源·pandas·pip
逆小舟9 小时前
【C/C++】指针
c语言·c++·笔记·学习
earthzhang20219 小时前
【1007】计算(a+b)×c的值
c语言·开发语言·数据结构·算法·青少年编程
迎風吹頭髮10 小时前
UNIX下C语言编程与实践63-UNIX 并发 Socket 编程:非阻塞套接字与轮询模型
java·c语言·unix
hello_25010 小时前
动手模拟docker网络-bridge模式
网络·docker·桥接模式
武文斌7710 小时前
项目学习总结:LVGL图形参数动态变化、开发板的GDB调试、sqlite3移植、MQTT协议、心跳包
linux·开发语言·网络·arm开发·数据库·嵌入式硬件·学习
爱吃喵的鲤鱼10 小时前
仿mudou——Connection模块(连接管理)
linux·运维·服务器·开发语言·网络·c++
爱吃小胖橘10 小时前
Unity网络开发--超文本传输协议Http(1)
开发语言·网络·网络协议·http·c#·游戏引擎