TCP服务的特点:
tcp面向链接、字节流和可靠;UDP则不同;
tcp通过调用send()/recv()函数处理数据,udp通过调用sendto()/recvfrom()处理数据;
可靠是指,接受确认、定时重发、由于IP分片对TCP报文段重排和整理;
可见,数据最终都是封成IP数据报发送的。
这里有个疑问,IP进行分片,tcp进行分组,那么怎么保证tcp协议不会被ip分片呢?
经查,有个MSS = MTU - 20(IP头) - 20(tcp头),而udp则为 MTU - 20(IP头)- 8 (udp)头,udp最大分片长度;
下面介绍TCP的头部结构:
长度:20字节
16位端口号
16位目标端口号
32位序号(seq):被系统初始化值为某个随机值ISN
32位确认号(ack)
4位头部长度,最长为60
6位保留
6位标志位:URG | ACK | PSH | RST | SYN | FIN
16位窗口大小
16位校验和
16位紧急指针
TCP头部选项:
最长长度为40字节
典型结构为: | kind(1字节) | length(1字节) | info(n字节)
kind=0
kind=1
kind=2 length=4 最大长度(2字节)
kind=3 length=3 移位数(1字节)
kind=4 length=2
kind=5 length=N*8+2 第1块左边沿第1块右边沿......第N块左边沿第N块右边沿
kind=8 length=10 时间戳(4) 时间戳回显应答(4)
TCP链接和关闭:
这就是大名鼎鼎的三次握手四次挥手了。
A -> seq=N (SYN) B
A seq=M(SYN) ack=N+1 <- B
A -> ack=M+1 B
A -> seq=N +1,ack=M+1(FIN) B
A ack=N+2 <- B
A seq=M+1,ack=N+2 (FIN) <- B
A -> ack=M+2 B
半关闭状态:
TCP是双工的允许,客户端执行半关闭状态,可以通过read()==0或者shutdown()函数对半关闭的支持;
所谓的半关闭是通讯的一端告诉对方已经发送完毕,这是发送FIN,但是允许接收对方的数据,直到对方的数据发送完毕后进行关闭。
链接超时:
由于链接一个距离远或者繁忙的服务器可能造成链接超时。
TCP状态转移:
服务端:连接:LISTEN 发送syn SYN_RECV 收到ack确认 ESTABLISHED
关闭:收到客户端的断开链接 CLOSE_WAIT 发送端口链接包 LAST_ACK 收到客户端ACK完全断开
客户端:调用connect SYN_SEND ESTABLISHED
FIN_WAIT1 FIN_WAIT2 TIME_WAIT
若在FIN_WAIT1收到最终的ack称为孤儿连接,由操作系统内核管理
为防止孤儿链接内核变量在/proc/sys/net/ipv4/tcp_max_orphans和/proc/sys/net/ipv4/tcp_fun_timeout中定义
TIME_WAIT状态:
客户端在链接结束后并未进入到CLOSED状态,而是进入到TIME_WAIT状态,需要等待2MSL时间,MSL是TCP最大生存时间,建议为2min
作用是:可靠的终止连接、保证让迟来的TCP报文能有足够的时间呗识别而不被丢弃。
解决办法是用socket选项的SO_REUSEADDR
复位报文段:
那么什么情况会被发复位报文呢?有三种情况分别是:
(1)访问不存在的端口,或该端口也处于TIME_WAIT状态
(2)异常终止连接,可以用SO_LINGER来发送复位报文段
(3)处理半打开连接,服务端关闭(可能是网线断了),客户端没收到最后一个ack确认包,这样无任何连接信息了,如果想向其发送报文,这时候需要客户端发送一个复位报文。
TCP交互数据流:
所携带的应用程序数据分为两种:交互数据和成块数据
nagle算法解决拥塞,TCP连接在通讯双方任意时刻只最多只能发送一个未确认的tcp报文段。这有效的解决的许多小包发许多次的问题;
TCP成块数据流:
带外数据(OOD):
带外数据是指具有比普通数据更高优先级的数据,应该立即被发送,有一条独立的传输通道。
有某些协议有带外数据的使用,如telnet和ftp;
UDP没有带外数据实现。TCP没有真正意义上的带外数据的实现。通过设置紧急标准和紧急指针可以在TCP变相实现带外数据。
可以通过socket选项中的SO_OOBINLINE将带外数据设置成和普通数据一样进行发送。
TCP超时重传:
为了保证每个确认包都在固定的超时时间内返回,所以TCP为每个TCP报文都生成一个计时器,若超过超时时间,那么将被包重发。
拥塞控制:
为了提高网络利用率,降低丢包率,保证网络资源对每条数据有公平性。
提供了慢启动、拥塞避免、快速重传、快速恢复等功能;
算法,拥塞控制在linux下有reno/vegas/cubic等
发送窗口:SWND
最大长度:SMSS
拥塞窗口:CWND
接收窗口:RWND
慢启动和拥塞避免:
慢启动:
CWND+=min(N,MSS) N为确认包中未被确认的字节数 (1)
拥塞避免:
CWND+=SMSS*SMSS/CWND(2)
那么CWND的值何时更新呢,根据RFC 5681中提到
每个RTT时间按照(1)形计算;
没收到一个新的确认报文段按(2)计算;
接下来介绍拥塞发生时的两种形式:
传输超时,或者说TCP重传定时器溢出;
接收重复的确认报文段;
对于第一种情况用慢启动和拥塞避免的方式进行解决,对于第二种方式用快速重传快速恢复的形式进行决绝;
如果是第一种情况,那么进行重传,并且修改sstrsh=max(FlightSize/2,2*SMSS) CWND<=SMSS
快速重传和快速恢复:
当发生端连续收到三个重复确认的报文段,就发生拥塞了,对此应用快速重传和快速恢复,具体做法如下:
1)当收到三个重复确认包时,计算ssthresh值,然后立即重传丢失的报文段,并且按下列式子设置CWND
CWND=ssthresh + 3*SMSS
2)每次收到一个重复确认,设置CWND=CWND+SMSS
3)收到新的确认时候设置CWND=ssthresh