bind()失败因端口被占,应设so_reuseaddr;accept()阻塞需用非阻塞i/o或select/poll;recv()返回0为正常断开,-1需据errno处理;send()须循环发送未完成部分。

bind() 失败:地址已在使用中怎么办
启动服务器时遇到 bind(): Address already in use,通常是因为上一次进程没彻底退出,端口还处在 TIME_WAIT 状态。这不是代码写错了,而是系统行为。
- 临时解决:在
socket()后、bind()前加setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)),其中opt = 1 - 注意:Windows 上还需额外设置
SO_EXCLUSIVEADDRUSE才能真正复用;Linux/macOS 仅SO_REUSEADDR即可 - 别在生产环境长期依赖它——它掩盖了未正确
close()连接的问题
accept() 阻塞导致无法处理多个客户端
单线程调用 accept() 是阻塞的,一旦有客户端连上来,后续逻辑就卡住,新连接只能排队等。这不是“服务器没响应”,是设计限制。
- 最轻量解法:把 socket 设为非阻塞模式,用
fcntl(sockfd, F_SETFL, O_NONBLOCK)(Linux/macOS)或ioctlsocket(sockfd, FIONBIO, &mode)(Windows) - 更实用的做法:用
select()或poll()监听listen_fd,只在有新连接到达时才调用accept() - 别直接 fork() 每个连接——在高并发下容易耗尽进程资源;现代服务更倾向线程池 + 非阻塞 I/O
recv() 返回 0 或 -1 怎么判断连接状态
recv() 返回值不是“数据长度”那么简单,它直接反映 TCP 连接生命周期:
- 返回 > 0:收到数据,字节数即返回值
- 返回 0:对端已调用
close()或shutdown(SHUT_WR),这是正常断开信号,应清理该连接 - 返回 -1 且
errno == EAGAIN || errno == EWOULDBLOCK:非阻塞模式下暂无数据,继续轮询即可 - 返回 -1 且
errno == ECONNRESET:对方异常断连(如崩溃、强制 kill),需立即close()对应 socket
send() 不保证一次发完所有数据
TCP 是流式协议,send() 声明返回“成功发送的字节数”,但不等于你传入的缓冲区长度。尤其在网络拥塞或接收方窗口小的时候,它可能只发出一部分。
- 必须检查返回值:若
sent ,剩余部分要重新传,不能丢弃 - 常见错误写法:
send(sockfd, buf, len, 0)后不检查返回值,以为数据一定发出去了 - 简单重发逻辑:用 while 循环,每次从
buf + sent开始,传len - sent字节,直到全部发出或出错 - 不要用
MSG_WAITALL强求一次性发完——它在非阻塞 socket 上会直接失败,在阻塞 socket 上可能无限等待
真正的难点不在写通第一个连接,而在于如何安全地管理多个 socket 的生命周期、避免 EBADF 和 EMFILE 错误、以及在 close() 时区分主动关闭和被动断连。这些细节藏在每次 recv() 和 send() 的返回值里,而不是文档开头的示例代码中。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











