网络编程实战
盛延敏
前大众点评云平台首席架构师
44207 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 40 讲
网络编程实战
15
15
1.0x
00:00/00:00
登录|注册

15 | 怎么老是出现“地址已经被使用”?

作用:本机服务器如果有多个地址,可以在不同地址上使用相同的端口提供服务
允许启动绑定在一个端口,即使之前存在一个和该端口一样的连接
为什么不是对已连接的套接字进行设置
对UDP设置SO_REUSEADDR套接字选项的场景和好处
服务器端程序都应该设置SO_REUSEADDR套接字选项
SO_REUSEADDR套接字选项
优化:开启了tcp_timestamps,使得新连接的时间戳比老连接的时间戳大
优化:新连接SYN告知的初始序列号比TIME_WAIT老连接的末序列号大
使用netstat查看服务器程序所在主机的TIME_WAIT状态连接
TIME_WAIT状态
连接建立后从连接中读取输入的字符流
服务器端程序绑定到一个本地端口
思考题
最佳实践
重用套接字选项
复习TIME_WAIT
从例子开始
怎么老是出现“地址已经被使用”?

该思维导图由 AI 生成,仅供参考

你好,我是盛延敏,这里是网络编程实战的第 15 讲,欢迎回来。
上一讲我们讲到 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 登录这个服务器,并在屏幕上输入一些字符,例如:network,good。
和我们期望的一样,服务器端打印出 Telnet 客户端的输入。在 Telnet 端关闭连接之后,服务器端接收到 EOF,也顺利地关闭了连接。服务器端也可以很快重启,等待新的连接到来。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了网络编程中常见的“地址已被使用”问题及其解决方法。通过引入TCP服务器端程序的实例,作者指出了服务器程序重启后出现“Address in use”错误的原因,并介绍了TIME_WAIT状态对此的影响。文章重点介绍了重用套接字选项SO_REUSEADDR的作用和使用方法,以避免因TIME_WAIT状态导致的错误,并允许在一台服务器上使用多个地址提供相同端口的服务。总的来说,本文通过实例和技术原理阐述了解决“地址已被使用”问题的方法,对于网络编程中遇到类似问题的读者具有一定的参考价值。最佳实践建议在所有TCP服务器程序中,在调用bind之前设置SO_REUSEADDR套接字选项,以便在极短时间内复用同一个端口启动服务端程序。此外,文章还提出了两个思考题,引发读者深入思考和讨论。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《网络编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(34)

  • 最新
  • 精选
  • J.M.Liu
    关于tcp_tw_reuse和SO_REUSEADDR的区别,可以概括为:tcp_tw_reuse是为了缩短time_wait的时间,避免出现大量的time_wait链接而占用系统资源,解决的是accept后的问题;SO_REUSEADDR是为了解决time_wait状态带来的端口占用问题,以及支持同一个port对应多个ip,解决的是bind时的问题。

    作者回复: 总结得不错。

    2019-09-07
    4
    87
  • xupeng1644
    老师 思考题第二题的答案是什么啊

    作者回复: 因为SO_REUSEADDR是针对新建立的连接才起作用,对已建立的连接设置是无效的。

    2020-01-15
    29
  • G先生
    UDP的SO_REUSEADDR使用场景比较多的是组播网络,好处是,如我们在接收组播流的时候,比如用ffmpeg拉取了一个组播流,但是还想用ffmpeg拉取相同的组播流,这个时候就需要地址重用了

    作者回复: 👍

    2019-10-13
    2
    22
  • 惘 闻
    老师我有个疑问,这里的服务器直接关闭了连接,在关闭连接之前发送了FIN报文,此时就已经关闭了吧?所以就收不到客户端回复的ack以及客户端的fin了,连接关闭发起方还未走到接收对端发送的fin的那一步,此时也会进入timewait阶段吗?

    作者回复: 有几个需要澄清的问题,所谓FIN-ACK这些都是协议栈在帮我们处理,虽然应用服务器已经关闭,这些处理能力还会在内核得以执行的,只不过我们应用程序进程已经退出,没有办法收到内核对这些事件的传递。 所以,连接关闭发起方,还是会进入TIME_WAIT状态。

    2020-09-11
    4
    14
  • HerofH
    老师您好,我有个疑问,根据我的理解,TIME_WAIT是主动关闭方才会存在的状态,而服务端很多时候都是被动关闭方,为什么也会有TIME_WAIT状态呢?还是说服务端套接字设置SO_REUSEADDR只是用于服务端主动关闭的情况(比如快速重启)呢?

    作者回复: 你的理解是对的。

    2019-12-26
    9
  • 海盗船长
    老师 是不是Address already in use。我最长等待2msl时间后 重启就不会有问题啦?

    作者回复: 你如果等也是可以的.......

    2020-04-10
    3
    7
  • vv_test
    TCP 的机制绝对不允许在相同的地址和端口上绑定不同的服务器。 老师您好请问一下,Nginx 的master跟woker都是监听80端口,他们都有各自的进程号。那他们这种为什么可以多次绑定的

    作者回复: 不是这样的哦,Nginx在80端口上监听,worker只是开启的内部干活的线程,并不真的监听在80端口上

    2021-06-12
    2
    5
  • pc
    老师 有几个疑问~ 1、对于客户端理论上也会发生Address already in use 的错误吧?(当没有SO_REUSEADDR、端口也重复了时)是在connect的时候报错吗? 2、“一个 TCP 连接是通过四元组(源地址、源端口、目的地址、目的端口)来唯一确定”--这句话不是很理解,对于服务端bind的时候不是没有目的地址吗?难道是在accept的时候报错吗?可是accept时候不是阻塞等待客户端连接吗?没有很理清楚....

    作者回复: 1.我觉得是的; 2.bind的时候还没有建立TCP连接哦,只有accept成功返回才真正的建立了一个TCP连接,这个时候才有四元组可以描述这个TCP连接。

    2020-06-03
    3
  • 凌空飞起的剪刀腿
    第一道,之前我们看到的例子,都是对 TCP 套接字设置 SO_REUSEADDR 套接字选项,你知道吗,我们也可以对 UDP 设置 SO_REUSEADDR 套接字选项。那么问题来了,对 UDP 来说,设置 SO_REUSEADDR 套接字选项有哪些场景和好处呢? UDP的SO_REUSEADDR使用场景比较多的是组播网络,好处是,如我们在接收组播流的时候,比如用ffmpeg拉取了一个组播流,但是还想用ffmpeg拉取相同的组播流,这个时候就需要地址重用了 第二道,在服务器端程序中,设置 SO_REUSEADDR 套接字选项时,需要在 bind 函数之前对监听字进行设置,想一想,为什么不是对已连接的套接字进行设置呢? 因为SO_REUSEADDR是针对新建立的连接才起作用,对已建立的连接设置是无效的。

    作者回复: 👍

    2021-05-20
    2
  • linker
    听完之后,程序也做了调试,但是一周又忘记了,看来还是得反复听,反复练习啊!

    作者回复: 嗯,反复练习最重要。

    2020-03-21
    2
收起评论
显示
设置
留言
34
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部