SO_REUSEADDR测试及应用


最近看了一篇socket编程:SO_REUSEADDR例解 的文章,写的比较详细,但对其中Richard Stevens的《Unix网络编程指南》中关于SO_REUSEADDR的讲解不甚清楚,自己做了个决定,花了两天的时间研究了一下,不算太深也不算太浅。

那篇文章的出处已找不到,还是非常感谢原作者,本文会借用一下那篇文章的一些内容,比较好的东西在于分享和应用吧~~~~

首先:

引用的内容:

首先声明一个问题:当两个socket的address和port相冲突,而你又想重用地
址和端口,则旧的socket和新的socket都要已经被设置了SO_REUSEADDR特性,只
有两者之一有这个特性还是有问题的。
    SO_REUSEADDR可以用在以下四种情况下。
    (摘自《Unix网络编程》卷一,即UNPv1)
    1、当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启
动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。 (这个是正确的)
    2、SO_REUSEADDR允许同一port上启动同一服务器的多个实例(多个进程)。但
每个实例绑定的IP地址是不能相同的。在有多块网卡或用IP Alias技术的机器可
以测试这种情况。 (测试是不正确的)
    3、SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个soc
ket绑定的ip地址不同。这和2很相似,区别请看UNPv1。 (觉得不对,测试在此情况下不需要使用SO_REUSEADDR也都正常)
    4、SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的
多播,不用于TCP。

引出本文的测试的基本点:测试SO_REUSEADDR和bind的关系,分为两部分:单进程---单个sock 和多个sock 和两个进程的情况。

主要内容:

测试过程中遇到的问题终结出得结论:

1)bind时必须是自己的本地地址,不然提示:#define EADDRNOTAVAIL   99  /* Cannot assign requested address */

2)先bind,后listen,最后connect到任意目的地址时,都会提示:#define EISCONN     106 /* Transport endpoint is already connected */

一)单进程

1)单个socket,主要引用的内容,稍有改动。代码如下:

#include <netinet/in.h> 
#include <sys/socket.h> 
#include <time.h> 
#include <stdio.h> 
#include <string.h> 
#include <stdlib.h>
#include <unistd.h>

#define MAXLINE 100 

int main(int argc, char** argv) 
{ 
	int listenfd,connfd; 
	struct sockaddr_in servaddr; 
	char buff[MAXLINE+1]; 
	time_t ticks; 
	unsigned short port; 
	int flag=1,len=sizeof(int); 

	port=10013; 
	if( (listenfd=socket(AF_INET,SOCK_STREAM,0)) == -1) 
	{ 
		perror("socket"); 
		exit(1); 
	} 
	bzero(&servaddr,sizeof(servaddr)); 
	servaddr.sin_family=AF_INET; 
	//servaddr.sin_addr.s_addr=htonl(INADDR_ANY); 
	inet_pton(AF_INET, "192.168.6.28", &servaddr.sin_addr);
	servaddr.sin_port=htons(port); 
	printf("%08x\n", INADDR_ANY);
	if( setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -
			1) 
	{ 
		perror("setsockopt"); 
		exit(1); 
	} 
	if( bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == 
			-1) 
	{ 
		perror("bind"); 
		exit(1); 
	} 
	else 
		printf("bind call OK!\n"); 
	if( listen(listenfd,5) == -1) 
	{ 
		perror("listen"); 
		exit(1); 
	} 
	for(;;) 
	{ 
		if( (connfd=accept(listenfd,(struct sockaddr*)NULL,NULL)) == -1)

		{ 
			perror("accept"); 
			exit(1); 
		} 
		if( fork() == 0)/**//*child process*/ 
		{ 
		close(listenfd);/**//*这句不能少,原因请大家想想就知道了。*/ /*
因为端口已经被绑定和监听了,需要先释放,注意是在监听的情况下 才需要释放,如果光绑定不监听,两个socket都可以绑定同一个端口。*/			ticks=time(NULL); 
			snprintf(buff,100,"%.24saa\r\n",ctime(&ticks)); 
			write(connfd,buff,strlen(buff)); 
			close(connfd); 
			sleep(1); 
			execlp("/home/ansx/ctest/apue/socket_option/reuseaddr_timewait",NULL); 
			perror("execlp"); 
			exit(1); 
		} 
		close(connfd); 
		exit(0);/**//* end parent*/ 
	} 
} 

第一个终端:

[root@localhost socket_option]# ./reuseaddr_timewait
00000000
bind call OK!
[root@localhost socket_option]# 00000000
bind call OK!
第二个终端:

[root@localhost ~]# telnet 192.168.6.28 10013
Trying 192.168.6.28...
Connected to bogon (192.168.6.28).
Escape character is '^]'.
Fri Sep 16 02:33:18 2011aa
Connection closed by foreign host.

2)两个socket的情况,代码如下:编译:gcc -o reuseaddr_1proc2port  reuseaddr_1proc2port.c -lpthread

#include <netinet/in.h> 
#include <sys/socket.h> 
#include <time.h> 
#include <stdio.h> 
#include <string.h> 
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

#define BUFSIZE 1024
#define MAXLINE 100 
//#define TEST_LISTEN 

#define IPSTR1 "127.0.0.1"
//#define IPSTR2 "127.0.0.1"
#define IPSTR2 "192.168.6.28"
//#define IPSTR1 "0.0.0.0"

void 
process_fd(int, int);

int main(int argc, char** argv) 
{ 
	int fd1,fd2; 
	struct sockaddr_in servaddr1,servaddr2; 
	char buff[MAXLINE+1]; 
	time_t ticks; 
	unsigned short port; 
	int flag=1,len=sizeof(int); 

	port=10013; 
	if( (fd1=socket(AF_INET,SOCK_STREAM,0)) == -1) 
	{ 
		perror("socket"); 
		exit(1); 
	} 
	if( (fd2=socket(AF_INET,SOCK_STREAM,0)) == -1) 
	{ 
		perror("socket"); 
		exit(1); 
	} 
	bzero(&servaddr1,sizeof(servaddr1)); 
	bzero(&servaddr2,sizeof(servaddr2)); 
	servaddr1.sin_family=AF_INET; 
	servaddr2.sin_family=AF_INET; 

	if( inet_pton(AF_INET, IPSTR1, &servaddr1.sin_addr) <= 0) 
	{ 
		printf("inet_pton() call error:127.0.0.1\n"); 
		exit(1); 
	} 
	if( inet_pton(AF_INET, IPSTR2, &servaddr2.sin_addr) <= 0) 

	{ 
		printf("inet_pton() call error:128.160.1.230\n"); 
		exit(1); 
	} 
	servaddr1.sin_port=htons(port); 
	servaddr2.sin_port=htons(port); 
#if 1 
	if( setsockopt(fd1, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) 
	{ 
		perror("setsockopt"); 
		exit(1); 
	} 
	if( setsockopt(fd2, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) 
	{ 
		perror("setsockopt"); 
		exit(1); 
	}
#endif	
	if( bind(fd1,(struct sockaddr*)&servaddr1,sizeof(servaddr1)) == -1)

	{ 
		perror("bind fd1"); 
		exit(1); 
	}

	if( bind(fd2,(struct sockaddr*)&servaddr2,sizeof(servaddr2)) == -1)

	{ 
		perror("bind fd2"); 
		exit(1); 
	} 
	printf("bind fd1 and fd2 OK!\n"); 
	if( listen(fd1,5) == -1) 
	{ 
		perror("listen fd1"); 
		exit(1); 
	} 
#ifdef TEST_LISTEN  (aa)
	if( listen(fd2,5) == -1) 
	{ 
		perror("listen fd2"); 
		exit(1); 
	} 
#endif
	/**//*put other process here*/ 
	process_fd(fd1, fd2);
	getchar(); 
	exit(0);/**//* end */ 
} 

void
pthread_process(void *arg)
{
	int listen_fd = (int)arg;
	struct sockaddr_in cli_addr;
	int conn_fd;
	char buff[100];
	int len;
	int thread_id = pthread_self();
	time_t ticks;

	while(1) {
		len = sizeof(cli_addr);
		conn_fd = accept(listen_fd, (struct sockaddr *)&cli_addr, (socklen_t *)&len);
		if (conn_fd == -1) {
			if (errno == EINVAL) {
				continue;
			}
			close(listen_fd);
			pthread_exit(NULL);
		}
		snprintf(buff, 100, "threadid = %08x,listen_fd = %d, cli_addr = %s, cli_port = %d\n", 
				thread_id, listen_fd, inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
		printf("%s", buff);
		ticks=time(NULL);
		snprintf(buff,100,"%.24s\r\n",ctime(&ticks));
		write(conn_fd, buff, strlen(buff));
		close(conn_fd);
	}
	
	pthread_exit(NULL);
}

void 
pthread_connect(void *arg)
{
	int cli_fd = (int)arg;
	struct sockaddr_in serv_addr;
	int ret;
	char buf[BUFSIZE];

	printf("pthread_connect fd = %d, serverip = %s\n", cli_fd, IPSTR1);
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(10013);
	ret = inet_pton(AF_INET, IPSTR1, &serv_addr.sin_addr);
	if (ret == -1) {
		perror("inet_pton");
		exit(1);
	}

	while (1) {
		printf("cli connect start\n");
		ret = connect(cli_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
		if (ret == -1) {
			if (errno == EINVAL) {
				continue;
			}
			printf("errno = %d\n", errno);//#define EISCONN     106 /* Transport endpoint is already connected */
			perror("cli connect");
			close(cli_fd);
			return ;
		}
		
		break;
	}
	printf("cli connect end\n");
	while (1) {
		printf("cli recv start\n");
		ret = recv(cli_fd, buf, BUFSIZE - 1, 0);
		if (ret < 0) {
			if (errno == EINTR) {
				continue;
			}
			perror("cli recv");
			return ;
		}
		buf[ret] = '\0';
		printf(buf);
		break;
	}
	printf("cli recv end\n");
	
	return ;
}


void
process_fd(int fd1, int fd2)
{
	pthread_t td1,td2;
	int sock_fd1 = fd1;
	int sock_fd2 = fd2;

	pthread_create(&td1, NULL, (void *(*)(void*))pthread_process, (void *)fd1);
#ifdef TEST_LISTEN
	pthread_create(&td2, NULL, (void *(*)(void*))pthread_process, (void *)fd2);
#else
	pthread_create(&td2, NULL, (void *(*)(void*))pthread_connect, (void *)fd2);
#endif
	printf("threadid = %08x, fd1 = %d, address = %s\n", td1, fd1, IPSTR1);
	printf("threadid = %08x, fd2 = %d, address = %s\n", td2, fd2, IPSTR2);

}


 

分为两种情况:

a)两个sock bind的ip和port都相同,在程序文件头部修改:#define IPSTR1 "127.0.0.1"  和 #define IPSTR2 "127.0.0.1",下面的修改都是在此基础上。

i)

      则两个sock都必须使用SO_REUSEADDR(只有一个使用不行),不然有#define EADDRINUSE  98  /* Address already in use */此错误。

      如其中的#if 1改为#if 0,编译运行,结果如下:    

    

[root@localhost socket_option]# ./reuseaddr_1proc2port 
bind fd2: Address already in use


ii)

    另个sock不能同时listen,如果把代码中的(aa)的去掉,则编译运行,结果如下:

[root@localhost socket_option]# ./reuseaddr_1proc2port 
bind fd1 and fd2 OK!
listen fd2: Address already in use


iii)

    直接编译运行,结果如下:

  第一个端口:

[root@localhost socket_option]# ./reuseaddr_1proc2port 
bind fd1 and fd2 OK!
threadid = b7f41b90, fd1 = 3, address = 127.0.0.1
threadid = b7540b90, fd2 = 4, address = 127.0.0.1
pthread_connect fd = 4, serverip = 127.0.0.1
cli connect start
cli connect end
cli recv start(从这可以看到没有recv 结果,比较奇怪,先不管那么多了) -----自己listen,自己connect自己,就会这样了,从状态上看是ESTABLISHED.但没有数据交互。
threadid = b7f41b90,listen_fd = 3, cli_addr = 127.0.0.1, cli_port = 44370

第二个端口:

[root@localhost socket_option]# telnet 127.0.0.1 10013
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
Fri Sep 16 02:58:55 2011(这是收到的结果)
Connection closed by foreign host.

第三个端口:

[root@localhost ~]# netstat -anp | grep :10013
tcp        0      0 127.0.0.1:10013             0.0.0.0:*                   LISTEN      20157/reuseaddr_1pr 
tcp        0      0 127.0.0.1:10013             127.0.0.1:10013             ESTABLISHED 20157/reuseaddr_1pr (这个也比较奇怪)----显示连接,但没有数据交互。注意只有一行ESTABLISHED,

b)两个sock bind的ip地址不同,port相同,此时经测试无须使用SO_REUSEADDR都正常。这两个sock既可以都作为客户端,也可以都作为服务器,另外两个也可以一个sock作为服务器,另一个      sock  作为客户端。都正常。

       i)

             一个sock作为服务器,一个sock作为客户端,同时这个客户端去向这个服务器端请求。编译测试结果如下:

 第一个终端:

[root@localhost socket_option]# ./reuseaddr_1proc2port 
bind fd1 and fd2 OK!
threadid = b7fe2b90, fd1 = 3, address = 127.0.0.1
threadid = b75e1b90, fd2 = 4, address = 192.168.6.28
pthread_connect fd = 4, serverip = 127.0.0.1
cli connect start
cli connect end
cli recv start
threadid = b7fe2b90,listen_fd = 3, cli_addr = 192.168.6.28, cli_port = 10013
Fri Sep 16 03:16:27 2011
cli recv end
threadid = b7fe2b90,listen_fd = 3, cli_addr = 127.0.0.1, cli_port = 35187

             第二个终端:

[root@localhost socket_option]# telnet 127.0.0.1 10013
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
Fri Sep 16 03:16:42 2011
Connection closed by foreign host.

             第三个终端:

[root@localhost ~]# netstat -anp | grep :10013
tcp        0      0 127.0.0.1:10013             0.0.0.0:*                   LISTEN      20737/reuseaddr_1pr 
tcp        0      0 192.168.6.28:10013          127.0.0.1:10013             CLOSE_WAIT  20737/reuseaddr_1pr  ---这是192的c一直没关闭,而127s已经关闭。等待本地的socket关闭。
tcp        0      0 127.0.0.1:10013             192.168.6.28:10013          FIN_WAIT2   -  (这是什么情况,难得一见啊)----这是127的s先关闭socket,而192的c一直没关闭。        
tcp        0      0 127.0.0.1:10013             127.0.0.1:10013             TIME_WAIT   -    

 ii)  两个sock都作为服务器端,这是需要添加 #define TEST_LISTEN ,编译运行:

             第一个终端:

 

[root@localhost socket_option]# ./reuseaddr_1proc2port 
bind fd1 and fd2 OK!
threadid = b7f44b90, fd1 = 3, address = 127.0.0.1
threadid = b7543b90, fd2 = 4, address = 192.168.6.28
threadid = b7f44b90,listen_fd = 3, cli_addr = 127.0.0.1, cli_port = 53069
threadid = b7543b90,listen_fd = 4, cli_addr = 192.168.6.28, cli_port = 53553


第二个终端:

Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
Fri Sep 16 03:22:59 2011
Connection closed by foreign host.
[root@localhost socket_option]# telnet 192.168.6.28 10013
Trying 192.168.6.28...
Connected to bogon (192.168.6.28).
Escape character is '^]'.
Fri Sep 16 03:23:13 2011
Connection closed by foreign host.


第三个终端:

[root@localhost ~]# netstat -anp | grep :10013
tcp        0      0 192.168.6.28:10013          0.0.0.0:*                   LISTEN      20941/reuseaddr_1pr 
tcp        0      0 127.0.0.1:10013             0.0.0.0:*                   LISTEN      20941/reuseaddr_1pr 
tcp        0      0 192.168.6.28:10013          192.168.6.28:53553          TIME_WAIT   -                   
tcp        0      0 127.0.0.1:10013             127.0.0.1:53069             TIME_WAIT   -      

二)  测试两个进程的情况,比较简单,测试结果及过程:

在终端执行命令:ip addr add 192.168.1.234/24 dev eth0(相当于ip alias)
然后把reuseaddr_timewait.c的程序的服务器地址修改成192.168.6.28 和 192.168.1.232 (并且去掉SO_REUSEDADDR)分别编译为a1a.c和a2a.c并运行,显示结果正常。
另外在有两个网卡的情况下也测试不用SO_REUSEDADDR也正常。



ok了,寥寥数语,一切在代码中见分晓。下班回家了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值