14| 怎么老是出现“地址已经被使用”?
引用上一讲我们讲到 UDP 也可以像 TCP 一样使用 connect 方法以快速获取异步错误的信息。在今天的内容里我们将讨论服务器端程序重启时地址被占用的原因和解决方法。我们已经知道网络编程中服务器程序需要绑定本地地址和一个端口然后就监听在这个地址和端口上等待客户端连接的到来。在实战中你可能会经常碰到一个问题当服务器端程序重启之后总是碰到“Address in use”的报错信息服务器程序不能很快地重启。那么这个问题是如何产生的我们又该如何避免呢今天我们就来讲一讲这个“地址已经被使用”的问题。从例子开始为了引入讨论我们从之前讲过的一个 TCP 服务器端程序开始说起static int count; static void sig_int(int signo) { printf(\nreceived %d datagrams\n, count); exit(0); } int main(int argc, char **argv) { int listenfd; listenfd socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in server_addr; bzero(server_addr, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_addr.s_addr htonl(INADDR_ANY); server_addr.sin_port htons(SERV_PORT); int rt1 bind(listenfd, (struct sockaddr *) server_addr, sizeof(server_addr)); if (rt1 0) { error(1, errno, bind failed ); } int rt2 listen(listenfd, LISTENQ); if (rt2 0) { error(1, errno, listen failed ); } signal(SIGPIPE, SIG_IGN); int connfd; struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); if ((connfd accept(listenfd, (struct sockaddr *) client_addr, client_len)) 0) { error(1, errno, bind failed ); } char message[MAXLINE]; count 0; for (;;) { int n read(connfd, message, MAXLINE); if (n 0) { error(1, errno, error read); } else if (n 0) { error(1, 0, client closed \n); } message[n] 0; printf(received %d bytes: %s\n, n, message); count; } }这个服务器端程序绑定到一个本地端口使用的是通配地址 ANY当连接建立之后从该连接中读取输入的字符流。启动服务器之后我们使用 Telnet 登录这个服务器并在屏幕上输入一些字符例如networkgood。和我们期望的一样服务器端打印出 Telnet 客户端的输入。在 Telnet 端关闭连接之后服务器端接收到 EOF也顺利地关闭了连接。服务器端也可以很快重启等待新的连接到来。$./addressused received 9 bytes: network received 6 bytes: good client closed $./addressused接下来我们改变一下连接的关闭顺序。和前面的过程一样先启动服务器再使用 Telnet 作为客户端登录到服务器在屏幕上输入一些字符。注意接下来的不同我不会在 Telnet 端关闭连接而是直接使用 CtrlC 的方式在服务器端关闭连接。$telnet 127.0.0.1 9527 network bad Connection closed by foreign host.我们看到连接已经被关闭Telnet 客户端也感知连接关闭并退出了。接下来我们尝试重启服务器端程序。你会发现这个时候服务端程序重启失败报错信息为bind failed: Address already in use。$./addressused received 9 bytes: network received 6 bytes: good client closed $./addressused bind faied: Address already in use(98)复习 TIME_WAIT那么这个错误到底是怎么发生的呢还记得之前提到的 TIME_WAIT 么当连接的一方主动关闭连接在接收到对端的 FIN 报文之后主动关闭连接的一方会在 TIME_WAIT 这个状态里停留一段时间这个时间大约为 2MSL。如果你对此有点淡忘没有关系我在下面放了一张图希望会唤起你的记忆。如果我们此时使用 netstat 去查看服务器程序所在主机的 TIME_WAIT 的状态连接你会发现有一个服务器程序生成的 TCP 连接当前正处于 TIME_WAIT 状态。这里 9527 是本地监听端口36650 是 telnet 客户端端口。当然了Telnet 客户端端口每次也会不尽相同。通过服务器端发起的关闭连接操作引起了一个已有的 TCP 连接处于 TME_WAIT 状态正是这个 TIME_WAIT 的连接使得服务器重启时继续绑定在 127.0.0.1 地址和 9527 端口上的操作返回了 Address already in use 的错误。重用套接字选项我们知道一个 TCP 连接是通过四元组源地址、源端口、目的地址、目的端口来唯一确定的如果每次 Telnet 客户端使用的本地端口都不同就不会和已有的四元组冲突也就不会有 TIME_WAIT 的新旧连接化身冲突的问题。事实上即使在很小的概率下客户端 Telnet 使用了相同的端口从而造成了新连接和旧连接的四元组相同在现代 Linux 操作系统下也不会有什么大的问题原因是现代 Linux 操作系统对此进行了一些优化。第一种优化是新连接 SYN 告知的初始序列号一定比 TIME_WAIT 老连接的末序列号大这样通过序列号就可以区别出新老连接。第二种优化是开启了 tcp_timestamps使得新连接的时间戳比老连接的时间戳大这样通过时间戳也可以区别出新老连接。在这样的优化之下一个 TIME_WAIT 的 TCP 连接可以忽略掉旧连接重新被新的连接所使用。这就是重用套接字选项通过给套接字配置可重用属性告诉操作系统内核这样的 TCP 连接完全可以复用 TIME_WAIT 状态的连接。代码片段已经放在文章中了int on 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, on, sizeof(on));SO_REUSEADDR 套接字选项允许启动绑定在一个端口即使之前存在一个和该端口一样的连接。前面的例子已经表明在默认情况下服务器端历经创建 socket、bind 和 listen 重启时如果试图绑定到一个现有连接上的端口bind 操作会失败但是如果我们在创建 socket 和 bind 之间使用上面的代码片段设置 SO_REUSEADDR 套接字选项情况就会不同。下面我们对原来的服务器端代码进行升级升级的部分主要在 11-12 行在 bind 监听套接字之前调用 setsockopt 方法设置重用套接字选项int main(int argc, char **argv) { int listenfd; listenfd socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in server_addr; bzero(server_addr, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_addr.s_addr htonl(INADDR_ANY); server_addr.sin_port htons(SERV_PORT); int on 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, on, sizeof(on)); int rt1 bind(listenfd, (struct sockaddr *) server_addr, sizeof(server_addr)); if (rt1 0) { error(1, errno, bind failed ); } int rt2 listen(listenfd, LISTENQ); if (rt2 0) { error(1, errno, listen failed ); } signal(SIGPIPE, SIG_IGN); int connfd; struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); if ((connfd accept(listenfd, (struct sockaddr *) client_addr, client_len)) 0) { error(1, errno, bind failed ); } char message[MAXLINE]; count 0; for (;;) { int n read(connfd, message, MAXLINE); if (n 0) { error(1, errno, error read); } else if (n 0) { error(1, 0, client closed \n); } message[n] 0; printf(received %d bytes: %s\n, n, message); count; } }重新编译过后重复上面那个例子先启动服务器再使用 Telnet 作为客户端登录到服务器在屏幕上输入一些字符使用 CtrlC 的方式在服务器端关闭连接。马上尝试重启服务器这个时候我们发现服务器正常启动没有出现 Address already in use 的错误。这说明我们的修改已经起作用。$./addressused2 received 9 bytes: network received 6 bytes: good client closed $./addressused2SO_REUSEADDR 套接字选项还有一个作用那就是本机服务器如果有多个地址可以在不同地址上使用相同的端口提供服务。比如一台服务器有 192.168.1.101 和 10.10.2.102 两个地址我们可以在这台机器上启动三个不同的 HTTP 服务第一个以本地通配地址 ANY 和端口 80 启动第二个以 192.168.101 和端口 80 启动第三个以 10.10.2.102 和端口 80 启动。这样目的地址为 192.168.101目的端口为 80 的连接请求会被发往第二个服务目的地址为 10.10.2.102目的端口为 80 的连接请求会被发往第三个服务目的端口为 80 的所有其他连接请求被发往第一个服务。我们必须给这三个服务设置 SO_REUSEADDR 套接字选项否则第二个和第三个服务调用 bind 绑定到 80 端口时会出错。最佳实践这里的最佳实践可以总结成一句话服务器端程序都应该设置 SO_REUSEADDR 套接字选项以便服务端程序可以在极短时间内复用同一个端口启动。有些人可能觉得这不是安全的。其实单独重用一个套接字不会有任何问题。我在前面已经讲过TCP 连接是通过四元组唯一区分的只要客户端不使用相同的源端口连接服务器是没有问题的即使使用了相同的端口根据序列号或者时间戳也是可以区分出新旧连接的。而且TCP 的机制绝对不允许在相同的地址和端口上绑定不同的服务器即使我们设置 SO_REUSEADDR 套接字选项也不可能在 ANY 通配符地址下和端口 9527 上重复启动两个服务器实例。如果我们启动第二个服务器实例不出所料会得到 Address already in use 的报错即使当前还没有任何一条有效 TCP 连接产生。比如下面就是第二次运行服务器端程序的报错信息$./addressused2 bind faied: Address already in use(98)你可能还记得之前我们提到过一个叫做 tcp_tw_reuse 的内核配置选项这里又提到了 SO_REUSEADDR 套接字选择你会不会觉得两个有点混淆呢其实这两个东西一点关系也没有。tcp_tw_reuse 是内核选项主要用在连接的发起方。TIME_WAIT 状态的连接创建时间超过 1 秒后新的连接才可以被复用注意这里是连接的发起方SO_REUSEADDR 是用户态的选项SO_REUSEADDR 选项用来告诉操作系统内核如果端口已被占用但是 TCP 连接状态位于 TIME_WAIT 可以重用端口。如果端口忙而 TCP 处于其他状态重用端口时依旧得到“Address already in use”的错误信息。注意这里一般都是连接的服务方。总结今天我们分析了“Address already in use”产生的原因和解决方法。你只要记住一句话在所有 TCP 服务器程序中调用 bind 之前请设置 SO_REUSEADDR 套接字选项。这不会产生危害相反它会帮助我们在很快时间内重启服务端程序而这一点恰恰是很多场景所需要的。

相关新闻

避开 AI 内卷红海,10 个细分赛道落地步骤、获客定价全拆解

避开 AI 内卷红海,10 个细分赛道落地步骤、获客定价全拆解

前言生成式 AI 普及之后,绝大多数入局者陷入同质化内卷困境:跟风做通用文案、基础对话工具,赛道挤满竞争者,单价持续走低,投入大量时间算力却很难形成稳定收入。很多人误以为想要靠大模型变现,必须精通底层…

2026/7/6 14:31:32阅读更多 →
零基础看懂 VLA 动作生成:Action Head 四大实现路线、真机落地全流程、完整 PyTorch 工程代码

零基础看懂 VLA 动作生成:Action Head 四大实现路线、真机落地全流程、完整 PyTorch 工程代码

目录 一、前言:解开VLA落地的核心难题——从“看懂”到“能动” 二、VLA完整闭环架构:动作生成的底层运行逻辑 2.1 VLA四大核心输入维度 2.2 VLA标准执行链路 2.3 机器人动作空间的三种标准化定义 三、四大Action Head核心实现路线(原理+优缺点+适配场景) 3.1 路线一…

2026/7/6 14:26:31阅读更多 →
5分钟解锁WPS-Zotero插件:让文献管理变得像聊天一样简单

5分钟解锁WPS-Zotero插件:让文献管理变得像聊天一样简单

5分钟解锁WPS-Zotero插件:让文献管理变得像聊天一样简单 【免费下载链接】WPS-Zotero An add-on for WPS Writer to integrate with Zotero. 项目地址: https://gitcode.com/gh_mirrors/wp/WPS-Zotero 还在为学术写作中的文献引用烦恼吗?WPS-Zote…

2026/7/6 14:26:31阅读更多 →
Camera Shakify:3分钟为Blender动画注入真实电影感的神器

Camera Shakify:3分钟为Blender动画注入真实电影感的神器

Camera Shakify:3分钟为Blender动画注入真实电影感的神器 【免费下载链接】camera_shakify 项目地址: https://gitcode.com/gh_mirrors/ca/camera_shakify 你是否厌倦了Blender动画中那些过于完美的镜头?想要为你的作品增添真实感和电影质感&…

2026/7/6 15:36:46阅读更多 →
如何从零开始掌握计算机科学:终极免费开源自学路线图

如何从零开始掌握计算机科学:终极免费开源自学路线图

如何从零开始掌握计算机科学:终极免费开源自学路线图 【免费下载链接】computer-science 🎓 Path to a free self-taught education in Computer Science! 项目地址: https://gitcode.com/GitHub_Trending/co/computer-science 想要获得系统性的计…

2026/7/6 15:36:46阅读更多 →
如何在PC上使用yuzu模拟器畅玩Switch游戏:完整问题解决指南

如何在PC上使用yuzu模拟器畅玩Switch游戏:完整问题解决指南

如何在PC上使用yuzu模拟器畅玩Switch游戏:完整问题解决指南 【免费下载链接】yuzu 任天堂 Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/yu/yuzu yuzu是目前最受欢迎的开源任天堂Switch模拟器,让玩家能够在Windows、Linux和Andr…

2026/7/6 15:36:46阅读更多 →
Layan GTK主题架构解析与高级配置实现

Layan GTK主题架构解析与高级配置实现

Layan GTK主题架构解析与高级配置实现 【免费下载链接】Layan-gtk-theme Layan-gtk-theme 项目地址: https://gitcode.com/gh_mirrors/la/Layan-gtk-theme Layan是一款基于Material Design设计理念的现代化GTK主题,专为GTK 3、GTK 2和GNOME Shell桌面环境提供…

2026/7/6 15:36:46阅读更多 →
如何用tiny11builder将Windows 11瘦身50%:新手也能掌握的终极精简指南

如何用tiny11builder将Windows 11瘦身50%:新手也能掌握的终极精简指南

如何用tiny11builder将Windows 11瘦身50%:新手也能掌握的终极精简指南 【免费下载链接】tiny11builder Scripts to build a trimmed-down Windows 11 image. 项目地址: https://gitcode.com/GitHub_Trending/ti/tiny11builder 你是否感觉Windows 11越来越臃肿…

2026/7/6 15:36:46阅读更多 →
ICM-42605与MK20DN128VFM5实现6DOF运动追踪方案

ICM-42605与MK20DN128VFM5实现6DOF运动追踪方案

1. ICM-42605与MK20DN128VFM5的硬件组合解析 在三维空间运动追踪领域,ICM-42605和MK20DN128VFM5这对组合堪称黄金搭档。ICM-42605是TDK旗下InvenSense推出的6轴IMU(惯性测量单元),集成了3轴加速度计和3轴陀螺仪,采用2.…

2026/7/6 15:31:45阅读更多 →
从GitHub安全案例解析常见漏洞与防护实践

从GitHub安全案例解析常见漏洞与防护实践

1. 项目概述:从GitHub Trending看安全实战 最近在GitHub Trending上看到一个项目,叫 skills4/skills ,它因为一些安全漏洞案例被大家讨论。这其实是一个挺典型的场景:一个旨在展示或教授某种技能的仓库,本身却成了安…

2026/7/6 4:26:20阅读更多 →
MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

# MLT 2026启示:因果推理与概率建模驱动下一代LLM应用## 一、背景与挑战:从“黑箱预测”到“可信推理”2026年6月,第7届机器学习与趋势国际会议(MLT 2026)将在悉尼召开。会议议程中,“因果与可解释机器学习…

2026/7/6 2:48:33阅读更多 →
通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

1. 项目概述与漏洞背景最近在梳理一些历史OA系统的安全风险时,通达OA v11.6版本中的一个老漏洞又进入了我的视线。这个漏洞位于/general/bi_design/appcenter/report_bi.func.php文件中,是一个典型的SQL注入点。虽然这个漏洞的利用方式看起来并不复杂&am…

2026/7/6 0:10:35阅读更多 →
Seraphine:基于LCU API的英雄联盟智能游戏助手技术解析与应用指南

Seraphine:基于LCU API的英雄联盟智能游戏助手技术解析与应用指南

Seraphine:基于LCU API的英雄联盟智能游戏助手技术解析与应用指南 【免费下载链接】Seraphine 英雄联盟战绩查询工具 项目地址: https://gitcode.com/gh_mirrors/se/Seraphine 技术架构先行:官方接口的合规应用 你是否曾在BP阶段手忙脚乱&#x…

2026/7/6 0:03:39阅读更多 →
多协议远程连接管理工具mRemoteNG:告别混乱,统一你的远程桌面管理

多协议远程连接管理工具mRemoteNG:告别混乱,统一你的远程桌面管理

多协议远程连接管理工具mRemoteNG:告别混乱,统一你的远程桌面管理 【免费下载链接】mRemoteNG mRemoteNG is the next generation of mRemote, open source, tabbed, multi-protocol, remote connections manager. 项目地址: https://gitcode.com/gh_m…

2026/7/6 0:03:39阅读更多 →
COUNT(DISTINCT) 与 GROUP BY 去重统计:5 亿数据量下的性能实测与选型指南

COUNT(DISTINCT) 与 GROUP BY 去重统计:5 亿数据量下的性能实测与选型指南

COUNT(DISTINCT) 与 GROUP BY 去重统计:5 亿数据量下的性能实测与选型指南在数据分析和处理领域,去重统计是最基础也是最频繁使用的操作之一。当数据量达到亿级规模时,不同的去重统计方法在性能上可能产生天壤之别。本文将基于 5 亿行数据的实…

2026/7/6 0:03:39阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/6 4:45:01阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/6 4:45:01阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/6 4:45:03阅读更多 →