[关闭]
@linux1s1s 2019-02-15T15:07:22.000000Z 字数 3948 阅读 1809

通过TCP包分析客户端网络请求

2016-02 Java


一次http完整的请求tcp报文分析

先来普及一下TCP抓包的基本知识。

此处输入图片的描述

序号1到3 建立连接的三次握手;

序号4到6 连接建立以后Client和Server开始传输内容;

序号7到10 断开连接的4次挥手;

频繁建立和关闭connection

接下来分析一下正常的Client端请求Server的TCP报文
此处输入图片的描述

根据上面的分析,connection关闭需要发送[FIN,ACK],我们通过上面的TCP报文可知,关闭还是比较频繁的,可以猜到connection显然没有得到有效的复用,这样每个API请求都要经过3次握手建立connection,然后经过4次挥手关闭connection,这样频繁的操作显然会影响效率,不过有个好处就是http的进程数就会减少,不会盲目的持续保持connection从而导致占用大量内存。
Client端的实现大体上看一下:

  1. DefaultHttpClient httpClient = new DefaultHttpClient(httpParams);
  2. HttpGet request = new HttpGet(url);
  3. setupHeaders(request, headers);
  4. HttpResponse response = httpClient.execute(request);
  5. //...
  6. InputStream in = null;
  7. try {
  8. // in = response.getEntity().getContent();
  9. HttpEntity entity = response.getEntity();
  10. if(entity == null){
  11. /*
  12. * 服务器可能返回 HTTP/1.1 304 Not Modified。其中response body为空,response.getEntity()返回null(android 5.0+)
  13. * 或者直接抛出异常(android 4.0+ 未处理因为不需要给用户任何提示)
  14. * */
  15. return "";
  16. } else {
  17. Header locationHeader = response.getFirstHeader("Location");
  18. if (locationHeader != null) {
  19. String locationUrl = locationHeader.getValue();
  20. if(!TextUtils.isEmpty(locationUrl)){
  21. //返回重定url
  22. return locationUrl;
  23. }
  24. }
  25. in = response.getEntity().getContent();
  26. }
  27. String encoding = headers.get("Content-Encoding");
  28. if (encoding != null && encoding.contains("gzip")) {
  29. in = new GZIPInputStream(in);
  30. }
  31. int temp;
  32. byte[] bytes = new byte[8096];
  33. while ((temp = in.read(bytes)) != -1) {
  34. buffer.append(bytes, 0, temp);
  35. }
  36. } catch (SocketTimeoutException e) {
  37. throw new HttpException(HttpException.MSG_NETWORK_ERROR, e);
  38. } catch (IOException e) {
  39. throw new HttpException(HttpException.MSG_NETWORK_ERROR, e);
  40. } finally {
  41. if (in != null) {
  42. try {
  43. in.close();
  44. } catch (IOException e) {
  45. e.printStackTrace();
  46. }
  47. }
  48. closeConnection(client);
  49. }
  1. private void closeConnection(HttpClient client){
  2. if (client != null) {
  3. client.getConnectionManager().shutdown();
  4. }
  5. }

从上面的代码可以看出每次API请求都要生成HttpClient的实例,然后在获得response流后关闭connection。所以这里也印证了TCP报文分析的结果。

Connection复用

如果我们不能忍受这样频繁的操作,而不太在意contention复用而导致的占用内存问题,那么可以稍作修改如下:

将HttpClient作为一个单例处理,然后关闭connection的操作注释掉,这样根据Apache的HttpClient内部的connection pool可以实现复用connection。

这里连接保持时长默认是5s(KeepAliveTimeOut=5),平时开发中这个值应该设置多少s合适?设置的过短,会导致Apache 频繁建立连接,给Cpu造成压力,设置的过长,系统中就会堆积无用的Http连接,消耗掉大量内存,具体设置多少,可以进行不断的调节,因你的网站浏览和服务器配置而异。

扩展-开启Keep-Alive,你需要了解的基本知识

在网页开发过程中,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压力。
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注