前言
- Socket 是什么?
- Socket 运行流程
- 基于 TCP
- 基于 UDP
- Socket TCP 是如何建立连接的
- 三次握手发生在 socket 的哪几个函数中
- Socket TCP 是如何断开连接的
- 第四次挥手后为何要等待 2MSL
前言
一说到网络,大家必然会想到 TCP、UDP、Http、三握四挥等,但是一说 Socket,大家可能会有点模糊了,只知道网络中会用到,但是 Socket 究竟是什么? 套接字又是啥?为啥网络离不开 Socket?
Socket 是什么?
Socket 其实就是套接字,大部分人对于 Socket 的理解就是它可以实现一个简单的网络通信,但是它「具体解决了哪些问题?有什么实际的作用?为什么会有一个 Socket 出现?」
Socket 其实是在「应用层与传输层之间的一个产物」,它把传输层的很多复杂操作封装成一些简单的接口,来让应用层调用以此来实现进程在网络中的通信,Socket 是对端口通信开发的工具,它要更底层一些。
Socket 其实类似于一台洗碗机,它的功能就是洗碗(网络通信),如果没有它,你可能需要自己手动去洗碗(手动调用传输层、应用层之间的各个 api),但是有了它你只需要点击开关、调整时长就行了(封装了 api),你可以不需要它,但是如果没有它,洗碗(应用层与传输层之间的交互)将变得非常繁琐。
一次完整的网络通信必不可少的会经过物理传输层的网线和网卡,网络传输层的 IP 协议可以知道要将数据传送给哪台机器,但是在计算机系统中会运行不同进程,那要如何把「网卡中的网络数据识别出来是给哪个进程的」,这其实就是 Socket 设计的想解决的一点了。
Socket 是「对 TCP/IP 或者 UDP/IP 协议的封装」,Socket 本身其实就是一个调用接口。通过这个接口我们在开发网络应用程序的时候,就可以不用关心底层是怎么实现的,减轻开发的难度。
Socket 运行流程
基于 TCP
Server
- socket():表示创建一个 socket,底层会生成一个文件描述符,用来表示该 socket
- bind():用来绑定服务的端口,地址,这里一般都是以固定的为主,因为在客户端连接的时候需要指定
- listen():当绑定完成之后,listen 就会监听这个端口的数据包
- accept():相当于一个开关,表示我准备好了,可以接受请求了,但是这里会一直阻塞,直到客户端连接成功
- read():读取客户端发送过来的内容
- write():客户端写入要返回的数据
- close():断开连接,「四次挥手」
Client
- socket():表示创建一个 socket,底层会生成一个文件描述符,用来表示该 socket
- connet():表示与指定地址进行连接,在此之前,会随机创建自己的端口,tcp 的「三次握手就是从这里开始」的
- write():客户端写入要发送的数据
- read():客户端读取服务端返回的数据
- close():断开连接,「四次挥手」,给客户端发送断开连接的信息
基于 UDP
这里我就不细写了,其实大同小异,从流程图上就可以看到
因为 UDP 是无状态的,所以对于服务端来说没有连接,并且其会在调用 Recvfrom() 方法后就收客户端的请求,并一直阻塞,直到收到信息
Socket TCP 是如何建立连接的
在 Socket 绑定完服务器的地址后,就开始和服务器建立连接了,TCP 建立连接的方式其实就是大名鼎鼎三次握手了
- 第一次握手:A 的 TCP 进程创建一个 传输控制块 TCB ,然后向 B 发出连接请求报文段。之后将同步位 SYN 设置为 1,同时选择一个初始序列号 seq=x,这时客户端 A 进入到 SYN-SENT(同步已发送)状态。
- 第二次握手:B 收到连接请求报文段,如果同意建立连接,则向 A 发送确认。在确认报文段中 同步位 SYN=1、确认位 ACK=1、确认号 ack=x 1,同时也为自己选择一个初始序列号 seq=y,这时服务器 B 进入 SYN-RCVID 状态。
- 第三次握手:A 收到 B 的确认以后,再向 B 发出确认。确认报文 ACK=1、确认号ack=y 1。这时A进入到 ESTAB-LISHED 状态。当B接收到A的确认后,也进入 ESTAB-LISHED 状态。连接建立完成
三次握手发生在 socket 的哪几个函数中
- 当客户端调用 connect 时,触发了连接请求,向服务器发送了SYN 信号,这时 connect 进入阻塞状态;
- 服务器监听到连接请求,即收到 SYN,调用 accept 函数接收,进入阻塞状态,在此之前会尽力 socket、bind、listen 函数;然后返回相关的 syn 以及 ack 信号
- 客户端接受到服务端的信息,此时 connect 完成,解除阻塞状态,并且向服务端发送 ack 信号
- 服务端收到 ack, accept 阻塞解除,完成连接
在建立连接之后,connect() 就已经执行完毕了,服务端就可以向客户端发送数据了。
Socket TCP 是如何断开连接的
- 第一次挥手:A 先发送连接释放报文段,段首部的终止控制位 FIN=1,序号seq=u(等于A前面发送数据的最后一个序号加1);然后 A 进入 FIN-WAIT-1(终止等待1)状态,等待 B 的确认。
- 第二次挥手:B 收到 A 的连接释放报文段后,立刻发出确认报文段,确认号 ack=u 1,序号 seq=v(等于 B 前面发送数据的最后一个序号加1);然后 B 进入 CLOSE-WAIT(关闭等待)状态。
- 第三次挥手:A 收到 B 的确认报文段后进入到 FIN-WAIT-2(终止等待2)状态,继续等待 B 发出连接释放报文段;
- 若 B 已经没有数据要发送,B 就会向 A 发送连接释放报文段,段首部的终止控制位 FIN=1,序号 seq=w(半关闭状态可能又发送了一些数据),确认号 ack=u 1,这时B进入 LAST-ACK(最后确认)状态,等待A的确认。
- 第四次挥手:A收到B的连接释放报文段并发出确认,确认段中 确认位 ACK=1,确认号 ack=w 1,序号 seq=u 1;然后 A 进入到TIME-WAIT(时间等待)状态。当 B 再接收到该确认段后,B 就进入 CLOSED状态。
第四次挥手后为何要等待 2MSL
首先 2MSL 的时间是从客户端(A)接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端(A)的 ACK 没有传输到服务端(B),客户端(A)又接收到了服务端(B)重发的 FIN 报文,那么 2MSL 时间会被重置。等待 2MSL 原因如下
- 1.得原来连接的数据包消失
- 如果B没有收到自己的ACK,会超时重传FiN那么A再次接到重传的FIN,会再次发送ACK
- 如果B收到自己的ACK,也不会再发任何消息
在最后一次挥手后 A 并不知道 B 是否接到自己的信息, 包括 ACK 是以上哪两种情况,A 都需要等待,要取这「两种情况等待时间的最大值,以应对最坏的情况发生」,这个最坏情况是:去向ACK消息最大存活时间(MSL) 来向FIN消息的最大存活时间(MSL)。这刚好是2MSL,这个时间,足以使得原来连接的数据包在网络中消失。
- 2.保证 ACK 能被服务端接收到从而正确关闭链接
因为这个 ACK 是有可能丢失的,会导致服务器收不到对 FIN-ACK 确认报文。假设客户端不等待 2MSL ,而是在发送完 ACK 之后直接释放关闭,一但这个 ACK 丢失的话,服务器就无法正常的进入关闭连接状态。