c/c :tcp bind error:address already in use-尊龙官方平台

c/c :tcp bind error:address already in use

el/2024/3/25 16:39:55

c/c :tcp bind error:address already in use

在编写、运行服务端程序时,经常会遇到的一个错误是:address already in use.

address already in use 是在调用bind系统调用时出现的错误。

原因有两个:

1.bind一个已经listen的端口

例如:

当前主机已经有服务器进程调用bind以及listen,在当前主机监听12500端口:

[jiang@localhost ~]$ netstat -an | grep 12500
tcp        0      0 0.0.0.0:12500               0.0.0.0:*                   listen 

你尝试在程序中再次调用bind,将12500端口和你的socket进行绑定,此时会产生系统调用错误:

[jiang@localhost ~]$ ./server/server 
socket bind error=98(address already in use)!!!
code:
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include #define backlog 16int main()
{// socketint listen_fd = socket(af_inet, sock_stream, 0);if (listen_fd < 0){printf("create socket error=%d(%s)!!!\n", errno, strerror(errno));exit(1);}// bindstruct sockaddr_in server_addr;server_addr.sin_family = af_inet; // ipv4server_addr.sin_port = htons(12500); // portserver_addr.sin_addr.s_addr = htonl(inaddr_any); // ipif (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){printf("socket bind error=%d(%s)!!!\n", errno, strerror(errno));exit(1);}// listenif (listen(listen_fd, backlog) < 0){printf("socket listen error=%d(%s)!!!\n", errno, strerror(errno));exit(1);}printf("server init ok, start to accept new connect...\n");int connidx = 0;while (1){// acceptint client_fd = accept(listen_fd, null, null);if (client_fd < 0){printf("socket accept error=%d(%s)!!!\n", errno, strerror(errno));exit(1);}printf("accept one new connect(%d)!!!\n", connidx);static char msg[1024] = "";memset(msg, 0, sizeof(msg));snprintf(msg, sizeof(msg)-1, "connidx=%d\n", connidx);if (write(client_fd, msg, strlen(msg)) != strlen(msg)){printf("send msg to client error!!!\n");exit(1);}close(client_fd);connidx;}// neverclose(listen_fd);return 0;
}

编译 && 生成可执行文件:

[jiang@localhost server]$ gcc -o server server.c

连续调用两次server:

1)第一次:

[jiang@localhost server]$ ./server 
server init ok, start to accept new connect...

bind成功,服务器进程正常运行,用netstat查看12500端口状态:

[jiang@localhost server]$ netstat -an | grep 12500
tcp        0      0 0.0.0.0:12500               0.0.0.0:*                   listen

2)第二次:

[jiang@localhost server]$ ./server 
socket bind error=98(address already in use)!!!

bind失败,进程退出。

2.bind的目标端口是本主机本地socket连接中的端口

例如:

a)主机启动一个监听服务器;
b)连接请求到达,派生一个子进程来处理这个客户端连接;
c)监听服务器终止,但是子进程继续为现有连接的客户提供服务;
d)重启监听服务器;

在d)步骤之前,已经有一条(多条)正在连接的tcp连接:

五元组=》

客户端ip:客户端port:tcp:服务端ip:服务端port(12500)

当想要再次启动监听服务器对 bind 12500 进行调用将会失败,导致重启监听服务器失败。

也就是说,本机已经有一个连接的本端socket是12500端口,要再 bind 12500 将失败。

请看一个有意思的示例程序。

code:

服务端程序见上文。

客户端程序:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include int main()
{int client_fd = socket(af_inet, sock_stream, 0);if (client_fd < 0){printf("create socket error=%d(%s)!!!\n", errno, strerror(errno));exit(1);}struct sockaddr_in server_addr;server_addr.sin_family = af_inet;server_addr.sin_port = htons(12500);if (inet_pton(af_inet, "192.168.44.144", &server_addr.sin_addr) <= 0){printf("inet_pton error!!!\n");exit(1);}if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){printf("socket connect error=%d(%s)!!!\n", errno, strerror(errno));exit(1);}printf("connect to server ok!\n");char msg[1024];int rbytes = read(client_fd, msg, sizeof(msg)-1);if (rbytes <= 0){printf("read error=%d(%s)!!!\n", errno, strerror(errno));exit(1);}msg[rbytes] = 0; // null terminateprintf("%s", msg);int finread = read(client_fd, msg, sizeof(msg)-1);if (finread < 0){printf("finread(%d): errno(%d): %s\n", finread, errno, strerror(errno));}else if (finread == 0){msg[finread] = 0;printf("finread(%d): read fin-segment\n", finread);}else{printf("finread(%d): %s\n", msg);}close(client_fd);return 0;
}

编译 && 生成可执行文件:

[jiang@localhost client]$ gcc -o client client.c
[jiang@localhost client]$ ll
total 16
-rwxrwxr-x. 1 jiang jiang 8476 may 14 14:09 client
-rw-rw-r--. 1 jiang jiang 1374 may 14 13:18 client.c

步骤如下:

a)启动监听服务器server:

[jiang@localhost server]$ ./server 
server init ok, start to accept new connect...
[jiang@localhost ~]$ netstat -an | grep 12500
tcp        0      0 0.0.0.0:12500               0.0.0.0:*                   listen

b)重启监听服务器server:

[jiang@localhost server]$ ./server 
server init ok, start to accept new connect...
^c
[jiang@localhost server]$ ./server 
server init ok, start to accept new connect...
[jiang@localhost ~]$ netstat -an | grep 12500
tcp        0      0 0.0.0.0:12500               0.0.0.0:*                   listen

我们看到,按下ctrl c宕掉进程,并重启监听服务器成功。

c)执行客户端程序,链接服务端的服务端口,然后宕掉服务端进程:

[jiang@localhost client]$ ./client 
connect to server ok!
connidx=0
finread(0): read fin-segment
[jiang@localhost server]$ ./server 
server init ok, start to accept new connect...
accept one new connect(0)!!!
^c

d)此时在服务端快速地执行:

[jiang@localhost server]$ netstat -an | grep 12500
tcp        0      0 192.168.44.144:12500        192.168.44.144:52656        time_wait

e)快速重启监听服务器:

[jiang@localhost server]$ ./server 
socket bind error=98(address already in use)!!!

重启失败,address already in use

f)稍等1分钟左右,重启监听服务器:

[jiang@localhost server]$ ./server 
server init ok, start to accept new connect...

重启成功。

概括来说:

1)如果没有客户端连接到服务器,那么可以不间断地重启服务器;
2)如果有客户端连接到服务器,那么服务端宕掉后,需要等待一段时间才可以 bind 12500 成功,重启成功。

原因:

监听服务程序中,是服务器主动对连接进行close,主动关闭连接,那服务器一侧就将进入到time_wait状态,持续2msl。

当监听服务器宕掉时,连接还是会被内核所记录,也就是说,四次挥手还未完成,这个连接还未完整地走完四次挥手,还处于“连接中”的状态。

这种情况,实际和一条运行中的正常的tcp连接没啥区别,都未完成四次挥手流程。

当监听服务器进程重启,12500还被内核持有,也就 bind 12500 失败,重启监听服务器失败。

直到连接在2msl时间之后(通常2msl=1min)连接被内核释放,再次 bind 12500 将会成功。

如果没有客户端连接到服务器,当然可以随意起宕服务器!因为服务器一侧没有连接处于time_wait状态!12500随时可以串行地 bind 12500 成功。

如何避免?

设置套接字选项:so_reuseaddr。

在《unix网络编程:卷一 套接字联网api》一书中写到:

所有的tcp服务器都应当指定so_reuseaddr选项。

修改代码,在 bind 12500 前新增:

	int flag = 1;if (setsockopt(listen_fd, sol_socket, so_reuseaddr, &flag, sizeof(flag)) < 0){printf("socket setsockopt error=%d(%s)!!!\n", errno, strerror(errno));exit(1);}

再次执行上面的步骤:

当有客户端连接被服务端处理并宕掉服务端进程,在服务端执行:

[jiang@localhost server]$ ./server 
server init ok, start to accept new connect...
accept one new connect(0)!!!
^c
[jiang@localhost server]$ netstat -an | grep 12500
tcp        0      0 192.168.44.144:12500        192.168.44.144:52660        time_wait

此时重启服务端进程可以 bind 12500 成功吗?

可以成功。

即,so_reuseaddr起作用啦!


http://www.ngui.cc/el/5127033.html

相关文章

go:disable http chunk mode

go:disable http chunk mode 1.结论 应用层显式设置 content-length,可以 disable chunk mode。 http.handlefunc("/", func(writer http.responsewriter, request *http.request) {writer.header().set("content-length", "2…

go:http transfer-encoding chunked 实时读写

go:http transfer-encoding chunked 实时读写 服务端: package mainimport ("fmt""net/http""time" )func main() {http.handlefunc("/", func(writer http.responsewriter, request *http.request) {flusher,…

go:http request cancelled 服务端感知

go:http request cancelled 服务端感知 1.背景 今天查问题的时候,偶然发现github上一个有意思的问题,记录下来。 原问题:https://github.com/golang/go/issues/23262 2.问题 首先,我们思考一个问题: 当…

go:read一个已经被canceled的http.request的应答

go:read一个已经被canceled的http.request的应答 1.复现 最近发现项目在处理chunk类型的http应答时,出现读数据异常报错,代码示例如下: server package mainimport ("bytes""net/http" )func main() {http…

docker:安装vim

docker:安装vim 安装命令:apt install vim -y 异常错误:unable to locate package vim # apt install vim reading package lists... done building dependency tree reading state information... done e: unable to locate package…

go:zap log rotate(日志轮转)

go:zap log rotate(日志轮转) demo: package mainimport ("go.uber.org/zap""go.uber.org/zap/zapcore""gopkg.in/natefinch/lumberjack.v2" )func main() {// 日志级别loglevel : "debug&qu…

go:zap 自义定时间戳格式

go:zap 自义定时间戳格式 1.背景 使得zap输出的日志时间戳形如:2021-05-25 22:36:23.107(毫秒) 2.demo: package mainimport ("go.uber.org/zap""go.uber.org/zap/zapcore""gopkg.in/natef…

go run command-line-arguments

go run command-line-arguments 1.复现 目录结构: $ tree test1280 test1280 |-- go.mod |-- hello.go -- main.go代码内容: /*go.mod*/ module test1280go 1.16/*hello.go*/ package mainimport "fmt"func hello() {fmt.println("hel…

go:unresolved reference xx(1)

go:unresolved reference xx(1) 1.环境 goland版本:goland 2020.3.5 go版本:1.16.4 ebdesktop-k45ia6v mingw64 ~/desktop/test1280 $ go version go version go1.16.4 windows/amd64goland中选择的sdk版本:1.16.4 goland已经en…

go:unresolved reference xx(2)

go:unresolved reference xx(2) 1.环境 goland版本:2021.1.2 go版本:1.16.4 ebdesktop-k45ia6v mingw64 ~ $ go version go version go1.16.4 windows/amd642.背景 我们通常执行go get指令完成第三方依赖库的安装。 go get指令具体做了哪…
网站地图