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

  ‍

相关文章
|
5月前
|
缓存 监控 Java
Java Socket编程最佳实践:优化客户端-服务器通信性能
【6月更文挑战第21天】Java Socket编程优化涉及识别性能瓶颈,如网络延迟和CPU计算。使用非阻塞I/O(NIO)和多路复用技术提升并发处理能力,减少线程上下文切换。缓存利用可减少I/O操作,异步I/O(AIO)进一步提高效率。持续监控系统性能是关键。通过实践这些策略,开发者能构建高效稳定的通信系统。
171 1
|
3月前
|
Python
python socket 简单通信
python socket 简单通信
43 1
|
3月前
|
网络协议 安全 网络安全
网络编程:基于socket的TCP/IP通信。
网络编程:基于socket的TCP/IP通信。
240 0
|
5月前
|
Java 应用服务中间件 开发者
【实战指南】Java Socket编程:构建高效的客户端-服务器通信
【6月更文挑战第21天】Java Socket编程用于构建客户端-服务器通信。`Socket`和`ServerSocket`类分别处理两端的连接。实战案例展示了一个简单的聊天应用,服务器监听端口,接收客户端连接,并使用多线程处理每个客户端消息。客户端连接服务器,发送并接收消息。了解这些基础,加上错误处理和优化,能帮你开始构建高效网络应用。
426 10
|
5月前
|
IDE Java 开发工具
从零开始学Java Socket编程:客户端与服务器通信实战
【6月更文挑战第21天】Java Socket编程教程带你从零开始构建简单的客户端-服务器通信。安装JDK后,在命令行分别运行`SimpleServer`和`SimpleClient`。服务器监听端口,接收并回显客户端消息;客户端连接服务器,发送“Hello, Server!”并显示服务器响应。这是网络通信基础,为更复杂的网络应用打下基础。开始你的Socket编程之旅吧!
84 3
|
5月前
|
Java 数据挖掘 开发者
Java网络编程进阶:Socket通信的高级特性与应用
【6月更文挑战第21天】Java Socket通信是分布式应用的基础,涉及高级特性如多路复用(Selector)和零拷贝,提升效率与响应速度。结合NIO和AIO,适用于高并发场景如游戏服务器和实时数据分析。示例展示了基于NIO的多路复用服务器实现。随着技术发展,WebSockets、HTTP/2、QUIC等新协议正变革网络通信,掌握Socket高级特性为应对未来挑战准备。
50 1
|
1月前
|
网络协议 Linux 应用服务中间件
Socket通信之网络协议基本原理
【10月更文挑战第10天】网络协议定义了机器间通信的标准格式,确保信息准确无损地传输。主要分为两种模型:OSI七层模型与TCP/IP模型。
|
5月前
|
Java
Java Socket编程与多线程:提升客户端-服务器通信的并发性能
【6月更文挑战第21天】Java网络编程中,Socket结合多线程提升并发性能,服务器对每个客户端连接启动新线程处理,如示例所示,实现每个客户端的独立操作。多线程利用多核处理器能力,避免串行等待,提升响应速度。防止死锁需减少共享资源,统一锁定顺序,使用超时和重试策略。使用synchronized、ReentrantLock等维持数据一致性。多线程带来性能提升的同时,也伴随复杂性和挑战。
103 0
|
5月前
|
安全 Java 网络安全
Java Socket编程教程:构建安全可靠的客户端-服务器通信
【6月更文挑战第21天】构建安全的Java Socket通信涉及SSL/TLS加密、异常处理和重连策略。示例中,`SecureServer`使用SSLServerSocketFactory创建加密连接,而`ReliableClient`展示异常捕获与自动重连。理解安全意识,如防数据截获和中间人攻击,是首要步骤。通过良好的编程实践,确保网络应用在复杂环境中稳定且安全。
103 0
|
2月前
|
网络协议 Linux 应用服务中间件
Socket通信之网络协议基本原理
【9月更文挑战第14天】网络协议是机器间交流的约定格式,确保信息准确传达。主要模型有OSI七层与TCP/IP模型,通过分层简化复杂网络环境。IP地址全局定位设备,MAC地址则在本地网络中定位。网络分层后,数据包层层封装,经由不同层次协议处理,最终通过Socket系统调用在应用层解析和响应。