1.TCP
TCP 是一种面向连接的单播的
,可靠的,流式传输协议,位于网络模型的传输层,为应用程序间提供数据传输。
2.TCP 连接的建立和终止
![TCP三次握手](https://tva1.sinaimg.cn/large/008i3skNly1gsa4ckwmfwj30l00hnmyy.jpg)
![四次挥手](https://tva1.sinaimg.cn/large/008i3skNly1gsa5pfte0gj30lt0kjacc.jpg)
- 1.服务器调用
socket
,bind
,listen
完成初始化,服务器做好准备等待外部的TCP连接的到来,这种方式为被动打开
,这个时候服务器处于LISTEN
状态,等待客户端的连接请求。 - 2.客户端通过
connect
发起主动打开
,向服务器发出连接请求,发送请求标识位SYN=1,同时发送初始化序号seq=x.SYN
报文不允许携带数据,消耗一个序号。此时客户端进入SYN-SEND
状态 - 3.服务器接收到客户端的请求,需要确认客户端的报文端,在确认报文中把SYN和ACK都置为1。确认号是:
ack = x+1
,同时服务器向客户端发起连接请求,初始化序号seq=y.服务器进入SYN-RECVIVED(同步收到)
状态。 - 4.客户端在收到服务器发出的响应后,给出确认连接,置
ACK=1,seq =y+1,ack=y+1
.此时客户端进入ESTABLISHED
状态. - 5.服务器收到客户端的确认后,也进入了
ESTABLISHED
状态.
总结:
服务器状态变化:LISTEN—>SENT-RECVIVED—>ESTABLISHED
客户端状态变化:SYN-SEND—>ESTABLISHED
- 1.客户端发出断开连接请求,停止发送数据,
主动关闭TCP连接
。客户端发送FIN,请求断开连接,序号为seq=u,此时客户端进入FIN-WAIT-1(终止等待1)
状态 - 2.服务器接收到客户端的断开连接请求,发出确认报文,置ACK=1.生成自己的序号seq=v,回复ack=u+1.服务器进入
CLOSE-WAIT(关闭等待)
状态 - 3.客户端收到服务器的确认后,就会进入
FIN-WAIT-2(终止等待2)
状态。 - 4.服务器发出断开连接的FIN,发送完断开连接请求之后,服务器主动进入了
LAST-ACK(最后确认)
状态。 - 5.客户端收到服务器的断开连接请求,客户端发出确认断开连接。客户端进入
TIME_WAIT(时间等待)
阶段,经过2MSL
,客户端进入CLOSED
状态。 - 6.服务器收到客户端的断开连接确认后,进入
CLOSED
状态。服务器结素TCP连接的时间要比客户端早。
3.TCP半关闭
TCP半关闭
指关闭了数据流的一个传输方向。在TCP半关闭的场景:我已经完成了数据发送,给对方发送了FIN,此时就是处于半关闭状态,这个状态下,我还继续可以接受对方的数据,但是我无法发送数据,知道对方发给我一个FIN,这个状态才会发生变化。
![](https://tva1.sinaimg.cn/large/008i3skNly1gsa5p65xobj30l20lbtat.jpg)
//设置半关闭的状态
#include <sys/socket.h>
int shutdown(int sockfd,int how);
-
参数:
-
sockfd: 要操作的文件描述符(socket的返回值)
-
how
SHUT_RD
关闭文件描述符对应的读操作SHUT_WR
关闭文件描述符对应的写操作SHUT_RDWR
关闭文件描述符对应的读写操作
-
返回值:
- 成功 0 ,失败 -1
-
4.TIME_WAIT状态
主动关闭连接的一方会进入TIME_WAIT状态。这个状态会等待2MSL,也就是最大段生存周期时间的两倍。
MSL是TCP段期望的最大生存时间。也就是网络中存在的最长时间。当TCP发送完最后的ACK后,等待2MSL时间后才会退出,这样能够让TCP重新发送最终的ACK以避免丢失的情况。重新发送最终的 ACK 并不是因为客户端要重传了ACK,而是因为服务器重传了 FIN,服务器经常回发送 FIN,因为它需要 ACK 的响应才能够关闭连接,如果生存时间超过了 2MSL 的话,客户端就会发送 RST,使服务端出错。(主动关闭方重新发送的最终ACK,是因为被动关闭方重传了它的FIN。事实上,被动关闭方总是重传FIN直到它收到一个最终的ACK。)
//由于2MSL的等待时间,所以一个端口段时间的启用,会报端口已占用,无法启动服务器
//设置端口复用
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
/*
sockfd 要操作的文件描述符(socket的返回值)
level:端口复用需要使用 SOL_SOCKET 宏
optname: SO_REUSEADDR 或者 SO_REUSEPORT
optval: 0 不设置。1设置
optlen: optval 指针指向的内存大小 sizeof (int)
int op = 1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,op,sizeof(op));
*/
5.状态转换
![TCP状态转换图](https://tva1.sinaimg.cn/large/008i3skNly1gsa6n0twhrj30rs0teaew.jpg)
客户端
- 第一次握手:发送 SYN,没有状态 -> SYN_SENT
- 第二次握手:收到回复的 ACK,SYN_SENT -> ESTABLISHED
- 主动断开连接,第一次挥手发送 FIN,状态 ESTABLISHED -> FIN_WAIT_1
- 第二次挥手,收到 ACK,状态 FIN_WAIT_1 -> FIN_WAIT_2
- 第三次挥手,收到 FIN,状态 FIN_WAIT_2 -> TIME_WAIT
- 第四次挥手,回复 ACK,等待 2 倍报文时长之后,状态 TIME_WAIT -> 没有状态
服务器
启动监听,没有状态 -> LISTEN
第一次握手,收到 SYN,状态 LISTEN -> SYN_RCVD
第三次握手,收到 ACK,状态 SYN_RCVD -> ESTABLISHED
收到断开连接请求,第一次挥手状态 ESTABLISHED -> CLOSE_WAIT
第三次挥手,发送 FIN 请求和客户端断开连接,状态 CLOSE_WAIT -> LAST_ACK
第四次挥手,收到 ACK,状态 LAST_ACK -> 无状态(没有了)