握手挥手是老生常谈的话题了,这次想把整个过程从网络编程的角度再记录一下,点到即止,不会过分深究细节。
三次握手
在握手之前,服务器与客户端都已经调用
socket
函数创建了套接字,并且服务器还调用了bind
函数绑定它的IP地址及端口号、然后调用listen
函数将服务器的套接字设定为监听套接字,最后调用accept
函数阻塞等待客户端发起新的连接。客户端用socket
函数创建的套接字默认是一个主动套接字,然后客户端调用connect
函数触发三次握手过程:
- 客户端向服务器发送一个SYN报文段,在发送之前会选择一个初始序列号(ISN),ISN是一个32位的无符号数,每4微秒增加1,到达最大值后再循环回到0,使用序列号主要是为了防止出现数据乱序的问题。发送后客户端会进入
SYN_SENT
状态;
.- 服务器在接收到客户端的连接请求后,将该连接信息放到半连接队列中。然后服务器向客户端发送一个ACK报文段,表示确认客户端发起的第一次连接请求,该报文段中包含了服务器的ISN和确认号,确认号就是客户端发过来的报文段中ISN的值加1,这个确认号字段只能在ACK报文段中使用。发送后服务器会进入
SYN_RCVD
状态;
.- 经过以上两次握手,客户端已经明确了自己能与服务器收发消息,但服务器还不确定客户端是否能收到自己的消息,所以还需要一次握手。客户端再给服务器发送ACK报文段,其中确认号也是服务器ISN的值加1。至此,三次握手完毕,服务器与客户端都会进入
ESTABLISHED
状态。服务器将该连接从半连接队列移动到全连接队列,全连接队列满了会出现丢包现象。可靠的连接建立后,
accept
返回一个已连接套接字描述符,服务器接着调用fork
函数创建一个子进程,由子进程来处理客户端请求,即与客户端通过read/write
,send/recv
函数进行通信,而父进程则通过监听套接字继续等待下一个连接。
fork
是昂贵的,即使当今的实现使用称为写时复制的技术进行优化,但fork
仍然是昂贵的,而且父子进程之间的信息传递需要通过IPC机制。线程的创建比进程快10~100倍,同一进程内的所有线程共享该进程的整个虚拟地址空间,这使得线程之间易于共享信息,然而伴随这种简易性而来的是同步问题。在accept函数返回之后,改为调用pthread_create
取代调用accept
,创建一个线程来处理客户端的请求,减少服务器的开销,还可以通过维护一个线程池来进一步优化。
四次挥手
挥手可以由客户端或服务器发起,区别在于主动关闭的一方会进入
TIME_WAIT
状态,通常由客户端执行主动关闭操作并进入TIME_WAIT
状态。客户端调用close
函数主动关闭连接,触发四次挥手(挥手过程中的ISN与握手的一样,不再赘述):
- 客户端向服务器发送一个FIN报文段,并进入
FIN_WAIT_1
状态;
.- 服务器收到FIN报文段后知道客户端想断开连接,但此时服务器不一定能做好准备,可能还有未传输完的消息,所以服务器只能先返回一个ACK报文段,并进入
CLOSE_WAIT
状态;
.- 服务器已经准备好断开连接,向客户端发送FIN报文段,关闭服务器到客户端的数据传输,服务器进入
LAST_ACK
状态;
.- 客户端收到FIN报文段后接着发送一个ACK报文段给服务器,然后进
入TIME_WAIT
状态并等待2MSL时间后进入CLOSED
状态,服务器收到客户端的FIN
报文段后会进入CLOSED
状态,完成四次挥手。
一些问题:
1、三次握手可以变成两次握手吗?变成四次握手呢?
不能变成两次握手,TCP是全双工通信,只用两次握手无法保证通信双方的接收与发送能力正常;
可以变成四次握手,但三次握手就能完成的连接,多一次握手反而降低了效率,没有必要。
2、四次挥手能不能变成三次挥手?
是可能的,如果服务器在收到客户端的FIN报文段后没有数据需要发送,那么对客户端的ACK报文段和自己的FIN报文段就可以合并成一个报文段发送,即三次挥手。
3、TCP三次握手后建立的连接是安全的吗?
只能说是可靠的,但不是安全的。记住,无论是TCP还是UDP,也无论是GET方法还是POST方法,只要是用的HTTP协议就是不安全的,因为HTTP是明文传输。想要安全就需要加密,得用HTTPS(HTTP + SSL),这又涉及到对称加密、非对称加密、数字签名、CA证书等一系列知识。
4、TCP如何保证可靠性?
TCP通过以下机制保证其可靠性:
校验和、序列号、确认应答、超时重传、拥塞控制、流量控制。
5、TCP如何保证数据传输的正确性?
首部校验。TCP首部中有一个字段是校验和,通过累加和的方式计算出一个数字,接收方收到报文段后重复这个过程,将计算出的校验和与接收到的首部校验和比较,如果不一致则说明数据在传输过程中出错。
追问:首部校验机制能够保证检查出一切错误吗?
不能,校验和只是做简单的累加计算,保护性较差。可以在应用层使用MD5校验,在发送数据前通过MD5计算摘要,并将MD5摘要一起发送,接收方收到数据有再次用MD5加密,把两次加密的摘要做对比,一致则说明数据正确。
6、TIME_WAIT状态为何要持续2MSL?
1、为了可靠地终止TPC连接:
假设第四次挥手时客户端发送的ACK报文段丢失,服务器会重发FIN报文段,这时客户端需要停留在某个状态(TIME_WAIT)以处理重复收到的FIN报文。
2、保证让迟来的TCP报文段有足够多的时间被识别并丢弃:
当一个 TCP 连接处于 TIME_WAIT 状态时,我们无法立即用该端口建立一个新连接。等待2MSL时间后网络上两个传输方向上尚未被收到的、迟到的报文段都已经消失(被中转路由器丢弃),这时新建立的连接就不会收到属于上一个连接的报文段了。
7、三次握手过程中可以携带数据吗?
- 第一次握手不能,因为会使服务器更容易遭受SYN泛洪攻击;
- 第二次握手也不能,因为第一次握手时客户端不能携带数据,所以服务器并不知道客户端需要请求哪些数据,也就无法发送数据;
- 第三次握手时,客户端已经处于ESTABLISHED状态,可以携带数据。
TCP首部字段:
三次握手四次挥手过程
卧槽 牛逼 今年哪个的一面 忘记了