@linux1s1s
2019-02-15T23:07:22.000000Z
字数 3948
阅读 2530
2016-02
Java
先来普及一下TCP抓包的基本知识。
序号1到3 建立连接的三次握手;
第一个包,113.31的主机(下边称之为客户端)给114.80的主机(下边称之为服务器)发送一个syn包请求建立连接
第二个包,服务器回复客户端syn+ack表示同意和客户端建立连接
第三个包,客户端回复服务器ack报文,表示,好,我那么我就建立连接吧
序号4到6 连接建立以后Client和Server开始传输内容;
第四个包,当连接建立成之后,客户端紧接着发给服务器一个http的head请求
第五个包,服务器回复客户端所请求的内容
第六个包,客户端回复服务器ack报文,告诉服务器你发送的我收到了
序号7到10 断开连接的4次挥手;
第七个包,客户端告诉服务器我需要的东西收到了后,我们可以关闭连接了,发送fin+ack报文
第八个包,服务器告诉客户端,我收到你的报文了,我知道该怎么做了
第九个包,服务器发送fin+ack告诉客户端关闭连接
第十个包,客户端回复ack报文确认关闭
接下来分析一下正常的Client端请求Server的TCP报文
根据上面的分析,connection关闭需要发送[FIN,ACK],我们通过上面的TCP报文可知,关闭还是比较频繁的,可以猜到connection显然没有得到有效的复用,这样每个API请求都要经过3次握手建立connection,然后经过4次挥手关闭connection,这样频繁的操作显然会影响效率,不过有个好处就是http的进程数就会减少,不会盲目的持续保持connection从而导致占用大量内存。
Client端的实现大体上看一下:
DefaultHttpClient httpClient = new DefaultHttpClient(httpParams);
HttpGet request = new HttpGet(url);
setupHeaders(request, headers);
HttpResponse response = httpClient.execute(request);
//...
InputStream in = null;
try {
// in = response.getEntity().getContent();
HttpEntity entity = response.getEntity();
if(entity == null){
/*
* 服务器可能返回 HTTP/1.1 304 Not Modified。其中response body为空,response.getEntity()返回null(android 5.0+)
* 或者直接抛出异常(android 4.0+ 未处理因为不需要给用户任何提示)
* */
return "";
} else {
Header locationHeader = response.getFirstHeader("Location");
if (locationHeader != null) {
String locationUrl = locationHeader.getValue();
if(!TextUtils.isEmpty(locationUrl)){
//返回重定url
return locationUrl;
}
}
in = response.getEntity().getContent();
}
String encoding = headers.get("Content-Encoding");
if (encoding != null && encoding.contains("gzip")) {
in = new GZIPInputStream(in);
}
int temp;
byte[] bytes = new byte[8096];
while ((temp = in.read(bytes)) != -1) {
buffer.append(bytes, 0, temp);
}
} catch (SocketTimeoutException e) {
throw new HttpException(HttpException.MSG_NETWORK_ERROR, e);
} catch (IOException e) {
throw new HttpException(HttpException.MSG_NETWORK_ERROR, e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
closeConnection(client);
}
private void closeConnection(HttpClient client){
if (client != null) {
client.getConnectionManager().shutdown();
}
}
从上面的代码可以看出每次API请求都要生成HttpClient的实例,然后在获得response流后关闭connection。所以这里也印证了TCP报文分析的结果。
如果我们不能忍受这样频繁的操作,而不太在意contention复用而导致的占用内存问题,那么可以稍作修改如下:
将HttpClient作为一个单例处理,然后关闭connection的操作注释掉,这样根据Apache的HttpClient内部的connection pool可以实现复用connection。
这里连接保持时长默认是5s(KeepAliveTimeOut=5),平时开发中这个值应该设置多少s合适?设置的过短,会导致Apache 频繁建立连接,给Cpu造成压力,设置的过长,系统中就会堆积无用的Http连接,消耗掉大量内存,具体设置多少,可以进行不断的调节,因你的网站浏览和服务器配置而异。
在网页开发过程中,Keep-Alive是HTTP协议中非常重要的一个属性。大家知道HTTP构建在TCP之上。在HTTP早期实现中,每个HTTP请求都要打开一个socket连接。这种做效率很低,因为一个Web 页面中的很多HTTP请求都指向同一个服务器。例如,很多为Web页面中的图片发起的请求都指向一个通用的图片服务器。持久连接的引入解决了多对已请求服务器导致的socket连接低效性的问题。它使浏览器可以再一个单独的连接上进行多个请求。浏览器和服务器使用Connection头ilai指出对Keep-Alive的支持。
KeepAlive选项到底有什么用处?如果你用过Mysql ,应该知道Mysql的连接属性中有一个与KeepAlive 类似的Persistent Connection,即:长连接(PConnect)。该属性打开的话,可以使一次TCP连接为同一用户的多次请求服务,提高了响应速度。
比如很多网页中图片、CSS、JS、Html都在一台Server上,当用户访问其中的Html网页时,网页中的图片、Css、Js都构成了访问请求,打开KeepAlive 属性可以有效地降低TCP握手的次数(当然浏览器对同一域下同时请求的图片数有限制,一般是2),减少httpd进程数,从而降低内存的使用(假定prefork模式)。MaxKeepAliveRequests 和KeepAliveTimeOut 两个属性在KeepAlive =On时起作用,可以控制持久连接的生存时间和最大服务请求数。
不过,上面说的只是一种情形,那就是静态网页居多的情况下,并且网页中的其他请求与网页在同一台Server上。当你的应用动态程序(比如:php )居多,用户访问时由动态程序即时生成html内容,html内容中图片素材和Css、Js等比较少或者散列在其他Server上时,KeepAlive =On反而会降低Apache 的性能。为什么呢?
当KeepAlive =On时,每次用户访问,打开一个TCP连接,Apache 都会保持该连接一段时间,以便该连接能连续为同一client服务,在KeepAliveTimeOut还没到期并且MaxKeepAliveRequests还没到阈值之前,Apache 必然要有一个httpd进程来维持该连接,httpd进程不是廉价的,他要消耗内存和CPU时间片的。
那么,什么情况下开启长连接:
- 当你的Server内存充足时,KeepAlive =On还是Off对系统性能影响不大。
- 当你的Server上静态网页(Html、图片、Css、Js)居多时,建议打开KeepAlive 。
- 当你的Server多为动态请求(因为连接数据库,对文件系统访问较多),KeepAlive 关掉,会节省一定的内存,节省的内存正好可以作为文件系统的Cache(vmstat命令中cache一列),降低I/O压力。