云计算百科
云计算领域专业知识百科平台

Linux TCP回显服务器实现(deepseek教我写代码)

我让deepseek写了一个程序,结果我竟然有好多看不明白的技巧,泪流满面了。

1 写一个linux程序,实现一个tcp服务器,功能是将收到的数据原路返回

已深度思考(用时 95 秒)

好的,我现在需要写一个Linux下的TCP服务器程序,功能是将收到的数据原路返回。也就是所谓的回显服务器,Echo Server。我之前没写过这样的程序,但有一些C语言和网络编程的基础知识,所以得仔细想想该怎么实现。

首先,我应该回忆一下TCP服务器的工作流程。一般来说,TCP服务器的步骤大概是这样的:创建套接字,绑定地址和端口,监听连接,接受连接,然后处理数据。对吧?

那具体到每个步骤,应该怎么做呢?首先,创建套接字。使用socket()函数,参数应该是AF_INET(IPv4),SOCK_STREAM(TCP),协议可以填0,让系统自动选择。然后需要绑定地址和端口到套接字,这需要设置一个sockaddr_in结构,包括地址族、端口号和IP地址。这里服务器的IP地址通常用INADDR_ANY,表示监听所有可用的接口。绑定时用bind()函数。

接着是监听,用listen()函数,设置等待连接队列的最大长度。然后进入循环,用accept()接受客户端的连接。accept()会返回一个新的套接字,用于和客户端通信。这时候,服务器通常会fork一个子进程或者创建一个线程来处理这个连接,或者使用非阻塞的方式,但这里可能简单处理,逐个处理连接?不过这样的话,一次只能处理一个客户端。或者是否应该使用多线程或者多进程?或者可能例子中用单线程,逐个处理,但这样可能不够实用。不过对于简单的示例来说,可能可以暂时不考虑并发,先处理单个连接,然后再优化?

然后,当客户端连接后,服务器需要读取客户端发来的数据,然后将数据回发回去。这个过程可能是在一个循环里,recv()读取数据,然后send()发送回去。直到客户端关闭连接,这时候recv()返回0,或者出错,然后关闭连接。

那具体到代码结构,大概是这样的:

1. 创建socket:socket()

2. 设置地址结构:sockaddr_in,包括端口号等。

3. bind()绑定地址

4. listen()开始监听

5. accept()接受连接,得到新的socket

6. 循环:recv()接收数据,send()发送回去

7. 关闭连接

但可能遇到的问题是,如何处理多个客户端?比如,当一个客户端在处理的时候,其他客户端无法连接。所以可能应该每接受到一个连接,就创建一个子进程或者线程来处理。比如,使用fork()创建子进程,在子进程中处理数据收发,而主进程继续等待其他连接。

这样,主进程在accept之后fork,子进程处理具体的通信,父进程关闭连接套接字,继续循环accept。

不过需要注意,子进程需要正确关闭不需要的套接字,否则可能导致资源泄露。此外,还需要处理僵尸进程的问题,通过signal(SIGCHLD, SIG_IGN)来忽略子进程结束信号,或者使用waitpid()回收资源。

那现在,我需要把这些步骤转化为C代码。

首先,头文件可能需要包括:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

然后,主函数的结构:

int main() {

int sockfd, newsockfd, portno;

struct sockaddr_in serv_addr, cli_addr;

socklen_t clilen;

// 创建socket

sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0) {

perror("ERROR opening socket");

exit(1);

}

// 设置serv_addr结构

bzero((char *) &serv_addr, sizeof(serv_addr));

portno = 12345; // 假设端口号是12345

serv_addr.sin_family = AF_INET;

serv_addr.sin_addr.s_addr = INADDR_ANY;

serv_addr.sin_port = htons(portno);

// bind

if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {

perror("ERROR on binding");

exit(1);

}

// listen

listen(sockfd, 5);

clilen = sizeof(cli_addr);

while (1) {

// accept

newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);

if (newsockfd < 0) {

perror("ERROR on accept");

exit(1);

}

// fork子进程

pid_t pid = fork();

if (pid < 0) {

perror("ERROR on fork");

exit(1);

}

if (pid == 0) { // 子进程

close(sockfd); // 子进程不需要监听socket

// 处理数据

char buffer[256];

int n;

while (1) {

n = recv(newsockfd, buffer, 255, 0);

if (n < 0) {

perror("ERROR reading from socket");

break;

} else if (n == 0) {

// 客户端关闭连接

break;

}

// 发送回去

n = send(newsockfd, buffer, n, 0);

if (n < 0) {

perror("ERROR writing to socket");

break;

}

}

close(newsockfd);

exit(0); // 子进程结束

} else { // 父进程

close(newsockfd); // 父进程关闭连接套接字

}

}

close(sockfd);

return 0;

}

不过,这里可能有几个问题需要注意:

1. 子进程结束后会成为僵尸进程,需要处理。可以通过signal(SIGCHLD, SIG_IGN)来让系统自动回收,或者在父进程中调用waitpid。

所以应该在主函数开始时设置忽略SIGCHLD信号:

signal(SIGCHLD, SIG_IGN);

这样,当子进程结束时,父进程不会积累僵尸进程。

2. 错误处理需要更完善。比如,accept可能被信号中断,所以需要考虑EINTR的情况?不过对于简单示例可能可以暂时忽略。

3. 端口号可能被占用,或者权限问题(比如绑定到低于1024的端口需要root权限),所以这里选一个较高的端口,比如12345。

4. recv和send的返回值处理:recv返回实际读取的字节数,可能比请求的少,所以需要循环读取?不过在这个例子中,可能不需要,因为如果数据量大,可能需要多次读取,但回显服务器一般可能不需要,每次读取尽可能多的数据,然后发送回去即可。但这里可能只是简单处理,每次最多读取255字节,然后回发。实际应用中可能需要处理更大的数据和更复杂的情况,但作为示例,可能足够。

另外,关于网络字节序的问题,serv_addr的端口号需要用htons转换,地址用INADDR_ANY已经是网络字节序吗?是的,因为INADDR_ANY通常是0,所以无所谓,但其他情况下需要inet_addr或inet_pton来处理IP地址的转换。

现在,测试这个程序是否可行。编译之后运行,用telnet或者nc命令连接,发送数据,看是否回显。

可能的错误点:

– 在fork之后,子进程和父进程都要正确关闭不需要的套接字。比如子进程关闭监听套接字,父进程关闭连接套接字,否则可能导致资源泄漏。

– 在循环读取数据时,当recv返回0时,表示客户端关闭了连接,这时候应该退出循环,关闭连接套接字,子进程结束。

这样应该可以处理每个连接。每个客户端连接后,服务器会创建一个子进程来处理回显,主进程继续等待其他连接。

此外,需要确保编译时链接相应的库,比如在Linux下用gcc编译,不需要特别的库,但要注意代码中的头文件是否正确。

综上,这样的代码应该可以实现一个基本的TCP回显服务器。可能还有一些细节需要调整,比如处理recv和send的返回值是否正确,比如n可能小于预期的字节数,是否需要循环发送?例如,send可能没有一次性发送所有数据,可能需要多次发送。但是在回显的情况下,可能可以忽略,因为如果网络正常的话,一般send会发送全部数据。但为了健壮性,可能需要循环发送,直到所有数据都发送完毕。

例如,在发送部分:

int bytes_sent = 0;

while (bytes_sent < n) {

int result = send(newsockfd, buffer + bytes_sent, n – bytes_sent, 0);

if (result < 0) {

perror("ERROR writing to socket");

break;

}

bytes_sent += result;

}

同样,recv可能也需要多次读取,直到收到所有预期的数据?不过在这个例子中,因为客户端发送的数据可能不定长,所以每次读取尽可能多的数据,然后立即回发。所以可能不需要循环读取,而是一次读取,一次发送。但这样的话,如果客户端发送的数据量很大,超过缓冲区,可能需要多次读取。不过对于这个例子来说,可能可以接受。

总结,代码的大致结构是正确的,可能需要注意一些细节,比如处理send和recv的返回值,确保所有数据都被正确发送和接收。此外,处理子进程的回收问题,避免僵尸进程。通过设置忽略SIGCHLD信号,可以自动处理。

现在,把这些思路转化为最终的代码。

以下是一个实现TCP回显服务器的C语言程序,该服务器将接收到的数据原样返回给客户端:

c

复制

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define BUFFER_SIZE 1024
#define PORT 12345

void handle_client(int sockfd) {
char buffer[BUFFER_SIZE];
ssize_t bytes_received;

while ((bytes_received = recv(sockfd, buffer, BUFFER_SIZE, 0)) > 0) {
ssize_t bytes_sent = 0;
while (bytes_sent < bytes_received) {
ssize_t result = send(sockfd, buffer + bytes_sent, bytes_received – bytes_sent, 0);
if (result < 0) {
perror("send failed");
close(sockfd);
return;
}
bytes_sent += result;
}
}

if (bytes_received < 0) {
perror("recv failed");
}
close(sockfd);
}

int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);

// 忽略SIGCHLD信号避免僵尸进程
signal(SIGCHLD, SIG_IGN);

// 创建TCP套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}

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

// 绑定套接字
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}

// 开始监听
if (listen(server_fd, 5) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}

printf("Server listening on port %d…\\n", PORT);

while (1) {
// 接受新连接
if ((client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len)) < 0) {
perror("accept failed");
continue;
}

// 显示客户端信息
printf("New connection from %s:%d\\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

// 创建子进程处理客户端
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
close(client_fd);
} else if (pid == 0) { // 子进程
close(server_fd); // 关闭不需要的监听套接字
handle_client(client_fd);
exit(EXIT_SUCCESS);
} else { // 父进程
close(client_fd); // 关闭不需要的客户端套接字
}
}

close(server_fd);
return 0;
}

程序说明:

  • 创建套接字:使用socket()创建TCP套接字

  • 绑定地址:绑定到所有接口(INADDR_ANY)的指定端口

  • 监听连接:使用listen()进入监听状态

  • 接受连接:通过accept()循环接受新连接

  • 多进程处理:

    • 父进程接受连接后创建子进程

    • 子进程处理客户端通信(handle_client函数)

    • 父进程继续监听新连接

  • 数据处理:

    • 使用recv()接收数据

    • 使用send()确保完整发送所有接收到的数据

    • 当客户端断开连接时自动清理资源

  • 编译运行:

    bash

    复制

    gcc echo_server.c -o echo_server
    ./echo_server

    测试方法:

    使用telnet或netcat连接服务器:

    bash

    复制

    nc 127.0.0.1 12345

    输入任意内容,服务器会将相同内容回显。

    特点:

    • 支持多客户端并发连接

    • 自动回收子进程资源

    • 完整处理TCP数据流(处理短包和粘包问题)

    • 详细的错误处理

    • 显示客户端连接信息

    这个实现使用了传统的多进程模型,适合演示基本的TCP服务器工作原理。在实际生产环境中,可能会使用更高效的I/O复用方式(如epoll)或线程池来处理并发连接。

    2 signal(SIGCHLD, SIG_IGN);为什么能避免僵尸进程

    已深度思考(用时 27 秒)

    好的,用户问的是为什么使用signal(SIGCHLD, SIG_IGN);可以避免僵尸进程。我需要先回顾一下僵尸进程的相关知识。

    首先,僵尸进程是什么?当子进程结束运行时,父进程需要调用wait或waitpid来获取子进程的退出状态,否则子进程的进程描述符仍然保留在系统中,变成僵尸进程。这会导致资源浪费,如果大量僵尸进程存在,可能会影响系统性能。

    那用户提供的代码中,父进程在处理完客户端连接后,通过fork创建子进程。子进程处理完客户端请求后退出,这时候父进程如果没有处理子进程的终止状态,就会产生僵尸进程。

    通常处理SIGCHLD信号的方法是在信号处理函数中调用wait或waitpid来回收子进程。但用户代码中使用的是signal(SIGCHLD, SIG_IGN);,也就是忽略SIGCHLD信号。这里需要理解为什么忽略这个信号可以避免僵尸进程。

    根据之前学的知识,当父进程忽略SIGCHLD信号时,系统会自动回收子进程的退出状态,不会让子进程变成僵尸进程。这是因为当信号被设置为SIG_IGN时,内核会处理终止的子进程,直接释放相关资源,不需要父进程主动调用wait。这可能是POSIX标准中的行为,但需要确认不同UNIX系统的兼容性。

    不过,需要注意的是,不是所有系统都支持这种行为。比如,在System V的传统中,忽略SIGCHLD并不能阻止僵尸进程的产生,必须显式处理。但根据用户提供的代码是在Linux环境下运行的,而Linux遵循的是BSD的行为,当忽略SIGCHLD时,子进程终止后会被自动回收,不会变成僵尸进程。因此,这样的设置确实有效。

    这样处理的好处是代码更简洁,不需要在父进程中设置信号处理函数或者调用wait。对于简单的服务器程序来说,这可能是一个方便的解决方案。但需要指出,这种做法可能在某些UNIX变种中不可行,但在Linux下是有效的。

    总结一下,用户的问题核心在于理解忽略SIGCHLD信号如何避免僵尸进程。需要解释SIGCHLD的作用,僵尸进程的成因,以及忽略该信号后系统如何处理子进程的退出状态。此外,还要提到不同系统的差异,确保用户知道这种方法的适用环境。

    在回答时,应该分步骤解释,首先说明僵尸进程的产生原因,然后说明SIGCHLD信号的作用,接着解释忽略该信号时系统的行为,最后提及兼容性和注意事项。这样用户能清晰地理解整个过程,并知道在何种情况下使用这种方法合适。

    在Linux系统中,signal(SIGCHLD, SIG_IGN); 这一行代码能避免僵尸进程的原因与UNIX系统的进程管理机制密切相关。以下是分步解释:


    一、僵尸进程的成因

  • 什么是僵尸进程 当子进程终止时,它不会立即从系统中消失。 父进程必须调用 wait() 或 waitpid() 来读取子进程的退出状态。 如果父进程没有做这件事,子进程的进程描述符(PID)会残留在系统中,成为僵尸进程(Zombie Process)。

  • 僵尸进程的危害 占用系统资源(PID有限),大量僵尸进程可能导致无法创建新进程。


  • 二、SIGCHLD 信号的作用

  • 信号触发条件 当子进程终止时,内核会向父进程发送 SIGCHLD 信号,通知父进程"有子进程退出了"。

  • 默认行为 默认情况下,SIGCHLD 信号的默认处理方式是忽略(SIG_DFL),但不会自动回收僵尸进程。 此时如果父进程不主动调用 wait(),僵尸进程仍然会存在。


  • 三、SIG_IGN 的特殊行为

  • 显式忽略信号 通过 signal(SIGCHLD, SIG_IGN); 显式告知系统:"父进程完全不想关心子进程的退出状态"。

  • 内核的优化处理 在Linux(以及大多数现代UNIX系统)中,当设置了 SIGCHLD 的处理方式为 SIG_IGN 时:

    • 内核会立即自动回收所有终止的子进程

    • 不再保留子进程的退出状态信息

    • 子进程不会进入僵尸状态

    • 父进程不需要(也无法)调用 wait() 函数


  • 四、与传统行为的区别

  • 历史差异

    • 在System V UNIX系统中,即使设置了 SIG_IGN,仍然会产生僵尸进程

    • 但在BSD派生的系统(包括Linux)中,SIG_IGN 会触发自动回收行为

  • POSIX标准 POSIX.1-2001明确规定了这种行为的合法性,因此现代系统都会遵守此规范。


  • 五、代码示例验证

    c

    复制

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <signal.h>

    int main() {
    signal(SIGCHLD, SIG_IGN); // 关键代码

    pid_t pid = fork();
    if (pid == 0) {
    // 子进程立即退出
    printf("Child process %d exiting\\n", getpid());
    exit(0);
    } else {
    // 父进程不调用wait()
    printf("Parent process %d sleeping\\n", getpid());
    sleep(30);
    }
    return 0;
    }

    运行观察: 使用 ps aux | grep Z 命令观察进程状态,不会出现僵尸进程。


    六、注意事项

  • 信息丢失 使用 SIG_IGN 后,父进程将完全无法获取子进程的退出状态(返回码等)。

  • 可移植性 虽然现代系统普遍支持该特性,但在一些旧版UNIX系统上可能不适用。 更标准的写法是显式设置信号处理函数并在其中调用 waitpid:

    c

    复制

    void sigchld_handler(int sig) {
    while(waitpid(-1, NULL, WNOHANG) > 0);
    }
    signal(SIGCHLD, sigchld_handler);

  • 与 waitpid() 的区别 SIG_IGN 方式适合"不关心子进程结果"的场景,而需要收集子进程信息时仍需使用传统方式。


  • 总结

    signal(SIGCHLD, SIG_IGN); 通过告诉内核"父进程完全不需要子进程的退出信息",使得内核自动回收终止的子进程资源,从而从根本上避免了僵尸进程的产生。这种方式:

    • 简洁高效(无需循环调用 wait())

    • 适合不需要收集子进程状态的场景

    • 是现代Linux系统中处理僵尸进程的推荐方式之一

    3 close(server_fd); // 关闭不需要的监听套接字 close(client_fd); // 关闭不需要的客户端套接字 这两行又是什么意思

    已深度思考(用时 26 秒)

    好的,用户问的是关于close(server_fd)和close(client_fd)这两行代码的意思。我需要先理解用户的问题背景。用户之前问过关于TCP服务器的代码,现在针对代码中的具体两行关闭套接字的操作有疑问。用户可能是在学习网络编程,或者正在编写类似的服务器程序,对资源管理不太清楚。

    首先,我要回顾之前的对话。用户之前得到了一个用C写的TCP回显服务器代码,里面在父进程和子进程中分别关闭了server_fd和client_fd。用户现在想知道这两行代码的具体作用,为什么需要分别关闭这两个文件描述符。

    接下来,我需要分析这两行代码的上下文。在父进程中,当accept到新的客户端连接后,fork出一个子进程来处理这个客户端。父进程在fork之后,立即关闭client_fd,而子进程则关闭server_fd,然后处理client_fd。

    用户可能不太理解为什么父子进程需要分别关闭不同的文件描述符,尤其是文件描述符在fork之后的行为。这里涉及到进程间文件描述符的继承以及引用计数的概念。

    我需要解释清楚,每个文件描述符都是一个系统资源,当fork时,子进程会复制父进程的文件描述符表,所以父进程和子进程各自持有这些文件描述符的副本。每个套接字都有一个引用计数,当所有持有它的进程都关闭了,套接字才会真正被释放。

    对于父进程来说,它只需要继续监听新的连接,所以不需要保持客户端的client_fd,关闭它可以释放资源,避免资源泄漏。同样,子进程处理客户端请求时不需要监听套接字server_fd,关闭它可以减少不必要的资源占用,同时防止子进程意外操作监听套接字。

    用户可能存在的疑问点包括:为什么要关闭?不关闭会怎样?父子进程之间的文件描述符是如何管理的?这时候需要详细解释引用计数的机制,以及如果不关闭的话可能导致的问题,比如资源耗尽,无法接受新连接,或者客户端连接无法正确关闭。

    另外,用户可能对多进程编程中的资源管理不太熟悉,需要强调在fork之后,父子进程应该各自关闭不需要的文件描述符,这是一种良好的编程习惯,确保资源及时释放,避免潜在的问题。

    总结来说,用户需要理解这两个close操作的必要性,以及它们如何帮助正确管理服务器资源,防止文件描述符泄漏,确保服务器的稳定运行。

    在TCP服务器的多进程实现中,close(server_fd) 和 close(client_fd) 这两行代码涉及UNIX文件描述符管理和多进程编程的核心机制。以下是深入分析:


    一、文件描述符的本质

  • 内核资源

    • 每个文件描述符(如 server_fd 和 client_fd)本质上是内核中一个打开资源的引用句柄

    • 套接字在内核中由引用计数管理,当引用计数归零时才会真正关闭

  • fork的特性

    • 调用 fork() 创建子进程时,子进程会复制父进程的文件描述符表

    • 父子进程中的相同文件描述符指向同一个内核套接字对象


  • 二、代码场景分析

    原始代码片段:

    c

    复制

    pid_t pid = fork();
    if (pid == 0) { // 子进程
    close(server_fd); // 关闭监听套接字
    handle_client(client_fd);
    exit(EXIT_SUCCESS);
    } else { // 父进程
    close(client_fd); // 关闭客户端套接字
    }


    三、close(server_fd) 在子进程中的意义

  • 资源释放必要性

    • 子进程仅需处理已接受的客户端连接(client_fd)

    • 监听套接字(server_fd)对子进程无用

  • 避免资源泄漏

    • 如果不关闭:

      • 子进程持有 server_fd 的副本

      • 即使父进程结束,只要子进程存在,监听套接字就不会关闭

      • 可能导致后续无法重启服务(端口占用)

  • 系统限制规避

    • 每个进程有文件描述符数量限制(ulimit -n)

    • 长期运行的子进程积累未关闭的 server_fd 可能耗尽资源


  • 四、close(client_fd) 在父进程中的意义

  • 职责分离原则

    • 父进程专职接受新连接(accept())

    • 已接受的连接由子进程全权处理

  • 引用计数管理

    • 原始状态:

      • 父进程持有 client_fd(引用计数=1)

      • 子进程持有 client_fd(引用计数=2)

    • 父进程执行 close(client_fd) 后:

      • 引用计数减为1(仅子进程持有)

      • 当子进程退出时会再次减为0,触发真正关闭

  • 预防竞态条件

    • 如果父进程不关闭 client_fd:

      • 父进程意外操作该描述符可能干扰子进程

      • 父进程崩溃会导致客户端连接异常(引用计数不归零)


  • 五、深度机制图解

    复制

    初始状态:
    父进程文件描述符表:
    [0] stdin
    [1] stdout
    [2] stderr
    [3] server_fd –> 监听套接字(引用计数=1)

    After accept():
    父进程新增:
    [4] client_fd –> 客户端套接字(引用计数=1)

    After fork():
    子进程文件描述符表:
    [0] stdin
    [1] stdout
    [2] stderr
    [3] server_fd –> 同一监听套接字(引用计数=2)
    [4] client_fd –> 同一客户端套接字(引用计数=2)

    After close()操作:
    父进程:
    close(4) –> 客户端套接字引用计数=1
    子进程:
    close(3) –> 监听套接字引用计数=1


    六、不关闭的后果演示

    假设注释掉这两个 close():

    c

    复制

    // close(server_fd); // 子进程未关闭
    // close(client_fd); // 父进程未关闭

  • 监听套接字泄漏

    • 所有子进程都持有 server_fd

    • 即使主进程想关闭服务,也无法真正释放端口

  • 客户端连接泄漏

    • 父进程持续积累 client_fd

    • 达到文件描述符上限后无法接受新连接

  • 连接无法正常关闭

    • 当子进程退出时,client_fd 引用计数仍为1(父进程持有)

    • 客户端连接保持打开直到父进程退出


  • 七、最佳实践扩展

  • 防御性关闭 在错误处理路径中也应添加 close() 操作:

    c

    复制

    if (connect() < 0) {
    close(fd);
    // 错误处理
    }

  • 现代替代方案 使用 fork() + close() 的传统方式之外,更推荐以下方式:

    • socketpair() + fd passing(高级IPC)

    • IO多路复用(select/poll/epoll)

    • 线程池

  • 诊断工具

    • 使用 lsof -p [PID] 查看进程持有的文件描述符

    • netstat -tulpn 查看端口占用情况

    • /proc/[PID]/fd 目录直接观察文件描述符


  • 总结

    这两个 close() 操作的本质是:

  • 明确进程职责:父子进程各司其职,关闭不需要的资源引用

  • 精细控制生命周期:通过引用计数机制确保资源及时释放

  • 系统健壮性保障:预防资源泄漏导致的级联故障

  • 这种模式体现了UNIX哲学中"做一件事并做好"的设计思想,是构建可靠网络服务的基础。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Linux TCP回显服务器实现(deepseek教我写代码)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!