什么是数据报套接字?
数据报套接字 => Datagram Socket 流套接字 => Stream Socket
创建一个数据报套接字与创建一个流套接字类似, 主要的区别是数据报套接字使用 UDP 协议而不是 TCP 协议,这意味着客户端和服务器不需要在发送数据之前建立连接。
这对于不需要维护连接的应用程序很有用,因为客户端和服务器可以在不必首先建立连接的情况下发送和接收数据。 例如,DNS 服务器使用 UDP 协议,因为它不需要维护连接,只需要发送数据并接收响应。
工作流程

image
图片来源: UDP Server-Client implementation in C
Server 如何工作?
Listener 通过创建一个套接字,将其绑定到一个端口,并等待传入的消息来工作。
首先,listener 创建一个 hints 结构体,该结构体包含了创建套接字所需的信息,例如协议族,套接字类型,以及端口号。随后调用 getaddrinfo() 函数获取地址信息,保存到 servinfo 链表中。
然后,listener 遍历 servinfo 链表,直到找到可以使用的地址信息,然后通过 socket() 函数创建套接字,如果成功,返回套接字描述符,如果失败,返回 -1。
然后,listener 调用 bind 函数将套接字绑定到指定的端口。这允许 listener 接收发送到该端口的消息。
在套接字绑定成功后,程序将监听来自 recvfrom 函数的传入消息。recvfrom 函数将阻塞程序,直到收到消息,将消息存储在 buf 缓冲区中,并将发送者的地址信息存储在 their_addr 中,最后将收到的消息和发送者的 IP 地址打印到屏幕上。
最后,listener 关闭套接字并退出。
Client 如何工作?
Talker 通过设置套接字,向监听器发送消息,关闭套接字来完成工作。
首先,talker 创建一个 hints 结构体,用于保存地址信息,例如协议族,套接字类型,以及端口号。随后调用 getaddrinfo() 函数获取地址信息,保存到 servinfo 链表中。
然后,talker 遍历 servinfo 链表,直到找到可以使用的地址信息,然后通过 socket() 函数创建套接字,如果成功,返回套接字描述符,如果失败,返回 -1。
在成功创建套接字后,talker 调用 sendto() 函数通过套接字发送消息,如果成功,返回发送的字节数,如果失败,返回 -1。
最后,talker 关闭套接字。
服务端 demo
客户端 demo
程序编译
gcc -o talker talker.c
gcc -o listener listener.c
运行与输出
./listener
# 在运行 talker 之前, listener 会一直等待
listener: waiting to recvfrom...
# 在另一个终端运行 talker 程序之后, listener 会输出以下内容
listener: got packet from ::1
listener: packet is 5 bytes long
listener: packet contains "hello"
# 另一个终端运行 talker 程序
./talker localhost hello
talker: sent 5 bytes to localhost
本文总结
本文的代码实现了一个简单的 UDP 客户端和服务器端程序, 客户端程序是 talker, 服务器端程序是 listener.
运行 listener 程序, 然后运行 talker 程序. listener 程序会等待来自 talker 程序的消息. 当 talker 程序运行时, 它会发送一个消息给 listener 程序, listener 程序会打印这个消息.
如果没有运行 listener 程序, talker 程序会发送消息, 然后退出. 这些消息会丢失, 因为没有程序来接收它们.
UDP 协议是一个简单的, 轻量级的协议, 它很容易使用. 但是, 它不提供任何错误检查或恢复, 并且不能保证将消息传递到目的地, 因此它不适合需要可靠通信的应用程序.