Go unix domain socket通信

简介: Go unix domain socket通信

Go unix domain socket通信

  ‍

  socket大家应该很熟悉,以tcp/ip协议族为传输协议,用于跨主机通信,而unixsocket就是在socket的框架上发展出一种IPC机制(进程间通信),UDS(UNIX Domain Socket)提供面向流和面向数据包两种API接口,类似于TCP和UDP,其中SOCK_STREAM是很可靠的,消息既不会丢失也不会顺序错乱,比传统的socket效率更高,一般是tcp传输的两倍,并且不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程

  unixsocket服务端和客户端连接及发送消息的过程如下:

  ​image

  ‍

C实现客户端与服务器

server

#include <sys/types.h>  
#include <sys/socket.h>  
#include <sys/un.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  

int main()  
{
   
     
  /* delete the socket file */  
  unlink("server_socket");  

  /* create a socket */  
  int server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);  

  struct sockaddr_un server_addr;  
  server_addr.sun_family = AF_UNIX;  
  strcpy(server_addr.sun_path, "server_socket");  

  /* bind with the local file */  
  bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));  

  /* listen */  
  listen(server_sockfd, 5);  

  char ch;  
  int client_sockfd;  
  struct sockaddr_un client_addr;  
  socklen_t len = sizeof(client_addr);  
  while(1)  
  {
   
     
    printf("server waiting:\n");  

    /* accept a connection */  
    client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);  

    /* exchange data */  
    read(client_sockfd, &ch, 1);  
    printf("get char from client: %c\n", ch);  
    ++ch;  
    write(client_sockfd, &ch, 1);  

    /* close the socket */  
    close(client_sockfd);  
  }  

  return 0;  
}

socket

  函数原型:

int socket(int domain, int type, int protocol)
  • domain:指定socket所属的域,常用的是AF_UNIX或AF_INET

    • AF_UNIX表示以文件方式创建socket,
    • AF_INET表示以端口方式创建socket
  • type:指定socket的类型,可以是SOCK_STREAM或SOCK_DGRAM

    • SOCK_STREAM:表示创建一个有序的,可靠的,面向连接的socket,因此如果我们要使用TCP,就应该指定为SOCK_STREAM
    • SOCK_DGRAM:表示创建一个不可靠的,无连接的socket,因此如果我们要使用UDP,就应该指定为SOCK_DGRAM
  • protocol:指定socket的协议类型,我们一般指定为0表示由第一第二两个参数自动选择。
  • socket()函数返回新创建的socket,出错则返回-1

地址格式

  地址格式:

  常用的有两种socket域:AF_UNIX或AF_INET,因此就有两种地址格式:sockaddr_un和sockaddr_in,分别定义如下:

struct sockaddr_un  
{
   
     
  sa_family_t sun_family;  /* AF_UNIX */  
  char sun_path[];         // socket的本地文件名
}
struct sockaddr_in  
{
   
     
  short int sin_family;          /* AF_INET */  
  unsigned short int sin_port;   // socket 端口号
  struct in_addr sin_addr;       // socket ip地址
}  

// 其中in_addr正是用来描述一个ip地址的:
struct in_addr  
{
   
     
  unsigned long int s_addr;  
}

bind

  创建完一个socket后,我们需要使用bind将其绑定:

int bind(int socket, const struct sockaddr * address, size_t address_len)

  如果我们使用AF_UNIX来创建socket,相应的地址格式是sockaddr_un,而如果我们使用AF_INET来创建socket,相应的地址格式是sockaddr_in,因此我们需要将其强制转换为sockaddr这一通用的地址格式类型,而sockaddr_un中的sun_family和sockaddr_in中的sin_family分别说明了它的地址格式类型,因此bind()函数就知道它的真实的地址格式。第三个参数address_len则指明了真实的地址格式的长度。

  bind()函数正确返回0,出错返回-1

listen

  接下来我们需要开始监听了:

int listen(int socket, int backlog)

  backlog:等待连接的最大个数,如果超过了这个数值,则后续的请求连接将被拒绝

  listen()函数正确返回0,出错返回-1

accept

  接受连接:

int accept(int socket, struct sockaddr * address, size_t * address_len)

  同样,第二个参数也是一个通用地址格式类型,这意味着我们需要进行强制类型转化

  这里需要注意的是,address是一个传出参数,它保存着接受连接的客户端的地址,如果我们不需要,将address置为NULL即可。

  address_len:我们期望的地址结构的长度,注意,这是一个传入和传出参数,传入时指定我们期望的地址结构的长度,如果多于这个值,则会被截断,而当accept()函数返回时,address_len会被设置为客户端连接的地址结构的实际长度。

  另外如果没有客户端连接时,accept()函数会阻塞

  accept()函数成功时返回新创建的socket描述符,出错时返回-1

read/write

  可以通过read/write来传递数据

close

  通信完成后,我们需要关闭socket:

  int close(int fd)

  close是一个通用函数(和read,write一样),不仅可以关闭文件描述符,还可以关闭socket描述符

client

#include <sys/types.h>  
#include <sys/socket.h>  
#include <sys/un.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  

int main()  
{
   
     
  /* create a socket */  
  int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);  


  struct sockaddr_un address;  
  address.sun_family = AF_UNIX;  
  strcpy(address.sun_path, "server_socket");  

  /* connect to the server */  
  int result = connect(sockfd, (struct sockaddr *)&address, sizeof(address));  
  if(result == -1)  
  {
   
     
    perror("connect failed: ");  
    exit(1);  
  }  

  /* exchange data */  
  char ch = 'A';  
  write(sockfd, &ch, 1);  
  read(sockfd, &ch, 1);  
  printf("get char from server: %c\n", ch);  

  /* close the socket */  
  close(sockfd);  

  return 0;  
}

connect

  客户端通过connect()函数与服务器连接:

int connect(int socket, const struct sockaddr * address, size_t address_len)

  对于第二个参数,我们同样需要强制类型转换

  address_len指明了地址结构的长度

  connect()函数成功时返回0,出错时返回-1

makefile

all: tcp_client.c tcp_server.c  
    gcc -g -Wall -o tcp_client tcp_client.c  
    gcc -g -Wall -o tcp_server tcp_server.c  

clean:  
    rm -rf *.o tcp_client tcp_server

Go实现客户端与服务器

服务器

package main

import (
        "bufio"
        "fmt"
        "net"
        "time"
)

func main() {
   
   
        var unixAddr *net.UnixAddr

        unixAddr, _ = net.ResolveUnixAddr("unix", "/tmp/unix_sock")

        unixListener, _ := net.ListenUnix("unix", unixAddr)

        defer unixListener.Close()

        for {
   
   
                unixConn, err := unixListener.AcceptUnix()
                if err != nil {
   
   
                        continue
                }

                fmt.Println("A client connected : " + unixConn.RemoteAddr().String())
                go unixPipe(unixConn)
        }

}

func unixPipe(conn *net.UnixConn) {
   
   
        ipStr := conn.RemoteAddr().String()
        defer func() {
   
   
                fmt.Println("disconnected :" + ipStr)
                conn.Close()
        }()
        reader := bufio.NewReader(conn)

        for {
   
   
                message, err := reader.ReadString('\n')
                if err != nil {
   
   
                        return
                }

                fmt.Println(string(message))
                msg := time.Now().String() + "\n"
                b := []byte(msg)
                conn.Write(b)
        }
}

客户端

package main

import (
        "bufio"
        "fmt"
        "net"
        "time"
)

var quitSemaphore chan bool

func main() {
   
   
        var unixAddr *net.UnixAddr
        unixAddr, _ = net.ResolveUnixAddr("unix", "/tmp/unix_sock")

        conn, _ := net.DialUnix("unix", nil, unixAddr)
        defer conn.Close()
        fmt.Println("connected!")

        go onMessageRecived(conn)

        b := []byte("time\n")
        conn.Write(b)

        <-quitSemaphore
}

func onMessageRecived(conn *net.UnixConn) {
   
   
        reader := bufio.NewReader(conn)
        for {
   
   
                msg, err := reader.ReadString('\n')
                fmt.Println(msg)
                if err != nil {
   
   
                        quitSemaphore <- true
                        break
                }
                time.Sleep(time.Second)
                b := []byte(msg)
                conn.Write(b)
        }
}

参考资料

  好用的进程间通信方式---UnixDomainSocket

  本地socket unix domain socket_bingqingsuimeng的博客-CSDN博客

  本机网络 IO 之 Unix Domain Socket 性能分析 - 知乎 (zhihu.com)

  https://blog.csdn.net/Nurke/article/details/77621782

  go语言实现unix domain socket 客户端/服务端_ebayboy的技术博客_51CTO博客

  ‍

相关文章
|
29天前
|
安全 Go
Go新手步步为赢:并发编程通信指南
Go新手步步为赢:并发编程通信指南
12 0
|
2月前
|
网络协议 应用服务中间件 Go
go语言中的socket和http
go语言中的socket和http
28 0
|
3月前
|
JSON Linux 测试技术
go语言处理数据、基本通信以及环境配置 -- json,protobuf,grpc
go语言处理数据、基本通信以及环境配置 -- json,protobuf,grpc
|
3月前
|
Unix Docker 容器
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker
|
4月前
|
Go 调度
Go 语言怎么通过通信共享内存?
Go 语言怎么通过通信共享内存?
35 0
|
12月前
|
存储 监控 安全
GO --- 实时通信Melody包的介绍和例子
GO --- 实时通信Melody包的介绍和例子
GO --- 实时通信Melody包的介绍和例子
|
Go 调度 Python
大道如青天,协程来通信,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang通道channel的使用EP14
众所周知,Go lang的作用域相对严格,数据之间的通信往往要依靠参数的传递,但如果想在多个协程任务中间做数据通信,就需要通道(channel)的参与,我们可以把数据封装成一个对象,然后把这个对象的指针传入某个通道变量中,另外一个协程从这个通道中读出变量的指针,并处理其指向的内存对象。
大道如青天,协程来通信,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang通道channel的使用EP14
|
Java Go API
玩转gRPC—Go使用gRPC通信实战
玩转gRPC—Go使用gRPC通信实战
|
Unix Linux Android开发
|
13天前
|
Unix Shell Linux
在Unix/Linux Shell中,管道(`|`)和重定向
在Unix/Linux Shell中,管道(`|`)和重定向
14 1