C/C++:TCP bind error:Address already in use

在编写、运行C/C++服务端程序时,调用bind系统调用常出现Address already in use错误。原因一是bind已监听的端口,二是目标端口是本地socket连接中的端口。监听服务器主动关闭连接会进入TIME_WAIT状态,导致重启失败。可通过设置SO_REUSEADDR选项避免该问题。

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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define BACKLOG 16

int main()
{
	// socket
	int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_fd < 0)
	{
		printf("create socket error=%d(%s)!!!\n", errno, strerror(errno));
		exit(1);
	}

	// bind
	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET; // IPv4
	server_addr.sin_port = htons(12500); // Port
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // IP
	if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
	{
		printf("socket bind error=%d(%s)!!!\n", errno, strerror(errno));
		exit(1);
	}

	// listen
	if (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)
	{
		// accept
		int 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++;
	}

	// never
	close(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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

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 terminate
	printf("%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起作用啦!

// #include <iostream> // #include <cstring> // #include <sys/socket.h> // #include <netinet/in.h> // #include <arpa/inet.h> // #include <unistd.h> // #include <string> // using std::string; // using namespace std; // int main() // { // // 1. 创建 socket // int sockfd =::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); // if (sockfd ==-1 ) // { // cout<<"creat socket fail"<<errno<<strerror(errno)<<endl; // } // else // cout<<"create socket success!"<<endl; // string ip="127.0.0.1 "; // int port=8080; // struct sockaddr_in sockaddr; // memset(&sockaddr,0,sizeof(sockaddr)); // sockaddr.sin_family=AF_INET; // sockaddr.sin_addr.s_addr=inet_addr(ip.c_str()); // sockaddr.sin_port=htons(0); // if (::bind(sockfd,(struct sockaddr *) &sockaddr,sizeof(sockaddr))<0) // { // cout<<"erro"<<errno<<strerror(errno); // } // //3.监听socket // if(::listen(sockfd,1024)<0) // cout<<"erro"<<errno<<strerror(errno); // else // cout<<"listen ing ......"<<endl; // while (true) // { // //4.接受客户端连接 // int clientfd= ::accept(sockfd,nullptr,nullptr); // if(clientfd<0) // { // cout<<"accept fail ......"<<endl; // return 1; // } // char buf[1024]={0}; // //5.接受客户端数据 // size_t iret=::recv(clientfd,buf,sizeof(buf),0); // cout<<buf<<endl; // strcpy(buf,"OK"); // ::send(clientfd,buf,sizeof(buf),0); // /* code */ // } // ::close(sockfd); // return 0; // } #include <iostream> #include <cstring> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string> using std::string; int main() { // 1. 创建 socket int sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sockfd < 0) { printf("create socket error: errno=%d errmsg=%s\n", errno, strerror(errno)); return 1; } else { printf("create socket success!\n"); } // 2. 绑定 socket string ip = "127.0.0.1"; int port = 8080; struct sockaddr_in sockaddr; std::memset(&sockaddr, 0, sizeof(sockaddr)); sockaddr.sin_family = AF_INET; sockaddr.sin_addr.s_addr = inet_addr(ip.c_str()); sockaddr.sin_port = htons(port); if (::bind(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) { printf("socket bind error: errno=%d, errmsg=%s\n", errno, strerror(errno)); return 1; } else { printf("socket bind success: ip=%s port=%d\n", ip.c_str(), port); } // 3. 监听 socket if (::listen(sockfd, 1024) < 0) { printf("socket listen error: errno=%d errmsg=%s\n", errno, strerror(errno)); return 1; } else { printf("socket listen ...\n"); } while (true) { // 4. 接收客户端连接 int connfd = ::accept(sockfd, nullptr, nullptr); if (connfd < 0) { printf("socket accept error: errno=%d errmsg=%s\n", errno, strerror(errno)); return 1; } char buf[1024] = {0}; // 5. 接收客户端的数据 size_t len = ::recv(connfd, buf, sizeof(buf), 0); printf("recv: conn=%d msg=%s\n", connfd, buf); // 6. 向客服端发送数据 ::send(connfd, buf, len, 0); } // 7. 关闭 socket ::close(sockfd); return 0; } 前面注释的代码与后面未注释的代码有什么区别,为什么注释的代码不能被正常连接而未注释的代码可以被正常连接
10-02
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值