TCP协议学习笔记
TCP是面向连接的,可靠的,基于字节流的传输层通信协议。
面向连接:一对一的连接,不像UDP协议可以一个主机同时向多个主机发送消息,也就是一对多是无法实现的
可靠的:无论网络链路中出现怎么样的链路变化,TCP都能保证一个报文一定能够到达接收端。
字节流:消息是没有边界的,无论消息有多大都可以进行传输。
TCP能够被广泛使用的原因:
1.数据可靠,必达 2.传输效率不低 3.数据有顺序
TCP 头部格式
TCP包头格式struct tcphdr{
unsigned short sport; // 源端口号 2个字节 16bit
unsigned short dport; // 目的端口号 2个字节 16bit
unsigned int seqnum; // 序列号 4个字节 32bit 每一个tcp包都会携带一个序列号,开始是随机产生的一个初始值然后开始增加,超过最大值后从头开始。用来解决网络包乱序的问题。
unsigned int acknum; // 确认号 4个字节 32bit 三次握手第二次返回的时候 带acknum的值;指下一次期望收到的数据的序列号,用来解决不丢包的问题。
unsigned char hdrlen:4, // 头部长度 4bit 最大值是1111 即 15 , 单位是4字节, 所以最大值是15*4 = 60字节;tcp头部最大值是60字节。
resv:4;
unsigned char cwr:1,
ece:1, //cwr 与 ece 理解为填充左右
urg:1, // 1紧急指针位 1bit 如果标志1,数据发送之后,对端收到后会立即从缓冲区发送给应用程序处理
ack:1, // 2确认位 该字段为1,确认应答的字段为有效,TCP规定除最初建立连接的SYN包之外的该位必须置为1。
psh:1, //
rst:1, // 该位为1时,表示TCP连接中出现异常必须强制断开连接
syn:1, //该位为1时,表示希望建立连接,并在其序列号字段进行序列号初始值的设定
fin:1; //该位为1时,表示今后不会再有数据发送,希望断开连接。
unsigned short cwnd; // 窗口大小,2个字节 16bit
unsigned short check; // 校验和 2个字节 16bit
unsigned short usr_point; // 紧急指针 2个字节 16bit
};
如何确定一个TCP连接?
TCP四元组可以唯一确定一个连接:源地址,源端口,目的地址,目的端口。
源地址和目的地址的字段(32位)在IP头部中;源端口和目的端口的字段(16位)在TCP头部中。
一个IP的服务器监听一个端口,TCP最大连接数是多少?
根据四元组,固定了目的ip地址和目的端口,可变的是源ip地址和源端口号,最大值是=源ip地址X源端口号。
对于ipv4,客户端IP数最多为2的32次方,端口号最多为2的16次方,最大为2的48次方
以上是理论值,实际受到系统文件描述符数量的限制,可以通过ulimit查看修改,另外受到内存大小的限制,每一个连接都是需要占用内存的。
UDP和TCP有何区别?分别适用于什么场景?
连接:TCP传输数据之前需要先建立连接;UDP不需要建立连接。
服务对象:TCP是一对一的连接;UDP支持一对一,一对多,多对多的交互通信。
可靠性:TCP可靠交付数据的,UDP不保证可靠交付数据。
首部开销:TCP首部较长,20-60字节;UDP只有8个字节,开销小
TCP经常用于FTP文件传输,HTTP/HTTPS;UDP使用于视频音频等多媒体通信.
TCP数据长度计算:
tcp数据长度 = IP总长度 - IP首部长度 - TCP首部长度。
TCP三次握手:
第一次:客户端随机生成一个seq序列号,将此序列号置于TCP首部的序号字段中,同时syn标志位设置为1,表示SYN报文。接着向服务器发送一个请求连接的报文,该报文不包含应用层数据,之后客户端处于SYN-SEND状态。
第二次:服务器收到SYN报文后,随机生成一个seq序列号,将此序列号放置在TCP首部的序列号字段中,同时SYN标志位设置为1,acknum设为为收到的seq+1,表示对收到数据的确认,也可以理解为下次期望收到的数据的起始编号,将ACK标志位设置为1,。最后将该报文发送给客户端,该报文不包含应用层数据,之后服务器处于SYN-RCVD状态。
第三次: 客户端收到服务端报文后,还需要向服务器发送一次确认,确认应答号字段被填入 收到的序列号+1,同时ACK标志位设置为1,最后把报文发送个服务端,这次报文可以携带客户端到服务器的数据,之后客户端处于ESTABLISHED状态。服务器收到客户端的应答报文后,也进入ESTABLISHED状态。
可以发现三次握手中,前两次是不可以携带数据的,第三次是可以携带数据的。
如何在linux中查看TCP状态?
netstat -napt
TCP握手为什么是三次,不是两次或者四次?
首先理解 TCP连接是什么,tcp连接是用于 保证可靠性 和 流量控制维护 的某些状态信息,包括socket,序列号,和窗口大小称之为连接。
两次连接 无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠同步双方的序列号;
四次握手,三次握手可以建立可靠性连接,就不在需要更多的通信次数了。
为何初始序号客户端和服务端ISN是不同的?
网络中报文会延迟,会复制重发,也有可能丢失,这样会造成不同连接之间产生互相影响,为了避免互相影响,客户端和服务端初始序号是随机且不同。
既然IP会分片,那么为什么TCP还需要MSS呢?
MTU:一个网络包的最大长度,以太网中一般1500字节;MTU = IP头部 + TCP头部 +TCP数据
MSS:除去IP包头和TCP头部之外,一个网络包所能容纳的TCP数据的最大长度。MSS = TCP数据。
当IP层有一个超过MTU大小的数据,IP层就要进行分片,数据分成若干个片,保证每一片都小于MTU,之后由目标主机IP层进行重新组装后交给TCP传输层。
如果其中某一片丢失,IP层没有超时重传,TCP负责超时和重传,接收方发现TCP某一片丢失后,不会响应ACK给对端,发送方在TCP超时后就会重发整个TCP报文。由此可见TCP分片效率不高。
为了达到最佳的传输效能,TCP协议建立连接时通常要协商双方的MSS值,TCP层发现超过MSS时,会先进行分片,这样TCP层数据IP的长度就不会大于MTU了,自然不用IP分片了。如果由TCP分哦按丢失,重发也是以MSS为单位,不用重发所有的分片。
TCP四次挥手过程:
客户端打算关闭连接,发送一个TCP首部FIN标志位被设置为1 的报文,之后客户端进入FIN_WAIT1的状态。
服务器收到该报文后,向客户端发送ACK应答报文,接着服务器进入CLOSE_WAIT状态。
服务端如果有尚未发送的数据,进行发送,然后向客户单发送一个FIN标志位被设置为1 的报文,服务端进入LAST_AVK状态。
客户端收到FIN报文后,向服务器发送一个ACK报文,之后进入TIME_WAIT状态。
服务器收到ACK之后,进入CLOSE状态,至此 服务端已经完成连接的关闭。
客户端等待2MSL秒之后,自动进入CLOSE状态,至此客户端也完成连接的关闭。
time_wait时间为什么是2MSL?
2MSL是所有报文在网络中生成的最大时间,超过这个时间报文将被丢弃;TCP是基于IP协议的,IP协议中有个TTL字段,是IP数据包可以经过的最大路由数,每经过一个路由,此值就会减去1,当此值到0时,报文将被丢弃。
MSL与TTL的区别:MSL单位是时间,TTL是经过路由跳数,,所有MSL应该大于TTL消耗为0的时间。确保报文已被自然消亡。
等待2倍的MSL:网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待2倍的时间。
2MSL时间从客户端接收到FIN后发送ACK开始计时的,如果在TIME_WAIT时间内,因为ACK没有传输到服务器端,客户端又收到服务器端重发的ACK,那么2MSL将重新计时。
为什么存在TIME_WAIT状态?
防止有相同的四元组 (旧的数据包)被收到。
保证被动关闭的一方能够正确关闭:如果最后的ACK对端没收到,服务器一直处于last_ack状态,当客户端再次请求连接时,服务器会发送RST报文给客户端,连接的建立过程就会被终止。
TIME_WAIT过多的危害?
服务器有处于TIME_WAIT状态的TCP,说明服务器主动发起的断开请求,危害有两种:第一 内存资源占用,第二 端口资源的占用,一个TCP连接消耗一个本地端口。服务器TIME_WAIT状态过多,占满了端口资源,则会导致无法创建新的连接。
程序中使用SO_LINGER
设置socket选项,设置调用close关闭连接行为
lingerl_onoff为1,linger为0,调用close后,立即发送一个RST标志位给对端,TCP直接跳过四次挥手进行关闭。危险行为,不值得提倡。
listen(fd,backlog)中backlog意义:
backlog代表syn队列长度 或者 syn队列(mac下面)+accept队列长度(linux下)两个版本都有存在
accept函数完成的功能:
从队列中取出一个节点,分配一个fd与节点一一对应;accept队列为空时 accept阻塞。
connect成功返回在第二次握手,服务器端accept成功返回在三次握手成功之后
send/recv 需要考虑的问题:
1 如何保证顺序? 2 在顺序的前提下保证高效?
send的本质:把数据放入了内核中去,其他的什么也没有做;内核将数据发送到对端内核后,对端内核协议栈接收完毕后通知应用程序,应用程序调用recv。
如何做到高效:使用滑动窗口
send、recv滑动窗口滑动窗口两边变化:左边的值通过acknum变化的,右边的值通过ack+cwnd的值变化的,cwnd是对端回复ack时包头中携带的。
tcp如何保证顺序:延迟确认,收到一个包后 启动一个定时器,延迟200ms;延迟时间到之后,检查收到的包,在顺序接收完整序列的地方回复acknum,比如收到1-7 9-10序列的包,序列号为8的包没有收到,则回复acknum=8.表示8之前的包全部收到。
既然有TCP可靠性连接,为何还要用UDP做可靠性传输?
UDP应用场景应用为下载和实时性要求的场景,
UDP下载:UDP不带拥塞控制,TCP有窗口的计算,有个拥塞控制,限制了下载的过程中包,UDP没有这种限制。
UDP实时性:TCP有延迟确认,为了保证传输效率。UDP牺牲了传输效率,保证了实时性。
滑动窗口到底多大,如何确定窗口cwnd的大小?
根据网络上传输的时间,RTT 一个往返的时间,RTT增加,拥塞控制
RTT计算方式:RTT = 0.9 *old rtt + 0.1 *cur_rtt; 之前9次的rtt加上本次的rtt
cwnd开始默认值是1,之后变化按照 cwnd = 2*cwnd 进行变化,增长的过程中 有个门限值,到达门限值之后 线性增长;门限值之前的增长叫做慢启动,门限值之后的增长叫做拥塞控制。
拥塞控制 线性增长过程中 RTT时间内没有返回数据时候,cwnd窗口减少为之前的一半,重新进行拥塞控制。