
本文介绍了如何在Go语言中使用Unix Socket构建一个简单的回声客户端和服务器。通过示例代码详细展示了服务器如何监听Unix Socket,接收客户端数据并原样返回,以及客户端如何连接服务器并读取返回的数据。同时,强调了客户端需要读取服务器响应的重要性,并提供了完整的、可运行的示例代码,帮助开发者快速上手Unix Socket编程。
Unix Socket是同一主机上进程间通信(IPC)的一种方式,相比于TCP Socket,它避免了网络协议栈的开销,因此效率更高。在Go语言中,可以使用net包来操作Unix Socket。下面我们将分别展示服务器端和客户端的实现。
服务器端实现
服务器端的主要职责是监听指定的Unix Socket文件,接受客户端连接,并创建一个goroutine来处理每个连接。在处理连接的goroutine中,服务器读取客户端发送的数据,然后将数据原样写回客户端。
package main
import (
"log"
"net"
)
func echoServer(c net.Conn) {
defer c.Close() // 确保连接关闭
for {
buf := make([]byte, 512)
nr, err := c.Read(buf)
if err != nil {
// 连接关闭或发生错误时退出循环
log.Println("Connection closed or read error:", err)
return
}
data := buf[:nr]
log.Printf("Server received: %s\n", string(data))
nw, err := c.Write(data)
if err != nil {
log.Println("Write error:", err)
return
}
if nw != nr {
log.Println("Write incomplete")
return
}
}
}
func main() {
socketPath := "/tmp/echo.sock"
l, err := net.Listen("unix", socketPath)
if err != nil {
log.Fatal("Listen error:", err)
}
defer l.Close() // 确保listener关闭
log.Println("Server listening on", socketPath)
for {
fd, err := l.Accept()
if err != nil {
log.Println("Accept error:", err)
continue // 继续监听其他连接
}
log.Println("Accepted connection from", fd.RemoteAddr())
go echoServer(fd)
}
}代码解释:
立即学习“go语言免费学习笔记(深入)”;
- net.Listen("unix", socketPath): 创建一个Unix Socket监听器,socketPath指定了Socket文件的路径。
- l.Accept(): 接受客户端的连接请求,返回一个net.Conn对象,代表一个连接。
- echoServer(fd): 启动一个goroutine来处理客户端连接。
- c.Read(buf): 从连接中读取数据。
- c.Write(data): 将数据写回连接。
- defer c.Close(): 使用defer语句确保在函数退出时关闭连接,释放资源。
- 错误处理:增加了详细的错误日志,方便调试。
- 完整性校验:增加了写入完整性校验,确保写入的数据和读取的数据长度一致。
客户端实现
客户端需要连接到服务器监听的Unix Socket文件,然后发送数据,并读取服务器返回的数据。
package main
import (
"bufio"
"fmt"
"io"
"log"
"net"
"os"
"time"
)
func reader(r io.Reader) {
buf := bufio.NewReader(r)
for {
line, err := buf.ReadString('\n')
if err != nil {
// 连接关闭或发生错误时退出循环
log.Println("Connection closed or read error:", err)
return
}
fmt.Printf("Client received: %s", line)
}
}
func main() {
socketPath := "/tmp/echo.sock"
c, err := net.Dial("unix", socketPath)
if err != nil {
log.Fatalf("Dial error: %v", err)
os.Exit(1) // 退出程序
}
defer c.Close()
log.Println("Connected to server")
go reader(c)
for {
message := "hi\n" // 添加换行符
_, err := c.Write([]byte(message))
if err != nil {
log.Println("Write error:", err)
break
}
log.Println("Client sent:", message)
time.Sleep(time.Second)
}
}代码解释:
立即学习“go语言免费学习笔记(深入)”;
- net.Dial("unix", socketPath): 连接到指定的Unix Socket文件。
- c.Write([]byte("hi\n")): 向服务器发送数据,注意添加换行符,以便bufio.ReadString('\n')可以正确读取一行。
- reader(c): 启动一个goroutine来读取服务器返回的数据。
- bufio.NewReader(r): 使用bufio.Reader来缓冲读取,提高效率。
- buf.ReadString('\n'): 读取一行数据,直到遇到换行符。
- 错误处理:增加了详细的错误日志,方便调试。
- 退出程序:在连接失败时,使用os.Exit(1)退出程序。
- 添加换行符:在客户端发送的消息末尾添加换行符,以便服务器端的bufio.ReadString('\n')可以正确读取一行。
注意事项:
- 确保Unix Socket文件存在,并且客户端和服务端有相应的权限进行读写。
- 如果服务器端和客户端运行在不同的用户下,可能需要修改Socket文件的权限,或者使用相同的用户运行。
- 在客户端,需要读取服务器返回的数据,否则客户端可能会一直阻塞在Write操作上。
- 在使用bufio.ReadString('\n')读取数据时,需要在发送的数据末尾添加换行符。
总结:
通过本文的示例代码,我们了解了如何在Go语言中使用Unix Socket构建一个简单的回声客户端和服务器。Unix Socket是一种高效的进程间通信方式,在需要高性能的本地通信场景中非常有用。掌握Unix Socket编程,可以帮助我们构建更高效、更可靠的应用程序。










