Go 使用 Unix 域套接字需注意:启动前用 os.RemoveAll 清理 socket 文件并校验绝对路径、父目录权限与长度限制;客户端需重试连接并显式构造 UnixAddr;通信必须处理粘包(如 4 字节长度头)和 Flush 缓冲区;关闭监听器后须手动删除 socket 文件。

Go 原生支持 Unix 域套接字(UDS),但直接用 net.Listen("unix", path) 和 net.Dial("unix", path) 很容易在生产环境出问题——不是语法错,而是路径、生命周期、粘包、清理这些环节漏了关键动作。
服务端启动前必须清理 socket 文件并校验路径
错误信息 listen unix /tmp/mysock: bind: address already in use 或 bind: invalid argument 几乎从不表示代码逻辑错误,而是路径残留或非法。
- 每次启动前先调
os.RemoveAll(sockPath),忽略os.IsNotExist错误 - 路径必须是绝对路径:用
filepath.Abs或硬编码如/run/myapp.sock,别用./sock - 检查父目录是否存在且可写:
dir := filepath.Dir(sockPath)→os.Stat(dir)+os.MkdirAll(dir, 0755) - 路径总长度 ≤ 108 字节(Linux 限制),含空格、控制符会触发
invalid argument
客户端连接失败的真正原因永远是服务端没起来
dial unix /tmp/mysock: connect: no such file or directory 这个错误只有一种解释:服务端压根没成功执行 net.ListenUnix。
- 客户端和服务端的 socket 路径字符串必须字节级完全一致(包括结尾 \x00 —— Go 标准库已处理,一般不用管)
- 别用
net.Dial("unix", path),改用显式构造:&net.UnixAddr{Net: "unix", Name: path} - 加简单重试逻辑:循环 3–5 次,每次
time.Sleep(10 * time.Millisecond),避免竞态 - 区分错误类型:
errors.Is(err, os.ErrNotExist)是路径不存在,errors.Is(err, os.ErrPermission)才是权限问题
流式通信必须自己处理粘包和 flush
UDS 的 SOCK_STREAM 和 TCP 一样是字节流,conn.Read() 不保证一次读完一条消息,也不保证一次只读一条;bufio.Writer 写完不 Flush() 就 close,消息就丢了。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 最简可靠方案:每条消息前加 4 字节大端长度头,接收方先
io.ReadFull(conn, lengthBuf),再按长度读正文 - 用
binary.Write(conn, binary.BigEndian, uint32(len(data)))发送长度头 - 所有
bufio.Writer必须在conn.Close()前调w.Flush(),否则数据滞留在内存缓冲区 -
conn.SetDeadline()对 UDS 有效,建议设读写超时防死锁(注意 Linux 上精度受限于系统定时器)
连接关闭后 socket 文件不会自动删除
listener.Close() 只关闭监听套接字,不会删磁盘上的 .sock 文件。下次启动时若不清理,就会 bind: address already in use。
务必在服务退出前执行 os.Remove(sockPath),且最好用 defer 包裹在 listener 创建之后——但要注意:如果程序 panic 或被 kill -9,仍可能残留。更稳妥的做法是在启动时无条件 os.RemoveAll,而不是依赖 shutdown 清理。


















