前文我们介绍了UDP的代码使用,流程也是非常简单的,创建了socket,然后填充对应的信息,最后bind,双方就可以进行通信了。通过代码的编写我们发现UDP是简单的,那么我们进入到源码方面学习UDP也是较为简单的。
今天的主题就是UDP协议的相关认识,比如涉及到了源码的指针,相关报文的管理,我们都会介绍到,话不多说,进入主题吧!
首先我们清楚的知道,最开始引入端口号的时候,是为了在网络世界中建议通信,那么IP地址+端口号就标识了网络世界中的唯一一个进程。
在TCP/IP协议中,我们可以利用源IP 目的IP 源端口号 目的端口号 协议号这五元组来标识一个通信,在Xshell中我们可以使用netstat -n进行查看
然后我们在最开始介绍Socket编程的时候,我们知道了端口号是不能随便绑定的,因为端口号和服务器是强关联的,比如https服务器的默认端口号是443,http服务器的默认端口号是80,我们也可以在/etc/services中查看不同的服务器的默认端口号是多少:
所以我们有一个观点是:在编写网络通信程序的时候,要尽量避开这些知名端口号。
那么在Linux内核中,进程和port的映射是通过一张哈希表来完成的,问题来了: 1.端口号是否可以被多个进程bind? 2.一个进程是否可以bind多个端口号?
对于以上两个问题,我们要清楚的是,端口号和进程之间是要保证端口号到进程的唯一性的,既然是唯一性,那么如果一个端口号被多个进程bind了,报文不管是向上交付的时候还是向下交付的时候,都会面临一个问题是:给哪个进程?
这里就和唯一性冲突了,那么反过来,一个进程如果bind了多个端口号,那么它的唯一性是确定的,因为我们不管是通过哪个端口号,都是只找到了一个进程。
以上是我们对端口号和进程关系的一个简单思考。
在我们理解上面这张图片之前, 我们回想一个知识点,在最开始引入网络编程的时候,我们花了一番功夫认识了协议是什么,我们仅仅是基于Linux网络编程的特点,认为协议就是通信双方都认识的结构体,那么在今天,我们将正式认识这个协议。
我们今天以及后续介绍协议都是使用的版本号为2.62.32.10。
UDP协议之所以简单,我们可以通过上述图片非常直观的感受出来,仅仅是通过5元组,就确定了一个报文。
它的成员变量只有四个,分别是两个端口号 数据的长度以及校验和,那么端口号我们理解,并且我们在编写代码的时候也是使用的uint16_t来接受的端口号,校验和我们这里不管。16位的UDP长度也是非常好理解的,它就是用来标识数据后面的长度嘛,以便OS进行封装和分用。
这里2.62.32.10中UDP的结构体设计,只能说非常简单了,source和dest就标识了端口号,len标识了长度。
那么问题来了,对于UDP来说,既然它的长度标识符的是16位的,那么意味着它的最大长度也只有64K左右,如果我们发送的数据超过了64K,就会导致一次发送数据发送不完,就要导致多出来的数据无效,甚至直接在系统层面报错。这个时候就需要我们手动的拆包分包了。
那么上述是UDP源码的简单理解。
对于UDP协议来说,它的特点是无连接,不可靠传输,面向数据报。
对于无连接,我们拿TCP协议举例,TCP协议是要求进行connect,客户端和服务器进行连接,没问题再进行下一步操作,对于UDP协议来说,它只需要创建好套接字,然后填充对应的服务器信息,最后bind,bind完管你连接不连接呢,直接传输了就开始。
对于不可靠传输,因为UDP一旦传输失败,它什么也不管,也不会进行重传,这点和TCP有很大的区别,TCP有多种方案应付传输失败的情况,比如超时重传等。
对于面向数据报来说,它如果一次性发送100字节的数据,客户端就比如调用recvfrom接受100个数据,不能调用10次recvfrom接受10个字节的数据。而对于TCP来说,它100字节的数据可能分多次接受,就像打电话,别人说一句话,你没听清你可以让他再说一遍。
全双工
UDP协议也是支持全双工的,即它支持边读边写,不过它的发送缓冲区是没有TCP那种支持缓存管理机制的,比如超时重传,流量控制等机制的,所以它的缓冲区在应用层是无法感知的。这就导致了很多人认为它是没有缓冲区的,它调用逻辑是sendto之后立即交给内核,尽快发送。
UDP本身是简单的,那么我们返回到OS内核,我们不得不承认在操作系统中存在了很多的报文,这些报文有的在向上交付,有的在向下交付,那么如何管理报文本身就是一个问题,对于如何管理,我们向来都认为的是6个字:先描述再组织。
那么今天我们就简单看一下报文的源码:
我们先不管sk_buff的两个指针,在操作系统看来,管理报文如果是杂乱无章的那可就太麻烦了,所以应该使用一种数据结构进行管理,在OS中使用的是双向链表的方式。
对于报文的基本格式是:[IP Header][UDP Header][Payload(你的数据)]
而报文都是在缓冲区的,我们在这里就需要考虑报文是如何向上解包和向下封装的了,这里利用到了sk_buff的两个指针,我们只要能找到UDP在缓冲区的那一部分,我们就可以操纵UDP里面的四个字段了。
所以这里用到了两个指针,分别是head和data:
一个结构体中的两个指针就分别指向了数据和UDP,那么数据因为是我们自己加的,我们就很清楚数据的地址,那么都在报文里面,我们可以head指针可以通过sizeof(udphdr)找到对应的udp的起始地址。
然后就可以对udp里面的字段进行赋值了,并且,这是一个报文的基本操作,对于多个报文,我们就可以使用next和prev进行多个报文的管理。
以上是对报文操作的一个简单理解。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有