unix domain socket 使用需严守路径合法性、文件生命周期和消息边界三原则:路径须绝对且≤108字节,启动前清理并验证父目录可写;客户端路径须与服务端字节级一致;流式通信必须自定义消息协议(如4字节长度头),否则必然粘包。

net.ListenUnix 和 net.DialUnix 能直接用,但不按规范走,十有八九在 bind: invalid argument 或 connect: no such file or directory 上卡住——问题几乎全出在路径、权限和消息边界上,不是代码写错。
服务端监听前必须确保路径合法且父目录可写
Linux 对 Unix domain socket 路径长度硬限制为 108 字节,且只接受绝对路径、无空格、无控制符。相对路径如 "./sock" 或 "sock" 会直接触发 bind: invalid argument。
- 启动前先调
os.RemoveAll(sockPath),否则残留文件导致address already in use(实际是文件已存在) - 用
filepath.Abs(sockPath)转成绝对路径,别依赖当前工作目录 - 检查父目录是否存在且可写:
os.Stat(filepath.Dir(sockPath))+ 尝试写一个临时文件验证 - 避免用
/root、/proc等受限路径;/tmp可能被PrivateTmp=true隔离,systemd 场景优先选/run/myapp.sock
客户端连接失败,90% 是路径不一致或服务端没就绪
dial unix /tmp/mysock: connect: no such file or directory 这个错误几乎一定表示服务端压根没成功 ListenUnix,而不是客户端写错了地址。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 客户端和服务端的
Name字符串必须字节级完全相同——包括是否以/结尾、大小写、编码,"/tmp/sock"和"/tmp/sock/"是两个不同路径 - 别用
net.Dial("unix", path),显式用&net.UnixAddr{Net: "unix", Name: path}更可控 - 加简单重试逻辑:循环 3 次,每次
time.Sleep(10 * time.Millisecond),避开启动竞态 - 手动执行
ls -l /path/to/your.sock,确认类型是s(socket),不是普通文件
流式通信必须自己处理消息边界,否则必然粘包
Unix domain socket 默认是 SOCK_STREAM,和 TCP 一样无消息边界。不加协议,conn.Read() 可能一次读到多条消息,或只读到半条。
- 最简方案:每条消息前加 4 字节大端长度,
binary.Write(conn, binary.BigEndian, uint32(len(data))),接收方先io.ReadFull(conn, header[:4])再读正文 - 别用
conn.Read(buf)直接读满缓冲区——它不保证读完一整条消息,得配合io.ReadFull或手动循环 - 若能接受不可靠性,可用
net.ListenUnixgram+net.DialUnixgram,天然保消息边界,但不保证送达、不保序,且需手动管理地址 -
bufio.Writer必须显式Flush(),否则数据卡在内存里发不出去
连接关闭后 socket 文件不会自动删除
listener.Close() 只关监听器,不删 socket 文件;conn.Close() 也不动它。残留文件会导致下次启动失败。
- 务必在服务退出前调
os.Remove(sockPath),并忽略os.IsNotExist错误 - 如果清理失败(比如被其他进程占用),用
lsof -U | grep myapp查残留句柄 - 不要依赖
defer os.Remove()—— panic 时可能不执行,建议在 main 函数末尾或 signal handler 中做 -
net.UnixListener不支持SetDeadline,只能对accept后的*net.UnixConn设置SetReadDeadline,防死锁
路径合法性、文件生命周期、消息协议——这三块不抠清楚,Unix domain socket 在 Go 里就是个定时炸弹。尤其要注意:错误信息看起来像网络问题,根源却在文件系统权限和字符串一致性上。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










