socket編程實戰-bind端口占用問題


https://www.cnblogs.com/rockyching2009/p/11032230.html

一、背景

端對端的通信中存在的一個問題是:如何唯一地標識通信主體。對於socket,解決這個問題的方式是四元組:自身IP,自身端口,對方IP,對方端口。

在socket編程中,作為client,端口號是由操作系統管理和分配的,所以不存在端口占用的情況。如果作為server,在bind某個端口的時候,或多或少遇到過如下錯誤:

bind: Address already in use

在進程異常終止或重啟網絡服務的時候,出現上述問題的情況很常見;而作為服務端又需要進程馬上步入正軌,這個問題就顯得尤其重要。

 

二、原因及解決方案

/** From APUE */
int initserver(int type, const struct sockaddr *addr, socklen_t alen)
{
	int fd;
	if ((fd = socket(addr->sa_family, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		return (-1);
	}

	if (bind(fd, addr, alen) < 0) {
		perror("bind");
		goto errout;
	}

	if (listen(fd, SOMAXCONN) < 0) {
		perror("listen");
		goto errout;
	}

	return (fd);

errout:
	err = errno;
	close(fd);
	errno = err;
	return(-1);
}

以上代碼段,進程終止后立即投入運行的話,就會產生開篇說的問題。原因在APUE這本書也有提及:

Normally, the implementation of TCP will prevent us from binding the same address until a timeout expires,
which is usually on the order of several minutes.

同時也提供了解決方法,給socket設置SO_REUSEADDR屬性:

int reuse = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int);

 

三、未完再續

但是,如果你的進程調用了fork()或system()函數,上述方案則無能為力。

對於子進程,創建成功后將復制父進程的數據空間、堆和棧副本,這其中也包括文件描述符(fork過程);而且,一般我們fork子進程后接着執行一個新程序(execve過程),這就導致copy過來的文件描述符一直無法關閉,如此即使父進程終止,其監聽的socket也無法釋放,進而相應端口一直處於Listen狀態(netstat命令),導致進程無法bind指定端口。

 

知道了原因,對症下葯即可:創建的子進程execve時禁止對文件描述符的復制,即close-on-exec

在Linux系統中,這是通過fcntl()設置文件描述符的FD_CLOEXEC標志實現:

int set_cloexec(int fd)
{
	int val;

	val = fcntl(fd, F_GETFD, 0);
	val |= FD_CLOEXEC; /* enable close-on-exec */
	return fcntl(fd, F_SETFD, val);
}

莎翁說:簡潔是智慧的靈魂,上面一段畢竟顯得臃腫,所以socket引入了SOCK_CLOEXEC實現同樣的功能

對於通過open()函數創建的文件描述符,可以設置flags入參O_CLOEXEC選項達到close-on-exec的目的。

 

參考資料

1、man 2 open

2、《UNIX環境高級編程》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM