TCP是面向连接的协议,所以使用TCP的时候要先建立连接,建立连接是通过三次握手来实现的。
三次握手的目标
同步 Sequence 序列号
- 初始序列号 ISN(Initial Sequence Number)
服务器端和客户端之间的所使用的seq都是不同的,所以在握手当中的三次都要分别交换这个isn。
交换 TCP 通讯参数
- 如 MSS(最大报文段)、窗口比例因子(想扩大滑动窗口的时候)、选择性确认(更有效的重传丢失的报文)、指定校验和算法
三次握手
client首先将它的ins发给server,server收到syn之后,它要回一个ack,server也要发自己的ins让客户端知道我的序列号在哪,当然客户端也要回一个ack。
第二步可以将syn/ack放在一个报文当中,一次性的发给client。
上面就是三次握手,三次握手当中使用了syn,就是同步的意思,也使用了ack确认。
-c只抓取3个包,因为3个包正好是前面的3次握手,-S用绝对序列号替换相对序列号。
这里client传输了它的isn,seq 1676510846,给到了server,server会回加1,ack 1676510847,client收到这个报文之后就知道server确认了,同时server也发送了自己的isn,seq 715862251,client通过最后一个ack通过对seq 715862251确认+1,那么就是3次握手。
这个isn seq是随机的,每次都是不同的。
三次握手(1):SYN 报文
syn要在标记位的第七位置为1,那么就代表这个一个syn报文。在seq当中填入我们的序列号码,也就是我们的isn。
三次握手(2): SYN/ACK 报文
在第二个报文当中其实就是ack+syn,这里面重点关注4个地方。
序列号,也就是server中使用的isn,确认号码就是我收到client的isn+1,为了让确认号码有效,我要在flag当中第四位ACK置为1,第七位SYN置为1.
三次握手(3): ACK 报文
最后一个ACK报文在ACK位置置为1,让确认号码有效。
TCP的连接建立
应用程序在通信的时候,也即是进程在通信的时候,浏览器访问web站点,HTTP协议要想发送的话,我这个计算机要先去调用TCP协议,先建立连接,发送tcp建立连接的请求,服务端给我一个确认,我再给服务器一个确认。需要发三个数据包。
建立了TCP连接之后,再发送http的报文,web站点再用这个连接将网页返回给我的客户端。
当所有的通信都结束了,那么浏览器还要释放连接。
tcp在通信之前需要发送3个数据包来建立连接,释放连接需要发送4个数据包。
之前说的可靠传输,流量控制,拥塞避免,这些都是在通信过程当中用到的技术。
建立连接要3次握手,两次握手行不行呢?
客户端要访问服务器的话,客户端向服务端发起建立tcp连接的请求,请求的时候带着一些参数,接受窗口是多少,最大报文段是多少,是不是支持选择性确认,在建立连接请求的时候就带着这些信息。
web服务器收到之后给它一个确认,我的接受窗口是多少,我的报文段最大支持多少,是不是支持选择性确认,将这些参数告诉客户端。
这一次请求,一次确认不就ok了吗?为啥还要求客户端再给服务器再发一个确认。
这里考虑了一种特殊的情况,客户端发送了一个请求,这个请求走的路比较远,这是第一个请求,客户端等了一下发送的请求没有响应,有没有收到确认?于是再发送了一个,这是第二个请求,第二个请求比第一个请求先到,第一个还没到第二个就先到了。
然后web服务器给第二个发了一个确认,然后第一个才到,客户端认为第一个请求作废了,因为没有响应才发送了第二个。
然后web服务器收到了第一个请求之后,它给这个请求发送一个确认,结果客户端一看这是无效的确认,第一个早失效了你给我发确认有啥用?于是就不搭理它了,但是web服务器不知道这个事情,它认为客户端发送了两个建立连接的请求,web服务器这边就会消耗资源。
如果tcp规定了三次握手,web服务器给你发送了确认之后,在一个规定的时间里面没有等到你这个确认,那么web服务器就将这个释放了。
3次握手是为了避免web服务器一直等着客户端的连接,但是客户端不搭理它,为了避免这种情况需要3次握手。
为什么两次握手不可以呢?
为了防止已经失效的连接请求报文段突然又传送到了 B,因而产生错误。比如下面这种情况:A 发出的第一个连接请求报文段并没有丢失,而是在网路结点长时间滞留了,以致于延误到连接释放以后的某个时间段才到达 B。本来这是一个早已失效的报文段。但是 B 收到此失效的链接请求报文段后,就误认为 A 又发出一次新的连接请求。于是就向 A 发出确认报文段,同意建立连接。
对于上面这种情况,如果不进行第三次握手,B 发出确认后就认为新的运输连接已经建立了,并一直等待 A 发来数据。B 的许多资源就这样白白浪费了。
如果采用了三次握手,由于 A 实际上并没有发出建立连接请求,所以不会理睬 B 的确认,也不会向 B 发送数据。B 由于收不到确认,就知道 A 并没有要求建立连接。
TCP的连接建立
客户端和服务端建立TCP连接的时候,这样的数据包是不带数据的,然后它还有个特点,SYN叫做同步标记位,syn这个标记位为1,ack为确认,确认为0说明,说明tcp首部确认的字段是无效的。
确认通常就是告诉对方收到了第多少个数据包,你该发多少个数据包,因为客户端从来没有收到服务器这边的数据包,所以这个确认号就没有意义,所以这个标记位为0。
参数可以看到最大传输报文段mss 1460。
允许选择性确认。
接受窗口是64240。
上面tcp建立请求包含了上面这些信息,接受窗口是多大,最大报文是多大,是不是支持选择性确认。
上面是首部+选项部分,没有数据部分。
上面是建立连接的请求。
建立TCP连接确认
ack syn标记位为1,说明ack确认标记位起作用了,说明确认字段有意义,可以看到序号为0,确认号为1,告诉客户端1以前的字节都收到了。
下面是服务器的参数。
确认的确认
这是第三个包,ack标志位为1,syn标记位为0了,也就是说只有前面两个数据包请求和确认的syn标记位才为1。
TCP三次握手
来看看客户端和服务端状态。
一开始客户端这里tcp连接是close的,发一个建立tcp连接的请求,syn标记位为1,ack标记位为0,序号是客户端随机给的。
服务器收到之后是listen状态,监听客户端的请求,之后变为syn received状态,之后给客户端发送确认,标记位都为1,y是服务器设定的一个序号,比较大的随机数,确认号为x+1,告诉客户端x收到了,你该发x+1个字节了。
客户端收到确认之后状态为establish建立连接。
之后客户端再给它发确认的确认,ACK大写的代表标记位,小写的代表序号和确认号。
当执行三次握手的时候,客户端和服务器关于这条连接的状态都在发生变迁,当理解了tcp状态是如何变迁的时候,就能够更有效的定位一些复杂的网络问题。
下面是三次握手当中所涉及的5种状态,
最初大家都是close的状态,其中服务器需要监听80 443等端口,所以处于listen状态,在三次握手完成之后,大家都需要进入establish的状态,对于状态怎么查看呢,使用netstat工具来查看。
可以看到有listen的状态,有establish的状态,在介绍4次握手关闭的时候,往往会看到大量time_wait状态。
回过头来看三次握手的状态变迁,首先客户端发起了syn,客户端立刻就会进入syn-sent的状态,在netstat当中很难看到这个状态,因为当我们收到syn+ack之后我们就会从syn-sent进入到establish状态了,这个过程可能非常当能只有几百毫秒,所以很难观察到这样一个状态。
当服务器收到syn之后,它会进入syn-received的状态,这个状态会一直延续下去,直到客户端返回ack,那么也会进入establish状态。
从syn-send和syn-received可以很清楚的看到,就是看这个syn有没有发,还是说我收到了,以此来命名的。
有些攻击者就会不使用操作系统内核提供的tcp栈,而是自己简单的构造出来syn的帧,发给了server之后,它却不发ack,使得大量的连接都处于syn-received的状态,以至于占用了server太多的资源,使得正常的连接无法正常的建立,这就是syn的一种攻击方式。
两端同时发送SYN:双方使用固定源端口且同时建连接
TCB: Transmission Control Block,保存连接使用的源端口、目的端口、目的 ip、序号、应答序号、对方窗口大小、己方窗口大小、tcp 状态、tcp 输入/输出队列、应用层输出队列、tcp 的重传有关变量等(保存连接当中使用的各种状态信息,比如我的端口和对方的端口,等)