TCP/IP 协议详解
1. TCP/IP协议模型
OSI参考模型分为七层:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。其实际是一个指导作用的协议,并没有实际的实现。主要还是看实际使用的TCP/IP协议。
TCP/IP协议分为四层:应用层、传输层、网络层、链路层(包含物理层)。它是我们在实际开发中使用的协议。
白话一点理解他们的功能:
- 应用层:用户应用程序按照一定的规范(即协议)包装数据,包括数据格式化、代码转换、数据加密等。将数据发送给操作系统内核,来完成接下来的操作。协议栈:TFTP、HTTP、SMTP、DNS、Telnet、FTP、SNMP等
- 传输层:为运行在不同主机上的应用进程之间提供逻辑通信。主要分为两种协议,TCP和UDP。传输层仍然只存在于端系统。
TCP:面向连接的可靠的协议。在发送端,它负责将上层传下来的字节流分组,添加TCP首部(有序)组成报文段发送到下层。在接收端它负责把收到的报文进行重组后递交给上层。同时要处理端到端的流量控制。
UDP:不可靠的,无连接的协议。中文名叫做用户数据报协议。主要用于不需要对报文进行排序和流量控制的场合。
- 网络层:提供端到端的逻辑通信。将运输层提交的报文段和目的地址,组成IP数据报。通过多种路由选择协议,进行路由的选择。协议栈:IP、ICMP、RIP、OSPF、BGP、IGMP等
IP协议:规定了在数据报中的各个字段,以及端系统和路由器应该如何作用于这些字段。IP仅有一个,所有具有网络层的因特网组件必须运行IP。
- 链路层:网络层通过源和目的地之间的一系列路由器路由数据报。为了将分组从一个节点移动到路径上的下一个节点,网络层必须依靠链路层的服务。在每个节点,网络层将数据报下传给链路层,链路层将数据报封装成帧,沿着路径将数据报传递给下一个节点。在该下一个节点,链路层将数据报上传给网络层。
链路层提供的可能服务包括:成帧、链路接入、可靠交付、差错检测和纠正。协议栈:PPP、ARP等
物理层:实际传递电信号的光纤、网线等。 标准:ISO2110、IEEE802、IEEE802.2
2. TCP报文头
![]() |
源端口(Source Port)\目的端口(Destination Port)2字节:通过IP确认端系统,通过端口和协议确认唯一进程。 seq序号(Sequence Number) 4字节: 表示该报文段所发送数据的第一个字节的编号,在TCP连接中所传输的数据字节流的每一个字节都会按顺序编号(数据部分)。一个报文头的编号如果为1001,携带1000个字节的数据。那么下一个报文段的序号就应该是2001。 Ack确认号(Acknowledgment Number) 4字节:表示接收方期望收到发送方下一个报文段的第一个字节数据的编号,也就是告诉发送方:我希望你下次发送的数据的第一个字节数据的编号为此确认号。 数据偏移(Offset):表示TCP报文段的首部长度,共4位,由于TCP首部包含一个长度可变的选项部分,需要制定这个TCP报文段到底有多长,他指出TCP报文段的数据起始距离距离TCP报文段的起始处有多远。 TCP标志位: URG:紧急指针标志,表示本报文中发送的数据是否包含紧急数据,后面的紧急指针字段周游荡URG=1才有效。 ACK:确认序号有效,只有ACK=1的时候,前面的确认号字段才有效,TCP规定,建立连接后,ACK必须为1,带ACK标志的TCP报文段称为确认报文段 PSH:push标志。如果为1,表示对方应当立即吧数据交给上层应用,而不是缓存起来,如果应用程序不将收到的数据读走,就会一直停留在TCP接收缓冲区中 RST:重置连接标志。如果收到RST=1的报文,说明与主机的连接出现严重错误(如主机崩溃),必须释放连接,然后重新建立连接 SYN:建立一个新连接。当SYN=1,ACK=0时,表示这是一个请求建立连接的报文段,当SYN=1,ACK=1时,表示对方同意建立连接。SYN=1,说明这是一个请求建立连接或同意建立连接的报文 FIN:断开一个连接,表示通知告知对方本段要关闭连接了,标记数据是否发送完毕,当FIN=1,表示告诉对方“我的数据已经发送完毕,你可以释放连接了”,代FIN标志的TCP报文段称为结束报文段 窗口(window):表示现在允许对方发送的数据量,也就是告诉对方,本报文段确认号开始允许对方发送的数据量,到达此值,需要ACK确认后才能继续发送数据。以此达到流量控制的目的。 检验和(Checksum):对整个报文段进行校验,发送端计算,接收端校验,以保证数据的完整和正确性。 紧急指针(Urgent Pointer):标记紧急数据在数据字段中的位置,只有当URG为1时才有效。
|
3. TCP 3次握手
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
第一次握手:建立连接时,客户端发送SYN包(SYN=1,seq= x)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到SYN包,必须确认客户的SYN(Ack = x+1),然后返回一个SYN包(seq = y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器端的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISH状态,完成三次握手。
4. TCP 4次挥手
第一次:客户端发送一个FIN包,用来关闭客户端到服务端的数据传送,客户端进入FIN_WAIT_1状态;
第二次:服务端收到FIN后,发送一个确认 ACK包给客户端(FIN与SYN一样占一个序号),服务进入CLOSE_WAIT状态;
——————这中间的过程服务端会将剩余的所有数据发送完毕之后
第三次:服务端发送一个FIN,用来关闭服务端到客户端的数据传送,服务端进入LAST_ACK状态;
第四次:客户端收到FIN后,客户端进入TIME_WAIT状态,紧着发送一个ACK给服务端,客户端关闭进入CLOSED状态。完成四次挥手。
客户端在TIME_WAIT状态等待2MSL时间后,关闭。MSL为最长报文段生命,Linux系统为30秒。
等待的原因:1. 如果最后一次ACK确认,服务端没有收到,服务端可能会重发FIN包。2. 避免新旧连接混淆,有足够的时间让这个连接,不会跟后面的连接混在一起,因为有些路由器会缓存IP数据包。如果连接被重用了,那么这些延迟被收到的包就有可能跟新连接混在一起。
服务器出现CLOSE_WAIT状态的原因???
问题其中一个表现是客户端一直在请求,但是返回给客户端的信息是异常的。服务端压根也没收到请求。
服务器保持大量的CLOSE_WAIT只有一种情况,那就是在对方发送一个FIN报文之后,程序这边没有进一步发送ACK,或者FIN报文以确认。
原因只有一点,服务器没有主动关闭,可能是bug,代码层面资源没有释放。也可能是各种原因导致的性能问题,导致不能及时释放。
5. TCP滑动窗口
RTT:发送一个报文段,到接收到ACK确认回执所需要的事件。
RTO:重传定时器,根据RTT计算时间间隔,未收到回执则进行重传。
窗口大小的计算: 即接收端剩余缓存的大小
AdvertisedWindow = MaxRcvBuffer - (LastByteRcvd - LastByteRead)
发送端可以发送的数据大小:即接收端剩余缓存大小 - 已发送未回执的数据大小
EffectiveWindow = AdvertiseWindow - (LastByteSent - LastByteAcked)
对于发送端,字节流可以分为4种状态,1. 发送已确认; 2. 发送未确认;3. 未发送但根据窗口大小计算可以发送;4. 未发送也不允许发送(即超出接收端缓存大小的部分)。当接收端不停的接收数据并发送回执,窗口也会向后移动。如下图所示: