- 人工智能
- AI 技能
- 文档
- 开发工具
【免费下载链接】DocAgent
导读
本文聚焦 Cangjie 标准库std.net中的UnixDatagramSocket(Unix 域数据报套接字),通过一个完整的可运行示例,讲解如何在同一台主机上的进程之间以数据报(datagram)形式收发报文。读完本文,你将掌握UnixDatagramSocket的创建、绑定、连接、发送与接收的完整调用流程,理解它与 UDP、TCP 及面向流的UnixSocket的差异,并能据此编写自己的进程间通信程序。
示例源码位于仓库 std/net/net_samples/unix_datagram.md,对应的完整类 API 说明见 UnixDatagramSocket 类文档。
一、UDS 与 UnixDatagramSocket 是什么
1.1 UDS:本机进程间通信的高效通道
Unix Domain Socket(UDS,Unix 域套接字)是用于在同一台计算机上的进程之间通信的机制。与网络套接字不同,UDS 不需要网络协议栈和网络设备,因此通信更快、延迟更低、吞吐量更高。它通过文件系统路径(而不是 IP 地址和端口号)来标识通信端点。
std.net包同时支持 UDP / TCP / UDS 三种 Socket 类型。其中UnixDatagramSocket属于数据报(datagram)类别,与之对应的还有面向流的UnixSocket(流式客户端)与UnixServerSocket(流式服务端)。从包概览中的类继承关系可以看出整体结构:
Resource ├StreamingSocket │ ├TcpSocket │ └UnixSocket ├DatagramSocket │ ├UdpSocket │ └UnixDatagramSocket └ServerSocket ├TcpServerSocket └UnixServerSocket(继承关系见 std/net/net_package_overview.md)
1.2 UnixDatagramSocket 的特点
按照类文档的描述,UnixDatagramSocket提供基于数据包的主机通讯能力,其关键特性包括:
- 创建后需显式
bind():实例创建后,应当显式调用bind()接口绑定到本地文件路径; - 无连接或可选连接:Unix 数据包套接字本身不需要连接、不需要与远端多次握手,但用户也可以通过
connect/disconnect接口与远端建连和断连; - 无固定数据包大小上限:不同于 UDP(一次最多处理 64KB 数据包),UDS 没有数据包大小限制,限制来源于操作系统和接口实现;
- 资源需显式回收:套接字资源需要用
close接口显式释放; - 平台限制:该类型不支持在 Windows 平台上运行。
数据报传输本身不是可靠的传输,不保证传输顺序;数据包大小在发送端决定,一端发送了 10 字节和 15 字节的报文,对端将收到相同大小的对应报文(相关语义见 DatagramSocket 接口)。
二、完整示例:服务端与客户端的数据报收发
下面直接给出仓库中的完整示例(见 unix_datagram.md)。它演示了一个典型场景:客户端向服务端发送 3 字节报文[1, 2, 3],服务端校验接收内容与客户端地址。
import std.net.* import std.sync.* import std.fs.* import std.random.* import std.env.* let barrier = Barrier(2) func createTempFile(): String { let tempDir: Path = getTempDirectory() let index: String = Random().nextUInt64().toString() return tempDir.join("tmp${index}").toString() } func runUnixDatagramServer(serverPath: String, clientPath: String) { try (serverSocket = UnixDatagramSocket(bindAt: serverPath)) { serverSocket.bind() barrier.wait() let buf = Array<Byte>(3, repeat: 0) let (clientAddr, read) = serverSocket.receiveFrom(buf) if (read == 3 && buf == [1, 2, 3]) { println("server received") } if (clientAddr.toString() == clientPath) { println("client address correct") } } } main(): Int64 { let clientPath = createTempFile() let serverPath = createTempFile() let fut = spawn { runUnixDatagramServer(serverPath, clientPath) } barrier.wait() try (unixSocket = UnixDatagramSocket(bindAt: clientPath)) { unixSocket.sendTimeout = Duration.second * 2 unixSocket.bind() unixSocket.connect(serverPath) unixSocket.send([1, 2, 3]) } fut.get() return 0 }运行结果:
server received client address correct三、逐段拆解示例的调用流程
3.1 准备:为两端生成独立的套接字文件路径
示例先通过createTempFile()生成两个唯一的临时路径,分别作为服务端(serverPath)与客户端(clientPath)的套接字文件路径:
func createTempFile(): String { let tempDir: Path = getTempDirectory() let index: String = Random().nextUInt64().toString() return tempDir.join("tmp${index}").toString() }其思路是:利用std.random的Random().nextUInt64()生成随机数拼接为文件名,放在系统临时目录(getTempDirectory(),来自std.env)下,从而避免与已有文件冲突——因为bind()要求目标路径当前不存在,路径已存在会直接绑定失败。
3.2 服务端:创建、绑定并阻塞接收
func runUnixDatagramServer(serverPath: String, clientPath: String) { try (serverSocket = UnixDatagramSocket(bindAt: serverPath)) { serverSocket.bind() barrier.wait() let buf = Array<Byte>(3, repeat: 0) let (clientAddr, read) = serverSocket.receiveFrom(buf) ... } }几个关键点:
- 构造函数:
UnixDatagramSocket(bindAt: serverPath)创建未连接的实例。构造函数接受bindAt!: String(文件路径)或bindAt!: SocketAddress(套接字地址)两种形式,且要求地址当前不存在,在bind时会创建对应的套接字文件(见 net_package_classes.md 中init(String)/init(SocketAddress)); bind():显式绑定套接字并创建监听队列。bind会自动在本地地址创建一个套接字文件,若文件已存在则绑定失败;失败后需要close套接字,不支持多次重试。该文件可通过 POSIX 的isSock()判断是否存在、用unlink()删除;receiveFrom(buf):阻塞式收取报文。返回值是(SocketAddress, Int64)二元组——报文发送端地址与实际收取到的报文大小。注意buf需要有合适的大小,否则报文会被截断且返回值可能大于buf大小;读取超时会抛出SocketTimeoutException。
服务端收到的报文来自已bind的客户端套接字,因此clientAddr.toString()应与clientPath相等——这正是示例中第二个断言所验证的内容。
3.3 客户端:创建、绑定、连接并发送
try (unixSocket = UnixDatagramSocket(bindAt: clientPath)) { unixSocket.sendTimeout = Duration.second * 2 unixSocket.bind() unixSocket.connect(serverPath) unixSocket.send([1, 2, 3]) }sendTimeout:设置send/sendTo操作超时时间。这里设为Duration.second * 2(2 秒)。超时时间过小会被设置为最小时钟周期值,过大则设置为最大超时时间(2^63-1 纳秒),默认值为None(不超时);设为负数会抛出IllegalArgumentException;bind():客户端同样需要先绑定自己的地址,这样服务端才能通过receiveFrom拿到客户端的地址;connect(remotePath: String):连接特定远端地址(即服务端路径)。该重载必须在bind后调用;连接后仅接受来自该远端地址的报文。类文档同时提示:连接后端口开始接收 ICMP 报文,若收到异常报文,可能导致后续send/sendTo执行失败;send([1, 2, 3]):将报文发送到connect连接的地址。若payload大小超出系统限制或系统发送失败,会抛出SocketException。
值得注意的是,示例中客户端使用了
connect + send的组合(面向单一对端的快捷路径)。UnixDatagramSocket同样支持无连接的sendTo(recipient: SocketAddress, payload)向任意对端发送,以及receive从已连接地址收取报文。disconnect()可撤销连接配置,它允许在connect前调用、也允许多次调用。
3.4 同步与并发控制
示例用std.sync的Barrier(2)让主线程与spawn出来的服务端协程/线程互相等待:
- 服务端在
bind()后barrier.wait(); - 主线程在
spawn后barrier.wait(); - 两边都到达屏障后才继续,从而保证「先绑定服务端、再启动客户端」的执行顺序,避免客户端发送时报文无处可收;
- 最后通过
fut.get()等待服务端协程执行完毕,再返回0。
3.5 资源管理:try-with-resources 自动关闭
两端均使用try (sock = UnixDatagramSocket(...)) { ... }的写法,退出作用域时自动调用close()释放套接字。close之后,除close/isClosed外的所有操作均不允许再调用;该接口允许多次调用(见 UnixDatagramSocket 类文档)。
四、与 UDP、面向流 UnixSocket 的对比
4.1 UnixDatagramSocket vs UdpSocket
两者都实现DatagramSocket接口(receiveFrom/sendTo/localAddress/remoteAddress/receiveTimeout/sendTimeout),但差异明显:
| 维度 | UdpSocket | UnixDatagramSocket |
|---|---|---|
| 通信范围 | 跨主机(IP + 端口) | 仅本机(文件系统路径) |
| 地址形式 | IPSocketAddress("127.0.0.1", port) | 文件路径字符串 /SocketAddress |
| 数据包大小 | 最大约 64KB | 无固定限制,取决于操作系统与实现 |
| 运行平台 | 跨平台 | 不支持 Windows |
作为参照,UDP 示例见 udp.md:其服务端用UdpSocket(bindAt: SERVER_PORT)+receiveFrom收包,客户端用UdpSocket(bindAt: 0)(随机端口)+sendTo(IPSocketAddress(...), [1, 2, 3])发包。与 UnixDatagramSocket 示例对比可以看出,数据报套接字的「绑定—接收 / 绑定—发送」骨架是一致的,差异主要体现在地址的构造方式上。
4.2 UnixDatagramSocket vs UnixSocket
UnixSocket是面向连接的流式套接字(继承StreamingSocket),需要通过accept()/connect()建立连接后以流的方式read/write,适合连续字节流场景;而UnixDatagramSocket以报文为边界,每条报文独立收发、大小由发送端决定。流式 UDS 的完整示例见 unix.md,其中服务端用UnixServerSocket监听、accept()后write("hello".toArray()),客户端UnixSocket(SOCKET_PATH)连接后read读取——收发模式与数据报有明显区别。选择哪一种,取决于应用是「消息边界明确、可容忍丢失的数据报」还是「需要可靠有序字节流」。
五、使用要点与注意事项
- 路径唯一性:
bind()前目标文件路径必须不存在,否则抛出SocketException;绑定失败后需close,不能重试。生产代码可用isSock()检查、unlink()清理残留的套接字文件(相关函数见 POSIX 包文档)。 - 先 bind 再 connect:
connect(remotePath: String)必须在bind之后调用;若使用connect(remote: SocketAddress)重载则默认执行bind,无需额外调用。 - 超时配置:
sendTimeout/receiveTimeout默认None(无限等待),可设置为Duration;为负值抛出IllegalArgumentException。示例中设置 2 秒发送超时,是一种避免长期阻塞的实用做法。 - 缓冲大小:
receiveFrom的buffer应足够大以容纳完整报文,否则报文会被截断(返回值大于 buffer 长度);可通过receiveBufferSize/sendBufferSize调整SO_RCVBUF/SO_SNDBUF,具体生效情况取决于系统。 - 平台与异常:
UnixDatagramSocket不支持 Windows;收发失败、超时会分别抛出SocketException、SocketTimeoutException(见 net 包异常文档)。 - 资源回收:务必通过
close()(或try表达式)显式关闭套接字,释放系统资源。
六、进一步阅读
- 类完整 API(构造函数、bind/connect/disconnect/send/sendTo/receive/receiveFrom 及各属性):UnixDatagramSocket 类文档
- 数据报套接字通用接口语义:DatagramSocket 接口
std.net包整体架构与 Socket 继承关系:net 包概览- 同类对比示例:UDP 使用示例、UNIX(流式)使用示例、TCP 使用示例、Socket 选项使用示例
- 人工智能
- AI 技能
- 文档
- 开发工具
【免费下载链接】DocAgent
相关推荐
仓颉 std.net 的 UnixDatagramSocket 详解:基于 UDS 数据报的进程间通信实战
仓颉 std.net 的 UnixDatagramSocket 详解:基于 UDS 数据报的进程间通信实战 Unix Domain Socket(UDS)数据报
人工智能AI 技能文档开发工具仓颉 std.net UnixDatagramSocket 使用指南:基于 Unix 域数据报套接字的进程间通信实战
仓颉 std.net UnixDatagramSocket 使用指南:基于 Unix 域数据报套接字的进程间通信实战 本篇技术指南以仓颉编程语言运行时与标准库(
编程语言语言运行时标准库垃圾回收并发编程DocAgent 标准库 std.net 实战:Cangjie IPv6Address 类全解析与编程指南
DocAgent 标准库 std.net 实战:Cangjie IPv6Address 类全解析与编程指南 本文以 std.net https://link.g
人工智能AI 技能文档开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考