您现在的位置是:首页 >技术教程 >【网络】-- UDP协议网站首页技术教程
【网络】-- UDP协议
目录
认识知名端口号(Well-Know Port Number)
传输层
再谈端口号
说白了就是进行通讯的不同的进程。所以当底层收到数据时,其如果只知道目标地址,其根据IP地址就能找到对应的主机。但是接下来需要知道的就是,这个数据报应该向上递交给哪一个进程,即根据对应的Port端口号。
端口号范围划分
认识知名端口号(Well-Know Port Number)
- ssh服务器,使用22端口
- ftp服务器,使用21端口
- telnet服务器,使用23端口
- http服务器,使用80端口
- https服务器,使用443
Note: 我们自己写一个程序使用端口号时,要避开这些知名端口号。
远程利用windows使用Xshell软件,连接远在外省的机器,因为我们使用的Xshell时客户端,而远端的服务器周而复始的,以启动一个绑定端口是22号的服务:
进一步查看该服务:
这个服务以d结尾,表示是守护进程(一般都是,不是强制规定),也可以看见其的SID与PID是相等的。
- 守护进程:这个进程不退出,一直在后端提供服务。
所以,当我们用Xshell连接之后,我们就能直接登上了,当我们启动的时候,一登陆成功,这个sshd就会在终端当中,为我们形成加载bash进程,然后给我们打出命令符。让我们进行输指令。
然后将我们的指令字符串不断以网络发送的形式发送给服务器,然后让服务端执行,然后执行完再将结果给我们。
两个问题
#问: 一个进程是否可以bind多个端口号?
可以的!
#问: 一个端口号是否可以被多个进程bind?
不能!
融汇贯通的理解:
一个端口号被建立好并和进程绑定,而当有一个报文到了的时候,其是如何将对应的数据交给对应的进程的?
常识:一个进程在进行服务的时候,除了listen套接字之外,每一次获取新连接(都会得到一个新的sockfd),对应的sockfd就是一个文件描述符。
换句话说:其实在我们看来一个连接可以说就是一个文件,所以所谓的收到数据的本质就是:将数据放到对应的文件的接收缓冲区里,然后当前进程通过文件描述符进行文件的读取(因为进程与文件是通过文件描述表建立的关系)。无论未来进程想读取数据还是从底层网络里将数据拿上来,我们只要找到了进程,我们就能找到给进程所相关的文件缓冲区。
所以:对于如何将底层TCP、UDP收到的数据交给特定的网络连接,其实就是转换成了:如何通过底层收到的端口号(携带端口号的相关报文),交付给特定进程。
其做法:在内核当中其是采取哈希映射的关系,可以理解为key值为端口号,value值为进程的PCB的地址,所以当底层一旦收到数据时,系统当中直接查哈希表,直接找到对应的进程,然后就可以通过我们(我们在网络操作中,调用系统调用读数据的时候传的就是文件描述符)文件描述符,直接放在特定的文件当中,然后进程在上层用户调用read的时候就可以读取到了。
netstat
- n 拒绝显示别名,能显示数字的全部转化成数字。
- l 仅列出有在 Listen (监听) 的服务状态。
- p 显示建立相关链接的程序名。
- t (tcp)仅显示tcp相关选项。
- u (udp)仅显示udp相关选项。
- a (all)显示所有选项,默认不显示LISTEN相关。
pidof
其中UDP就属于传输层,并且端口号也是在传输层起作用。
UDP协议
#问:几乎任何协议都要首先解决的两个问题:a、如何分离(封装)? b、如何交付?
- 16位源端口号:表示数据从哪里来。
- 16位目的端口号:表示数据要到哪里去。
- 16位UDP长度:表示整个数据报(UDP首部+UDP数据)的长度。
- 16位UDP检验和:如果UDP报文的检验和出错,就会直接将报文丢弃。
- a、如何分离(封装)?
采用固定报头长度的策略(8个字节)。因为udp是不可靠协议,只要能够保证把数据能够交到对方主机(当然不保证也可以,反正只要发出去)就可以了,所以是固定长度。
可以保证:将报头和有效载荷的分离。
- b、如何交付?
根据报头中的16位端口号,进行向上交付。因为进程bind了端口号!之所以可以,就是因为这是一个约定,是一个客户端与服务端都知道,并且必须遵守的约定,这就叫做 "协议" 。
总结:
- 所以对于应用层的代码编写的时候,都是用uint16_t类型来接收端口号。
- udp通过固定长度的报头中提取到16位的UDP长度,以此知道如何正确的提取整个完整的报文数据。
即:根据上述可知,UDP是具有将报文一个一个正确接收的能力的!则,UDP是面向数据报的。
#问:如何理解udp报文的本身?
底层中,这里所谓的报头实际上叫做:struct udp_hdr。传说中的udp报头,以及未来的所有报头,其实就是一个结构体类型(位断)。
struct udp_hdr
{
uint32_t src_port:16;
uint32_t dst_port:16;
uint32_t udp_len:16;
uint32_t udp_check:16;
}
UDP的特点
UDP传输的过程类似于寄信。
- 无连接:知道对端的IP和端口号就直接进行传输,不需要建立连接。
- 不可靠:没有确认机制,没有重传机制。如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息。
不可靠:
指的是中性的特点,没有贬义的意思。在长距离传输的时候,如果要保证可靠性,一定意味着要做更多的工作,就意味着匹配的协议在实践上更复杂,其由于更加的复杂度,所以,其在使用成本以及维护成本上都是更高的。
所以不可靠并不是十分的不好,一定原则上来说,反而更简单。
- 面向数据报: 不能够灵活的控制读写数据的次数和数量。
面向数据报:
应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并。
用UDP传输100个字节的数据:
- 如果发送端调用一次sendto,发送100个字节,那么接收端也必须调用对应的一次recvfrom,接收100个字节。
- 而不能循环调用10次recvfrom,每次接收10个字节。
怎么发就必须怎么收,发的报文必须是一个完整的报文。
#问:sendto / recvfrom / write / read / recv / send ……io类接口?
在以往看来:这些函数是在网络中进行数据的收发(根本不是!)
这些函数,其实本质是:拷贝函数!
应用层用的都是系统调用接口,而目前我们所用的所有协议(通讯原则),全部用的是UDP / TCP提供的接口,所以内核层中的缓冲区一般用的都是传输层协议提供的。
也就是说:对应传输层 -> 传输,即:缓冲区数据什么时候发,发多少,出错了怎么办,由其关心并处理。所以我们在应用层所写的代码,本质上:还是系统编程。我们将数据交给了参数层的缓冲区,并没有发送出去。
UDP的缓冲区
UDP没有真正意义上的发送缓冲区。因为调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作。所以其对于发送缓冲区并没有特别强的需求,反正有数据就往下交。
但是,UDP需要具有一定的接收缓冲区。因为UDP上面就是应用层,应用层就是用户,如果程序员来不及调用recvfrom(因为,同时可能会到来很多UDP报文),来不及接收就有可能导致有些报文直接就被丢弃了,UDP不可靠,但是也要为丢包所带来的成本负责。(一个报文从一台主机传输到另一台主机,在传输过程中会消耗主机资源和网络资源。如果UDP收到一个报文后仅仅因为上次收到的报文没有被上层读取,而被迫丢弃一个可能并没有错误的报文,这就是在浪费主机资源和网络资源)
这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致。如果缓冲区满了,再到达的UDP数据就会被丢弃。
UDP的socket既能读,也能写,这个概念叫做:全双工。
全双工 VS 半双工:
全双工 :一个文件描述符,如果既能读,同时又能写(可以理解为两个线程,同时一个线程读,一个线程写)。保证:只要接收缓冲区和发送缓冲区不冲突即可,不要用同一个缓冲区。
半双工:一个文件描述符,再任意时刻,要么只能读,要么只能写,不能二者同时并行(可以理解为两个人正常聊天,只能其中一个人在说话)。
UDP使用注意事项
我们注意到,UDP协议首部中有一个16位的最大长度,也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部)。
然而64K在当今的互联网环境下,是一个非常小的数字。
如果我们需要传输的数据超过64K,就需要在应用层手动的分包,多次发送,并在接收端手动拼装。
基于UDP的应用层协议
- NFS:网络文件系统。
- TFTP:简单文件传输协议。
- DHCP:动态主机配置协议(比如:笔记本连接网络前,上不了网,本质:没有IP地址。当连上网后,笔记本会自动获取一个IP地址,是路由器给的IP地址,就是路由器中部署了一种服务:DHCP)。
- BOOTP:启动协议(用于无盘设备启动)。
- DNS:域名解析协议。
同时,也包括我们字节所写得UDP程序时,自定义的应用层协议。