setsockopt参数之SO_REUSEADDR

http://www.cnblogs.com/mydomain/archive/2011/08/23/2150567.html

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

http://www.sudu.cn/info/html/edu/20050101/296180.html

网友vmstat多次提出了这个问题:SO_REUSEADDR有什么用处和怎么使用。而
且很多网友在编写网络程序时也会遇到这个问题。所以特意写了这么一篇文章,
希望能够解答一些人的疑难。
    其实这个问题在Richard Stevens的《Unix网络编程指南》卷一里有很详细的
解答(中文版P166-168页)。这里我只是写几个基本的例子来验证这个问题。
    首先声明一个问题:当两个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。
    4、SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的
多播,不用于TCP。
例子1:测试上面第一种情况。
#include 
#include 
#include 
#include 
#include 
#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);
   servaddr.sin_port=htons(port);
   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!
");
   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);/*这句不能少,原因请大家想想就知道了。*/
        ticks=time(NULL);
        snprintf(buff,100,"%.24s
",ctime(&ticks));
        write(connfd,buff,strlen(buff));
        close(connfd);
        sleep(1);
        execlp("f1-Arrayd",NULL);
        perror("execlp");
        exit(1);
     }
     close(connfd);
     exit(0);/* end parent*/
  }
}
测试:编译为f1-Arrayd程序,放到一个自己PATH环境变量里的某个路径里,例如$HO
ME/bin,运行f1-Arrayd,然后telnet localhost 10013看结果。
2、第二种情况我没有环境测,所以就不给测试程序了,大家有条件的可以自己写
一个来测试一下。
    
3、测试第三种情况的程序
#include 
#include 
#include 
#include 
#include 
#define MAXLINE 100
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, "127.0.0.1", &servaddr1.sin_addr) <= 0)
{
printf("inet_pton() call error:127.0.0.1
");
exit(1);
}
if( inet_pton(AF_INET, "128.160.1.230", &servaddr2.sin_addr) <= 0)
{
printf("inet_pton() call error:128.160.1.230
");
exit(1);
}
servaddr1.sin_port=htons(port);
servaddr2.sin_port=htons(port);
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);
}
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!
");
/*put other process here*/
getchar();
exit(0);/* end */
}
4、由于第四种情况只用于UDP的多播,和TCP的使用没多大关系,所以就不写测试
例子了。自己有兴趣的可以写。
以上的程序都是在Linux下编译通过的。也可以在其他unix平台运行。
参考资料:
1、《Unix网络编程》卷一 R. Stevens
2、《Socket programming FAQ》 http://www.ibrado.com/sock-faq/

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

http://blog.sina.com.cn/s/blog_53a2ecbf010095db.html

Q: 我正在写一个unix server程序,不是daemon,经常需要在命令行上重启它,绝大多数时候工作正常,但是某些时候会报告"bind: address in use",于是重启失败。

A: Andrew Gierth
server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项。至于TIME_WAIT状态,你无法避免,那是TCP协议的一部分。

Q: 如何避免等待60秒之后才能重启服务

A: Erik Max Francis
使用setsockopt,比如
--------------------------------------------------------------------------
int option = 1;

if (setsockopt ( masterSocket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option) ) < 0)
{
die( "setsockopt" );
}
--------------------------------------------------------------------------

Q: 编写 TCP/SOCK_STREAM 服务程序时,SO_REUSEADDR到底什么意思?

A: 这个套接字选项通知内核,如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息,指明"地址已经使用中"。如果你的服务程序停止后想立即重启,而新套接字依旧使用同一端口,此时 SO_REUSEADDR 选项非常有用。必须意识到,此时任何非期望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不可能。

一个套接字由相关五元组构成,协议、本地地址、本地端口、远程地址、远程端口。SO_REUSEADDR 仅仅表示可以重用本地本地地址、本地端口,整个相关五元组还是唯一确定的。所以,重启后的服务程序有可能收到非期望数据。必须慎重使用 SO_REUSEADDR 选项。

Q: 在客户机/服务器编程中(TCP/SOCK_STREAM),如何理解TCP自动机 TIME_WAIT 状态?

A: W. Richard Stevens <1999年逝世,享年49岁>

下面我来解释一下 TIME_WAIT 状态,这些在<>中2.6节解释很清楚了。

MSL(最大分段生存期)指明TCP报文在Internet上最长生存时间,每个具体的TCP实现都必须选择一个确定的MSL值。RFC 1122建议是2分钟,但BSD传统实现采用了30秒。

TIME_WAIT 状态最大保持时间是2 * MSL,也就是1-4分钟。

IP头部有一个TTL,最大值255。尽管TTL的单位不是秒(根本和时间无关),我们仍需假设,TTL为255的TCP报文在Internet上生存时间不能超过MSL。

TCP报文在传送过程中可能因为路由故障被迫缓冲延迟、选择非最优路径等等,结果发送方TCP机制开始超时重传。前一个TCP报文可以称为"漫游TCP重复报文",后一个TCP报文可以称为"超时重传TCP重复报文",作为面向连接的可靠协议,TCP实现必须正确处理这种重复报文,因为二者可能最终都到达。

一个通常的TCP连接终止可以用图描述如下:

client server
FIN M
close -----------------> (被动关闭)
ACK M+1
<-----------------
FIN N
<----------------- close
ACK N+1
----------------->

为什么需要 TIME_WAIT 状态?

假设最终的ACK丢失,server将重发FIN,client必须维护TCP状态信息以便可以重发最终的ACK,否则会发送RST,结果server认为发生错误。TCP实现必须可靠地终止连接的两个方向(全双工关闭),client必须进入 TIME_WAIT 状态,因为client可能面临重发最终ACK的情形。

{
先调用close()的一方会进入TIME_WAIT状态
}

此外,考虑一种情况,TCP实现可能面临先后两个同样的相关五元组。如果前一个连接处在 TIME_WAIT 状态,而允许另一个拥有相同相关五元组的连接出现,可能处理TCP报文时,两个连接互相干扰。使用 SO_REUSEADDR 选项就需要考虑这种情况。

为什么 TIME_WAIT 状态需要保持 2MSL 这么长的时间?

如果 TIME_WAIT 状态保持时间不足够长(比如小于2MSL),第一个连接就正常终止了。第二个拥有相同相关五元组的连接出现,而第一个连接的重复报文到达,干扰了第二个连接。TCP实现必须防止某个连接的重复报文在连接终止后出现,所以让TIME_WAIT状态保持时间足够长(2MSL),连接相应方向上的TCP报文要么完全响应完毕,要么被丢弃。建立第二个连接的时候,不会混淆。

A: 小四

在Solaris 7下有内核参数对应 TIME_WAIT 状态保持时间

# ndd -get /dev/tcp tcp_time_wait_interval 240000
# ndd -set /dev/tcp tcp_time_wait_interval 1000

缺省设置是240000ms,也就是4分钟。如果用ndd修改这个值,最小只能设置到1000ms,也就是1秒。显然内核做了限制,需要Kernel Hacking。

# echo "tcp_param_arr/W 0t0" | adb -kw /dev/ksyms /dev/memphysmem 3b72
tcp_param_arr: 0x3e8 = 0x0
# ndd -set /dev/tcp tcp_time_wait_interval 0

我不知道这样做有什么灾难性后果,参看<>的声明。

Q: TIME_WAIT 状态保持时间为0会有什么灾难性后果?在普遍的现实应用中,好象也
就是服务器不稳定点,不见得有什么灾难性后果吧?

D: rain@bbs.whnet.edu.cn

Linux 内核源码 /usr/src/linux/include/net/tcp.h 中

#define TCP_TIMEWAIT_LEN (60*HZ)

最好不要改为0,改成1。端口分配是从上一次分配的端口号+1开始分配的,所以一般不会有什么问题。端口分配算法在tcp_ipv4.c中tcp_v4_get_port中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值