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博客

  ‍

相关文章
|
2月前
|
数据处理 C# C++
如何使用C#和C++结构体实现Socket通信
如何使用C#和C++结构体实现Socket通信
|
2月前
|
监控 安全 Unix
UNIX域套接字(Unix Domain Socket)在安全性和隐私性
UNIX域套接字(Unix Domain Socket)在安全性和隐私性
50 2
|
2月前
|
网络协议 安全 Unix
UNIX域套接字(Unix Domain Socket,UDS)之所以高效
UNIX域套接字(Unix Domain Socket,UDS)之所以高效
47 3
|
4月前
|
网络协议 程序员 Python
揭秘Python网络编程:深入理解Socket通信
在当今信息时代,网络通信已经成为人们生活中不可或缺的一部分。而Python作为一种高效、易用的编程语言,自然也能够很好地支持网络编程和Socket通信。本文将介绍Python网络编程与Socket通信的相关知识,包括Socket通信模型、Socket编程接口、网络套接字等内容。
|
4月前
|
网络协议 开发者 Python
Python网络编程与Socket通信:连接世界的无限可能
在当今数字化时代,Python作为一种强大的编程语言,通过网络编程与Socket通信为我们打开了连接世界的无限可能。本文将深入探讨Python网络编程的基础知识、Socket通信的原理以及实际应用,帮助读者更好地理解并运用这一技术。
|
4月前
|
安全 Go 数据处理
Go语言CSP编程实战:通道通信技术
Go语言CSP编程实战:通道通信技术
43 0
|
6天前
|
存储 网络协议 关系型数据库
Python从入门到精通:2.3.2数据库操作与网络编程——学习socket编程,实现简单的TCP/UDP通信
Python从入门到精通:2.3.2数据库操作与网络编程——学习socket编程,实现简单的TCP/UDP通信
|
28天前
|
网络协议 Unix Linux
Socket通信详细介绍1
Socket通信详细介绍
31 0
|
3月前
|
Go 调度 开发者
Go语言并发基础:轻量级线程与通道通信
【2月更文挑战第6天】本文介绍了Go语言在并发编程方面的基础知识和核心概念。我们将深入探讨goroutine(轻量级线程)的创建与调度,以及如何利用channel进行goroutine间的通信与同步。此外,还将简要提及select语句的使用,并解释其在处理多个channel操作时的优势。