码迷,mamicode.com
首页 > 其他好文 > 详细

TCP/IP-UDP协议

时间:2018-03-29 00:12:23      阅读:207      评论:0      收藏:0      [点我收藏+]

标签:组播   opera   需要   相加   进程   计时   直接   监听   分割   

User Datagram Protocol 用户数据报协议

UDP是一个保留消息边界的简单的面向数据包的传输层协议。不提供:差错纠正、队列管理,重复消除流量控制和拥塞控制提供:差错检验简单来讲,就是UPD发送的数据包一个就是一个,又开始有结束(分组除外),用户写一个UDP发一个,服务端收一个。

UDP提供最小功能,因此,如果需要的话其上层需要实现想要的功能。

upd的无连接特征,比其他的传输协议使用更少的开销,广播和组播操作更多的使用udp这样的无连接传输协议。

2 upd头部

报文格式

技术分享图片

  • 端口号:帮助协议辨认发送和接受的进程,纯属抽象的,不与主机上任何物理实体相关联。端口号为16bit的无符号整数。同时,因为IP数据报在IP层根据协议分配给了TCP和UDP,因此不同的协议独立维护一整套端口号,也就是同一台主机,同一时刻可以开启TCP,UDP切指定的端口号一致。传输层利用端口号辨别数据报需要投送的进程。外界主机通过端口号来选择服务

  • 源端口号是可选的:发送者不需要接受者回复,设置为0

  • 长度UDP长度字段是,UDP头部和UDP数据的总长度,以字节为单位。但是如果不满足是8的倍数,也不会被填充。最小值为8。如果不够还不能填充,因此,UDP的长度可以是奇数。UDP的长度字段是没有必要存在的,因为IP数据报包含数据报总长度,其减去IP数据报头部,就是荷载的有效长度了。冗余的根本原因在于,upd没有可选部分,也就是udp的头部是固定长度。

  • 校验和发送端可以不校验,但是默认是要校验和。当不校验时其值为0

UDP的校验和

包括如下部分:这个结构是在计算校验和的时候构造出来了,只是校验和的时候使用,在发送的时候并不会发送这个结构。技术分享图片和ICMP的校验和相同,UDP的校验和也包含了荷载的数据。

UDP校验和不包括TTL这样,经过路由器的时候就不需要重新计算。如果通过NAT,改变目的地址,那么就需要重新计算校验和。

在计算校验和的时候,如果数据长度为奇数,那么会填充一个0,但是仅仅是计算的时候填充,发送的时候并不会填充。在计算的时候多加的12字节,(32*3bit)的伪头部,同样发送出去UDP数据报也不会包括伪头部。在校验和中加入这个伪头部的意义在于:能够让接受UDP数据报的一端能够确认自己接受到的数据包是否是发给自己的,也就是目的ip地址是自己。(可能是因为UDP数据报头部没什么辨别标志吧)。

这里UDP长度被计算了两次。计算过程应该是:

short checksum(unsigned short *buf, int nwords)
{
 //一个大于16位的变量
   unsigned long sum;
 //低位开始叠加,每次16bit
   for (sum = 0; nwords > 0; nwords--)
        sum += *buf++;
 //一个大于16位的值,如果左移16位不是0,表明发生进位
   while (sum >> 16)  
 //进位的值都是1,然后再加上原来的sum,但是使用0xffff将高位清空,也就是进位清空
 //只剩下低16位的值
        sum = (sum >> 16) + (sum & 0xffff);
 //返回得是反码
   return ~sum;
}

取反码是因为,当再次相加的时候结果就是0

如果接收端在计算校验和时,发现一个校验差错,也就是校验和不一样:校验和错误的话,接收端会丢弃该数据报,并且没有任何的差错消息。这个校验码在一定程度上也是多余的,因为在数据链路层也有crc校验

10.7分片

链路层对每一个帧的最大长度有一个上限(MTU),如果数据报长度超过该值,将会被分片,同时,数据报分片还是能够再次分片的。

分片重组

当数据报被分片了,只有他到达最终目的地时,才会被重组。原因在于,路由器充足分片会加重路由器负担。且不能保证所有分片都经过同一路由器,因此不可能在路由器重组。

报文

技术分享图片

只有第一片带有一个头部,其他的分片使用IP数据报的标识共享一个UDP头部。标识字段是每一个数据报独有一个,不会重复。

在分片的时候,标识字段被复制为同一个,标志字段指示该片是否是最后一片(0),分片偏移指示,荷载区数据在源数据报中的偏移量,单位为字节。当分片产生是,第一片的偏移量为0,荷载为1480(包括头部),第二片偏移量为1480。只要产生分片,那么除了最后一片,其他的分片大小都是8的倍数。MF表示是否是最后一片:1表示不是,0表示是最后一片。

问题是,当分片通过NAT时,这个校验和怎么计算?

IP数据报

技术分享图片

如果一片丢失,需要重传整个数据报,因此通常要避免分片。

分片传输

顺序

较大偏移量的分片比第一片优先投递,原因在于,较大的偏移量能够通知接收方,缓冲区设置为多大。但另一方面,只有第一片有端口号。重组以后才会用到,因此是可以的。

超时

每一片分片达到,IP层启动一个计数器,且新的分片到达以后不会被重置。这样计时器给出同一数据报分片之间被分割的最大时间限度。当接受第一片30s后,所有分片仍未到达,那么发送ICMP报文,通知对端超时,数据丢失,同时携带第一片的拷贝。

IP分片与ARP

当数据报分片以后,如果假设需要进行arp建立ip与mac的映射的话,过程是这样的:某一片发送arp请求报文,如果在1秒内收到,那么其他分片直接发出。否则,每一片都会发送arp请求,间隔为1s.

10.8数据报最大长度

这和分片的长度不同。IP数据报最大长度为65535出去IP报头,剩余65527字节,可供UDP使用。

但是数据报的最大长度仍被其他因素限制:

  1. 系统本地协议实现

  2. 接收方可能没准备好去处理这么大的数据缓存不够?UDP编程接口允许应用程序指定每次网络操作完成是返回的最大字节数,如果超过,就会出现API截断,也就是超出的数据直接丢弃了。

10.11UDP服务器设计

IP地址与UDP端口号

我们需要拿到发送方的端口号和IP地址,而这需要操作系统以其他的方式告诉进程。因为发送给进程的数据,已经不在包含IP数据报头部,和UDP数据报头部,只有荷载数据。也就是通常的accept()

限制本地IP地址

主要使用在一台服务器有多个网卡,多个对外地址时。UDP服务器通常时期本地IP具有通配符的特点,也就是bind()时不绑定具体的IP。这样,所有能够达到的数据报都会被接受。如果指定了具体的IP,那么只有数据报的目的地址是指定IP的数据报才能被UDP接收,否则直接丢弃。产生一个ICMP端口不可达。

多地址

多个ip公用一个端口同一个主机上的多个服务器,可以同时绑定同一个端口。也就是多个服务端程序同时监听一个端口。需要告诉系统允许重用端口需要指定bind()时的IP地址,同时SO_REUSEADDR需要被设置。这个场景中潜在的条件是:服务器有多个ip,然后着多个ip都绑定了相同的端口。当同时又指定ip的服务器和统配符的ip,那么指定ip的服务器具有较高的优先级。

限制远端IP

只接受指定IP的主机发送的信息。

同一端口多服务程序监听

默认同一端口对应一个套接字(IP:port),但是仍然可以在同一ip地址和端口上启动多个服务器程序。在组播的情况下,需要设置SO_REUSEPORT,当数据报到达时,系统将一个数据报拷贝多分分别送给不同的服务器程序。

服务器共用套接字

一个ip地址,一个端口,绑定了多个服务器。(可能是多个进程)

同时需要开启SO_REUSEADDR当一个udp数据报到到达服务器的时候,该服务其只有一个进程能够接收到数据报,当服务器上多个进程都在监听同一个套接字,那么只有一个进程能够接收到数据报。当队列溢出是,超额的数据被丢弃。没有ICMP报文UDP没有拥塞控制,发送端没有合适的机制进行控制。称为拥塞控制缺失

SO_REUSEADDR和SO_REUSEPORT异同

TCP/UDP连接是被一个五元组确定的:五元组是通信术语。通常是指源IP地址,源端口,目的IP地址,目的端口和传输层协议,,任何两个连接都不可能拥有相同的五元组,否则系统将无法区别这两个连接

当使用socket()函数创建套接字的时候,我们就指定了该套接字使用的protocol(协议),bind()函数设置了源地址和源端口号,而目的地址和目的端口号则由connect()函数设定。

由于UDP是一个无连接协议,UDP套接字仍然可以不经连接就使用。"未连接"的UDP套接字在数据被第一次发送之前并不会绑定,只有在发送的时候被系统自动绑定,因此未绑定的UDP套接字也就无法收到(回复)数据。未绑定的TCP也一样,它将在连接的时候自动绑定。

默认情况下,任意两个socket都无法绑定到相同的源IP地址和源端口(即源地址和源端口号均相同)。只要源端口号不相同,那么源地址实际上没什么关系。将socketA绑定到地址A和端口X (A:X),socketB绑定到地址B和端口Y (B:Y),只要X != Y,那么这种绑定都是可行的。然而当X==Y的时候只要A != B,这种绑定方式也仍然可行,比如:一个FTP server的socketA绑定为192.168.0.1:21而属于另一个FTP server的socketB绑定为 10.0.0.1:21,这两个绑定都将成功。记住:一个socket可能绑定到本地"any address"。例如一个socket绑定为 0.0.0.0:21,那么它同时绑定了所有的本地地址,在这种情况下,不论其它的socket选择什么特定的IP地址,它们都无法绑定到21端口,因为0.0.0.0和所有的本地地址都会冲突。

如果在绑定一个socket之前设置了SO_REUSEADDR,除非两个socket绑定的源地址和端口号都一样,那么这两个绑定都是可行的。关键是SO_REUSEADDR改变了在处理源地址冲突时对通配地址("any ip address")的处理方式。

当没有设置SO_REUSEADDR的时候,socketA先绑定到0.0.0.0:21,然后socketB绑定到192.168.0.1:21的时候将会失败(EADDRINUSE错误),因为0.0.0.0意味着"任意本地IP地址”,也就是"所有本地IP地址“,因此包括192.168.0.1在内的所有IP地址都被认为是已经使用了。但是在设置SO_REUSEADDR之后socketB的绑定将会成功,因为0.0.0.0和192.168.0.1事实上不是同一个IP地址,一个是代表所有地址的通配地址,另一个是一个具体的地址

一个socket有一个发送缓冲区,当调用send()函数成功后,这并不意味着所有数据都真正被发送出去了,它只意味着数据都被送到了发送缓冲区中。对于UDP socket来说,如果不是立刻发送的话,数据通常也会很快的发送出去,但对于TCP socket,在数据加入到缓冲区和真正被发送出去之间的时延会相当长。这就导致当我们close一个TCP socket的时候,可能在发送缓冲区中保存着等待发送的数据(由于send()成功返回,因此你也许认为数据已经被发送了)。如果TCP的实现是立刻关闭socket,那么所有这些数据都会丢失而你的程序根本不可能知道。TCP被称为可靠协议,像这种丢失数据的方式就不那么可靠了。这也是为什么当我们close一个TCP socket的时候,如果它仍然有数据等待发送,那么该socket会进入TIME_WAIT状态(这里应该不是啊!!!)。这种状态将持续到数据被全部发送或者发生超时。

在内核彻底关闭socket之前等待的总时间(不管是否有数据在发送缓冲区中等待发送)叫做Linger Time。Linger Time在大部分系统上都是一个全局性的配置项而且在默认情况下时间相当长(在大部分系统上是两分钟)。当然对于每个socket我们也可以使用socket选项SO_LINGER进行配置,可以将等待时间设置的更长一点儿或更短一点儿甚至禁用它。禁用Linger

Time绝对是一个坏主意,虽然优雅的关闭socket是一个稍微复杂的过程并且涉及到来回的发送数据包(以及在数据包丢失后重发它们),并且这个过程还受到Linger Time的限制。如果禁用Linger Time,socket可能丢失的不仅仅是待发送的数据,而且还会粗暴的关闭socket,在绝大部分情况下,都不应该这样使用

拥塞控制

UPD服务器大多是迭代的,也就是说只有一个线程在服务有所的udp连接,而系统为每一个UDP端口维持一个有限大小的队列。UPD连接自动排入队列中,接收到UDP数据报以他们达到的顺序被传给应用程序(进程或进程)。UPD不提供流量控制,当UDP处理队列满时,超额数据包被丢弃,且没有ICMP差错报文,

TCP/IP-UDP协议

标签:组播   opera   需要   相加   进程   计时   直接   监听   分割   

原文地址:https://www.cnblogs.com/perfy576/p/8666705.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!