TCP 连接与TCP keep alive 保活检测机制

生产环境中一台2核4G的linux服务器TCP连接数时常保持在5-7w间徘徊,查看日志每秒的请求数也就100-200,怎么会产生这么大的TCP连接数。检查了下客户端上行的HTTP协议,Connection 头字段是Keep-Alive,并且客户端在请求完之后没有立即关闭连接。而服务端的设计也是根据客户端来的,客户端上行如果Connection:Keep-Alive,服务端是不会主动关闭连接的。在客户端与服务端交互比较频繁的时候,这样的设计还是比较合理的,可以减少TCP的重复握手。显然如果只交互一次,就没有这个必要了。我们的生产环境就属于这种情况。客户端在请求响应完之后就得立即释放连接,上代码:

  public static void send(request req){
	  InputStream input=null;
	  ByteArrayOutputStream out=null;
	  HttpClient client=new DefaultHttpClient();
	  try{
		  HttpPost post=new HttpPost("http://ip:port");
		  post.setHeader("User-Agent", "Mozilla/5.0 (Linux; U; Android 2.2.1; en-us; Nexus One Build/FRG83) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1");
		  post.setHeader("Expect", "100-continue");
		  post.setHeader("Client_IP", "ip");
		  post.addHeader("Connection","close");

		  HttpEntity entity=new ByteArrayEntity(encoder.encodeXip(req));
		  post.setEntity(entity);
		  HttpResponse resp=client.execute(post);
		  HttpEntity content = resp.getEntity();
		  input=content.getContent();
		  out=new ByteArrayOutputStream();
		  byte by[]=new byte[1024];
		  int len=0;
		  while((len=input.read(by))!=-1){
			  out.write(by, 0, len);
		  }
	     System.out.println(new String(out.toByteArray()));

	  }catch(Exception e){
		  e.printStackTrace();
		  return null;
	  }finally{
		  try {
			out.close();
			input.close();
			client.getConnectionManager().shutdown();//主动释放连接
		} catch (IOException e) {
			e.printStackTrace();
		}
	  }
  }

解决方法好像很简单。但现实环境是客户端有很多版本,并且已经运行在客户手中了,改客户端来减少服务器TCP连接数,是一件比较耗时间的活。没有办法只能从服务端自身解决,解决方法只能从服务端主动关闭TCP连接。

一开始不要着急从程序层面去看问题,从操作系统角度看能否解决问题,简单为linux内核增加了一些keep-alive的参数,发现也能降低一些TCP连接数。操作很简单,往 /etc/sysctl.conf 配置文件里增加如下参数:

net.ipv4.tcp_keepalive_intvl = 3
net.ipv4.tcp_keepalive_probes = 2
net.ipv4.tcp_keepalive_time = 6

最后 sysctl -p 使之生效。

上面参数的意思是说,客户端与服务端空闲时间达到6秒之后,服务端每隔3秒检测下客户端存活情况,一共检测两次,如果在6+3*2=12之内客户端进程退出了,服务端就会主动关闭该连接。服务端检测客户端存活是通过发送基于TCP的keep alive报文,客户端进程如果没有退出,就会发送确认keep alive的响应报文。如图wireshark中报文:

刚调整参数的那几天没有注意,后来才发现这样的操作会带来带宽的严重浪费,以前只要1M左右的响应带宽,现在要5M左右,都是成本啊,虽然是公司出钱。

因为我们的客户端存活时间比较长,TCP的keep alive保活机制能回收的TCP连接数是比较有限的,但是每隔6秒的报文却能让服务器带宽翻上好几倍,得不偿失啊。

没办法只能从程序设计方面在想想办法,调整服务端的设计,原先服务端不主动去关闭连接,而是根据客户端上行Connection的状态决定是否关闭,新的设计方案就是服务端可以配置连接的状态,如果服务端配置了Connection:close,服务端优先采用配置信息,如果没有配置,则还是由客户端上行来决定连接状态。

服务端是java写的,基于netty的通信框架。netty里keep alive相关的参数只有一个选项配置,相关代码如下:

    this.bootstrap.setPipelineFactory(new ChannelPipelineFactory()
    {
      public ChannelPipeline getPipeline()
        throws Exception
      {
        ChannelPipeline pipeline = new DefaultChannelPipeline();

        pipeline.addLast("codec", new HttpServerCodec());
        pipeline.addLast("aggregator", new HttpChunkAggregator(maxContentLength));
        pipeline.addLast("handler", new HttpRequestHandler());
        return pipeline;
      }
    });
    this.bootstrap.setOption("allIdleTime", Integer.valueOf(this.idleTime));
    this.bootstrap.setOption("child.keepAlive", Boolean.valueOf(true));
    this.bootstrap.setOption("child.tcpNoDelay", Boolean.valueOf(true));
    this.bootstrap.setOption("child.soLinger", Integer.valueOf(-1));
    this.bootstrap.setOption("child.sendBufferSize", Integer.valueOf(-1));

this.bootstrap.setOption("child.keepAlive", Boolean.valueOf(true));  这行代码只配置是否启用TCP保活检测,如果启用了,多久检测一次还是取决于操作系统本身。说白了,还是跟编辑  sysctl.conf文件的效果一样,因为它们都是从TCP层的检测。netty从应用层主动关闭连接的话,可以简单增加一个监听器,代码如下:

    ChannelFuture future = channel.write(response);
    if ((!HttpHeaders.isKeepAlive(response)) || (!response.containsHeader("Content-Length"))) {
      future.addListener(ChannelFutureListener.CLOSE);
    }

当channel write操作完成时,CLOSE监听器主动close掉channel。

问题基本解决,后来发现其实也可以通过增加一层nginx代理解决问题,nginx通过短连接与后端进行交互,与前端保持长连接,不过不是很清楚nginx长连接的检测机制,但根据生产环境表现出的现象,肯定不是用操作系统参数。

TCP保活机制可以参数这篇文章 http://www.blogjava.net/yongboy/archive/2015/04/14/424413.html

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-25 21:34:38

TCP 连接与TCP keep alive 保活检测机制的相关文章

TCP三次握手及TCP连接状态 TCP报文首部格式

建立TCP连接时的TCP三次握手和断开TCP连接时的4次挥手整体过程如下图: 开个玩笑 ACK: TCP协议规定,只有ACK=1时有效,连接建立后所有发送的报文ACK必须为1 SYN(SYNchronization同步):在连接建立用来同步序号.当SYN=1而ACK=0时,表明这是一个连接请求报文.对方若同意建立连接,则应在响应报文中使用SYN=1 ACK=1因此,SYN置1表示这是一个连接请求或连接接受报文 FIN(FINIS)即完,终结的意思,用来释放一个连接.当FIN=1时,表明此报文段发

TCP连接的建立和终止。

为帮助大家理解connect,accept和close函数并使用netstat 调试TCP应用程序,我们必须了解如何建立和终止TCP连接以及TCP的状态转换图.这是一个通过加深了解底层网络协议以帮助我们编写网络程序的例子. 模拟三路握手: 下述步骤建立一个TCP连接 1.服务器必须准备好接受外来的连接.通过调用socket,bind 和 listen函数来完成,称为被动打开(passive open). 2.客户通过调用connect进行主动打开(active open).这引起客户TCP发送一

TCP 连接的建立和终止

三路握手 建立一个TCP连接时会发生下述情形. (1)服务器必须准备好接受外来的连接.这通常通过调用socket.bind和listen这3个函数来完成的,我们称之为被动打开. (2)客户通过调用connect发起主动打开.这导致客户TCP发送一个SYN(同步)分节,它告诉服务器客户将在(待建立的)连接中发送的数据的初始序列号.通常SYN分节不携带数据,其所在IP数据报只含有一个IP首部.一个TCP首部及可能有的TCP选项. (3)服务器必须确认(ACK)客户的SYN,同时自己也得发送一个SYN

TCP连接状态变化

文章转自:TCP连接状态变化 TCP连接状态变化 参考文章:TCP连接的状态详解以及故障排查 TCP建立连接--三次握手 CLOSED:起始状态,无任何连接. LISTEN:服务端建立socket之后需要listen进入LISTEN(侦听)模式,侦听来自远方的TCP连接请求. SYN_SENT:客户端建立socket之后需要connect服务器,向服务端发送SYN=j(随机数)申请连接,然后会进入SYN_SENT状态. SYN_RCVD:服务端在** 侦听模式 **下收到SYN后会向客户端回应A

(转)iOS 各种网络编程总结--进程、线程、Socket、HTTP、TCP/IP、TCP和UDP

######################################################### 进程与线程 进程和线程都是由操作系统分配和调度的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性. 进程是一块包含了某些资源的内存区域.操作系统利用进程把它的工作划分为一些功能单元.进程中所包含的一个或多个执行单元称为线程(thread).进程还拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更

TCP连接探测中的Keepalive和心跳包. 关键字: tcp keepalive, 心跳, 保活

1. TCP保活的必要性 1) 很多防火墙等对于空闲socket自动关闭 2) 对于非正常断开, 服务器并不能检测到. 为了回收资源, 必须提供一种检测机制. 2. 导致TCP断连的因素 如果网络正常, socket也通过close操作来进行优雅的关闭, 那么一切完美. 可是有很多情况, 比如网线故障, 客户端一侧突然断电或者崩溃等等, 这些情况server并不能正常检测到连接的断开. 3. 保活的两种方式: 1) 应用层面的心跳机制 自定义心跳消息头. 一般客户端主动发送, 服务器接收后进行回

(转)TCP连接异常断开检测

TCP是一种面向连接的协议,连接的建立和断开需要通过收发相应的分节来实现.某些时候,由于网络的故障或是一方主机的突然崩溃而另一方无法检测到,以致始终保持着不存在的连接.下面介绍一种方法来检测这种异常断开的情况 TAG: TCP连接异常断开  TCP断链 TCP是一种面向连接的协议,连接的建立和断开需要通过收发相应的分节来实现.某些时候,由于网络的故障或是一方主机的突然崩溃而另一方无法检测到,以致始终保持着不存在的连接.下面介绍一种方法来检测这种异常断开的情况 1) 在TCP协议中提供了KEEPA

TCP连接检测机制

采用TCP连接的C/S模式软件,连接的双方在连接空闲状态时,如果任意一方意外崩溃.当机.网线断开或路由器故障,另一方无法得知TCP连接已经失效,除非继续在此连接上发送数据导致错误返回.很多时候,这不是我们需要的.我们希望服务器端和客户端都能及时有效地检测到连接失效,然后优雅地完成一些清理工作并把错误报告给用户. 客户端采用如下步骤: 1, 连接 2, 拔掉网线 经过以上两步: 从上图中可以看到,此时服务端的连接依然存在. 所以,tcp只是数据的发送与接收,包括握手,断开以及rst,time_wa

如何在socket编程的Tcp连接中实现心跳协议

心跳包的发送,通常有两种技术 方法1:应用层自己实现的心跳包 由应用程序自己发送心跳包来检测连接是否正常,大致的方法是:服务器在一个 Timer事件中定时 向客户端发送一个短小精悍的数据包,然后启动一个低级别的线程,在该线程中不断检测客户端的回应, 如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线:同样,如果客户端在一定时间内没 有收到服务器的心跳包,则认为连接不可用. 方法2:TCP的KeepAlive保活机制 因为要考虑到一个服务器通常会连接多个客户端,因此由用户在应用层自己实现心