一、问题背景

实习所在公司的A、B两个应用部署在同一台机器上,之前是采用http://127.0.0.1:xxx进行调用,协议选用tcp。而对于同一台机器的进程间通信来说,tcp协议为了确保传输的可靠性带来了一些不必要的负担,因此计划用unix domain socket“取代”tcp。此外采用此种方式也方便后续运维人员维护。

二、什么是unix socket

关注重点在于:进程间通信,同一台主机。更详细的可以自行上网搜索。

三、原生的unix socket通信方式

go中原生的net包包含了对unix socket通信的封装,关键语句为:

服务端:

const sockAddr="file path"
l, err := net.Listen("unix", SockAddr)

注意同tcp方式不同在于,这里的地址不再是一个类似127.0.0.1:xxx这样的端口号,而是一个.sock文件的地址,文件地址可以自己选定。

设定地址和sock文件名后,服务端启动成功后会在对应目录生成该sock文件。

由于已经存在该文件,服务会启动失败,因此在每次启动前都应该先移除该文件。

os.Remove(sockAddr)

注意使用Remove要慎重,更改参数要小心,不要一不小心把自己本身重要的东西删除了

然后监听成功后就可以进行对应响应了:

for {
        conn, err := l.Accept()
        if err != nil {
            log.Fatal("accept error:", err)
        }

        go handler(conn)
    }
func handler(conn *net.UnixConn){
    ...
}

客户端:

关键语句为:

client, err := net.Dial("unix", sockAddr)

四、http+unix socket通信,服务端基于gin框架

​ 由于http协议位于应用程序,tcp位于传输层,socket位于两层之间,因此http和unix socket是并不冲突的,虽然只依靠unix socket就能实现两个进程间的通信,但是考虑到原有项目是基于http的,因此还是需要保留http调用方式(否则项目改动过大)。

并且原先服务端是采用gin框架做http路由调用,因此这里仍然不变。

实现的一个小demo如下:

服务端代码:

package main

import (
   "fmt"
   "github.com/gin-gonic/gin"
   "net"
   "net/http"
   "os"
)
const sockAddr="D:/GoCode/go/src/http_unix_socket_demo/http.sock"
func main(){
   router:=gin.New()
   router.GET("/testGet",handlerGet)
   os.Remove(sockAddr)
   unixAddr, err := net.ResolveUnixAddr("unix", sockAddr)
   if err!=nil{
      fmt.Println(err)
      return
   }

   listener, err := net.ListenUnix("unix", unixAddr)
   if err!=nil{
      fmt.Println("listening error:",err)
   }
   fmt.Println("listening...")
   http.Serve(listener,router)
}

func handlerGet(c *gin.Context){
   c.JSON(http.StatusOK,gin.H{
      "resp":"ok",
   })
}

客户端代码:

由于客户端服务端在同一个module下面,因此将客户端写在Test方法里面。

package main

import (
   "context"
   "fmt"
   "net"
   "net/http"
   "testing"
)


func TestClientGet(t *testing.T){
   httpc := http.Client{
      Transport: &http.Transport{
         DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
            return net.Dial("unix",sockAddr)
         },
      },
   }

   resp, err := httpc.Get("http://http.sock/testGet")
   if err!=nil{
      t.Fail()
      return
   }
   fmt.Println(resp)
}

五、demo的实现移步到项目中

这里初步在两个项目中实现了一个接口的unix socket通信,还存在很多瑕疵,细节后续会完善。

然后对实现的该接口进行了两组测试对比。

原项目采用http+tcp的测试结果如下:

在这里插入图片描述

目前采用http+unix socket的测试结果如下:
在这里插入图片描述

六、结论

可以看出在同等条件下,unix socket确实比tcp socket快上一点,多次测试的也是如此。这里大概快了七分之一左右。也印证了tcp保证的可靠传输(校验和,流量控制等)确实会导致一些效率上的牺牲。

七、后续工作

后续需要调通java端和golang端的unix socket 通信