UDP协议
应用层
正确读取和写入,读取我们讲了,正确写入还没说。
传输层
整个TCP/IP协议栈一共四层协议
应用层在用户层实现
中间两次是在内核实现
链路层是在驱动层实现
我们学习网络,学习传输层本质就是在学习Linux内核的网络部分
传输层是OS内部,所以提供系统调用供上层进行正常数据读取
socket套接字,listen,read,write…
再谈端口号
服务器可能会绑定各种应用层服务,包括FTP,SSH登录协议,HTTP协议,每个底层协议都是TCP,每个应用层服务都绑定确定的端口号
端口的意义是将数据根据端口号做区分的。
比如说服务器启动了http客户端,服务器可能是多线程版服务器,他会接收到来自客户端发来的各种http请求,客户端的端口号可能一样,但IP地址不一样,就可以区分不同的主机了。
可能客户端A发起很多次Http请求,可能会有画面1,画面2但浏览器进程只有一个,客户端A用两个不同画面访问服务器。那服务器响应时用目标Ip地址都是同一个,那你怎么区分响应是给画面1还是画面2呢,我们有端口号。
IP标识全网唯一主机
端口标识该主机唯一进程
本质上就是客户端进程和服务端进程双方通过网络发送消息
现在问题转为我们只要有一对IP和端口(客户端和服务器),再带上我们两个想用什么样的协议来通信,此时我们就完整的描述清楚客户端和服务器通信的过程。
今天我们加个东西,往后我们用 “源IP”, “源端口号”, “目的IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信
那两台主机要通信,那两个进程要通信,通过什么协议来通信,我们就可以把他们标识清楚了。
怎么查后面再说
未来对应的TCP ip协议在内核中传输层和网络层协议他重点要给我们提供使用五元组来通信的功能,那协议怎么办呢?,协议我们用协议号或者协议的端口号。
先验性的给大家画了一堆报头,TCP报头里面有源端口目的端口,IP报文里有源IP目的IP,里面也有TCP,双方就可以通过五元组来表示一对通信了。
总结
不谈应用层如何序列化,反序列化,应用层都不考虑,单纯站在两个主机通信角度,我们是需要五元组的,分别由不同层不同协议来给我们提供对应的字段,提供对应的功能,来保证通信的正常
毕竟通信如果都不正常,你订协议序列反序列化没有任何意义。
端口范围划分
端口不支持绑定特殊端口,除非root
0 - 1023: 知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的
1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号
客户端绑定端口是随机的
知名端口号
知名端口号相当于火警和119
netstat
默认查看本地和网络通信,有些查不到你要用root用户查
unix 域间套接字 本地通信
iostat 查io设备
pidof
如果我们想杀掉一堆Http的进程pid
,你无法通过管道交给kill -9直接杀,你得用xargs来杀,因为Kill -9 pid ,kill是可执行程序,-9 pid是命令行参数,管道把一堆Pid以标准输入的方式交给Kill是不行的,必须要xargs来交给命令行参数,此时就可以批量化删除
管道是前一个的标准输出,变为下一个的标准输入
我们今天没这么麻烦
pidof httpd
直接得到进程相关的所有pid
未来讲解协议的宏观思路,最根本是A
任何一种协议,下三层协议我永远都会先让你回答两个问题
A
1。报头和有效载荷如何分离(网络计算器用的是长度\n,http用的是空行,应用层也要做这个工作)
2。有效载荷应该交付给哪一个上层协议
下三层每一层协议脑袋上都有协议,所以收到一个报文时,有效载荷应该交付给哪一个上层协议(对应的协议字段,方案)
B
认识报头
C
学习该协议周边的问题
UDP协议
报头就是现在这样子
整个报文的宽度是0-31也就是4字节
UDP报头是由OS自动给我们加的,也就是用UDP ,sendto把报文发出去,发个你好,你好就在数据里,OS会添加报头。
所以把报头加上之后未来报头和有效载荷如何分离呢?
从UDP来看,1,2,3,4这四个字段(每个16位也就是2字节)是UDP的8字节报头,所以UDP如何报头和有效载荷分离呢?
非常简单采用的是定长报头
UDP双方OS约定是定长前八个字节就是UDP报头,所以收到UDP报文前8个字节直接拿走就是报头,剩下的就是有效载荷。
第二个我们收到报文,我怎知道我收到UDP报文要交给上层哪个协议呢?
报头中包含16位源端口,和16位目的端口号,我收到一个报文,根据目的端口号,交给对应的进程,通过端口号标定目标进程。
所以如何将报文有效载荷交给上层哪个协议?
根据目的端口号来做。
你看到这个报文是什么感觉呢?
我发现我没有很好的切入点理解图里这个所谓的报头
如何理解呢?
UDP协议
源端口号,目的端口号,那谁来填?
源端口号客户端发送数据时随机形成端口,OS内部添加报头直接添加端口号。
目的端口号命令行参数你传的。
端口号都是16位的,因为内核中是16位,协议定的是16位,所以取值范围是65535。
现在能够收到UDP报文把报头部分和有效载荷部分能分开了,可是你怎么知道网络通信时你的报文数据是全的,数据有没有可能出问题呢?
所以报头有16位UDP长度,指的是整个报文的长度,如果写的是16位有效载荷长度指的是正文部分。
但因为报头是定长的,所以16位UDP长度就可以表示整个报文的长度-8字节就是有效载荷的长度。
所以当我收到UDP报文我要对正文数据做提取,用UDP长度-8就是对应数据的长度。
这个UDP长度是给OS看的,告诉OS我这个报文应该是多少,长度不符合要求就要把报文直接丢弃。
下一个16位UDP检验和,数据传输中数据少了,二进制级别比特位翻转,UDP是可以检验出来的,虽然UDP不保证可靠性,但他要保证数据是对的,所以他要检验,检验成功之后整个报文,报头和有效载荷分离,有效载荷交付给上层,这就叫UDP报文
那检验失败呢?UDP把报文直接丢弃了,无论是客户端还是服务器,丢弃后对方知道丢了吗,不知道,他也根本不需要知道。
他要做的只是把报文丢弃掉,并不通知对方重新发送,这叫做不保证可靠性。
这叫做UDP报头
UDP特点
1 无连接,没有connnect
2 不可靠,UDP可能丢包,乱码,检验失败,发送数据量过大,但UDP都不做处理。
3 面向数据报
你发几次,对方就必须收几次
一个UDP报文和另一个UDP报文没有直接关系的,报文和报文之间必须有明显边界,保证读取是一个完整的报文。
UDP原样发送,既不拆分,也不合并
如果发送端调用一次sendto, 发送100个字节, 那么接收端也必须调用对应的一次recvfrom,接收100个字节
你曾经发了100个,各自是什么,收的时候也必须收100个各自是什么,这种特征叫做UDP面向数据报。
这个道理就像寄信,写邮件,我给你发,你要吗收到一份,你不会收到半份邮件。你发了10分,你看到的也是10封,而且你必须一个一个看。
每个报文之间不会出现TCP那里对读取内容进行数据分析,报文和报文之间用\n隔开,UDP不需要做这个工作,UDP要进行网络通信不需要保证自己读到一个完整报文还是没读到,直接读到一个报文就一定是完整的,你直接对该报文反序列化就可以。
发送的时候直接序列化,直接做字符串发出去,对方收到这个字符串直接反序列化,不用做过多的io处理。
把网络版本计算器变成UDP版,把自定义协议报头去掉了,只需要“1 + 1”直接字符串发。
UDP缓冲区
TCP双方皆有收发缓冲区。
所以TCP既有收又有发
UDP协议只有接受缓冲区,需要这个缓冲区来保存用户来不及处理的报文。
但是UDP没有发送缓冲区,因为他不需要,客户端发数据只要把报文交给UDP,UDP直接添加报头直接向下交付直接就发了。
UDP在内核中,横线上方是用户层,下面是UDP缓冲区发送流程
UDP不保证可靠性,所以UDP接受缓冲区收到的报文,
客户端向服务器连续发送了10个报文,对方必须收10个报文,发送的时候顺序是12345678910,收的时候是不是同样顺序呢?
先出发的不一定先到,对方收到的顺序于发送顺序不一致。
这种情况称为数据发送时报文发生了乱序。
乱序本身就表明本次发送过程是不可靠的。
乱序是不可靠的一种场景
UDP接受缓冲区顺序如果乱序了,UDP不做处理直接向上交付。
另外一种情况,客户端给服务器发消息,量很大,导致接受缓冲区被写满,UDP不可靠,再到达的数据直接被接收方OS丢弃
1。不通知
2。收到了也就丢了
就这叫UDP不保证可靠性
UDP套接字既能读,也能写
我们之前实现了本地版多线程聊天室UDP,所以一个线程读,一个线程写,证明UDP套接字全双工。
16位UDP长度
单个UDP报文最大是2^16=64x1024=64KB
如果通过UDP报文发送一个报文超过64K了我还能用不?
能,但是你要发送这么大数据,你就必须在应用层把报文拆成64,64,不能发送超过64字节的大小。
至此UDP协议学完了,很简单吧。
理解并不难, 因为我们有套接字使用经历。
可是原理说完,我们看这个报头图不是很理解,因为这个图叫示意图。
所以该如何理解ta呢?
UDP协议的本质是双方OS所做的约定,无论是Windows还是Linux。
Linux系统是C语言写的。所以这个图
数据就是比如“helloworld” {json} "协议字符串“”
可是数据上方的8字节字段是什么东西呢?
UDP的报头本质是什么东西呢?
协议本质是一种约定,用户层定协议都用结构化字段,只不过为了方便对协议做变更,所以应用层有序列化,反序列化的工作的。
Linux是C语言写的,对方的Linux系统也是C写的。
所以UDP所谓的报头字段,源端口目的端口,长度,检验和最终怎么表示他整个UDP,添加报头,这个报头是什么东西呢?
在Linux内核里协议的报头采用的是结构体来构建的报头
所以
struct udp_header
{
uint32_t src_port:16;
uint32_t dst_port:16;
uint32_t length:16;
uint32_t check_code:16;
}
用Struct结构体定义出来的类型,语言上叫位段类型,协议上叫做UDP报头。
所以你现在能不能理解UDP报头宽度0-31?
就是uint32_t的宽度
原端口和目的端口各占16位,
所以内核的所有协议定的时候其实就是定义出来的结构化的字段。
每个字段代表什么含义双方约定好。
这个东西在C语言叫做自定义类型
是类型,可以定义变量,开辟空间吗???
可以
所以搞一个UDP报头
struct udp_header h;
这就叫我定义了一个UDP报头,然后我要填充UDP报头
h.src_port = 1234;
h.dst_port = 8888;
h…
作为用户想用UDP传送“hello world”
所以什么叫做封装呢?
OS内为网络文件提供的缓冲区(其实是构建了)
进程正在使用UDP向对方发送UDP请求,张三王五都在发送UDP报文,UDP没有发送缓冲区,但还是要在内核里进行流动,因为他要向下层交付,所以注定了OS内部网络通信期间一定存在大量udp报文,所以OS要不要对同时存在的UDP报文进行管理呢?
要,怎么管理呢?
先描述在组织。
所以你所理解的一个报文呢,在内核里是有对应的一个描述结构体来描述报文的,
sk_buff结构体
描述报文的缓冲区
所以构建UDP报文,在内核层面包括未来的TCP一个道理,在内核层面定义一段缓冲区,也叫一个报文的封装,我们在缓冲区里把struct upd_head h拷贝到缓冲区,用户空间内容拷贝到缓冲区,此时缓冲区使用情况就知道了
start指向缓冲区开头,end指向结尾,pos指向报文的有效部分。
此时就构建好了。一个报文就有了。
未来OS可能存在大量的UDP报文没有发送出去,所以此时再把sk_buff用链表的形式管理起来。
在内核中,有很多报文没有来得及发,我们对UDP报文的管理转化成了sk_buff对应描述结构体的管理。
所以理解UDP报文,虽然代码没看过,但如果订协议来让你把内核实现,虽然做的不是最优方案,但是我有思路。
今天是UDP明天就是TCP,所以能够很好的对它做管理。
对报头有了这样的认识之后,重新看UDP协议,OS收到一个报文实际上收到一个sk_buff,收到报文交给UDP接受缓冲区,如果缓冲区满了把报文丢弃,就是把sk_buff结构体对象释放掉,sk_buff指向的报头和有效载荷缓冲区释放掉
学完UDP协议其实并不理解,但借鉴学习系统的思路,就六个字放之四海而皆准,就可以把UDP报文在OS内部怎么做的,立马找个OS的裂缝进去就初步理解UDP报文,未来把报文像下层交付,不就是把结构体sk_buff交给IP层所对应的一套方法,对sk_buff指向的缓冲区的数据在做封装不就可以了吗
原文地址:https://blog.csdn.net/weixin_50809457/article/details/144290016
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!