socket服务端关闭连接,主动关闭socket

  socket服务端关闭连接,主动关闭socket

  众所周知,socket连接的时候是三次握手,但是socket关闭连接的时候需要挥四次手。但是很多人不知道这四波的具体过程,吃了不少苦头。本文将为您做一个分析。

  在socket的编程过程中,经常会出现一些问题,有的是并发访问太多造成的,有的是代码中编程不慎造成的。例如,最常见的错误是程序中打开的文件太多。众所周知,插座连接时是三次握手,但插座关闭时需要挥四次。但是很多人不知道这四波的具体过程,吃了不少苦头。

  

CLOSE_WAIT分析

  套接字是一种全双工通信模式。套接字连接建立后,任何被连接方都可以发起关闭操作。让我们假设连接的关闭是由客户端发起的。客户端的代码如下:

  

代码片段1.1

  ret=CS_GetConnect(客户端,ipAddr,9010);

  if (ret==0) {

  printf(连接成功);

  }

  CloseSocket(客户端);

  基本上,连接建立后会立即关闭。其中CloseSocket函数是一个自定义函数,只封装了windows和linux下关闭Socket的不同实现。

  

代码片段1.2

  #如果已定义(WIN32) 已定义(WIN64)

  # define close socket(FD)do { close socket(FD);/*关机(fd,2);*/}while(0)

  #否则

  # define close socket(FD)do { close(FD);/*关机(fd,2);*/}while(0)

  #endif

  调用CloseSocket后,客户端向服务器发送FIN信号,告诉Socket程序连接已经断开。在接收到FIN信号后,服务器会将其TCP状态设置为“CLOSE_WAIT ”,并向客户端回复ACK信号。接收到该ACK信号后,客户端将处于“FIN _ Wait _ 2”状态。

  但是tcp是全双工通信协议。虽然客户端关闭了连接,但是如果服务器忽略了这个关闭动作该怎么办?对于服务器来说,这是一个不幸的消息,因为它将一直处于CLOSE _ WAIT’状态。虽然客户端不再需要与服务器通信,但是服务器的套接字连接句柄并没有被释放。如果这种情况一直发生,服务器端连接句柄会随着时间耗尽。对于启动关闭的客户端,它处于“fin _ wait _ 2”状态。如果服务器总是处于“关闭_WATI”状态,客户端就不会总是处于“fin _ wait _ 2”状态,因为这种状态有超时,可以在/etc/sysctl.conf中配置,在这个文件中配置“net.ipv4.tcp _ fin _ timeout=30 ”,保证“fin _ wait _ 2”状态最多持续30秒,过了这个时间,就进入TIME_WAIT状态(这个状态下面会讨论)。

  注意:这里,套接字的关闭是从客户端发起的,只是为了说明,套接字的关闭也可以从服务器发起。比如你写一个爬虫从互联网上的一些web服务器下载资源,有些要下载的web资源是不存在的,web服务器会立即关闭当前的socket连接。但是,你的爬虫不够健壮。如果您不处理这种情况,您的crawler客户机也将处于CLOSE_WAIT状态。

  那么如何防止SOCKET处于CLOSE_WATI状态呢?答案就在这里:

  

代码片段1.3

  while(true) {

  memset(getBuffer,0,MY _ SOCKET _ BUFFER _ SIZE);

  Ret=recv(client,getBuffer,MY_SOCKET_BUFFER_SIZE,0);

  if (Ret==0 Ret==SOCKET_ERROR)

  {

  Printf(另一个套接字已经退出,ret [%d]!\n ,Ret);

  Ret=套接字_读取_错误;//无法接收服务器端信息

  打破;

  }

  }

  clear:

  if (getBuffer!=NULL) {

  free(get buffer);

  getBuffer=NULL

  }

  closesocket(客户端);

  下面是服务器端的摘录。

  分代码,注意这个recv函数,这个函数在连接建立时,会堵塞住当前代码,等有数据接收成功后才返回,返回值为接收到的字节数;但是对于连接对方socket关闭情况,它能立即感应到,并且返回0.所以对于返回0的时候,可以跳出循环,结束当前socket处理,进行一些垃圾回收工作,注意最后一句closesocket操作是很重要的,假设没有写这句话,服务器端会一直处于CLOSE_WAIT状态。如果写了这句话,那么socket的流程就会是这样的:

  

  

TIME_WAIT分析

  服务器端调用了CloseSocket操作后,会发送一个FIN信号给客户端,客户端进入`TIME_WAIT`状态,而且将维持在这个状态一段时间,这个时间也被成为2MSL(MSL是maximum segment lifetime的缩写,意指最大分节生命周期,这是IP数据包能在互联网上生存的最长时间,超过这个时间将在互联网上消失),在这个时间段内如果客户端的发出的数据还没有被服务器端确认接收的话,可以趁这个时间等待服务端的确认消息。注意,客户端最后发出的ACK N+1消息,是一进入`TIME_WAIT`状态后就发出的,并不是在`TIME_WAIT`状态结束后发出的。如果在发送ACK N+1的时候,由于某种原因服务器端没有收到,那么服务器端会重新发送FIN N消息,这个时候如果客户端还处于`TIME_WAIT`状态的,会重新发送ACK N+1消息,否则客户端会直接发送一个RST消息,告诉服务器端socket连接已经不存在了。

  有时,我们在使用netstat命令查看web服务器端的tcp状态的时候,会发现有成千上万的连接句柄处在`TIME_WAIT`状态。web服务器的socket连接一般都是服务器端主动关闭的,当web服务器的并发访问量过大的时候,由于web服务器大多情况下是短连接,socket句柄的生命周期比较短,于是乎就出现了大量的句柄堵在`TIME_WAIT`状态,等待系统回收的情况。如果这种情况太过频繁,又由于操作系统本身的连接数就有限,势必会影响正常的socket连接的建立。在linux下对于这种情况倒是有解救措施,方法就是修改/etc/sysctl.conf文件,保证里面含有以下三行配置:

  

配置型 2.1

  

#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
net.ipv4.tcp_tw_reuse = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
net.ipv4.tcp_tw_recycle = 1
#表示系统同时保持TIME_WAIT的最大数量,如果超过这个数字,
#TIME_WAIT将立刻被清除并打印警告信息。默认为180000,改为5000。
net.ipv4.tcp_max_tw_buckets = 5000

  

  关于重用`TIME_WAIT`状态的句柄的操作,也可以在代码中设置:

  

代码片段2.1

  

int on = 1;

  if (setsockopt(socketfd/*socket句柄*/,SOL_SOCKET,SO_REUSEADDR,(char *)&on,sizeof(on)))

  {

   return ERROR_SET_REUSE_ADDR;

  }

  如果在代码中设置了关于重用的操作,程序中将使用代码中设置的选项决定重用或者不重用,/etc/sysctl.conf中`net.ipv4.tcp_tw_reuse`中的设置将不再其作用。

  当然这样设置是有悖TCP的设计标准的,因为处于`TIME_WAIT`状态的TCP连接,是有其存在的积极作用的,前面已经介绍过。假设客户端的ACK N+1信号发送失败,服务器端在1MSL时间过后会重发FIN N信号,而此时客户端重用了之前关闭的连接句柄建立了新的连接,但是此时就会收到一个FIN信号,导致自己被莫名其妙关闭。

  一般`TIME_WAIT`会维持在2MSL(linux下1MSL默认为30秒)时间,但是这个时间可以通过代码修改:

  

代码片段2.2

  

struct linger so_linger;

  so_linger.l_onoff = 1;

  so_linger.l_linger = 10;

  if (setsockopt(socketfd,SOL_SOCKET,SO_LINGER,(char *)&so_linger,sizeof(struct linger)))

  {

   return ERROR_SET_LINGER;

  }

  这里代码将`TIME_WAIT`的时间设置为10秒(在BSD系统中,将会是0.01*10s)。TCP中的`TIME_WAIT`机制使得socket程序可以优雅的关闭,如果你想你的程序更优雅,最好不要设置`TIME_WAIT`的停留时间,让老的tcp数据包在合理的时间内自生自灭。当然对于`SO_LINGER`参数,它不仅仅能够自定义`TIME_WAIT`状态的时间,还能够将TCP的四次挥手直接禁用掉,假设对于so_linger结构体变量的设置是这个样子的:

  

so_linger.l_onoff = 1;
so_linger.l_linger = 0;

  

  如果客户端的socket是这么设置的那么socket的关闭流程就直接是这个样子了:

  

  这相当于客户端直接告诉服务器端,我这边异常终止了,对于我稍后给出的所有数据包你都可以丢弃掉。服务器端如果接受到这种RST消息,会直接把对应的socket句柄回收掉。有一些socket程序不想让TCP出现`TIME_WAIT`状态,会选择直接使用RST方式关闭socket,以保证socket句柄在最短的时间内得到回收,当然前提是接受有可能被丢弃老的数据包这种情况的出现。如果socket通信的前后数据包的关联性不是很强的话,换句话说每次通信都是一个单独的事务,那么可以考虑直接发送RST信号来快速关闭连接。

  

补充

  1.文中提到的修改/etc/sysctl.conf文件的情况,修改完成之后需要运行`/sbin/sysctl -p`后才能生效。

  2.图1中发送完FIN M信号后,被动关闭端的socket程序中输入流会接收到一个EOF标示,是在C代码中处理时recv函数返回0代表对方关闭,在java代码中会在InputStream的read函数中接收到-1:

  

代码片段3.1

  

Socket client = new Socket();//,9090

   try {

   client.connect(

   new InetSocketAddress("192.168.56.101",9090));

   while(true){

   int c = client.getInputStream().read();

   if (c > 0) {

   System.out.print((char) c);

   } else {//如果对方socket关闭,read函数返回-1

   break;

   }

   try {

   Thread.currentThread().sleep(2000);

   } catch (InterruptedException e) {

   e.printStackTrace();

   }

   }

   } catch (IOException e2) {

   e2.printStackTrace();

   } finally {

   try {

   client.close();

   } catch (IOException e) {

   e.printStackTrace();

   }

   }

  }

  3.如果主动关闭方已经发起了关闭的FIN信号,被动关闭方不予理睬,依然往主动关闭方发送数据,那么主动关闭方会直接返回RST新号,连接双方的句柄就被双方的操作系统回收,如果此时双方的路由节点之前还存在未到达的数据,将会被丢弃掉。

  4.通信的过程中,socket双发中有一方的进程意外退出,则这一方将向其对应的另一方发送RST消息,所有双发建立的连接将会被回收,未接收完的消息就会被丢弃。

  5.项目的配套代码可以从这里得到http://git.oschina.net/yunnysunny/socket_close

  以上就是socket连接关闭问题分析的详细内容,更多关于socket连接关闭的资料请关注盛行IT软件开发工作室其它相关文章!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: