目录
TCP协议的三次握手和四次挥手是面试中最爱问的问题之一,我将我的理解总结在这里,希望能派上用场。
三次握手
理解两次握手
首先要知道两次握手是最基本的!
第一次握手:客户端发送一个连接请求到服务端,服务端知道自己和客户端可以连成功
第二次握手:但是客户端不知道服务端是否已经接收到了它的请求,所以服务端接收到请求之后需要应答,客户端收到反馈之后,才确定自己可以连接上
只有客户端和服务端确定了自己能互相连接才会开始收发数据。
为什么会有第三次握手呢?
原因1
第三次握手是为了防止已经失效的连接请求报文突然到又传送到服务端
举个例子:
客户端发起的一个连接请求由于各种原因滞留在网络中导致了延迟,直到某个时间才到达服务端
这是一个已经失效的的报文,但是此时服务端人认为这是有效的,并且回应,建立连接
客户端当然不会发送数据,服务端却在傻等,所以会造成资源浪费
所以需要第三次握手,客户端告诉服务端连接建立好了,可以建立连接了!
这样最安全可靠,最后一次握手可能会失败
第三次握手成功服务器才认为连接成功
原因2
三次握手为保证失败连接的结构体放在客户端上(奇数就行)
为了建立及管理连接,操作系统必须消耗内存,为该连接创建数据结构存放标记它
即先描述再组织
建立连接都是客户端向服务端发起请求,如果是两次握手,那么服务端在回应请求连接时
必须要将描述结构体也就是连接建立成功!,这时如果客户端意外无法收到回应,或是无法发送数据
那么这个连接将一直建立在服务端。严重浪费服务器资源,如果是三次握手,相当于连接首先建立在客户端,不会占据服务器资源
SYN洪泛攻击
SYN攻击属于DOS (Denial of Service,拒绝服务)攻击的一种,它利用TCP协议缺陷,通过发送大量的半连接请求,耗费CPU和内存资源。
SYN攻击除了能影响主机外,还可以危害路由器、防火墙等网络系统,事实上SYN攻击并不管目标是什么系统,只要这些系统打开TCP服务就可以实施。
服务器接收到连接请求(syn=j),将此信息加入未连接队列,并发送请求包给客户(syn=k,ack=j+1),此时进入SYN_RECV状态。当服务器未收到客户端的确认包时,重发请求包,一直到超时,才将此条目从未连接队列删除。配合IP欺骗,SYN攻击能达到很好的效果
通常,客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送syn包,服务器回复确认包,并等待客户的确认,由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,目标系统运行缓慢,严重者引起网络堵塞甚至系统瘫痪
三次握手生活案例:女神和屌丝
在一个月黑风高的晚上,你走在路边,迎面走来一个妹子好像是你女神
1:于是你开心的挥手示意(syn),这是女神也发现了你,可是女神身边屌丝太多,一时之间无法反应上来是谁
2:于是微笑示意(ack),仔细一看原来是备胎啊,于是也挥了挥手(syn)
3:看到女神认出自己,你也微笑着(ack)的开始尬聊!
四次挥手
TCP是全双工的,因此,每个方向都需要单独关闭,
及一方发送FIN只是意味着不能再发送消息了【半关闭状态】,但是仍然可以接收数据,直到另一方也发送FIN
分为主动关闭方和被动关闭方,主动关闭方会进入TIME_WAIT状态,不能释放套接字资源
(标准时间4分钟),两个MSL(报文最大生存周期)(两个MSL,一来一回的时间,超过生存周期就认为传输完毕),主动关闭后需要能接收资源,需要等可能延误的所有资源全都被接收到
为什么会是4次挥手?
这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
关闭连接时,TCP是全双工的,因此,每个方向都需要单独关闭,另外,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了
所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。
四次挥手生活案例:分手
因为你和女神走夜路被女朋友看见了,女朋友吵架要分手(断开连接)
1:女:我们分手吧,渣男!(FIN)
2: 男:听我说完,给我一次解释的机会(ACK),然而这时女生我不听,我不听,,,
3: 男:那好吧,我不会缠着你的,分手就分手吧(FIN)
4: 女:拜拜!(ACK)
分手是两个人的是(全双工),所以一方必须等待另一个也结束,不然死缠烂打。
提出分手虽然很是失望懒得说话,但是肯定要等被分手的人说完(半关闭),完同意才能走(TIME_WAIT)
详细图解
理解很好理解,例子和血腥很生动,下面是许多细节方面的~
tcp连接断开检测
对接收端来说,recv返回值如果是0,就代表连接断开了
对发送端来说,send会触发Broken pipe异常,接收到SIGPIPE信号,导致出现退出
(对于大多数程序来说,连接断开了不应该退出程序,而是重新连接,所以需要对SIGPIPE信号,自定义处理方式)
服务器初始化
- 调用socket,创建文件描述符
- 调用bind,将当前文件描述符和ip/port绑定在一起,如果这个端口被进程占用了就会bind失败
- 调用listen,声明当前这个文件描述符作为一个服务器的文件描述符,为后面的accept做好准备
- 调用accept,并且阻塞,等待客户端连接过来
建立连接过程
- 调用socket,创建文件描述符
- 调用connect,向服务器发起请求
- connect会发出SYN段并阻塞等待服务器应答(第一次)
- 服务器收到客户端的SYN,会应答一个SYN+ACK表示同意建立连接(第二次)
- 客户端收到SYN+ACK之后,会从connect()返回,同时答应一个ACK段(第三次)
这个过程称为三次握手
数据传输过程
- 建立连接之后TCP协议提供全双工的通信服务,即在同一条连接中,同一时间,通信双方可以
同时写数据,(相对概念叫半双工,即在同一条连接中,同一时间,通信双方不能同时写) - 服务器从accept()返回之后,调用read(),读socket就像读管道一样了,如果没有数据就阻塞等待
- 这时客户端调用write()发送请求给服务器,收到之后从read()返回,对客户端请求进行处理
- 在此期间调用read()阻塞等待服务器应答
- 服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求
- 客户端收到后从read()返回,发送下一条请求,如此循环
断开连接过程
- 如果客户端调用close()关闭,客户端会向服务器发送FIN(第一次)
- 此时服务器收到FIN之后,会回应一个ACK,同时read()会返回0(第二次)
- read()返回之后,服务器就知道客户端关闭了连接,也会调用close()关闭连接,
这时服务器会向客户端在发送一个FIN(第三次) - 客户端收到FIN,再返回一个ACK给服务器(第四次)