【网络协议-3】TCP/UDP协议

TCP报头格式:

一共20个字节;

  • 源、目标端口号字段:各占2个字节,18比特。TCP协议通过使用”端口”来标识源端和目标端的应用进程。端口号可以使用0到65535之间的任何数字。在收到服务请求时,操作系统动态地为客户端的应用程序分配端口号。在服务器端,每种服务在"众所周知"的端口”(Well-Know Port)为用户提供服务。
  • Sequence number(顺序号码) Acknowledge number(确认号码) 各占4个字节,32比特。
  • 标志位:占6比特 SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)。
  • 数据偏移:占4比特,该数据偏移不是ip分片中的数据偏移,是指数据部分的开头到TCP开头的部分距离,就是整个TCP包头的长度,单位是4字节
  • 保留(3比特长)—须置0
  • 标志符(9比特长)
    • NS—ECN-nonce。 
    • CWR—Congestion Window Reduced。
    • ECE—ECN-Echo有两种意思,取决于SYN标志的值。
    • URG—为1表示高优先级数据包,紧急指针字段有效。
    • ACK—为1表示确认号字段有效
    • PSH—为1表示是带有PUSH标志的数据,指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满。
    • RST—为1表示出现严重差错。可能需要重现创建TCP连接。还可以用于拒绝非法的报文段和拒绝连接请求。
    • SYN—为1表示这是连接请求或是连接接受请求,用于创建连接和使顺序号同步
    • FIN—为1表示发送方没有数据要传输了,要求释放连接
  • 窗口大小(WIN,2字节16比特长)表示从确认号开始,本报文的接受方可以接收的字节数,因为缓存区的有限,防止对方发送的数据过快,导致数据丢失。
  • 校验和(Checksum,2字节16比特长)对整个的TCP报文段,包括TCP头部和TCP数据,以16位字进行计算所得。这是一个强制性的字段。
  • 紧急指针(2字节,16比特长)—本报文段中的紧急数据的最后一个字节的序号。紧急数据放在数据的开头。
  • 选项字段—最多40字节。每个选项的开始是1字节的kind字段,说明选项的类型。
    • 0:选项表结束(1字节)
    • 1:无操作(1字节)用于选项字段之间的字边界对齐。
    • 2:最大报文段长度(4字节,Maximum Segment Size,MSS)通常在创建连接而设置SYN标志的数据包中指明这个选项,指明本端所能接收的最大长度的报文段。通常将MSS设置为(MTU-40)字节,携带TCP报文段的IP数据报的长度就不会超过MTU,从而避免本机发生IP分片。只能出现在同步报文段中,否则将被忽略。
    • 3:窗口扩大因子(4字节,wscale),取值0-14。用来把TCP的窗口的值左移的位数,使窗口值乘倍。只能出现在同步报文段中,否则将被忽略。这是因为现在的TCP接收数据缓冲区(接收窗口)的长度通常大于65535字节。
    • 4:sackOK—发送端支持并同意使用SACK选项。
    • 5:SACK实际工作的选项。
    • 8:时间戳(10字节,TCP Timestamps Option,TSopt)
      • 发送端的时间戳(Timestamp Value field,TSval,4字节)
      • 时间戳回显应答(Timestamp Echo Reply field,TSecr,4字节)
  • 填充字段:让整个报文长度是4字节的倍数。

TCP三次,四次握手:

TCP协议提供可靠的连接服务,采用三次握手建立一个连接: 

  • 第一次握手:建立连接时,客户端把数据包标志符中的SYN(建立联机)=1,标志符变成00000010,随机产生一个序列号码number_client(假设number_client = 10000)的数据包SYN发给服务器, 并进入SYN_SENT状态,服务器由客户端发来的标志符中SYN=1知道,客户端要求建立联机; 
  • 第二次握手:服务器收到客户端发来的数据包后,给客户端发数据包,数据包中的SYN(建立联机)=1,ACK(确认字段)=1,标志符变成00010010。随机产生一个序列号码number_server(假设number_server = 20000),确认序列号码为(number_client+1),此时服务器进入SYN_RECV状态;
  • 第三次握手:客户端收到服务器的数据包后,检查服务器返回的确认号码是否正确(number_client + 1 = 10001),如果为10001说明正确, 向服务器发送数据包,数据包中标志符ACK = 1,确认序列号码为(number_server + 1 = 20001) 此包发送完毕,客户端和服务器进入ESTAB_LISHED状态,完成三次握手,客户端与服务器开始传送数据.

 

TCP通过序列号与确认应答提高可靠性

TCP发送数据,是以字节流的形式发送。它不关心数据是什么类型,但是为了确保数据正确性,重发控制和重复控制等。

扫描二维码关注公众号,回复: 4287303 查看本文章

这些功能都以序列号来实现。序列号初始值并非为0,而是由客户端建立连接时候,随机产生的

(TCP的数据长度并未写入TCP首部。实际通信中求得TCP包的长度的计算公式是:IP首部中的数据包长度-IP首部长度TCP首部长度。)

上图每次发送数据长度1000是在三次握手的时候,在两端主机之间被计算得出。两端的主机在发出建立连接的请求时,会在TCP首部中写入MSS选项,告诉对方自己的接口能够适应的MSS的大小(为附加MSS选项,TCP首部将不再是20字节,而是4字节的整数倍)。然后会在两者之间选择一个较小的值投入使用(在建立连接时,如果某一方的MSS选项被省略,可以选为IP包的长度不超过576字节的值(IP首部20字节,TCP首部20字节,MSS 536字节))
 

选中三次握手中MSS选项中值较小的1460,作为一个传输数据的一个段单位。

 

滑动窗口方式并行处理

每次传输数据都需要接收数据的主机确认的话,效率会很慢。因此可以设置窗口大小,就是指无需等待确认应答而可以继续发送数据的最大值。这种机制称为滑动窗口方式并行处理:

滑动窗口方式处理重传:

 

1-1000字节数据已发送到主机B,但是1-1000的数据确认应答丢失。在收到确认序号ACK = 2001时,就可以把客户端1-1000的数据从缓冲中删除了,因为主机B已发送确认序号ACK = 2001,说明1-1000数据已经发送成功了。

 

滑动窗口方式-高速重发控制

当某段保温丢失后,接收端会给发送端不停的发报文确认应答,如上图所示,接收端在收到3次重复应答后,

知道此段报文丢失,重发。

流控制:

当接收端在高负载下无法接收发送端的数据包,把发送端发送的数据丢弃时候,发送并不知道,会一直发送数据。

造成网络流量的浪费。TCP提供一种机制可以让发送端根据接收端的实际接收能力控制发送的数据量。这就是所谓的流控制。

具体操作就是改变有接收端改变发送端的数据包的窗口大小。

未连接队列

在三次握手协议中,服务器维护一个未连接队列,该队列为每个客户端的SYN包(syn=j)开设一个条目,该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户的确认包时,删除该条目,服务器进入ESTAB_LISHED状态

1.为什么需要三次握手,两次不可以吗?或者四次、五次可以吗? 

  1. 如果client发送的连接请求由于网络延时到客户端连接释放后才到达server,这是早已失效的报文,如果只进行二次握手,服务器就认为有新连接请求,但是client并没有建立连接,不会给server发送数据,但是server为了这个连接,会一直有资源消耗。
  2. 如果是两次握手,假设服务器给客户端在第二次握手时发送数据,数据从服务器发出,服务器认为连接已经建立,但在发送数据的过程中数据丢失,客户端认为连接没有建立,会进行重传。假设每次发送的数据一直在丢失,客户端一直SYN,服务器就会产生多个无效连接,占用资源,这个时候服务器可能会挂掉。这个现象就是我们听过的“SYN的洪水攻击”。 

四次挥手过程(关闭客户端到服务器的连接)

  1. 首先,客户端发送一个FIN,用来关闭客户端到服务器的数据传送,然后等待服务器的确认。其中终止标志位FIN=1,序列号seq=u。
  2. 服务器收到这个FIN,它发送一个ACK,确认ack为收到的序号加一。
  3. 关闭服务器到客户端的连接,发送一个FIN给客户端。
  4. 客户端收到FIN后,并发回一个ACK报文确认,并将确认序号seq设置为收到序号加一。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

客户端发送FIN后,进入终止等待状态,服务器收到客户端连接释放报文段后,就立即给客户端发送确认,服务器就进入CLOSE_WAIT状态,此时TCP服务器进程就通知高层应用进程,因而从客户端到服务器的连接就释放了。此时是“半关闭状态”,即客户端不可以发送给服务器,服务器可以发送给客户端。 
此时,如果服务器没有数据报发送给客户端,其应用程序就通知TCP释放连接,然后发送给客户端连接释放数据报,并等待确认。客户端发送确认后,进入TIME_WAIT状态,但是此时TCP连接还没有释放,然后经过等待计时器设置的2MSL后,才进入到CLOSE状态。

2.为什么需要2MSL时间? 
首先,MSL即Maximum Segment Lifetime,就是最大报文生存时间,是任何报文在网络上的存在的最长时间,超过这个时间报文将被丢弃。《TCP/IP详解》中是这样描述的:MSL是任何报文段被丢弃前在网络内的最长时间。RFC 793中规定MSL为2分钟,实际应用中常用的是30秒、1分钟、2分钟等。

TCP的TIME_WAIT需要等待2MSL,当TCP的一端发起主动关闭,三次挥手完成后发送第四次挥手的ACK包后就进入这个状态,等待2MSL时间主要目的是:防止最后一个ACK包对方没有收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可以继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。

3.为什么是四次挥手,而不是三次或是五次、六次? 
双方关闭连接要经过双方都同意。所以,首先是客服端给服务器发送FIN,要求关闭连接,服务器收到后会发送一个ACK进行确认。服务器然后再发送一个FIN,客户端发送ACK确认,并进入TIME_WAIT状态。等待2MSL后自动关闭。

总结: 
(1)为了保证客户端发送的最后一个ACK报文段能够到达服务器。即最后一个确认报文可能丢失,服务器会超时重传,然后服务器发送FIN请求关闭连接,客户端发送ACK确认。一个来回是两个报文生命周期。

如果没有等待时间,发送完确认报文段就立即释放连接的话,服务器就无法重传,因此也就收不到确认,就无法按步骤进入CLOSE状态,即必须收到确认才能close。 
(2)防止已经失效的连接请求报文出现在连接中。经过2MSL,在这个连续持续的时间内,产生的所有报文段就可以都从网络消失。



UDP报头格式:

  • 来原连接端口:发送端端口(IPv4可选)
  • 目的连接端口:接收端端口
  • 报文长度:UDP首部的长度跟数据的长度之和,单位字节
  • 校验和:用于校验UDP数据报的数字段和包含UDP数据报首部的“伪首部”。
  • 伪首部,又称为伪包头(Pseudo Header):是指在TCP的分段或UDP的数据报格式中,在数据报首部前面增加源IP地址、目的IP地址、IP分组的协议字段、TCP或UDP数据报的总长度等,共12字节,所构成的扩展首部结构,如下图。此伪首部是一个临时的结构,它既不向上也不向下传递,仅仅是为了保证可以校验套接字的正确性。

 

UDP工作流程:

UDP套接口是无连接的、不可靠的数据报协议;既然他不可靠为什么还要用呢?其一:当应用程序使用广播或多播时只能使用UDP协议;其二:由于他是无连接的,所以速度快。因为UDP套接口是无连接的,如果一方的数据报丢失,那另一方将无限等待,解决办法是设置一个超时



TCP和UDP的区别:

  1.基于连接与无连接
  2.TCP要求系统资源较多,UDP较少; 
  3.UDP程序结构较简单 
  4.流模式(TCP)与数据报模式(UDP); 
  5.TCP保证数据正确性,UDP可能丢包 
  6.TCP保证数据顺序,UDP不保证 

    PS:

  1. 调用了一次write,发送了100个字节,但是对方可以分10次收完,每次10个字节;你也可以调用10次write,每次10个字节,但是对方可以一次就收完。
  2. UDP数据报模式,发几次,服务器读几次。


具体方法:

TCP: 
TCP编程的服务器端一般步骤是: 
  1、创建一个socket,用函数socket(); 
  2、设置socket属性,用函数setsockopt(); * 可选 
  3、绑定IP地址、端口等信息到socket上,用函数bind(); 
  4、开启监听,用函数listen(); 
  5、接收客户端上来的连接,用函数accept(); 
  6、收发数据,用函数send()和recv(),或者read()和write(); 
  7、关闭网络连接; 
  8、关闭监听; 

TCP编程的客户端一般步骤是: 
  1、创建一个socket,用函数socket(); 
  2、设置socket属性,用函数setsockopt();* 可选 
  3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选 
  4、设置要连接的对方的IP地址和端口等属性; 
  5、连接服务器,用函数connect(); 
  6、收发数据,用函数send()和recv(),或者read()和write(); 
  7、关闭网络连接;

UDP:
与之对应的UDP编程步骤要简单许多,分别如下: 
  UDP编程的服务器端一般步骤是: 
  1、创建一个socket,用函数socket(); 
  2、设置socket属性,用函数setsockopt();* 可选 
  3、绑定IP地址、端口等信息到socket上,用函数bind(); 
  4、循环接收数据,用函数recvfrom(); 
  5、关闭网络连接; 

UDP编程的客户端一般步骤是: 
  1、创建一个socket,用函数socket(); 
  2、设置socket属性,用函数setsockopt();* 可选 
  3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选 
  4、设置对方的IP地址和端口等属性; 
  5、发送数据,用函数sendto(); 
  6、关闭网络连接;

参考博文:

https://blog.csdn.net/Pk_zsq/article/details/6087367 https://blog.csdn.net/sinat_37138973/article/details/72822229 https://zh.wikipedia.org/wiki/%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE https://blog.csdn.net/ZWE7616175/article/details/80432486 https://www.cnblogs.com/rootq/articles/1377355.html https://blog.csdn.net/deramer1/article/details/73527336  https://www.cnblogs.com/HPAHPA/p/7737531.html https://blog.csdn.net/Li_Ning_/article/details/52117463 https://blog.csdn.net/hanchaoman/article/details/6409106

猜你喜欢

转载自blog.csdn.net/qq_19269527/article/details/84405294