网络编程实战
盛延敏
前大众点评云平台首席架构师
立即订阅
6034 人已学习
课程目录
已完结 39 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 学好网络编程,需要掌握哪些核心问题?
免费
第一模块:基础篇 (9讲)
01 | 追古溯源:TCP/IP和Linux是如何改变世界的?
02 | 网络编程模型:认识客户端-服务器网络模型的基本概念
03丨套接字和地址:像电话和电话号码一样理解它们
04 | TCP三次握手:怎么使用套接字格式建立连接?
05 | 使用套接字进行读写:开始交流吧
06 | 嗨,别忘了UDP这个小兄弟
07 | What? 还有本地套接字?
08 | 工欲善其事必先利其器:学会使用各种工具
09丨答疑篇:学习网络编程前,需要准备哪些东西?
第二模块:提高篇 (10讲)
10 | TIME_WAIT:隐藏在细节下的魔鬼
11 | 优雅地关闭还是粗暴地关闭 ?
12 | 连接无效:使用Keep-Alive还是应用心跳来检测?
13 | 小数据包应对之策:理解TCP协议中的动态数据传输
14丨UDP也可以是“已连接”?
15 | 怎么老是出现“地址已经被使用”?
16 | 如何理解TCP的“流”?
17 | TCP并不总是“可靠”的?
18 | 防人之心不可无:检查数据的有效性
19丨提高篇答疑:如何理解TCP四次挥手?
期中复习周 (2讲)
期中大作业丨动手编写一个自己的程序吧!
免费
期中大作业丨题目以及解答剖析
免费
第三模块:性能篇 (12讲)
20 | 大名⿍⿍的select:看我如何同时感知多个I/O事件
21 | poll:另一种I/O多路复用
22 | 非阻塞I/O:提升性能的加速器
23 | Linux利器:epoll的前世今生
24 | C10K问题:高并发模型设计
25 | 使用阻塞I/O和进程模型:最传统的方式
26 | 使用阻塞I/O和线程模型:换一种轻量的方式
27 | I/O多路复用遇上线程:使用poll单线程处理所有I/O事件
28 | I/O多路复用进阶:子线程使用poll处理连接I/O事件
29 | 渐入佳境:使用epoll和多线程模型
30 | 真正的大杀器:异步I/O探索
31丨性能篇答疑:epoll源码深度剖析
第四模块:实战篇 (4讲)
32 | 自己动手写高性能HTTP服务器(一):设计和思路
33 | 自己动手写高性能HTTP服务器(二):I/O模型和多线程模型实现
34 | 自己动手写高性能HTTP服务器(三):TCP字节流处理和HTTP协议实现
35 | 答疑:编写高性能网络编程框架时,都需要注意哪些问题?
结束语 (1讲)
结束语丨我相信这不是结束,让我们江湖再见
网络编程实战
登录|注册

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

盛延敏 2019-09-04
你好,我是盛延敏,这里是网络编程实战的第 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/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《网络编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(12)

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

    作者回复: 总结得不错。

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

    作者回复: 👍

    2019-10-13
    3
  • 传说中的成大大
    我竟然是沙发
    第一问: 百度出来的 针对udp是允许完全的重复的捆绑 就是是udp允许把ip地址绑定到多个套接口上,大概是为了在同一机器上运行多个多播程序的情况下,具体的实例却想不出来
    2. 因为我觉得bind函数时告诉内核我要监听这个ip地址和端口是在内核层的事情, 如果bind过后再进行设置套接字选项的话虽然是在应用层对套接字进行了修改,但是没告诉内核,这个地址需要避开timewait状态直接重用,大意就是没有影响到内核的处理

    作者回复: UDP用的比较少,实例确实比较难以想到。

    2019-09-04
    1
  • Geek_68d3d2
    没明白复用socket,如果复用了socket是不是就是相当于旧有的socket连接没有了time_wait时间了?也就是说对端可能收不到fin而一直处于等待状态?

    作者回复: 应该这样说是将原来处于TIME_WAIT状态的连接变成可利用的,而FIN是在TIME_WAIT状态之前已经发送出去了,不会引起对端一直处于等待状态。

    2019-12-07
    1
  • godtrue
    最佳实践:
    服务器端程序,都应该设置 SO_REUSEADDR 套接字选项,以便服务端程序可以在极短时间内复用同一个端口启动。
    在所有 TCP 服务器程序中,调用 bind 之前请设置 SO_REUSEADDR 套接字选项。

    实际开发,这个遇到的确实多一点,自己都是找到是谁占用的端口,然后弄死它,再重启应用。

    最早时都不知道怎么找,直接重启机器,再重启应用。

    作者回复: 太暴力了吧

    2019-11-23
    1
  • 林林
    老师,我们项目也存在服务器无法快速重启的问题,然而底层代码不开放,无法加上so_reuseaddr,不知道有没有其他办法可以解决?

    作者回复: 这个,给他们提issue吧。

    2019-11-01
    1
  • chs
    老师请讲一下SO_REUSEPORT

    作者回复: SO_REUSEPORT用在多个不同的socket监听在同一个端口上,这种情况比较罕见,容易出现所谓的"惊群"现象。当然,如果用的好,也可以解决一些特定场景的问题。

    2019-10-30
  • 小美
    使用SO_REUSEADDR感觉跟前面讲的time_wait状态持续2MSL的原因有冲突呀。服务能立即重启的话,就能收到上一次连接的旧数据包呀

    作者回复: 不是冲突的,SO_REUSEADDR是直接复用TIME_WAIT状态的连接,上一次连接的旧数据包会被忽略掉。

    2019-10-15
    1
  • 石将从
    怎么用telnet连接,求老师回答

    作者回复: $telnet 127.0.0.1 43211

    2019-09-07
  • 不动声色满心澎湃
    老师 有个疑问想问下:如果我的服务器是双网卡。一个192.168.1.220 一个是192.68.1.221 然后我让220和端口8010 处于time_wait状态, 这个时候再用221和8010去启动一个程序,那会报addr in use吗

    作者回复: 当然不会,TCP四元组,里面就有你这里提到的server_port。它改变了,四元组就不唯一了呀。

    2019-09-04
  • 不动声色满心澎湃
    老师, 希望可以多讲一点。 感觉听了很爽 但是觉得不够 哈哈哈

    作者回复: 哈哈,我写得很累的说。

    2019-09-04
  • nil
    学生时代写网络编程作业,调试的时候经常有遇到这个问题,然后通过每次改变端口号绕过这个问题。想想当时遇到问题一知半解,也不知道去寻找根本原因,哈哈哈,估计心思都在完成作业上,而根本不是想要掌握这个技术底层的原理

    作者回复: 哈哈,好在我们有大把的端口可以使用的。

    2019-09-04
收起评论
12
返回
顶部