TCP是面向连接的协议,在通信之前需要先建立连接,其本质就是打开一个socket文件,这个文件有自己的缓冲区,如果要发送数据,上层把数据拷贝到发送缓冲区;如果是接收数据,OS直接把来自网络的数据拷贝到接收缓冲区里。
那么三次握手期间,Server和Client都做了哪些工作?以及为什么要有三次?不可以是一次?两次?四次?
目录
一、握手之前的准备工作
1、Server端
2、Client端
二、TCP三次握手
1、第一次握手
2、第二次握手
3、第三次握手
三、为什么需要三次握手?
1、原因一:三次是确认对方主机状态及收发能力的最小次数
2、原因二:降低被攻击的风险的最小次数
(1) 如果只有一次握手
(2) 如果只有两次握手
(3)如果是三次握手
三次握手开始的时刻就是,Client端调用connect函数连接 Server端,此时需要有个前提条件就是Server端的套接字要处在监听状态。
第一步,调用Socket函数创建通信套接字。其本质就是打开用于通信的socket文件;
第二步,绑定服务器IP地址和端口号。其实就是将打开的socket文件与存放地址信息的sockaddr_in 结构体绑定,方便对方能够找到这个文件
第三步,调用listen函数将套接字设为监听状态。可以理解为设置文件的读写权限,即允许other读写;
站在客户端的角度其实是认为服务端是时刻准备好的,不然也不会主动发起连接。所以Client端只要调用Socket函数创建通信套接字,即打开用于通信的socket文件,随后就可以调用connect函数连接服务端了。
注意:作为主动连接别人的一方,必须要知道你要连谁,即对方的IP地址和端口号;但是你自己无需主动绑定IP地址和监听,因为客户端一般不会被其他主机连接。就好比你是送快递的,你要知道客户的住址;但是客户没必要知道你的住址。
客户端主动调用 connect函数的时候,就是发起第一次握手,表明Client端请求和Server端建立连接。发送的报文携带SYN标志位。
客户端状态 : SYN_SENT(连接请求已发送)
Server端收到Client 端的请求以后,理解了客户端的意图,同意建立连接(Client ==》Server方向上的),至此一个方向上的连接就建立好了。Server也发送SYN请求顺带应答,希望建立连接(Server ==》Client方向上的)。
服务端状态:SYN_REVD(接收到连接请求)
注意:服务端发送ACK之后才会有状态变化,在发送之前需要判断对方是否为恶意连接,如果是恶意连接,服务端不会接受连接的。这也是在第一次握手之后服务端没有直接建立连接的原因之一。详细原因在“为什么需要三次握手”说明。
Client端收到Server端的确认应答,并给Server端发送ACK同意建立连接(Server ==》Client方向上的)。给Server端发送ACK以后,客户端就认为连接已经建立成功了。
客户端状态:ESTABLISHED(建立连接)
Server端如果收到Client的应答,就说明Client 也同意建立连接,至此 Server ==》Client方向上的连接也就建立好了。服务端也认为连接建立成功了;如果迟迟没有收到,Server端认为客户端不希望与自己建立 Server ==》Client方向上的连接,此时会连接失败。
服务端状态:ESTABLISHED(建立连接)/ CLOSED(连接失败)
注意:从这个角度上看,TCP建立连接并非百分之百成功,因为最后一次ACK是没有应答的,Client也没法确认Server是否收到了ACK,即便没收到,由于最后一次ACK没有应答,也无法触发超时重传机制。
第三次握手需要多说一句,服务端收到ACK以后,此时服务端的状态变为 ESTABLISHED,但并不代表上层会立马会调用accept并返回一个文件描述符。传输层收到连接,然后接受是传输层的事;上层此时可能忙于处理其他事情,没时间调用accept。
因此,实际情况是,服务端收到了ACK,服务端状态变为ESTABLISHED,此时会进入一个名为“全连接”的队列,专门存放连接状态为ESTABLISHED的连接,等上层调用accept函数的时候,就会从全连接队列中取走连接,并返回。
那么问题来了,为什么需要三次握手呢?为什么不可以是一次?两次?四次?五次?原因大致有两个,你可以选择你能接受的理由。我个人比较喜欢第二个理由。
Client给Server发,如果能收到Server的ACK,说明Server的主机状态正常,Server的接受能力也正常;
Server给Client发,如果能收到Client的ACK,说明Client的主机状态正常,Client的接受能力也正常。
相反,如果有任意一方即便是经过超时重传也没收到ACK,说明发送ACK的一方可能主机异常或者不具备接受能力。这说明为了确认双方的状态至少需要三次握手,握手次数太多只会增加连接成本。
连接建立成功的时候,服务端需要创建一个新的文件结构体来维护当前连接,这个文件结构体是需要占用服务端资源的!
服务端可能会收到几万个主机的连接请求,每台主机通过for循环发送几十个连接请求,因为只握手一次,服务端直接照单全收,每次都创建一个新的文件结构体,这样的话,服务端的资源很快就会被耗尽。一个新手小白就可以搞垮一台服务器,很显然一次握手不行。
只有两次握手也是一样,服务端收到一次连接,只要服务端发出一次ACK,就说明连接就建立起来了。如果服务端接收到一百万次连接,逐一发送ACK后,服务器依然要创建对应的结构体,此时依然要消耗服务器的资源。
如果一个客户端一次发送大量的SYN,服务端当然会回复相应的ACK+SYN,但是此时服务端不会创建新的结构体,因为连接并没有建立成功。要想成功建立连接,客户端需要发送大量的ACK,一旦客户端发送一个ACK,客户端这边认为连接建立成功,就需要消耗大量资源来维护这个连接。
万一客户端就头铁呢?服务端也不会这么傻,服务器如果发现上百条连接都是来自同一个IP,那么在应用层层面可以把这个IP地址加入黑名单,下次连接的时候,可以拒绝该主机的连接。这样的话,如果是小白想通过某种工具一次发大量SYN的话,第一次可能没问题,第二次就直接被拉黑了。
补充:剩下的就是一些黑客会给其他用户的主机注入木马病毒,劫持其他用户的主机定时给服务器发送连接,有可能会有上百台被劫持的主机同时在发。这种情况就无法应对,因为每台主机是在正常发连接请求,服务端没法拒绝。
如有错误,希望能及时指正,谢谢。