Socket编程代码拆解:TCP一对一/一对多与UDP广播聊天室实现
简介这是一份面向计算机网络实验的Socket编程代码包聚焦TCP/UDP协议覆盖一对一、一对多及多人聊天室三种典型通信场景适合正在学习网络编程或需要完成课程设计的学生参考也可作为计算机网络课程实验报告的辅助材料。压缩包共14个文件以6个C语言源码和2个Python脚本为主体另有编译生成的服务端与客户端可执行文件目录结构清晰整体仅34KB轻巧易用已有1204人浏览学习。代码演示了TCP面向连接通信中服务器监听、客户端连接请求处理、数据逆序回传等关键流程也通过UDP广播机制实现无需持久连接的多人聊天室并加入异常捕获处理以优雅应对网络中断同时体现了多客户端并发处理思路。通过学习这些代码可深入理解Socket API在多用户交互场景下的实际运用为后续开发更复杂的网络应用打下坚实基础。1. 实验三 Socket 编程代码拆解TCP 一对一/一对多与 UDP 广播聊天室的完整实现计算机网络实验里Socket 编程这道坎几乎人人都踩过。我拆过不少学生提交的实验三源码包最常见的情况是报告写得头头是道代码一跑就崩——不是 bind 失败就是 recv 阻塞要么 UDP 聊天室里消息发出去收不到。这次拿到的资源包包含 C 和 Python 两套实现C 版覆盖 TCP 一对其逆序回显和一对多并发处理Python 版实现 UDP 广播聊天室代码量不大但覆盖了 socket 编程最核心的几个知识点socket 创建、bind 绑定、listen 监听、accept 接受连接、recv/send 数据收发、select 或多线程处理并发、UDP 广播收发。实际场景里无论是应付课程设计、准备网络编程面试还是想搞懂 TCP 和 UDP 在代码层面的真正差异这套代码都值得逐个文件过一遍。下面按我的拆解习惯从 C 源码开始再进入 Python UDP 实现最后讲清楚那些不跑一遍根本发现不了的坑。2. 先把资源包的文件结构和代码跑起来从 TCP 一对一逆序回显开始2.1 实验包文件清单与编译环境准备拿到压缩包解开后会看到三个 task 目录对应实验的三个阶段。task 1 里是 client1.c、server1.c 以及带 bind 后缀的变体是 TCP 一对一通信的 C 代码task 2 里是 client2.c、server2.c对应 TCP 一对多并发处理task 3 里是 server3.py、client3.py用 Python 实现了 UDP 广播多人聊天室。我一般先看 C 代码的编译条件。这份代码没有用任何第三方库纯 POSIX socket API编译命令就是最常见的 gcc 一行gcc server1.c -o server1 -Wall gcc client1.c -o client1 -Wall这里说明一下-Wall 打开编译警告对于学习 socket 编程足够用了。代码里用到的头文件是 sys/socket.h、netinet/in.h、arpa/inet.h、unistd.h全部是 Linux 系统自带的。如果你拿到的是带 bind 后缀的版本比如 server1_1.3bind.c那是同一个程序的迭代版本改的是 bind 地址和端口处理部分核心 socket 流程不变。编译环境推荐 Ubuntu 或 CentOS 这类 Linux 发行版Windows 上用 WSL 也可以但要注意 Windows 原生 socket 编程是 Winsock API需要 WSAStartup 初始化和这份代码差异很大不建议硬搬。2.2 TCP 一对一流程socket、bind、listen、accept、recv、sendtask 1 的 server1.c 是整个实验的地基。它的逻辑非常标准创建套接字、绑定端口、进入监听状态、接受一个客户端连接、接收数据做逆序处理再回传。我在代码里标注几个关键行#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h int main() { int server_fd, client_fd; struct sockaddr_in server_addr, client_addr; socklen_t client_len sizeof(client_addr); char buffer[1024]; // 创建 TCP 套接字AF_INET 表示 IPv4SOCK_STREAM 表示面向流 server_fd socket(AF_INET, SOCK_STREAM, 0); if (server_fd 0) { perror(socket); return 1; } // 绑定端口 8888INADDR_ANY 表示监听所有网卡 memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(8888); server_addr.sin_addr.s_addr htonl(INADDR_ANY); if (bind(server_fd, (struct sockaddr *)server_addr, sizeof(server_addr)) 0) { perror(bind); return 1; } // 进入监听状态backlog 设为 5 if (listen(server_fd, 5) 0) { perror(listen); return 1; } // 接受单个客户端连接 client_fd accept(server_fd, (struct sockaddr *)client_addr, client_len); if (client_fd 0) { perror(accept); return 1; } // 接收客户端消息并逆序回传 while (1) { int n recv(client_fd, buffer, sizeof(buffer) - 1, 0); if (n 0) break; buffer[n] \0; printf(收到: %s\n, buffer); // 字符串逆序 int len strlen(buffer); for (int i 0; i len / 2; i) { char tmp buffer[i]; buffer[i] buffer[len - 1 - i]; buffer[len - 1 - i] tmp; } send(client_fd, buffer, len, 0); } close(client_fd); close(server_fd); return 0; }几个参数值得展开说。sin_port 用 htons 把主机字节序转成网络字节序端口 8888 是实验代码里选的一个高位端口避开 1024 以下的特权端口这样普通用户就能 bind。htonl(INADDR_ANY) 让服务器监听所有网卡地址如果只想监听某个具体 IP比如本机 192.168.1.10就把 htonl(INADDR_ANY) 换成 inet_addr(192.168.1.10)。backlog 参数是连接等待队列长度5 在实验场景够用生产环境一般会调到 128 左右。recv 的第四个参数 flags 传 0表示阻塞方式读取。buffer 大小 1024 字节网络编程里缓冲区尺寸是个权衡点太小会频繁触发系统调用太大浪费内存。这个实验场景 1024 完全足够。2.3 client.c 的对应实现与启动顺序client1.c 的逻辑更简单创建 socket 后直接 connect 到服务器地址然后循环发消息收消息#include stdio.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h int main() { int sock_fd; struct sockaddr_in server_addr; char buffer[1024]; sock_fd socket(AF_INET, SOCK_STREAM, 0); if (sock_fd 0) { perror(socket); return 1; } // 连接 127.0.0.1:8888即本机服务器 memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(8888); server_addr.sin_addr.s_addr inet_addr(127.0.0.1); if (connect(sock_fd, (struct sockaddr *)server_addr, sizeof(server_addr)) 0) { perror(connect); return 1; } while (1) { printf(请输入消息: ); fgets(buffer, sizeof(buffer), stdin); buffer[strcspn(buffer, \n)] \0; send(sock_fd, buffer, strlen(buffer), 0); int n recv(sock_fd, buffer, sizeof(buffer) - 1, 0); if (n 0) break; buffer[n] \0; printf(服务器回显: %s\n, buffer); } close(sock_fd); return 0; }connect 是在 TCP 三次握手完成后才返回的所以只要 connect 成功连接就是可用的。inet_addr(127.0.0.1) 把点分十进制字符串转成网络字节序的 32 位整数这是 C socket 编程里最常用的地址转换函数。启动顺序必须是先起服务器再起客户端因为客户端 connect 时如果服务器还没有 listen会直接返回 ECONNREFUSED——连接被拒绝。这个顺序搞反了是新手最常见的第一坑。2.4 一对一变一对多task 2 代码里的并发思路task 2 的 server2.c 是实验的重点——一对多聊天服务。这里有两个实现路线一个是循环 accept 每次 accept 后 fork 子进程去处理客户端另一个是单线程里用 select 或 poll 多路复用。实验代码里用的是循环加子进程方式代码结构简单但能看出设计思路while (1) { client_fd accept(server_fd, (struct sockaddr *)client_addr, client_len); if (client_fd 0) { perror(accept); continue; } // 子进程处理客户端通信父进程继续 accept pid_t pid fork(); if (pid 0) { close(server_fd); // 子进程不需要监听套接字 handle_client(client_fd); // 接收、逆序、回传 close(client_fd); exit(0); } else if (pid 0) { close(client_fd); // 父进程不需要连接套接字 } }这里必须理解 fork 之后父子进程各自拥有的文件描述符是什么。fork 会把调用进程的整个地址空间复制一份包括打开的文件描述符表。所以子进程里要 close(server_fd) 关闭自己那份监听套接字父进程要 close(client_fd) 关闭自己那份连接套接字。各关各的真正的资源释放靠内核的引用计数机制——只有当所有进程都把某个 fd 关闭内核才会真正释放这个 socket。这个点如果搞混了会看到大量 TIME_WAIT 连接堆积。3. TCP 一对多并发处理fork 进程模型与端口占用排查3.1 为什么一对多必须做并发task 1 到 task 2 的本质差别是task 1 的 accept 在循环外面程序 accept 一次就进入 while 循环处理这一个客户端的数据处理完再 accept 下一个。这意味着如果第一个客户端连上来之后不发数据服务器就永远阻塞在 recv 上其他客户端连不进来。这就是 TCP 一对多要引入并发的原因。fork 进程模型是最直接的做法。每个客户端一个子进程父进程继续 accept 新的连接。这样做的好处是代码逻辑清晰每个子进程处理一个客户端不需要考虑多个客户端共享状态的同步问题。代价是进程开销大客户端多了之后性能扛不住。对于计算机网络实验来说这个取舍完全够用你要真正搞懂的是 fork 之后描述符的归属问题。选 fork 而不是 pthread 还有一个原因历史上很多 Linux 教材的网络编程章节用 fork 做例子实验代码自然照搬。pthread 多线程模型需要处理共享内存的互斥实验报告里涉及并发和同步两个考点fork 只需考并发。但从代码可维护性看pthread 更轻量fork 进程间数据隔离更彻底各有利弊。3.2 多客户端连接时的 fd 生命周期我用一个实际场景说明这个模型怎么工作。假设三个客户端 A、B、C 先后连接服务器服务器启动socket 得到 fd 3bind 后 listen客户端 A connect服务器 accept 返回 fd 4然后 fork子进程 1 持有 fd 4父进程 close(fd 4) 后继续 accept客户端 B connect父进程 accept 返回 fd 5fork 出子进程 2父进程 close(fd 5)客户端 C 同理得到子进程 3 处理三个子进程各自用自己的 fd 4/5/6 与客户端通信互不干扰。父进程手里只有监听 fd 3。这里有个关键点每个子进程虽然只处理一个客户端但 fork 复制出来的描述符表里最初包含所有 fd所以必须在子进程里显式 close 掉不用的。我在上面代码里已经标出来了。假设代码里漏了 close(server_fd)子进程不退出父进程也 close 不掉监听套接字——事实上如果父进程 close 了自己的那份但子进程还持有一份副本监听 socket 不会真正关闭端口会一直保持占用状态。这就是很多人实验做完了服务器进程退出了再启动新服务器却报 Address already in use 的原因。真正释放端口需要等所有子进程退出或者显式设置 SO_REUSEADDR。3.3 实验里的循环接收陷阱很多提交的代码里server2.c 是通过循环接收来处理多个客户端的类似这样while (1) { int n recv(client_fd, buffer, sizeof(buffer) - 1, 0); ... }这个写法本身没问题前提是一次 accept 之后只管一个客户端。但如果想用单循环遍历多个客户端没有 select 或者非阻塞 socket 根本做不到。原因很简单recv 是阻塞调用如果客户端 A 不发数据recv(fd_A) 就一直不返回后面的客户端 B、C 即使有数据到达也没机会被 recv 到。这就是为什么实验指导书里会要求用 fork 或者 select。从验收角度只跑到一对一就交差的学生不少但想要高分一对多的并发处理是必须展示的。3.4 TCP 调试必备工具与端口状态观察跑 TCP 实验时我建议你每一步都通过 netstat 观察系统状态netstat -tlnp | grep 8888输出会显示监听套接字的状态是 LISTEN。客户端 connect 成功后服务端多出一个 ESTABLISHED 连接。如果客户端主动断开但代码里没处理会看到 CLOSE_WAIT如果服务器关闭但客户端没退出客户端那边会看到 FIN_WAIT_2。这些都是排查故障的线索。对于 fork 多进程模型用 ps 看进程树更直观ps -ef | grep server2正常情况会看到一个父进程加多个子进程每个子进程对应一个客户端。如果只看到父进程说明代码可能没有 fork或者 fork 失败。配合 strace 可以看到系统调用层面的阻塞位置strace -p pid -e traceaccept,recv,send在排查「为什么客户端连不上」或者「为什么数据没发出去」时这是最快的定位手段。4. UDP 多人聊天室Python 实现与广播机制分析4.1 TCP 与 UDP 的核心差异在代码上的体现task 3 换到了 Python实现 UDP 多人聊天室。UDP 最核心的特性是无连接所以 Python 代码里不会出现 listen 和 accept也没有 connect——对于 UDPsendto 直接指定目标地址就行。另一个关键差异是 recvfrom 返回两个值数据和发送方地址。这个地址就是回信时 sendto 的第二参数。对比 TCP 的 recv/send你会发现 UDP 的 socket API 设计更简单。这是因为 UDP 的语义就是「发一个数据报出去能不能到、顺序对不对、有没有重复都不保证」。实验说明里提到 UDP 不可靠、无连接、低延迟全部体现在 API 层面。可靠性的缺失意味着如果聊天室里有人消息丢了UDP 层不会重传这要在应用层自己处理。4.2 UDP 聊天室代码逐段拆解server3.py 的核心逻辑非常短通常长这样import socket # AF_INET 指定 IPv4SOCK_DGRAM 指定 UDP server_socket socket.socket(socket.AF_INET, socket.SOCK_DGRAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind((0.0.0.0, 8888)) clients set() # 保存所有客户端地址 print(UDP 聊天室启动监听 8888 端口) while True: try: # recvfrom 返回数据和来源地址 data, addr server_socket.recvfrom(1024) message data.decode(utf-8) print(f{addr} 说: {message}) if addr not in clients: clients.add(addr) # 新客户端上线 # 广播给所有客户端 for client in clients: if client ! addr: server_socket.sendto(data, client) except KeyboardInterrupt: print(服务器关闭) break server_socket.close()这段代码有几个关键点。setsockopt(SO_REUSEADDR) 允许端口重用否则服务器上次运行时留下的 TIME_WAIT 状态会导致 bind 失败。clients 集合保存着所有上线客户端的地址每收到一条消息就遍历所有人广播——这就是 UDP 聊天室能「一对多」的机制。注意广播时排除发送者本人否则会把自己发的消息原样回传。client3.py 的逻辑也一样清晰import socket import threading client_socket socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定固定端口方便服务器识别不同用户 client_socket.bind((0.0.0.0, 8889)) server_addr (127.0.0.1, 8888) def receive_messages(): while True: try: data, addr client_socket.recvfrom(1024) print(data.decode(utf-8)) except: break # 启动接收线程否则 recvfrom 会阻塞主线程 threading.Thread(targetreceive_messages, daemonTrue).start() while True: message input() client_socket.sendto(message.encode(utf-8), server_addr)客户端绑定 8889 端口这样服务器通过 recvfrom 拿到的 addr 就是 (IP, 8889)能在识别不同客户端时区分来源。接收消息单独开一个线程因为 recvfrom 是阻塞的如果放在主线程用户输入消息时无法同时接收别人的消息。4.3 UDP 广播与端口绑定解读这段代码里最容易被误解的是「广播」这个词。严格说这里实现的是「向所有已上线客户端循环发送消息」不是网络层的广播地址255.255.255.255。网络层广播需要 setsockopt(SO_BROADCAST) 并且发送到 255.255.255.255而实验代码用的是应用层遍历。两者的区别很重要网络层广播是发到局域网里所有主机的某个端口不管那个主机有没有聊天室客户端在跑实验代码的应用层广播只发给已经用相同客户端程序上线的人数据不会打扰局域网其他设备。做实验报告时建议明确写清楚这一点是自己实现的逻辑广播而非套接字选项广播。聊天室场景对消息丢失不敏感因为每条消息都是独立广播发出去就完事。考虑到 UDP 本身的不可靠性如果哪条消息丢了聊天室不会像 TCP 那样自动重传。实验代码里没有做可靠性补偿这没问题实验目的本来就是展示 UDP 的语义而不是实现可靠 UDP。真要可靠聊天室就得自己加序号和 ACK复杂度立刻上去。4.4 多客户端模拟方法跑 UDP 聊天室最好的方式是开多个终端窗口每个窗口跑一个 client3.py。注意每个客户端必须用不同的端口否则无法区分。如果只能在一台机器上测试多开终端即可不需要虚拟机。如果想在同一台机器模拟局域网多设备用 127.0.0.1 就够了因为 socket 不关心地址是回环还是真实网卡 IP只要端口不同即可。实操时我习惯开四个终端一个跑 server3.py三个跑 client3.py每个客户端绑定不同端口。改端口的方式是在代码里改 bind 那一行的 8889 为 8890、8891 等。客户端收到的广播消息会显示「来自 (127.0.0.1, 8889)」就能直观看到哪个客户端在发言。5. 避坑手册bind 失败、recv 阻塞、TIME_WAIT 与 UDP 收不到消息的排查5.1 bind 报 Address already in use现象第二次运行服务器时报 bind: Address already in use或者服务器自己挂了再重启也报错。原因上一次运行的服务器进程没有完全退出或者连接关闭后端口仍处于 TIME_WAIT 状态。TIME_WAIT 是 TCP 主动关闭方必须停留的状态持续约 2MSL约 1 到 4 分钟。实验反复改代码重编译重跑频繁触发这个状态很正常。另外一个可能原因是前一次程序用 CtrlZ 挂起而不是正常退出进程还在后台占着端口。解决先看进程是否真正退干净。ps -ef | grep server2如果还有残留kill 掉。然后代码里加上 SO_REUSEADDRint opt 1; setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt));注意 SO_REUSEADDR 不是「允许两个进程同时 bind 同一个端口」而是「允许端口在 TIME_WAIT 状态下被重新绑定」。它对这些残留问题基本都能解决但两个进程同时 bind 同一个端口依然不行——那不是 SO_REUSEADDR 的职责范围。5.2 客户端 connect 被拒绝 ECONNREFUSED现象客户端先跑connect 返回 Connection refused。原因服务器没有启动、服务器绑定端口和客户端连接端口不一致、或者服务器处于非 LISTEN 状态。解决顺序问题最好解决——先启动服务器确认进入 accept 循环再启动客户端。端口一致性用 netstat -tlnp 查看服务器监听的端口号确认和代码里 htons 的目标端口一致。我在多台机器上联调时经常发现端口不一致是因为代码里写死了端口常量但两台机器的代码版本不同一个改过端口另一个没改。所以联调之前先统一代码版本。5.3 recv 返回 0 导致程序卡死现象客户端断开了连接服务器还在循环 recv没有任何反应或者出现奇怪行为。原因recv 有三种返回值需要区分大于 0 是收到数据等于 0 是对方正常关闭——对端发送了 FIN 并完成了四次挥手小于 0 是出错。很多同学只处理小于 0 的情况忽略了等于 0。解决对返回 0 的情况必须 break 并关闭套接字。代码里加上if (n 0) { printf(客户端断开连接\n); break; }如果不处理服务器会一直卡在 recv 上。对于非阻塞 socket 还要额外处理 EAGAIN但实验代码是阻塞模型不需要处理。5.4 客户端能连上但服务器收不到数据现象客户端 send 正常返回值但服务器端没有打印任何消息。原因一种情况是客户端和服务器不在同一台机器客户端连了错误的 IP。代码里默认 inet_addr(127.0.0.1) 只在本机有效跨机器必须改成服务器的实际 IP。另一种情况是数据还在缓冲区里send 只是把数据复制到内核缓冲区真正的发送由内核完成——如果服务器进程没有调用 recv数据就一直在内核队列里堆积。解决先用 ping 确认两台机器网络通。再看客户端 connect 的 IP 是否是正确的服务器地址。最后确认服务器在对应网卡监听——绑定 0.0.0.0 或 INADDR_ANY 时监听所有网卡绑定具体 IP 时只监听那个网卡。我排查这类问题时会用 tcpdump 抓包tcpdump -i any port 8888抓包能立刻看出 SYN、ACK、数据包的流向比干看代码快得多。5.5 UDP 聊天室的新用户加入前丢了前面所有消息现象新客户端加入聊天室后只能看到加入之后其他人发的消息之前的聊天记录完全看不到。原因这不是 Bug而是 UDP 无状态特性的直接后果。服务器端保存的 clients 集合只记录当前在线客户端新用户加入时服务器不会把历史消息补发给新人。TCP 聊天室里可能有历史记录同步的逻辑但 UDP 代码默认不保存历史。解决如果实验要求展示「所有消息对所有客户端可见」可以在服务器端维护一个消息列表新客户端加入时把历史消息逐个发给它。这个逻辑很好加在 clients.add(addr) 后面遍历历史消息列表发送即可。代码改动量很小但能作为实验报告里的「功能扩展」亮点。5.6 端口 8888 在 Windows 上被系统进程占用现象Windows 机器上跑服务器 bind 8888 失败netstat 显示端口被某个 PID 占着但那个进程看起来不像是自己的程序。原因Windows 系统服务可能占用某些端口尤其是一些云服务控制台或者 Hyper-V 保留端口范围。之前遇到过 Hyper-V 动态端口范围包含 8888导致用户态程序无法绑定。解决换一个端口比如 6666 或 9999。代码里把 server_addr.sin_port 和客户端 connect 的端口同步改掉。如果必须用 8888可以用 PowerShell 查看保留端口范围netsh int ipv4 show excludedportrange protocoltcp然后在保留范围之外选端口。实验代码对端口没有硬性要求只要客户服务器统一即可。5.7 fork 后僵尸进程问题现象跑了一段时间 task 2 之后ps -ef里看到很多defunct僵尸进程。原因子进程退出时父进程没有调用 wait/waitpid 回收子进程状态。子进程先变成僵尸态等待父进程收取退出状态。实验里客户端反复断开重连子进程退出频繁僵尸进程就堆积起来。解决在父进程循环里增加信号回调或者用 waitpid(pid, NULL, WNOHANG) 非阻塞回收signal(SIGCHLD, SIG_IGN);或者while (waitpid(-1, NULL, WNOHANG) 0);signal(SIGCHLD, SIG_IGN) 是操作系统层面直接丢弃子进程退出状态最简单。waitpid 循环可以避免 SIG_IGN 导致某些版本下的问题但需要小心放在哪里调用。这个坑在实验验收时经常被问到代码里没有处理的话建议补上。6. 从实验代码到独立实现用 gobuster 思路验证多路复用与性能边界实验代码提供的 C 和 Python 实现各有侧重点但它的边界也很明确C 版本用的是阻塞式 socket 和 fork 并发模型Python 版本用的是阻塞式 recvfrom 加纯遍历广播。这些模型在实验环境没问题但并发量上到几百个客户端时就要换思路了。我自己验证这份代码价值的方法是先把 TCP 一对一改成带日志的时间戳版然后压测。压测工具用 Python 写个简单脚本最方便不用装额外依赖。下面是我常用的一个压力测试思路import socket import time import threading def tcp_test(server_ip, server_port, message_count): # 每轮测试创建一个新连接发送多条消息 client socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.settimeout(5) client.connect((server_ip, server_port)) total_time 0 for i in range(message_count): payload fmsg_{i}.encode() start time.time() client.send(payload) resp client.recv(1024) total_time time.time() - start client.close() return total_time # 起 100 个线程并发连接观察服务器的 accept 和 fork 表现 threads [] results [] for i in range(100): t threading.Thread(targetlambda ii: results.append(tcp_test(127.0.0.1, 8888, 10))) t.start() threads.append(t) for t in threads: t.join() print(f平均每轮耗时: {sum(results)/len(results):.3f}s)压测时一边跑一边用 netstat 观察连接数用vmstat 1观察进程上下文切换。如果是 fork 模型每来一个客户端就 fork 一个进程100 个客户端就是 100 个进程系统上下文切换开销会非常明显。换成 select 或 epoll 多路复用后同样的并发量单进程就能扛住。我自己折腾这份实验代码后的套路是这样的先跑通 TCP 一对一确认收发方向没问题再跑通一对多 fork 模型观察每个客户端独立处理然后跑 UDP 聊天室验证广播机制和端口隔离最后把 UDP 聊天室里新增客户端收不到历史消息的短板补上。每次动手之前先看一遍目标协议的核心特性——TCP 可靠有序、UDP 不可靠无序——再回到代码里验证是不是这样实现的。从那以后我每次做网络实验都强制先跑一遍最小闭环再去叠加并发和可靠性要求基本没翻过车。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

基于2500张COVID肺部感染图像的分割模型训练全流程指南

基于2500张COVID肺部感染图像的分割模型训练全流程指南

简介:这份医学图像分割数据集面向医学影像分析、深度学习分割算法实践者与相关课程学习者,聚焦肺部感染(COVID)区域的像素级分割任务,可用于训练与评估U-Net等分割网络。数据统一为256256分辨率,前景为感染…

2026/10/1 13:44:26 阅读更多 →
显示面板测试工程师TE岗位详解:职责、核心技能与成长路径

显示面板测试工程师TE岗位详解:职责、核心技能与成长路径

我叫陈工,在显示面板行业摸爬滚打了近十年。这些年走过Array、Cell、Module三个段的产线,做过工艺、干过设备,最后在测试工程师(TE)这个岗位上扎了根。经常有新入职的同事或者跨行来的朋友问我:TE到底是干什…

2026/10/1 13:44:26 阅读更多 →
AI驱动CBB级PPA优化:从RTL修改到综合闭环

AI驱动CBB级PPA优化:从RTL修改到综合闭环

1. 这不是“让AI写代码”,而是让AI做综合决策:CBB设计中PPA权衡的范式转移“让 AI 带着综合结果改 RTL”——这个标题乍看像一句技术口号,但拆开每个词,它指向的是数字芯片设计流程中一个长期被忽视、却正在被彻底重构的断层。我带…

2026/10/1 13:44:26 阅读更多 →

最新新闻

Rust容器核心:Vec与HashMap从基础用法到性能优化实战

Rust容器核心:Vec与HashMap从基础用法到性能优化实战

Rust里有一对组合拳,几乎所有搞Rust开发的人都绕不过去:Vec和HashMap。不管你是写命令行工具、Web后端还是桌面应用,只要涉及批量数据,这两个类型就是最常用的容器。对刚入门的Rust开发者来说,Vec和HashMap不只是“存数…

2026/10/1 15:54:29 阅读更多 →
百考通一站式考试平台:海量题库与精准学情分析系统拆解

百考通一站式考试平台:海量题库与精准学情分析系统拆解

1. 项目概述与需求拆解 1.1 百考通是什么:从标题说起 先把这个标题拆开看。百考通,名字已经说明了一半,这是一个专注于考试辅助场景的一站式服务平台。后半句“海量源码与精准分析”则点明了它的两大核心卖点:一个是资源端&#…

2026/10/1 15:54:29 阅读更多 →
数字IC与NPU设计的三大能力断层:从RTL到流片的工程真相

数字IC与NPU设计的三大能力断层:从RTL到流片的工程真相

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 15:54:28 阅读更多 →
用MLX和Swift把Mac变成本地AI工作站:端侧模型推理与Agent实战

用MLX和Swift把Mac变成本地AI工作站:端侧模型推理与Agent实战

1. 苹果这套Swift AI工具链到底补了什么“Apple官方正在补齐Swift AI工具链”这个判断,我举双手赞成。最近大半年,我基本把自己手上的Mac当成主力AI开发机在用。从最早在本地用Python脚本调MLX跑Qwen,到后来把Swift写的小工具和Agent串成一条…

2026/10/1 15:54:28 阅读更多 →
Godot Node 详解:场景树、生命周期与节点路径实践

Godot Node 详解:场景树、生命周期与节点路径实践

第一次打开 Godot 的 Scene 面板,大多数人都会愣一下:新建场景时编辑器先问你选什么根节点,之后光照是节点、碰撞是节点、连播放声音和定时器都是节点。Godot 的 Node 不是某个具体的"游戏对象",它是整个引擎的最小组织…

2026/10/1 15:54:28 阅读更多 →
Git Submodule 统一管理移动端多项目,AI编程一次改三端的实战技巧

Git Submodule 统一管理移动端多项目,AI编程一次改三端的实战技巧

欢迎访问 AI Skills Video ! 海量优质视频教程,助你提升技能。 Git Submodule 统一管理移动端多项目,AI编程一次改三端的实战技巧 越来越多的一人公司、一人团队开始承担更多的项目工作,那么移动端维护安卓、iOS共4个仓库、同一需求改三遍太费Token&am…

2026/10/1 15:53:28 阅读更多 →

日新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/30 13:14:22 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/30 18:13:06 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/30 13:14:49 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →