说明:TCP协议十分复杂和重要,对于个人已经掌握的点在本文就不在详述。
常识:服务器和客户端判断对方是否已经关闭连接的方法:read函数返回0
[1]TCP头部结构、TCP头部选项结构
TCP选项详解:
[2]紧急指针和带外数据OOB
[A]概念
- 带外数据用于通知对方本端发送的重要事件
- 因为是重要事件,因此带外数据具有更高的优先级:无论发送缓冲区中是否有普通数据,带外数据都会立即发送
- 实际应用中,带外数据很少用
- UDP没有实现带外数据,TCP没有真正的带外数据,不过TCP利用头部中的URG和16位的紧急指针两个字段,给应用程序提供了一种紧急方式。
[B]相关API
- send/recv函数的flag设置为MSG_OOB,表示发送或接收紧急数据。
- 带外标记
(1)Linux内核检测到TCP紧急标志时,将通知应用程序有带外数据到来,需要接受。通知有两种方式:I/O复用产生异常事件、SIGURG信号。
(2)int sockmark(int fd);
应用程序可以使用sockmark判断套接字fd下一个被读取到的数据是否是带外数据。如果是,返回1,此时可以使用recv+MSG_OOB读取带外数据;否则,返回0。
[C]TCP发送带外数据过程
[D]TCP接收带外数据过程
case1:TCP接收带外数据过程(默认接收方式)
先检查TCP头部的紧急指针标志URG;然后根据紧急指针所指向的位置确定带外数据的位置,并将它读入一个特殊的缓存中(带外缓存)。
说明:这个缓存只有一个字节,称为带外缓存。如果上层应用程序没有及时将带外数据从带外缓存中读走,则后序的带外数据到来时将会覆盖带外缓存中的数据。
case2:TCP接收带外数据过程(设置SO_OOBLINE选项)
给TCP连接设置SO_OOBLINE选项,则带外数据将和普通数据一样被TCP模块存放在TCP接收缓冲区中(而不是放在带外缓存中),此时应用程序需要向读取普通数据一样来读取带外数据。==那么问题来了:这种情况下如何区分带外数据和普通数据呢?==显然,紧急指针可以用来指出带外数据的位置,socket编程接口也提供了系统调用来识别带外数据。
[E]内核通知应用程序带外数据到达的方法
- select的异常事件触发:带外数据到来时,select的异常事件将触发
- 使用SIGURG信号捕捉:当带外数据到来时,将产生SIGURG信号
void handler(int signo)
{
//使用recv+MSG_OOB读取带外数据
}
int main()
{
//注册信号SIGURG以及信号处理函数handler
signal(SIGURG,handler);
//使用SIGURG信号之前,必须设置socket的宿主进程或进程组
fcntl(connfd, F_SETOWN, getpid());
}
[3]使用tcpdump观察TCP连接的建立和关闭
可以看到:在telnet登陆和退出的过程中,使用tcpdump可以抓取到三次握手和四次挥手的数据包
[4]TCP的11种状态、三次握手/四次挥手、TIME_WAIT/CLOSE_WAIT
参考《TCP的11种状态、三次握手/四次挥手、TIME_WAIT/CLOSE_WAIT》一文https://blog.csdn.net/weixin_36750623/article/details/84951261
[5]复位报文段RST: flags [R.]
收到RST段的一方应该关闭连接或重新建立连接,而不能回应这个复位报文段
产生复位报文段的3种情况
1.访问/connect一个不存在的端口
2.向处于TIME_WAIT状态的端口发起连接请求时
当客户端向服务器的某个端口发起连接,而服务器的该端口处于TIME_WAIT状态时,客户端会收到RST段
3.异常终止连接
初始时,A和B已经建立了连接
(1)但是A使用socket选项SO_LINGER向B发送RST段,此时A缓冲区中所有排队等待的数据将都会丢弃(详细请看socket套接字选项详解的SO_LINGER)
(2)B收到A发送来的RST段后,将异常的断开A和B之间的连接
4.处理半打开连接
初始时,A和B已经建立了连接
(1)A关闭或者异常终止了连接,但是B没有收到FIN段(比如网络发生了故障)。此时AB双方还维持着原来的连接,而A即使重启,也没有该连接的任何信息,我们将这种状态称为半打开状态。处于半打开状态的连接称为半打开连接。
(2)B向A中写入数据,则A将回复RST段给B
总结一句话:向处于半打开状态的连接写入数据,则对方将回应一个RST段。
[6]TCP超时重传
- A向B发送TCP报文段&&开启重传定时器,假设A没有收到B对该TCP报文段的ACK,那么A将在重传定时器到时时,重传TCP报文段。
- 每次重传的超时时间都增加一倍,默认是0.2s、0.4s、0.8s、1.6s… …
- 当重传次数等于允许的最大重传次数时,底层的IP和ARP开始接管,直到A端放弃连接为止
Linux有两个重要参数与TCP超时重传相关:/proc/sys/net/ipv4/tcp_retries1(在底层IP接管之前TCP最少执行的重传次数)、/proc/sys/net/ipv4/tcp_retries2(连接放弃前TCP最多可以执行的重传次数)
[7]拥塞控制
- TCP模块还有一个重要任务,就是提高网络利用率,降低丢包率,保证网络资源对每条数据流的公平性。这就是所谓的拥塞控制。
- 拥塞控制包括4部分:
慢启动
拥塞避免
快重传
快恢复
[8]TCP数据流
TCP之间交换应用程序数据,数据按照长度分为两种:交互数据、成块数据。
交互数据 | 成块数据 |
---|---|
仅包含很少的字节 | 成块数据的长度=TCP报文段允许的最大数据长度 |
对实时性要求高 | 对传输效率要求高 |
telnet、ssh | FTP |