☰
Linux C手写UDP通信:核心原理、常见坑与性能优化
2026/10/22 21:29:27 网站建设 项目流程

手写UDP这件事,我前前后后折腾过不止一次。最近帮某同事排查一个跨服务器数据传输问题,看到他代码里 sendto 之后立刻 close,丢了一大半报文还找不到原因,我就想把这些年踩过的坑、调过的参数、看过的内核行为整理成一篇能真正派上用场的东西。你要在自己的项目里用 Linux C 手写UDP通信,先别急着抄代码,把下面这些原理和细节搞明白,能少走很多弯路。

1. 拆解需求:手写UDP前先想清楚这几件事

1.1 UDP的无连接特性到底意味着什么

UDP的全称是 User Datagram Protocol,中文叫用户数据报协议。它的无连接特性,很多人只是背概念——“不用建立连接”,但实际写代码时这个特性的影响比想象中大得多。

无连接意味着通信双方不需要像TCP那样三次握手、四次挥手。发送端只要知道对方的IP和端口,就可以直接调 sendto 把数据扔出去;接收端只要socket绑定了端口,就能收到任意来源发来的数据。从系统调用层面看,UDP通信的核心流程只有三件事:创建socket、绑定端口、收发数据。

但这里有个容易被忽略的点:无连接不代表没有状态。你在代码里看不到连接,内核里却有一个四元组匹配的过程。每一次 recvfrom 返回的数据,内核会根据“源IP、源端口、目的IP、目的端口”把报文分配到对应的UDP socket。也就是说,“谁给我发、我发给谁”这个信息是逐个报文携带的,不是靠连接维护的。

我在实际项目里遇到过一种情况:接收端用同一个socket处理多个客户端上报的数据,一开始以为能自动区分来源,结果发现业务逻辑里根本没有记录来源IP和端口,导致回复消息时不知道发给谁。后来才意识到,UDP的每个报文都是独立的,来源信息必须自己从 recvfrom 的返回参数里取出来,再自己维护映射表。这个设计思路要在一开始就想清楚。

1.2 报文边界与MTU:一个包到底能塞多大

TCP是流协议,没有边界,你send了两次,对端recv时可能一次全收,也可能分两次收。UDP是数据报协议,每个sendto对应一个完整的报文,对方一次recvfrom只能收到一个完整的报文,多余的数据会被截断丢弃。

这个特性既是优点也是缺点。优点是应用层写起来简单,不需要自己组帧、拆包,收到一个报文就是一条完整消息;缺点是报文大小受限,一个包塞不下太大内容。

限制来自MTU(Maximum Transmission Unit)。以太网链路的标准MTU是1500字节,IP头一般占20字节,UDP头占8字节,留给应用层数据的空间就是1500 - 20 - 8 = 1472字节。如果数据超过这个值,IP层就会分片,接收端要重组。但分片在网络层是个比较敏感的操作,一旦某个分片丢了,整个报文都废了,而且UDP没有重传机制,这个报文就白白丢了。

我在实际测试中更保守:局域网内部,我一般把单个UDP报文控制在1400字节以内;跨公网通信,我直接压到1200字节以内。不是说1472就不能用,而是留出IP选项、VLAN标签等额外开销的空间,降低分片概率。

1.3 别用错协议:UDP和TCP怎么选

技术选型不是越高级越好,而是越匹配场景越好。我的经验判断标准很简单:

  • 如果你的消息是“发一次就没后续了”,比如状态上报、心跳包、日志采集,UDP很合适。
  • 如果单次消息超过1400字节,而且无法拆成多包,建议老老实实用TCP。
  • 如果对可靠性要求极高,比如传输配置文件、转账指令,别指望UDP加一堆重传逻辑来硬撑,直接用TCP更省心。
  • 如果是“大量小消息、丢几条无所谓、延迟要低”,UDP几乎是唯一选择。

UDP有时延迟比TCP低,因为没有握手、挥手、拥塞控制。但要注意,UDP没有拥塞控制,如果发送速度超过链路容量或接收端处理能力,丢包率会直线上升。所以别把UDP当成“包发出去就一定会到”,要把程序逻辑设计成“允许部分数据丢失,或者应用层自己做补偿”。

2. 核心API逐行拆解:socket、bind、sendto、recvfrom

2.1 socket():第一行代码的参数怎么选

创建UDP socket的代码是:

int sfd = socket(AF_INET, SOCK_DGRAM, 0);

三个参数分别是:

  • AF_INET:地址族,代表IPv4。如果需要支持IPv6,用AF_INET6。
  • SOCK_DGRAM:数据报类型的socket,对应UDP协议。这个类型名特别直白,就是“承载数据报”。
  • 0:协议号填0,表示由系统根据前两个参数自动选择协议。对于IPv4 + SOCK_DGRAM,系统自动选UDP。

这里有个小的细节:第三个参数你也可以显式填IPPROTO_UDP,效果一样。不过我习惯写0,因为这是最不容易出错、意图也最明确的写法。

socket()返回的是文件描述符,在Linux里一切皆文件。之后对这个描述符的读写、关闭操作,跟操作普通文件的接口是同一套范式,只是行为完全不同。

2.2 bind():为什么不想绑定端口也不行

发送端可以不調bind,系统会从临时端口中自动选一个空闲端口作为源端口。接收端必须调bind,否则没有端口可以监听,别人也不知道往哪发。

bind函数签名:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

传参时经常配合sockaddr_in结构体使用:

struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(9527); bind(sfd, (struct sockaddr *)&addr, sizeof(addr));

INADDR_ANY代表监听所有网卡IP,适合服务器端。如果只想监听某个特定网卡的IP,可以把这里替换成那个IP的二进制形式。

一个很常见的错误是:memset没做,结构体内存里的脏数据污染了sin_zero字段,导致bind失败。我见过太多人栽在这,所以每次构造sockaddr_in前,第一个动作永远是memset清零。

bind之后,这个socket就拥有了唯一的标识:IP + 端口。别人发送到这个IP和端口的数据包,内核就会投递到你这个socket的接收队列。

2.3 sendto和recvfrom:收发报文的核心操作

发送函数:

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
  • buf和len:要发送的数据内容及长度。注意,整个len字节的数据会被封装成一个UDP报文,如果len超过MTU,就会触发IP分片。
  • flags:几乎总是填0。特殊场景用到MSG_DONTWAIT(非阻塞发送)、MSG_NOSIGNAL(忽略SIGPIPE信号)等。
  • dest_addr和addrlen:目标地址信息,即对方的IP和端口。

接收函数:

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
  • len:接收缓冲区大小。如果缓冲区比实际报文小,报文尾部会被截断丢弃。
  • src_addr和addrlen:返回值,存放发送方的地址信息。通过它们你才知道数据是谁发来的。

两个函数返回值都是实际发送或接收的字节数。如果失败,返回-1,具体原因看errno。

sendto的阻塞行为分两种情况:如果socket是默认的阻塞模式,而且内核发送缓冲区已满,sendto会阻塞等待;如果用了非阻塞模式,缓冲区满时直接返回-1,errno为EAGAIN或EWOULDBLOCK。接收端同理,默认阻塞模式下,recvfrom会一直等着,直到有报文到达;非阻塞模式下,没有报文时立即返回-1。

2.4 主机字节序与网络字节序:小端机器上的坑

字节序是手写UDP绕不过去的问题。x86和ARM处理器普遍采用小端字节序,而网络传输标准规定使用大端字节序。如果发送端直接把内存里的整数拷贝到sendto缓冲区,接收端也直接读,在两台同样是x86的机器上可能没问题,但只要涉及跨架构通信,立刻出错。

举个例子,端口号9527,十六进制是0x2537。在小端机器内存里,低字节0x37存在低地址,高字节0x25存在高地址。网络字节序要求高字节在前,所以必须通过htons把内存里的数据转换成网络字节序再填充到结构体里。

常用转换函数:

  • htons():主机字节序转网络字节序,操作16位整数,用于端口。
  • htonl():主机字节序转网络字节序,操作32位整数。
  • ntohs()、ntohl():反向转换,接收方解析头字段时用。

如果发送应用层数据里包含整数类型的字段,也必须自己做字节序转换。我见过一个项目里只转换了端口号,业务数据里直接塞int,结果换了一台ARM设备后发现数值全部错乱。

3. 完整可运行的UDP通信Demo:代码和验证

3.1 接收端实现

#include <stdio.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> int main() { int sfd = socket(AF_INET, SOCK_DGRAM, 0); if (sfd < 0) { perror("socket"); return 1; } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(9527); if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; } char buf[1024]; struct sockaddr_in peer; socklen_t peer_len = sizeof(peer); while (1) { ssize_t n = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr *)&peer, &peer_len); if (n < 0) { perror("recvfrom"); continue; } buf[n] = '\0'; printf("recv %zd bytes from %s:%d: %s\n", n, inet_ntoa(peer.sin_addr), ntohs(peer.sin_port), buf); // 回一条消息给发送方 const char *reply = "ACK"; sendto(sfd, reply, strlen(reply), 0, (struct sockaddr *)&peer, peer_len); } close(sfd); return 0; }

注意peer_len的用法,它是值-结果参数。调用前赋值为sizeof(peer),内核会在返回时把它改成实际地址长度。如果只初始化一次、不重设,也可以,因为每次收到的地址类型没变;但如果多个客户端地址类型可能不同,每次recvfrom前要重置。

sendto的dest_addr直接用了recvfrom拿到的peer地址,这是UDP回包的典型写法:谁发给我的,我就回给谁。

3.2 发送端实现

#include <stdio.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <message>\n", argv[0]); return 1; } int sfd = socket(AF_INET, SOCK_DGRAM, 0); if (sfd < 0) { perror("socket"); return 1; } struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(9527); if (inet_pton(AF_INET, "127.0.0.1", &server.sin_addr) <= 0) { perror("inet_pton"); return 1; } const char *msg = argv[1]; ssize_t n = sendto(sfd, msg, strlen(msg), 0, (struct sockaddr *)&server, sizeof(server)); if (n < 0) { perror("sendto"); return 1; } char buf[1024]; struct sockaddr_in peer; socklen_t peer_len = sizeof(peer); n = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr *)&peer, &peer_len); if (n >= 0) { buf[n] = '\0'; printf("reply from %s:%d: %s\n", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port), buf); } close(sfd); return 0; }

发送端的socket创建后可以不bind,系统自动分配临时端口。但如果你想固定源端口,也可以在sendto之前bind一个固定端口。如果同一台机器上有多个进程同时往同一服务发UDP,自动临时端口一般也不会冲突。

有一个细节值得注意:发送端调了recvfrom,收到的是接收端回发的ACK。前提是接收端回包时目标地址就是发送端的临时端口,这个由内核自动处理,不需要发送端手动bind。

3.3 编译、运行和验证

编译命令:

gcc -Wall -o udp_server udp_server.c gcc -Wall -o udp_client udp_client.c

先启动接收端:

./udp_server

再开另一个终端执行:

./udp_client "hello udp"

接收端会打印:

recv 9 bytes from 127.0.0.1:xxxxx: hello udp

发送端会打印:

reply from 127.0.0.1:9527: ACK

有人会问:为什么发送端不bind也能收到回包?因为发送端socket在首次sendto时,内核自动给它分配了一个临时端口;接收端回包时,目标地址就是这个临时端口,内核根据端口找到发送端的socket,把ACK放进它的接收队列。所以发送端调recvfrom可以收到。

如果想要更详细的数据,可以加一个调试函数,打印内核为这个socket维护的收发统计信息:

#include <netinet/udp.h> // 在合适的时机读取 /proc/net/udp 或 getsockopt int get_udp_info(int sfd) { struct udp_info { int state; int uid; }; // 实际用 getsockopt 不太好取,还是读 /proc/net/udp return 0; }

我没有在demo里加这个函数,因为实战中直接读/proc/net/udp更直观:里面每一行对应一个UDP socket,有本地地址、远程地址、状态和收发队列长度。

3.4 非阻塞、超时与多路复用改造

默认的阻塞模式有一个问题:如果对端很久不发消息,recvfrom会一直卡住,主线程什么都干不了。实际项目中很少裸用阻塞recvfrom,通常用三种方式改造。

第一种是设置接收超时:

struct timeval tv = {0, 200000}; // 200毫秒 setsockopt(sfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

这样recvfrom超过200毫秒没有数据就返回-1,errno为EAGAIN或EWOULDBLOCK。主线程可以做自己的业务,再回头继续收。发送超时同理,用SO_SNDTIMEO。

第二种是改成非阻塞:

int flags = fcntl(sfd, F_GETFL, 0); fcntl(sfd, F_SETFL, flags | O_NONBLOCK);

非阻塞模式下,recvfrom没数据立即返回,调用方需要自己轮询。简单场景可以接受,但高并发下轮询浪费CPU,不建议用。

第三种是配合poll或epoll使用,这也是服务端主流做法。把sfd注册进epoll,等EPOLLIN事件来了再recvfrom,保证线程利用率最大化。如果是单进程处理多个socket,epoll几乎是必选的。UDP socket在epoll里的用法和TCP socket完全一致,唯一差别是数据到达时不代表有一条字节流,而是有一个报文可取。

4. 踩坑实录:UDP通信常见问题速查

4.1 收不到数据?先做这五个检查

排查UDP通信故障,顺序很重要,我从上到下给你排好:

  1. 防火墙是否放行了目标端口。Linux的iptables/nftables默认策略可能是drop,测试时可以先临时放行,或者直接在内网测试环境禁掉防火墙。
  2. socket是否成功bind了预期的端口。可以用netstat -lunp | grep 9527确认监听状态。
  3. 发送端的目标IP、端口是否真的正确。打印一下sendto里填的地址,别用字符串硬编码,要确认实际填充进去的值。
  4. 接收端的缓冲区是否够大。如果报文长度超过recvfrom的len参数,多余部分被截断丢弃。可以把buf开大,或者用recvmsg配合MSG_PEEK|MSG_TRUNC先查询真实报文长度。
  5. 是否在同一个网络命名空间。容器、虚拟机环境经常遇到网络隔离问题,升级排查前先确认两端网络可达。

我之前排查过一个跨机通信问题,抓包显示报文已经到达接收端网卡,但应用收不到。最后发现是计算机关了防火墙,但容器网络策略没放行目标端口。这类问题用tcpdump或ss -lunp能快速定位到底卡在哪一层。

4.2 Address already in use与SO_REUSEADDR

bind时报Address already in use,通常有几种可能:

  • 端口被其他进程占用,用ss -lunp | grep 9527查出来。
  • 上次运行的程序没有正常释放端口。UDP socket在进程退出时会自动释放,理论上不会一直占用;但如果进程被kill -9,内核也会回收。
  • 同一个进程内多个socket bind同一端口,默认不允许。

比较常见的需求是服务器重启后立刻重启程序,结果新进程bind失败。因为在TIME_WAIT状态下的TCP连接会占用本地端口,UDP虽然不像TCP那样有TIME_WAIT,但多进程/多实例场景里,SO_REUSEADDR依然很有用。添加方式:

int on = 1; setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

如果你要多个进程同时bind同一个端口做负载均衡,UDP还有一个更高级的选项SO_REUSEPORT,它允许多个socket绑定同一个IP+端口,内核会在这些socket之间做负载均衡。这个机制对多核性能扩展非常有效,我后面讲并发时再展开。

4.3 内核缓冲区调小导致的丢包问题

UDP的接收队列在内核里有上限。如果应用处理速度跟不上数据到达速度,内核会直接丢包,而且应用程序完全感知不到,因为没有报错。检查丢包的方法是:

netstat -s | grep -i udp

输出里会有packet receive errors、packets to unknown port received等统计字段。如果这个数值持续增长,说明有丢包。

内核默认接收缓冲区大小可以通过下面命令查看:

sysctl net.core.rmem_default sysctl net.core.rmem_max

默认值一般是208KB左右。如果业务流量大,需要调大单个socket的缓冲区:

int rcvbuf = 4 * 1024 * 1024; // 4MB setsockopt(sfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));

发送缓冲区同理,用SO_SNDBUF。不过要注意,UDP的发送缓冲区一般不大,因为sendto只有在有大量堆积数据时才会触顶,正常模式下报文都是即时发出。

如果应用层已经比较高效,但仍然丢包,可能就是内核单队列的处理瓶颈。这时可以用前面提到的SO_REUSEPORT,多开几个进程分别绑定同一个端口,内核会按哈希把不同来源/不同报文分发给不同socket,相当于把收包负载分散到多个核上。

4.4 数据错乱和字节序问题的排查

数据接收正常,但内容看起来不对,十有八九是字节序、结构体对齐或字段顺序问题。

  1. 整数大小端不符:发送端用htonl转了,接收端没用ntohl读回来。
  2. 结构体直接memcpy进sendto缓冲区:C结构体的字段对齐规则在不同编译器、不同平台上可能不同,导致两端解析错位。解决方案是定义结构体时用__attribute__((packed)),每个协议字段明确网络字节序。
  3. 打印时把网络字节序的端口直接%d打印了,比如收到端口12345,结果打印出12344或者别的值,其实只是没调用ntohs。

我在一个跨平台项目里遇到过:服务端在x86上按大端解析报文,客户端在ARM上按小端发送,结果所有端口和长度字段都是反的。排查过程特别痛苦,因为抓包看着是正常的,一进业务代码就错。最后把所有%d改成ntohs/ntohl后解决。之后就养成了习惯:所有网络传输字段,只用be16、be32这样的自定义类型或宏,防止直接写错。

5. 进阶方向与个人使用体会

5.1 可靠性改造:自己实现确认与重传

游戏行业实时对战、视频会议的音画传输,这些场景对实时性要求高,但对少量丢包宽容,用UDP+应用层可靠性是合理的。但如果你要传文件、传配置,不建议在UDP上硬造可靠性。

最简单的可靠性框架需要三个元素:

  • 序列号:每个报文带一个递增编号,接收方检测到乱序或缺失时发起重传请求。
  • ACK/NACK:接收方回复确认帧。ACK是确认最后一个连续包,NACK是通知发送方缺失的具体序号。NACK的方式效率更高,因为一次能上报多个缺失包。
  • 超时重传:发送方维护一个发送队列,超过一定时间未得到ACK的包要重传。超时时间根据网络RTT动态估算,太短会导致大量重复传输,太长会增大延迟。

我自己用UDP做可靠性改造时,序列号和ACK都放在业务负载里,用固定头部承载。重传策略选择的是NACK为主,ACK只用于周期性确认进度,这样正常情况下反馈包数量很少。但实际测试下来有一个很现实的坑:如果链路本身丢包率高,NACK触发重传后又会因为拥塞导致更多丢包,形成恶性循环。所以UDP可靠性传输一定需要配合限速和重传退避,不能盲目加速。

5.2 并发接收:从单进程到多进程

单独一个进程的recvfrom循环,如果处理完每个报文需要1毫秒,那么每秒最多处理1000个报文。要提升到每秒几万报文,需要从两个方向努力。

第一个方向是提高单核处理能力。不做不必要的锁、不用printf打印每个包、避免在收包线程里做耗时业务,都能明显提高吞吐。

第二个方向是跨核并行。除了SO_REUSEPORT,还可以考虑一个主进程acceptor多个worker进程,通过本地socket分发。不过在纯UDP场景下,SO_REUSEPORT是更简洁的方案。每个接收进程各自调用socket+bind+setsockopt(SO_REUSEPORT)+bind到同一IP和端口,内核根据四元组哈希或简单的round-robin分发,保证同一个对端的报文基本落在同一个socket上,减少乱序概率。

我测试过的多进程UDP服务里,SO_REUSEPORT配合每个进程独立线程池,吞吐量能线形扩展到多核算力饱和。但要注意一点:如果分发到不同socket导致同一来源的报文乱序到达,而业务对顺序敏感,需要在应用层增加序号重排,这就增加了复杂度。对于无状态查询响应类的服务,SO_REUSEPORT非常合适。

5.3 性能调优:套接字选项与内核参数

UDP性能瓶颈通常不在应用层代码,而在内核和协议栈配置。我列一份实际项目中验证过的调优清单:

  • 网卡RSS(Receive Side Scaling):让网卡从硬件层面分发报文到多个CPU队列,减少单核中断压力。
  • 关闭校验和卸载:发送时可以设置SO_NO_CHECK关闭UDP校验和(仅IPv4),换一部分性能。但这会降低链路数据完整性检测,内网可考虑,公网不建议。
  • 调整收发缓冲区:对应前面提到的SO_RCVBUF和SO_SNDBUF,同时确认net.core.rmem_max大于应用设置的缓冲区值,否则内核会截断。
  • 减少recvfrom系统调用次数:一次recvfrom返回一个报文,如果报文很小、频率很高,系统调用开销会占大头。可以用recvmmsg一次接收多个报文,大幅减少系统调用次数。
  • 避免每个报文都触发一次拷贝:传统API需要把数据从内核拷贝到用户缓冲区。可以用零拷贝技术,比如recvmsg配合MSG_ZEROCOPY,但这套机制复杂度比较高,适合需要极高吞吐的特殊场景。

我自己做的一个高吞吐日志采集服务,在带宽占满1Gbps时,裸用recvfrom只能跑约3.5万PPS,改用recvmmsg批量接收后提升到6.8万PPS,差距非常明显。如果你的业务也是小报文高频率,一定要考虑批量收包。

5.4 我在实际项目中的体会

最后分享几个我在项目实操中形成的习惯,不一定是最优解,但都是踩过坑换来的。

第一,善用抓包工具。遇到UDP通信问题,不要一味看代码,先用tcpdump -i any udp port 9527 -XX抓包看一眼实际报文。抓包结果能快速把问题分成“发送端没发出去、网络没传过来、接收端没收进来、数据内容错了”四种情况。我调试跨机通信问题时,超过一半的时间花在抓包分析上。

第二,日志要记录来源地址。服务端收包后打印日志,一定带上inet_ntoa(src.sin_addr)和ntohs(src.sin_port)。我曾见过一个系统所有日志都不打来源地址,问题发生时无据可查,完全没法定位是哪个客户端发的错误数据。

第三,UDP通信代码要尽早做压力测试。单测只验证功能,压测才能暴露缓冲区、丢包、字节序等问题。用iperf3 -u或者自己写一个发包工具,发远超正常业务量的数据,观察netstat -s里的丢包统计。很多UDP应用在低负载跑得好好的,一上线就死,就是因为没做这个测试。

第四,协议字段尽量统一用网络字节序。不要“端口用htons、长度用htonl、其他字段直接复制内存”,规范一旦松散,后续所有对接方都要跟着踩坑。我在团队里定了条规矩:所有超过1字节的整数字段全部走网络字节序,接收统一用ntoh解析,解析函数单独封装,不直接裸读内存。

第五,UDP报文最好有长度字段和应用层校验和。UDP自带16位校验和只能检测报文在传输过程中是否损坏,不能检测内容是否被篡改或业务层是否完整。如果数据被中间人改了,UDP校验和也发现不了。对业务来说,在报文头部加上总长度和简单的CRC32或hash校验,回包时带上对应序号或校验值,能有效防止静默数据损坏。

手写Linux C UDP通信这件事,说简单很简单,八个API调用就能跑通;说复杂也复杂,从字节序到MTU到内核缓冲区到多核并发,每个环节都可能变成性能或稳定性瓶颈。很多项目初期只用到了最基础的收发功能,后期遇到吞吐瓶颈又是重新设计一遍。我的建议是刚开始就按本文里的这些知识点逐项排查一遍你的代码和配置,尤其是缓冲区大小、字节序规范、异常重传这三个点,能真正帮你把UDP通信做到“能用、稳用、高效用”。以后遇到问题再回来翻这篇东西,应该能省下不少排查时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询