标签:修改 png wireshark check 显示 stat 验证 产生 36-6
TCP是什么
首先看一下OSI七层模型:
然后数据从应用层发下来,会在每一层都加上头部信息进行封装,然后再发送到数据接收端,这个基本的流程中每个数据都会经过数据的封装和解封的过程,流程如下图所示:
在OSI七层模型中,每一层的作用和对应的协议如下图所示:
说回TCP,简单说TCP(Transmission Control Protocol)即传输控制协议,是一种面向连接的、可靠的、基于ip的传输层协议。
TCP协议头部格式
要学习TCP协议,首先得知道TCP协议头部的格式,我在网上找了一张觉得画得比较好的TCP协议头部格式的图片:
这张图把TCP协议头部格式的每部分都描述得比较清楚:
从图上我们可以看到,TCP头部的固定大小为20个字节,不过由于有可选字段,实际上TCP头部的大小有可能超过20字节。
TCP三次握手
TCP三次握手是TCP一个比较重点的内容,来学习一下。
TCP三次握手其实就是TCP连接建立的过程,三次握手的目的是同步连接双方的序列号和确认号并交换TCP窗口大小信息。下面是TCP三次握手的流程图:
画得很清晰,可惜不是我画的。整个流程为:
为什么在第3步中客户端还要再进行一次确认呢?这主要是为了防止已经失效的连接请求报文段突然又传回到服务端而产生错误的场景:
所谓"已失效的连接请求报文段"是这样产生的。正常来说,客户端发出连接请求,但因为连接请求报文丢失而未收到确认。于是客户端再次发出一次连接请求,后来收到了确认,建立了连接。数据传输完毕后,释放了连接,客户端一共发送了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,没有"已失效的连接请求报文段"。 现在假定一种异常情况,即客户端发出的第一个连接请求报文段并没有丢失,只是在某些网络节点长时间滞留了,以至于延误到连接释放以后的某个时间点才到达服务端。本来这个连接请求已经失效了,但是服务端收到此失效的连接请求报文段后,就误认为这是客户端又发出了一次新的连接请求。于是服务端又向客户端发出请求报文段,同意建立连接。假定不采用三次握手,那么只要服务端发出确认,连接就建立了。
由于现在客户端并没有发出连接建立的请求,因此不会理会服务端的确认,也不会向服务端发送数据,但是服务端却以为新的传输连接已经建立了,并一直等待客户端发来数据,这样服务端的许多资源就这样白白浪费了。
采用三次握手的办法可以防止上述现象的发生。比如在上述的场景下,客户端不向服务端的发出确认请求,服务端由于收不到确认,就知道客户端并没有要求建立连接。
TCP四次握手
TCP三次握手是TCP连接建立的过程,TCP四次握手则是TCP连接释放的过程。下面是TCP四次握手的流程图:
当客户端没有数据再需要发送给服务端时,就需要释放客户端的连接,这整个过程为:
这里的一个问题是,为什么TCP连接的建立只需要三次握手而TCP连接的释放需要四次握手呢:
因为服务端在LISTEN状态下,收到建立请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而连接关闭时,当收到对方的FIN报文时,仅仅表示对方没有需要发送的数据了,但是还能接收数据,己方未必数据已经全部发送给对方了,所以己方可以立即关闭,也可以将应该发送的数据全部发送完毕后再发送FIN报文给客户端来表示同意现在关闭连接。 从这个角度而言,服务端的ACK和FIN一般都会分开发送。
使用Wireshark抓包验证TCP三次握手过程
为了加深对TCP三次握手的理解,抓包看一下TCP三次握手的过程。我这里访问的是我们公司自己的网站,不打广告,访问的具体什么页面、哪个ip就不透露了。
抓包下来的内容为:
这里多说一句,由于wireshark抓包针对的是网卡,因此只要某张网卡上有网络访问,就会有数据包,这会导致Wireshark的抓包结果里面会有大量数据包,而大多数都不是想要的,这种情况可以使用Wireshark的过滤规则。我这里由于知道目标ip,因此使用的是"ip.src == xxx.xxx.xxx.xxx or ip.dst == xxx.xxx.xxx.xxx"这条规则只过滤特定的ip。
从抓包结果看来,整个过程符合TCP三次握手的预期:
至于Sequence Number和Acknowledge Number就不看了,但是注意,前面说了Sequence Number是随机产生的一个值,但是这里确是0,不光这里是0,抓其他的任何包这个值都是0。但其实这里并不是真的0,而是Wireshark为了显示更好阅读,使用了relative sequence number相对序号,Sequence Number具体值我们也是可以看到的:
第一个红框就是上面说的relative sequence number,第二个红框就是Sequence Number的真实值0xc978aa7e,转换为十进制为3380128382,就是随机产生的Sequence Number。
顺便能看到,下一个数据包就是HTTP的数据包,因为TCP三次握手已完成,连接建立,正式传输应用层数据,传输的HTTP内容大小为704字节。
TCP的backlog
在学习TCP的时候发现的一个比较重要的知识点。
在TCP连接建立的过程中有如下的流程和队列:
如图所示,这里面有两个队列,分别为syns queue(半连接队列)与accept queue(全连接队列)。整个流程总结用文字如下:
backlog的定义是已连接但未进行accept处理的socket队列大小,如果这个队列满了,将会发送一个ECONNREFUSED错误信息给到客户端,即 linux 头文件 /usr/include/asm-generic/errno.h中定义的“Connection refused”。
Java支持原生的Socket,我们可以写一段代码来验证一下。首先是一个普通的客户端Socket,模拟向本地的8888端口发起连接:
1 public class ClientSocketClass { 2 3 private static Socket[] clients = new Socket[30]; 4 5 public static void main(String[] args) throws Exception { 6 for (int i = 0; i < 10; i++) { 7 clients[i] = new Socket("127.0.0.1", 8888); 8 System.out.println("Client:" + i); 9 } 10 } 11 12 }
接着是服务端Socket,监听8888端口,ServerSocket构造函数的第二个参数就是backlog的大小,如果backlog小于1或者不传会给一个默认值50,代码很简单:
1 public class ServerSocketClass { 2 3 public static void main(String[] args) throws Exception { 4 ServerSocket server = new ServerSocket(8888, 5); 5 6 while (true) { 7 // server.accept(); 8 } 9 } 10 11 }
先把注释关闭,运行ServerSocketClass,先发起监听,再运行ClientSocketClass,运行结果为:
1 Client:0 2 Client:1 3 Client:2 4 Client:3 5 Client:4 6 Exception in thread "main" java.net.ConnectException: Connection refused: connect 7 at java.net.DualStackPlainSocketImpl.connect0(Native Method) 8 at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79) 9 at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339) 10 at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200) 11 at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182) 12 at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) 13 at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) 14 at java.net.Socket.connect(Socket.java:579) 15 at java.net.Socket.connect(Socket.java:528) 16 at java.net.Socket.<init>(Socket.java:425) 17 at java.net.Socket.<init>(Socket.java:208) 18 at org.xrq.test.socket.ClientSocketClass.main(ClientSocketClass.java:11)
看到Client只发起了五个请求,第六个请求发起被拒绝了,因为三次握手建立后,前五个请求占据了全连接队列并没有被处理,于是第六个请求进来,全连接队列中没有它的位置了,因此请求被拒绝。
如果注释打开,又是不一样的效果:
1 Client:0 2 Client:1 3 Client:2 4 Client:3 5 Client:4 6 Client:5 7 Client:6 8 Client:7 9 Client:8 10 Client:9
这里所有的十个客户端请求全部被接受,因为accept()方法从全连接队列中取出了连接请求进行处理。看得出来,backlog提供了容量限制功能,避免过多的客户端Socket占据大量的服务端资源。
全连接队列大小的问题
接着说说全连接队列大小的问题。首先上面提到了backlog,不同的应用对backlog的默认值定义不同,比如:
Tomcat可以通过server.xml配置文件中<Connector />节点中的acceptCount来修改backlog。如果请求量不是很大,使用Tomcat默认的100也可以,但如果访问量比较大,建议这个值设置得大一些,比如1024或者更大。如果Tomcat前一层对SYC FLOOD攻击的防御没有把握的话,最好将SYN COOKIE防御也开启。
但是,全连接队列的大小未必是backlog的值,它是backlog与somaxconn(一个os级别的系统参数)的较小值。Linux环境下可以通过执行"cat /proc/sys/net/core/somaxconn"来查看:
这个值系统默认的是128,假如传入的backlog是10,取128和10的较小值,那么最终的全连接队列大小就是10。同样,如果要修改Linux系统默认的全连接队列大小的话,可以通过修改/proc/sys/net/core路径下的somaxconn。
半连接队列大小的问题
说完了全连接队列大小的问题,接着说一下半连接队列大小的问题,它是64与tcp_max_syn_backlog的较大值。
可以通过"cat /proc/sys/net/ipv4/tcp_max_syn_backlog"命令或者"cat /etc/sysctl.conf"命令来查看半连接队列的大小。以后者为例,其实就是打开了/ect/sysctl.conf这个文件:
标红的即tcp_max_syn_backlog默认值,默认值为1024,可以通过修改这个值来修改系统默认的半连接队列大小。
通过ss查看Socket统计状态
前面说了这么多全连接队列,那么如何查看全连接队列大小?
在Linux环境下可以通过ss命令查看,ss命令全称为Socket Statistics,顾名思义它用于统计Socket。netstat命令其实也可以显示类似内容,但是ss命令相比netstat命令能够显示更多更详细的有关TCP和连接状态的信息,而且比netstat更快速更高效。
ss命令的参数就不列举了,可以自己上网查看,这里使用ss -lnt,即查看处于LISTEN状态的TCP套接字,且不解析服务名称:
Send-Q表示当前端口的全连接队列大小,Recv-Q表示全连接队列当前使用了多少。
从Send-Q可以看到,它的值只有三种:128、50、1。这也印证了我们的结论,全连接队列的大小为传入的backlog与somaxconn的较小值。
参考文章
http://blog.csdn.net/oney139/article/details/8103223
http://www.jellythink.com/archives/705
http://jm.taobao.org/2017/05/25/525-1/
https://www.cnxct.com/something-about-phpfpm-s-backlog/
http://jaseywang.me/2014/07/20/tcp-queue-的一些问题/
标签:修改 png wireshark check 显示 stat 验证 产生 36-6
原文地址:http://www.cnblogs.com/Leo_wl/p/6931243.html