当前位置: 首页 > news >正文

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)的作用与使用细节有明确说明,每个参数的正确设置直接影响连接接收与客户端地址获取的正确性,以下是参数的详细解析:

参数名类型核心作用使用注意事项(基于文档)
sint服务器端的侦听套接字描述符,需先通过socket()创建、bind()绑定、listen()设置侦听状态。1. 必须是有效的套接字描述符(非负数),否则返回EBADF
2. 必须已调用listen()设置为侦听状态,否则返回EINVAL
3. 必须是TCP流套接字(SOCK_STREAM),UDP数据报套接字调用accept会失败。
addrstruct sockaddr *输出参数,用于存储客户端的协议地址信息(如IPv4的struct sockaddr_in、IPv6的struct sockaddr_in6)。1. 若为NULL,表示不需要获取客户端地址,函数仅创建新套接字;
2. 实际使用时需强制类型转换(如将struct sockaddr_in *转为struct sockaddr *),因为struct sockaddr是通用地址结构;
3. 需确保该指针指向的内存空间已分配,避免野指针导致内存错误。
addrlensocklen_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) {// 被信号中断,重试acceptperror("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 {// 其他错误,返回-1perror("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,提示“无效文件描述符”EBADF1. 传入的侦听套接字描述符(s)无效(如为负数、已关闭);
2. 套接字描述符被意外修改(如变量被覆盖)。
1. 检查socket()的返回值,确保侦听套接字创建成功;
2. 调用accept前,确认套接字未被close()关闭;
3. 排查代码中是否有意外修改套接字描述符变量的逻辑。
accept返回-1,提示“无效参数”EINVAL1. 套接字未调用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_fdsfor (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网络编程模型的基础。

http://www.dtcms.com/a/465243.html

相关文章:

  • wordpress博客站点云狄网站建设
  • 智能OCR助力企业办公更高效-发票识别接口-文字识别接口-文档识别接口
  • Spring Boot自动配置:原理、利弊与实践指南
  • HTTPS原理:从证书到加密的完整解析
  • CNN与ANN差异对比
  • 小迪web自用笔记61
  • Docker 公有仓库使用、Docker 私有仓库(Registry)使用总结
  • Comodo HTTPS 在工程中的部署与排查实战(证书链、兼容性与真机抓包策略)
  • 推广网站怎么做能增加咨询免费域名申请与解析
  • ES6开发实案例
  • 使用大模型技术构建机票分销领域人工智能客服助手
  • R语言 读取tsv的三种方法 ,带有注释的tsv文件
  • 淘宝数据网站开发查邮箱注册的网站
  • H200服务器维修服务体系构建:捷智算的全链条保障方案
  • Windows安装RabbitMQ保姆级教程
  • 申请网站服务器网络营销的特点和作用
  • Java-Spring入门指南(二十二)SSM整合前置基础
  • vim 中设置高亮
  • 记一次病毒分析
  • 岳阳网站开发收费亚马逊网站
  • JPA读取数据库离谱问题-No property ‘selectClassByName‘ found-Not a managed type
  • C++ 类与对象(上)笔记(整理与补充)
  • 基于Python 实现企业搜索系统(信息检索)
  • 学习爬虫第四天:多任务爬虫
  • 专注大连网站建设长沙网站设计咨询电话
  • 网站备案编号查询做网站的语言版本
  • 预训练基础模型简介
  • 【笔记】WPF中如何的动态设置DataGridTextColumn是否显示
  • 告别手动复制,API助您完成电商数据获取数据分析店铺搬家
  • 软件工程的核心原理与实践