资讯动态

深入理解TCP send与recv:内核缓冲区、返回值处理与生产级避坑指南

发布时间:2026/10/20 18:42:35 来源:尧图企业网站定制
1. 从两个函数说起为什么 send 和 recv 值得单独拎出来讲做网络编程的人绕不开 TCP。而做 TCP 编程的人绕不开send和recv这两个函数。很多人刚接触 socket 编程的时候觉得这两个函数太简单了——send就是发数据recv就是收数据参数填一填返回值看一眼能跑通就行。但真正在生产环境里踩过坑的人都知道这两个函数的返回值处理、阻塞行为、缓冲区机制每一个细节都可能让你的服务在某个深夜突然雪崩。我自己刚做后端开发那会儿写过一个简单的文件传输服务逻辑就是客户端循环send服务端循环recv本地测试一切正常。结果上线之后只要传输超过几兆的文件就必然出现数据截断或者程序卡死。当时排查了很久最后发现根因就是对send和recv的返回值理解不到位——send返回的字节数可能小于你请求发送的字节数recv返回 0 不代表出错而是对端关闭了连接。这些细节教科书上往往一笔带过但实际写代码的时候每一个都是致命的。这篇文章就是想把send和recv这两个函数彻底讲透。从内核缓冲区的工作机制到阻塞与非阻塞模式下的行为差异再到返回值处理的正确姿势以及那些只有真正写过生产级网络代码的人才会知道的避坑经验。不管你是刚学网络编程的新手还是已经写过一些 socket 代码但总觉得理解不够深入的老手相信都能从中找到对自己有用的东西。文章会涉及一些内核层面的原理但我会尽量用生活化的类比来解释保证不会让你觉得在看内核源码分析。2. 理解 send 和 recv 之前必须先搞懂内核缓冲区2.1 发送缓冲区和接收缓冲区到底是什么很多人写 socket 代码的时候脑子里面的模型是这样的我调用send数据就沿着网线飞到了对面对面调用recv数据就从网线里被捞出来。这个模型在应用层看起来没问题但它完全掩盖了 TCP 协议栈内部真正发生的事情。实际情况是当你调用send的时候数据并没有直接发到网络上。它首先被拷贝到内核中该 socket 对应的发送缓冲区send buffer里。然后 TCP 协议栈会根据当前拥塞窗口、对端接收窗口、MSS 等参数决定什么时候、以多大的块把数据真正推送到网络上。send函数返回的时候仅仅意味着数据已经被成功拷贝到了发送缓冲区并不代表对端已经收到了数据。这一点非常关键很多初学者会误以为send返回成功就万事大吉了。接收端也是类似的。当网卡收到数据后内核协议栈会把数据放入对应 socket 的接收缓冲区receive buffer中。应用程序调用recv的时候实际上是从这个接收缓冲区里把数据拷贝到用户空间的 buffer 里。如果接收缓冲区是空的recv就会根据 socket 是阻塞还是非阻塞模式决定是等待还是立即返回。你可以把发送缓冲区想象成一个快递站的发货区你把包裹放到发货区调用send快递站什么时候发车、走哪条路线那是快递公司TCP 协议栈的事情。接收缓冲区则是收件方的驿站包裹到了驿站内核收到数据你什么时候去取调用recv驿站不管但驿站容量有限满了之后新的包裹就进不来了。2.2 缓冲区大小对 send 和 recv 行为的直接影响发送缓冲区和接收缓冲区的大小直接决定了send和recv在特定时刻的行为。发送缓冲区满了send就会阻塞阻塞模式下或者返回部分发送非阻塞模式下。接收缓冲区空了recv就会阻塞等待阻塞模式下或者返回EAGAIN/EWOULDBLOCK非阻塞模式下。在 Linux 上你可以通过setsockopt来设置这两个缓冲区的大小int sndbuf_size 256 * 1024; // 256KB int rcvbuf_size 256 * 1024; setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, sndbuf_size, sizeof(sndbuf_size)); setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, rcvbuf_size, sizeof(rcvbuf_size));但要注意你设置的值和内核实际使用的值可能不一样。Linux 内核对这个值有上下限限制而且设置SO_SNDBUF的时候内核实际分配的大小通常是你设置值的两倍因为内核需要额外的空间来存储协议头等元数据。你可以通过getsockopt来查看实际生效的值。提示不要盲目把缓冲区设得特别大。缓冲区越大意味着在发生网络拥塞时内核里积压的数据越多应用程序感知到拥塞的时间越晚反而可能加剧问题。一般建议根据实际带宽时延积BDP来估算合适的缓冲区大小。2.3 阻塞模式与非阻塞模式的核心差异阻塞和非阻塞是理解send和recv行为的另一个关键维度。在阻塞模式下如果send的发送缓冲区没有足够空间调用线程会一直睡眠直到有空间为止。同样如果recv的接收缓冲区为空调用线程也会一直睡眠直到有数据到达。非阻塞模式下情况就完全不同了。send在发送缓冲区空间不足时会尽可能多地拷贝数据然后立即返回实际拷贝的字节数。如果缓冲区完全满了一个字节都拷不进去就返回 -1 并设置errno为EAGAIN或EWOULDBLOCK。recv在接收缓冲区为空时也是立即返回 -1 并设置相应的errno。这两种模式没有绝对的优劣关键看你的应用场景。阻塞模式写起来简单逻辑直观适合连接数不多、每个连接的数据量也不大的场景。非阻塞模式配合 I/O 多路复用select、poll、epoll可以轻松处理成千上万的并发连接但代码复杂度会显著上升需要你自己管理读写状态和缓冲区。3. send 函数的返回值每一个字节都有意义3.1 send 返回值的三种情况send函数的原型是这样的ssize_t send(int sockfd, const void *buf, size_t len, int flags);它的返回值有三种可能第一种返回一个大于等于 0 且小于等于len的值。这个值表示实际被拷贝到发送缓冲区的字节数。注意这个值可能小于len这就是所谓的“部分发送”。在阻塞模式下如果发送缓冲区有一些空间但不够放下所有数据send会先把能放下的部分拷贝进去然后阻塞等待剩余空间直到所有数据都拷贝完才返回。所以阻塞模式下除非发生错误send通常会返回len。但在非阻塞模式下send会尽可能多地拷贝然后立即返回实际拷贝的字节数这个值完全可能小于len。第二种返回 -1同时errno被设置为EAGAIN或EWOULDBLOCK。这通常发生在非阻塞模式下发送缓冲区完全满了一个字节都放不下。注意这种情况下没有任何数据被发送你需要等缓冲区可写之后重新发送。第三种返回 -1同时errno被设置为其他错误码比如EPIPE对端已关闭连接、ECONNRESET连接被重置等。这些是真正的错误通常意味着连接已经不可用了。3.2 为什么必须处理部分发送很多初学者写send调用的时候是这样的send(sockfd, buf, len, 0);完全不检查返回值。这在本地测试或者数据量很小的时候可能不会出问题但一旦数据量变大或者网络状况变差就必然出问题。因为send可能只发送了一部分数据剩下的数据你如果不继续发送对端就永远收不到完整的数据。正确的做法是写一个循环确保所有数据都被发送出去ssize_t send_all(int sockfd, const void *buf, size_t len) { size_t total_sent 0; const char *ptr (const char *)buf; while (total_sent len) { ssize_t n send(sockfd, ptr total_sent, len - total_sent, 0); if (n 0) { if (errno EINTR) { continue; // 被信号中断重试 } if (errno EAGAIN || errno EWOULDBLOCK) { // 非阻塞模式下缓冲区满需要等待可写事件 // 这里简化处理实际应该配合 epoll 等机制 continue; } return -1; // 真正的错误 } total_sent n; } return total_sent; }这个send_all函数看起来简单但里面有几个细节值得注意。首先是EINTR的处理当send被信号中断时会返回 -1 并设置errno为EINTR这时候应该重试而不是当作错误。其次是EAGAIN的处理在非阻塞模式下如果缓冲区满了应该等待可写事件而不是忙等待。上面的代码用了continue来简化实际生产代码中应该配合 I/O 多路复用机制。注意在阻塞模式下send通常不会返回EAGAIN因为阻塞模式下的send会一直等待直到有足够空间。但有一种例外情况如果你通过setsockopt设置了发送超时SO_SNDTIMEO那么超时后send会返回 -1 并设置errno为EAGAIN或EWOULDBLOCK。3.3 一个容易被忽略的细节send 返回成功不等于数据已送达这是我在实际项目中踩过的一个大坑。当时我们做一个消息推送服务客户端调用send成功之后就认为消息已经送达然后把消息从待发送队列中删除了。结果有一次机房网络抖动大量消息在发送缓冲区里还没发出去连接就断了这些消息全部丢失。send返回成功只代表数据被拷贝到了内核的发送缓冲区。数据什么时候真正发出去、对端有没有收到send完全不知道。如果你需要确认对端收到了数据必须在应用层实现确认机制ACK或者使用TCP_NODELAY关闭 Nagle 算法来减少延迟但即便如此也无法保证对端一定收到了数据。这个认知非常重要它直接影响到你的应用层协议设计。如果你在做金融交易、即时通讯等对可靠性要求极高的场景必须在应用层实现消息确认和重传机制不能依赖 TCP 本身的可靠性保证。4. recv 函数的返回值0 和 -1 的区别关乎生死4.1 recv 返回值的四种情况recv函数的原型是ssize_t recv(int sockfd, void *buf, size_t len, int flags);它的返回值有四种情况第一种返回大于 0 的值表示实际接收到的字节数。这个值可能小于len因为 TCP 是字节流协议没有消息边界的概念一次recv可能只收到部分数据。第二种返回 0。这表示对端已经优雅地关闭了连接发送了 FIN。这是很多初学者容易误解的地方——recv返回 0 不是出错而是对端关闭了连接。你收到 0 之后应该也关闭自己这一端的连接或者至少不再从这个 socket 读取数据。第三种返回 -1同时errno为EAGAIN或EWOULDBLOCK。这发生在非阻塞模式下接收缓冲区为空没有数据可读。这不是错误只是暂时没有数据。第四种返回 -1同时errno为其他错误码比如ECONNRESET连接被重置、ETIMEDOUT超时等。这些是真正的错误。4.2 为什么 recv 返回 0 必须关闭连接我见过不少代码recv返回 0 之后不关闭连接继续在循环里调用recv。结果就是 CPU 占用率飙升到 100%因为recv会立即返回 0程序陷入死循环。recv返回 0 是一个明确的信号对端不会再发送任何数据了。这时候你应该如果还有数据要发送给对端可以继续发送TCP 的半关闭特性允许一方关闭发送方向而另一方继续发送。如果不需要再发送数据调用close或shutdown关闭连接。绝对不要在recv返回 0 之后继续循环调用recv。正确的处理方式ssize_t n recv(sockfd, buf, sizeof(buf), 0); if (n 0) { // 对端关闭了连接 printf(Peer closed the connection\n); close(sockfd); // 清理资源退出循环 } else if (n 0) { if (errno EINTR) { // 被信号中断重试 } else if (errno EAGAIN || errno EWOULDBLOCK) { // 非阻塞模式下暂时无数据 } else { // 真正的错误 perror(recv failed); close(sockfd); } } else { // 正常收到数据处理数据 }4.3 字节流协议没有消息边界TCP 是字节流协议这意味着send端发送的“消息”在recv端看来只是一串连续的字节没有天然的边界。你send了两次每次 100 字节recv端可能一次收到 200 字节也可能分三次收到比如 50、100、50。这就是所谓的“粘包”问题。解决这个问题的唯一办法是在应用层定义消息边界。常见的方法有固定长度消息每条消息长度固定接收方按固定长度读取。简单但不够灵活。长度前缀在消息头部用固定字节数比如 4 字节表示消息体长度接收方先读长度再读消息体。这是最常用的方法。分隔符用特殊字符如换行符分隔消息。适合文本协议但需要处理转义问题。以长度前缀为例接收端的代码大概长这样// 先读取 4 字节的长度头 uint32_t msg_len; ssize_t n recv_all(sockfd, msg_len, 4); if (n ! 4) { // 处理错误或连接关闭 } msg_len ntohl(msg_len); // 网络字节序转主机字节序 // 再根据长度读取消息体 char *msg malloc(msg_len); n recv_all(sockfd, msg, msg_len); if (n ! msg_len) { // 处理错误 }这里的recv_all和前面的send_all类似需要循环读取直到读满指定字节数或发生错误。提示recv_all在阻塞模式下比较简单但在非阻塞模式下需要配合 I/O 多路复用并且要维护一个状态机来记录当前读到哪个阶段读长度头还是读消息体。这是非阻塞网络编程中最容易出错的地方之一。5. 阻塞与非阻塞下的 send/recv 行为对比5.1 阻塞模式简单但危险阻塞模式下的send和recv行为非常直观send会一直等到所有数据都拷贝到发送缓冲区才返回recv会一直等到接收缓冲区有数据才返回。代码写起来简单逻辑清晰。但阻塞模式有一个致命的问题一个线程只能处理一个连接。如果你有 1000 个连接就需要 1000 个线程。线程的创建和切换开销很大而且每个线程都需要独立的栈空间默认 8MB1000 个线程就是 8GB 内存这显然不现实。更危险的是阻塞模式下的send和recv可能永久阻塞。如果对端不发送数据recv就会一直等下去。如果发送缓冲区一直满比如对端接收窗口为 0send也会一直等下去。虽然可以通过设置SO_RCVTIMEO和SO_SNDTIMEO来设置超时但超时时间很难设置得恰到好处。5.2 非阻塞模式复杂但高效非阻塞模式下send和recv永远不会阻塞。send尽可能多地拷贝数据然后立即返回recv有数据就返回数据没数据就返回EAGAIN。配合 epoll 这样的 I/O 多路复用机制一个线程可以轻松管理成千上万个连接。但非阻塞模式的复杂度也成倍增加。你需要自己管理每个连接的读写状态维护发送缓冲区和接收缓冲区处理部分发送和部分接收的情况。代码量可能是阻塞模式的好几倍而且更容易出 bug。下面是一个非阻塞模式下send的典型处理流程// 尝试发送数据 ssize_t n send(sockfd, buf offset, len - offset, MSG_DONTWAIT); if (n 0) { offset n; if (offset len) { // 发送完成 } else { // 还有数据没发完注册 EPOLLOUT 事件 // 等 epoll 通知可写时继续发送 } } else if (n 0) { if (errno EAGAIN || errno EWOULDBLOCK) { // 缓冲区满注册 EPOLLOUT 事件 } else { // 真正的错误 } }5.3 如何选择场景决定一切选择阻塞还是非阻塞取决于你的应用场景场景推荐模式理由连接数少100数据量小阻塞模式代码简单不易出错连接数多1000长连接非阻塞 epoll资源利用率高需要同时处理多种 I/O非阻塞 epoll统一事件循环简单的客户端工具阻塞模式快速开发高并发服务器非阻塞 epoll性能要求高我个人的经验是如果是写客户端工具或者内部小工具阻塞模式足够了没必要为了“高性能”而引入非阻塞的复杂度。但如果是写服务器尤其是需要支撑大量并发连接的服务器非阻塞 epoll 是必经之路。6. 那些只有踩过坑才知道的实战经验6.1 SIGPIPE 信号一个能让进程直接崩溃的坑当你向一个已经关闭的连接调用send时内核会向进程发送SIGPIPE信号。这个信号的默认行为是终止进程。也就是说如果你的程序没有处理SIGPIPE一次对已关闭连接的send调用就能让你的整个服务挂掉。我第一次遇到这个问题的时候完全不知道发生了什么——程序突然就没了日志里也没有任何错误信息。后来查了很久才发现是SIGPIPE搞的鬼。解决办法有两种第一种忽略SIGPIPE信号signal(SIGPIPE, SIG_IGN);第二种在send时使用MSG_NOSIGNAL标志send(sockfd, buf, len, MSG_NOSIGNAL);推荐第二种因为它只影响当前这次send调用不会全局改变信号处理行为。但如果你用的是write而不是send那就只能用第一种方法了。注意忽略SIGPIPE之后send会返回 -1 并设置errno为EPIPE。你需要在代码中检查这个错误并妥善处理。6.2 TCP_NODELAY什么时候该关掉 Nagle 算法Nagle 算法是 TCP 协议栈的一个优化机制它的核心思想是如果发送缓冲区里还有未被确认的小数据包就把新的小数据包缓存起来等到收到 ACK 或者积累到足够大的数据包再一起发送。这样可以减少网络上的小包数量提高带宽利用率。但 Nagle 算法在某些场景下会带来明显的延迟。比如你做一个实时游戏或者交互式应用每次发送的数据都很小比如几个字节的操作指令Nagle 算法会把这些小数据包攒起来再发导致操作延迟明显增加。这时候就需要关闭 Nagle 算法int flag 1; setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, flag, sizeof(flag));关闭 Nagle 算法之后每次send都会立即把数据推送到网络上延迟最低但网络上的小包数量会增加。所以这是一个权衡要低延迟就关掉要高带宽利用率就开着。我的经验是对于交互式应用游戏、SSH、即时通讯关掉 Nagle对于文件传输、批量数据同步保持 Nagle 开启。6.3 发送缓冲区大小的动态调整在高并发场景下发送缓冲区的大小对性能影响很大。缓冲区太小send会频繁返回EAGAIN导致频繁的 epoll 事件通知和上下文切换。缓冲区太大内存占用高而且一旦发生网络拥塞积压的数据多恢复时间长。Linux 提供了自动调整发送缓冲区大小的机制你可以通过以下命令查看和修改# 查看当前值 cat /proc/sys/net/ipv4/tcp_wmem # 输出示例4096 16384 4194304 # 分别是最小值、默认值、最大值对于大多数场景使用系统默认值就可以了。但如果你在做大带宽、高延迟的传输比如跨地域的数据同步可能需要手动调大缓冲区。估算公式是缓冲区大小 带宽 × 往返时延比如带宽 100Mbps往返时延 100ms那么缓冲区至少应该是 100Mbps × 0.1s 10Mb 1.25MB。6.4 常见问题速查表问题现象可能原因排查方法解决方案send 返回 -1errno 为 EPIPE对端已关闭连接检查对端是否已关闭忽略 SIGPIPE处理 EPIPE 错误recv 返回 0CPU 占用 100%对端关闭后继续循环 recv检查 recv 返回 0 后的处理逻辑收到 0 后关闭连接退出循环send 返回字节数小于请求字节数发送缓冲区空间不足检查是否处理了部分发送循环发送直到所有数据发完recv 返回 -1errno 为 EAGAIN非阻塞模式下无数据可读检查 socket 是否非阻塞等待 epoll 可读事件后重试数据传输不完整没有处理部分发送/接收检查 send/recv 返回值处理实现 send_all 和 recv_all程序突然崩溃无日志收到 SIGPIPE 信号检查是否有对已关闭连接的 send忽略 SIGPIPE 或使用 MSG_NOSIGNAL小数据包延迟高Nagle 算法生效检查是否设置了 TCP_NODELAY设置 TCP_NODELAY 关闭 Nagle6.5 一个真实的排查案例之前我们有一个服务偶尔会出现客户端收不到响应的情况。日志显示服务端已经调用了send并且返回了成功但客户端就是没收到数据。排查了很久最后发现问题出在send的返回值处理上。服务端的代码是这样的send(sockfd, response, response_len, 0); // 认为发送成功了继续处理下一个请求但实际上send返回成功只代表数据被拷贝到了发送缓冲区。由于当时网络状况不好发送缓冲区积压了很多数据而连接随后因为超时被关闭了这些积压在缓冲区里的数据就永远发不出去了。修复方案是在应用层增加确认机制服务端发送响应后等待客户端发送确认消息收到确认后才认为响应发送成功。如果超时没有收到确认就重发或者记录错误。这个案例告诉我们永远不要假设send成功就等于数据送达。在可靠性要求高的场景应用层的确认机制是必不可少的。7. 从代码到生产构建健壮的收发模块7.1 封装一个可靠的 send_all 和 recv_all基于前面的讨论我们可以封装一套相对健壮的收发函数。这些函数处理了部分发送、部分接收、EINTR、EAGAIN 等常见情况可以直接用在生产代码中。#include sys/socket.h #include errno.h #include unistd.h // 阻塞模式下发送所有数据 ssize_t send_all_blocking(int sockfd, const void *buf, size_t len) { size_t total 0; const char *ptr (const char *)buf; while (total len) { ssize_t n send(sockfd, ptr total, len - total, MSG_NOSIGNAL); if (n 0) { if (errno EINTR) continue; return -1; } if (n 0) break; total n; } return total; } // 阻塞模式下接收指定字节数 ssize_t recv_all_blocking(int sockfd, void *buf, size_t len) { size_t total 0; char *ptr (char *)buf; while (total len) { ssize_t n recv(sockfd, ptr total, len - total, 0); if (n 0) { if (errno EINTR) continue; return -1; } if (n 0) return total; // 对端关闭 total n; } return total; }这两个函数在阻塞模式下工作得很好。但在非阻塞模式下你需要配合 epoll 使用并且需要维护每个连接的状态。这部分代码比较复杂我建议使用成熟的网络库如 libevent、libuv、Boost.Asio而不是自己从头实现。7.2 设置合理的超时时间无论是阻塞还是非阻塞模式设置合理的超时时间都是必要的。没有超时的网络操作就像没有刹车的汽车一旦出问题就是大问题。对于阻塞模式可以通过SO_SNDTIMEO和SO_RCVTIMEO设置超时struct timeval tv; tv.tv_sec 5; // 5 秒超时 tv.tv_usec 0; setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, tv, sizeof(tv)); setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, tv, sizeof(tv));对于非阻塞模式超时逻辑需要自己在应用层实现。通常的做法是记录每个连接的最后活跃时间定期检查并关闭超时的连接。超时时间设置多长合适这取决于你的应用场景。内部服务的 RPC 调用几秒就够了。跨地域的数据同步可能需要几十秒甚至几分钟。关键是要根据实际的网络状况和业务需求来定不要拍脑袋。7.3 缓冲区管理的经验之谈在实际项目中我见过太多因为缓冲区管理不当导致的问题。这里分享几条经验第一接收缓冲区不要设得太大。有些人为了“保险”把接收缓冲区设成几兆结果一个慢速攻击就能耗尽服务器内存。接收缓冲区的大小应该根据你期望的最大消息长度来定通常 64KB 到 256KB 足够了。第二发送缓冲区要能容纳至少一个完整的消息。如果发送缓冲区比你的最大消息还小那么send一个大消息时必然会发生部分发送增加代码复杂度。确保发送缓冲区至少能放下一个完整消息可以简化发送逻辑。第三使用内存池管理频繁分配释放的缓冲区。在高并发场景下频繁的malloc和free会成为性能瓶颈。使用内存池可以显著减少分配开销同时避免内存碎片。第四注意缓冲区对齐。虽然 TCP 是字节流协议不要求对齐但在某些平台上未对齐的内存访问会导致性能下降甚至崩溃。使用posix_memalign或类似函数分配对齐的内存可以避免这个问题。7.4 监控与日志让问题无处遁形生产环境中的网络问题往往难以复现所以完善的监控和日志至关重要。我建议在收发模块中加入以下监控指标每次send和recv的返回值分布正常、EAGAIN、错误发送缓冲区和接收缓冲区的使用率部分发送和部分接收的发生频率连接的生命周期建立、活跃、关闭错误码的分布情况这些指标可以帮助你快速定位问题。比如如果发现EAGAIN的频率突然升高说明发送缓冲区经常满可能需要调大缓冲区或者检查对端的接收速度。如果发现ECONNRESET增多说明对端频繁异常关闭连接需要排查对端的稳定性。日志方面建议记录每次异常情况的详细信息包括时间戳、socket 描述符、返回值、errno、当前缓冲区状态等。这些信息在排查问题时非常有用。8. 写在最后一些个人体会send和recv这两个函数看起来简单但真正用好并不容易。我做了这么多年网络编程到现在也不敢说完全掌握了它们的所有行为。每次遇到新的问题都会发现自己对某个细节的理解还不够深入。如果你刚开始学网络编程我的建议是不要满足于“能跑通”要多问几个为什么。为什么send返回的字节数可能小于请求的字节数为什么recv返回 0 要关闭连接为什么需要send_all和recv_all把这些问题的答案搞清楚你的网络编程水平就会上一个台阶。如果你已经有一些经验我的建议是多看看生产环境的代码尤其是那些经过大规模并发考验的开源项目。看看它们是怎么处理send和recv的返回值的怎么管理缓冲区的怎么处理各种异常情况的。这些实战经验比任何教科书都有价值。最后分享一个我个人的习惯每次写网络相关的代码我都会在send和recv的调用处加上详细的注释说明返回值可能的情况和处理逻辑。这个习惯帮我避免了很多低级错误也让我在 review 别人的代码时能快速发现问题。网络编程的细节太多了好记性不如烂笔头把关键逻辑写清楚对自己对同事都是好事。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑