本篇文章简单描述了 UDP 传输协议的工作原理及特点.
理解 UDP
UDP 和 TCP 一样同属于 TCP/IP 协议栈的第二层, 即传输层.
UDP 套接字的特点
UDP 的工作方式类似于传统的信件邮寄过程. 寄信前应先在信封上填好寄信人和收信人的地址, 之后贴上邮票放进邮筒即可. 当然信件邮寄过程可能会发生丢失, 我们也无法随时知晓对方是否已收到信件. 也就是说信件是一种不可靠的传输方式, 同样的, UDP 所提供的也是一种不可靠的数据传输方式 (以信件类比 UDP 只是通信形式上一致性, 之前也以电话通信的方式类比了 TCP 的通信方式, 而实际上从通信速度上来讲 UDP 通常是要快于 TCP 的; 每次交换的数据量越大, TCP 的传输速率就越接近于 UDP). 因此, 如果仅考虑可靠性, TCP 显然由于 UDP; 但 UDP 在通信结构上较 TCP 更为简洁, 通常性能也要优于 TCP.
区分 TCP 和 UDP 最重要的标志是流控制, 流控制赋予了 TCP 可靠性的特点, 也说 TCP 的生命在于流控制.
UDP 内部工作原理
与 TCP 不同, UDP 不会进行流控制, 其在数据通信中的作用如下图所示. 可以看出, IP 的作用就是让离开主机 B 的 UDP 数据包准确传递到主机 A, 而 UDP 则是把 UDP 包最终交给主机 A 的某一 UDP 套接字. UDP 最重要的作用就是根据端口号将传输到主机的数据包交付给最终的 UDP 套接字.
数据包传输过程 UDP 和 IP 的作用
UDP 的高效使用
TCP 用于对可靠性要求较高的场景, 比如要传输一个重要文件或是压缩包, 这种情况往往丢失一个数据包就会引起严重的问题; 而对于多媒体数据来说, 丢失一部分数据包并没有太大问题, 因为实时性更为重要, 速度就成为了重要考虑因素. TCP 慢于 UDP 主要在于以下两点:
收发数据前后进行的连接及清理过程
收发数据过程中为保证可靠性而添加的流控制
因此, 如果收发的数据量小但需要频繁的连接时, UDP 比 TCP 更为高效.
基于 UDP 的服务器端 / 客户端
和 TCP 不同, UDP 服务器端 / 客户端并不需要在连接状态下交换数据, UDP 的通信只有创建套接字和数据交换的过程. TCP 套接字是一对一的关系, 且服务器端还需要一个额外的 TCP 套接字用于监听连接请求; 而 UDP 通信中, 无论服务器端还是客户端都只需要一个套接字即可, 且可以实现一对多的通信关系. 下图展示了一个 UDP 套接字与两台主机进行数据交换的过程.
UDP 套接字通信模型
基于 UDP 的数据 I/O 函数
TCP 套接字建立连接之后, 数据传输过程便无需额外添加地址信息, 因为 TCP 套接字会保持与对端的连接状态; 而 UDP 则没有这种连接状态, 因此每次数据交换过程都需要添加目标地址信息. 下面是 UDP 套接字数据传输函数, 与 TCP 传输函数最大的区别在于, 该函数需要额外添加传递目标的地址信息.
- #include <sys/socket.h>
- ssize_t sendto(int sock, void *buff, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
-> 成功时返回传输的字节数, 失败时返回 - 1
由于 UDP 数据的发送端并不固定, 因此, UDP 套接字的数据接收函数定义了存储发送端地址信息的数据结构.
- #include <sys/socket.h>
- ssize_t recvfrom(int sock, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t addrlen);
-> 成功时返回接收的字节数, 失败时返回 - 1
基于 UDP 的回声服务器端 / 客户端
UDP 通信函数调用流程
UDP 不同于 TCP, 不存在请求连接和受理连接的过程, 因此某种意义上并没有明确的服务器端和客户端之分. 如下是示例源码.
uecho_server
uecho_client
示例代码运行结果
UDP 客户端套接字的地址分配
从上述示例源码来看, 服务器端 UDP 套接字需要手动 bind 地址信息, 而客户端 UDP 套接字则无此过程. 我们已经知道, 客户端 TCP 套接字是在调用 connect 函数的时机, 由操作系统为我们自动绑定了地址信息; 而客户端 UDP 套接字同样存在该过程, 如果没有手动 bind 地址信息, 则在首次调用 sendto 函数时自动分配 IP 和端口号等地址信息. 和 TCP 一样, IP 是主机 IP, 端口号则随机分配 (客户的临时端口是在第一次调用 sendto 时一次性选定, 不能改变; 然而客户的 IP 地址却可以随客户发送的每个 UDP 数据报而变动 (如果客户没有绑定一个具体的 IP 地址到其套接字上). 其原因在于如果客户主机是多宿的, 客户有可能在两个目的地之间交替选择).
UDP 数据传输特性和 connect 函数调用
之前我们介绍了 TCP 传输数据不存在数据边界, 下面将会验证 UDP 数据传输存在数据边界的特性, 并介绍 UDP 传输调用 connect 函数的作用.
存在数据边界的 UDP 套接字
UDP 协议具有数据边界, 这就意味着数据交换的双方输入函数和输出函数必须一一对应, 这样才能保证可完整接收数据. 如下是验证 UDP 存在数据边界的示例源码.
bound_hostA
bound_hostB
示例代码运行结果
调用 connect 函数的 UDP 套接字
TCP 套接字需要手动注册传输数据的目标 IP 和端口号, 而 UDP 则是调用 sendto 函数时自动完成目标地址信息的注册, 该过程如下
第一阶段: 向 UDP 套接字注册目标 IP 和端口号
第二阶段: 传输数据
第三阶段: 删除 UDP 套接字中注册的目标地址信息
每次调用 sendto 函数都会重复执行以上过程, 这也是为什么同一个 UDP 套接字可和不同目标进行数据交换的原因. 像 UDP 这种未注册目标地址信息的套接字称为未连接套接字, 而 TCP 这种注册了目标地址信息的套接字称为已连接 connected 套接字. 当需要和同一目标主机进行长时间通信时, UDP 的这种无连接的特点则会非常低效. 通过调用 connect 函数使 UDP 变为已连接套接字则会有效改善这一点, 因为上述的第一阶段和第三阶段会占用整个通信过程近 1/3 的时间.
针对 UDP 套接字调用 connect 函数并非真的与目标 UDP 套接字建立连接, 仅仅是向本端 UDP 套接字注册了目标 IP 和端口号信息而已. 已连接的 UDP 套接字不仅可以使用之前的 sendto 和 recvfrom 函数, 还可以使用没有地址信息参数的 write 和 read 函数. 修改之前的 ucheo_client 代码为已连接 UDP 套接字如下.
uecho_con_client
来源: http://www.bubuko.com/infodetail-3357157.html