基础知识
UDP 输出 TCP/IP 协议分层中的传输层。
UDP 是一个不可靠的通信协议,没有重传和确认,没有有序控制,也没有拥塞控制。
UDP 不保证报文的有效传递,不保证报文的有序,也就是说使用 UDP 的时候,我们需要做好丢包、重传、报文组装等工作。
UDP 比较简单,适合的场景还是比较多的,我们常见的 DNS 服务,SNMP 服务都是基于 UDP 协议的,这些场景对时延、丢包都不是特别敏感。另外多人通信的场景,如聊天室、多人游戏等,也都会使用到 UDP 协议。
报文格式
UDP 报头长 8 字节,分别是源端口、目的端口、UDP 报文长度、校验和。
因为 UDP 报文长度只用 2 个字节记录,所以包含报头长度的报文最大长度为 65535 字节。
用UDP协议发送时,用sendto函数最大能发送数据的长度为:65535- IP头(20) - UDP头(8)=65507字节。用sendto函数发送数据时,如果发送数据长度大于该值,则函数会返回错误。
由于IP有最大MTU,因此,
UDP 包的大小应该是 1500 - IP头(20) - UDP头(8) = 1472(Bytes)
UDP 编程
服务器:
1、创建 socket。
2、绑定要监听的 ip 和 port。
3、循环:
3.1、调用 recvfrom 读取接收到的报文,如果没有报文则阻塞在这里。
3.2、收到报文处理完后调用 sendto 将相应发给客户端。
客户端:
1、创建 socket。
2、循环:
2.1、调用 sendto 发送请求。
2.2、调用 recvfrom 接收相应。
1 |
|
代码参考我之前的文章:UDP 回显程序https://blog.csdn.net/Hanoi_ahoj/article/details/105358383
UDP 报文的“无连接”的特点,可以在 UDP 服务器重启之后,继续进行报文的发送,这就是 UDP 报文“无上下文”的最好说明。
有连接的 UDP
通过上文,在 UDP 中是不需要类似于 TCP 编程中的 connect 建立连接的。
其实 UDP 也可以是 “有连接” 的。
下面通过一个程序来测试一下:
客户端: 需要注意的是在创建完 socket 后进行了 connect,绑定了服务器的 ip 和 port。
1 | // UDP connect 测试客户端 |
服务器: 就是正常的服务器,收到请求原封不动返回。
1 |
|
1 | gcc client.c -o client |
测试:
1、不运行 server,只运行 client。
可以看到,在调用 sendto 的时候发送了,但是当走到 recvfrom 的时候出错 Connection refused。
2、运行 server 和 client。
正常的接收请求、处理请求过程。
UDP connect 的作用
不用 connect 的话,不开启 server,运行 client,程序会阻塞在 recvfrom 上。直到服务器重启或者超时。
通常 UDP 的服务器是不需要进行 connect 的,因为 connect 以后这个服务器就只能对这个客户端进行服务器了。
==connect 的作用就是让程序尽早收到错误信息返回:==
通过对 UDP 套接字进行 connect 操作,将 UDP 套接字建立了“上下文”,该套接字和服务器端的地址和端口产生了联系,正是这种绑定关系给了操作系统内核必要的信息,能够将操作系统内核收到的信息和对应的套接字进行关联。
调用 sendto 或者 send 操作函数时,应用程序报文被发送,我们的应用程序返回,操作系统内核接管了该报文,之后操作系统开始尝试往对应的地址和端口发送,因为对应的地址和端口不可达,一个 ICMP 报文会返回给操作系统内核,该 ICMP 报文含有目的地址和端口等信息。
进行了 connect 操作,帮助操作系统内核从容建立了(UDP 套接字——目的地址 + 端口)之间的映射关系,当收到一个 ICMP 不可达报文时,操作系统内核可以从映射表中找出是哪个 UDP 套接字拥有该目的地址和端口,别忘了套接字在操作系统内部是全局唯一的,当我们在该套接字上再次调用 recvfrom 或 recv 方法时,就可以收到操作系统内核返回的“Connection Refused”的信息。
在对 UDP 进行 connect 之后,关于收发函数的使用,很多书籍是这样推荐的:
使用 send 或 write 函数来发送,如果使用 sendto 需要把相关的 to 地址信息置零;
使用 recv 或 read 函数来接收,如果使用 recvfrom 需要把对应的 from 地址信息置零。
其实不同的 UNIX 实现对此表现出来的行为不尽相同。
==效率因素:==
因为如果不使用 connect 方式,每次发送报文都会需要这样的过程:
连接套接字→发送报文→断开套接字→连接套接字→发送报文→断开套接字 →………
而如果使用 connect 方式,就会变成下面这样:
连接套接字→发送报文→发送报文→……→最后断开套接字
我们知道,连接套接字是需要一定开销的,比如需要查找路由表信息。所以,UDP 客户端程序通过 connect 可以获得一定的性能提升。
参考:极客时间 - 网络编程实战(https://time.geekbang.org/column/article/129807)
EOF