golang之跨语言ipc通信

2023-11-19

1 golang之跨语言ipc通信


进程间通信(IPC,InterProcess Communication),指不同进程之间通过进程间通信机制来传递数据的方式,进程间通讯广义上可以是统一主机或者不同主机间进程进行通信,这里专指同一主机间的通信,不包含基于TCP/IP的各种通信机制。IPC通信方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享内存、unix domain Socket等方式。

由于IPC通信的文章,在百度上一搜一大把,这里就不做系统性的介绍,只介绍golang与C++跨语言的进程通信方式:unix domain Socket,这种方式也是文章中介绍比较少的,文章大都集中在AF_INET类型的socket,通过127.0.0.1回环进行通信,此种方式与AF_UNIX相比,需要经过网络层处理,性能下降了一两倍,而AF_UNIX类型的socket,不经过网络层,几乎相当于进程间内存拷贝,性能非常高,且可以跨编程语言,针对需要跨语言的进程通信,推荐此种方式。其他通信方式可参考:GOLANG之IPC:进程间通信进程间通信(IPC)介绍

1.1 unix domain Socket(unix域套接字)介绍

Unix domain socket 最开始就是指 IPC socket通信,用于实现同一主机上的进程间通信,所以进程说的IPC socket通信机制,并不是指网络socket,socket的出现确实是为网络通讯设计的,但后来在 socket 的框架上发展出一种 IPC 机制,就是 UNIX domain socket,虽然网络 socket 也可用于同一台主机的进程间通讯(通过 loopback机制,IP地址赋值 127.0.0.1),但是此种方式需要经过网络协议栈处理,UNIX domain socket 不需要经过网络协议栈,就不涉及打包拆包、计算校验和、维护序号和应答等处理操作,只是将应用层数据从一个进程拷贝到另一个进程,IPC 机制本质上是可靠的通讯。
IPC socket通信是全双工的,基本上可以像网络socket一样操作,可跨编程语言通信,相比其它 IPC 机制有明显的优越性。Unix domain socket 是 POSIX 标准中的一个组件,不仅是unix相关系统,linux系统和windows系统(win10版本及以上,只支持SOCK_STREAM方式)也支持,具备很好的移植特性。

1.2 IPC SOCKET通信

1.2.1 函数及地址定义介绍

各编程语言用法都类似,这里以c++语言为例,对器c/s通信过程进行介绍。

首先看下socket文件描述符创建函数:
SOCKET PASCAL FAR socket (_In_ int af, _In_ int type, _In_ int protocol);

unix domain socket通信与网络socket流程基本一致,首先创建socket文件描述符,其相关参数赋值如下:

  • af:family,赋值为AF_UNIX,使用 AF_UNIX,在socket地址初始化时,指定的地址是一个socket文件的路径,而不是IP地址和端口,其会在系统上创建一个socket文件,不同进程通过读写这个文件来实现通信,此文件仅仅是为了客户端与服务器连接时使用,具体数据传输是通过内存传输。注意:服务端创建socket文件,客户端连接时指定此文件路径作为连接地址,就像网络socket的ip和端口一样,服务端创建时,此路径文件必须不存在,否则会报错。
  • type: 可以赋值为SOCK_DGRAM或SOCK_STREAM,与网络socket一样,SOCK_STREAM 意味着会提供按顺序的、可靠、双向、面向连接的比特流。SOCK_DGRAM 意味着会提供定长的、不可靠、无连接的通信。在unix domain socket只是指定不同的封装格式,性能上差别不大,由于windows仅支持SOCK_STREAM,一般默认此格式。
  • protocol:可默认选择0

UNIX domain socket 地址结构体格式用结构体 sockaddr_un 表示,这与网络socket地址结构sockaddr_in不一样,sockaddr_un定义如下:

typedef struct sockaddr_un
{
     ADDRESS_FAMILY sun_family;     /* AF_UNIX */
     char sun_path[UNIX_PATH_MAX];  /* pathname */
} SOCKADDR_UN, *PSOCKADDR_UN;

网络ocket 地址是IP 地址加端口号,UNIX domain socket的地址是一个 socket类型的文件在文件系统中的路径,socket文件由bind()调用创建,如果调用 bind时该文件已存在,则 bind错误返回,所以在调用 bind()时要检查socket文件是否存在,如果存在删除后再bind。

1.2.2 UNIX domain socket服务端程序

还是以c++为例,操作系统为windows 10,有关linux的代码也类似,都文件需要更换下,示例代码如下:

#include <stdio.h>
#include <iostream>
#include <cstring>
#include <fstream>
#include <winsock2.h>//socket相关操作
#include <windows.h>
#include <ws2tcpip.h>
#include <afunix.h> //sockaddr_un
#include <stdlib.h>


#pragma comment(lib, "ws2_32.lib")

using namespace std;

#define SERVER_SOCKET_FILE "D:\\server_sock" //socket文件,bind时需要检查是否存在,存在的话先删除

int main()
{
 
 /*初始化启动信息*/
 WORD sockVersion = MAKEWORD(2, 2);//调用2.2版本的socket
 WSADATA wsaData;      //WSA(Windows Sockets Asynchronous)异步套接字

 //将指定版本的socket与该应用程序绑定
 if (WSAStartup(sockVersion, &wsaData) != 0) //返回为0则表示初始化成功
  return 0;
 /*创建服务器UNIX domain socket*/
 /*c++ win只支持SOCK_STREAM,其他平台及语言也支持SOCK_DGRAM,如果跨编程语言最好选择SOCK_STREAM*/
 SOCKET serverSocket = ::socket(AF_UNIX, SOCK_STREAM, 0);
 if (serverSocket == INVALID_SOCKET)   //如果创建失败,则输出错误
 {
  cout << "socket error:" << WSAGetLastError() << endl;
  WSACleanup();   //中止Windows Sockets DLL的使用;与上面WSAStartup()配套使用
  return 0;
 }
 
 //定义UNIX domain socket地址
 sockaddr_un serverAddr;
 serverAddr.sun_family = AF_UNIX;
 strncpy_s(serverAddr.sun_path, SERVER_SOCKET_FILE,sizeof(SERVER_SOCKET_FILE));//指定socket文件路径
 //Socket绑定地址,这里没检查sun_path对应的文件是否存在,正式代码需要判断
 if (bind(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
 {
  cout << "Bind Error!" << endl;
  closesocket(serverSocket);
  WSACleanup();
  return 0;
 }

 //建立监听客户端请求的信号,设置最多有5个客户端
 if (listen(serverSocket, 5) == SOCKET_ERROR)//如果建立失败
 {
  cout << "Listen Error !" << endl;
  closesocket(serverSocket);    //关闭Socket套接字
  WSACleanup();
  return 0;
 }
 cout << "listen..." << endl;

 //开始不断处理各个客户端请求
 while (true)
 {
  SOCKET clientSocket = INVALID_SOCKET; //初始化一个客户端socket
  sockaddr_un clientAddr;     //客户端的地址结构
  int iAddrLength = sizeof(clientAddr); //求出该结构的长度
  cout << "waiting..." << endl;
  //接收客户端的连接请求,如果客户端绑定客户端socket地址,则clientAddr的sun_path既是客户端地址
  clientSocket = accept(serverSocket, (SOCKADDR*)&clientAddr, &iAddrLength);//accept为阻塞函数

  //查询当前读取缓冲区
  int defTcpRcvBufSize = -1;
  socklen_t optlen2 = sizeof(defTcpRcvBufSize);
  if (getsockopt(clientSocket, SOL_SOCKET, SO_RCVBUF, (char*)&defTcpRcvBufSize, &optlen2) < 0)
  {
   break;
  }
  //配置读取缓冲区,因此测试代码需要传输图片数据,这里配置接收缓冲器为8M
  int rcvBufSize = 1024 * 1024 * 8;
  printf("you want to set socket recv buff size to %d\n", rcvBufSize);
  int optlen = sizeof(rcvBufSize);
  if (setsockopt(clientSocket, SOL_SOCKET, SO_RCVBUF, (char*)&rcvBufSize, optlen) < 0)
  {
   break;
  }
  printf("set domain socket(%d) recv buff size to %d OK!!!\n", clientSocket, rcvBufSize);

  //开始不断接收该客户端数据
  char* buffFromClient;   //用于接收客户端传来的数据
  buffFromClient = new char[1024 * 1024 * 8];
  char sendToClientBuff[1024];
  while (true)
  {
   memset(buffFromClient, 0, sizeof(buffFromClient));
   //recv也为阻塞函数,只有客户端发送数据过来后,程序才会往下继续走
   int iLenOfRecvData = recv(clientSocket, buffFromClient, 1024 * 1024 * 8, 0);

   if (iLenOfRecvData > 0)  //如果接收的数据不为空
   {
    cout <<"iLenOfRecvData:"<< iLenOfRecvData << endl;
   }
   else
   {
    cout << "connect error..." << endl;
    break;
   }

   memset(sendToClientBuff,1,1024)
   //发送数据到客户端
   send(clientSocket, sendToClientBuff, sizeof(sendToClientBuff), 0);
  }
  closesocket(clientSocket);//关闭与该客户端的套接字
 }
 closesocket(serverSocket);//关闭服务器套接字
 WSACleanup();
 return 0;
}

网络 socket 编程基本一样,bind服务socket地址之后 listen,表示通过 bind 的地址(也就是 socket 文件)提供服务,然后通过accept() 函数等待并获取连接连接。accept为每个连接创立新的套接字并从监听队列中移除这个连接,由于 UNIX domain socket为本地内存拷贝,当传输大尺寸文件和数据时,尽量把发送缓冲区和接收缓冲区配置大于其传输的最大值,这样可避免分包发送和组包接收带来的性能损耗,具体可通过setsockopt来配置。

1.2.3 UNIX domain socket客户端程序

还是以c++为例,代码运行再windows 10上,具体代码如下:

#include <stdio.h>
#include <iostream>
#include <cstring>
#include <fstream>
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <afunix.h>
#include <stdlib.h>


#pragma comment(lib, "ws2_32.lib")

using namespace std;

#define SOCKET_SERVER "D:\\server_sock"
#define SOCKET_CLIENT "D:\\client_sock"

int main()
{

 /*初始化启动信息*/
 WORD sockVersion = MAKEWORD(2, 2);//调用2.2版本的socket
 WSADATA wsaData;      //WSA(Windows Sockets Asynchronous)异步套接字

 //将指定版本的socket与该应用程序绑定
 if (WSAStartup(sockVersion, &wsaData) != 0) //返回为0则表示初始化成功
  return 0;
 /*创建客户端Socket*/
 SOCKET clientSocket = ::socket(AF_UNIX, SOCK_STREAM, 0);
 if (clientSocket == INVALID_SOCKET)   //如果创建失败,则输出错误
 {
  cout << "socket error:" << WSAGetLastError() << endl;
  WSACleanup();   //中止Windows Sockets DLL的使用;与上面WSAStartup()配套使用
  return 0;
 }

 sockaddr_un clientAddr;
 clientAddr.sun_family = AF_UNIX;
 strncpy_s(clientAddr.sun_path, SOCKET_CLIENT, sizeof(SOCKET_CLIENT));
 //客户端显示绑定客户端地址,便于服务端区分客户端,也可不调用,服务端将无法获得地址,但是可通过socket句柄区分
 //此处需要检查SOCKET_CLIENT对应的socket客户端文件是否存在,如果存在要删除,这里没写
 if (bind(clientSocket, (SOCKADDR*)&clientAddr, sizeof(clientAddr)) == SOCKET_ERROR)
 {
  cout << "Bind Error!" << endl;
  closesocket(clientSocket);
  WSACleanup();
  return 0;
 }


 sockaddr_un serverAddr;
 serverAddr.sun_family = AF_UNIX;
 strncpy_s(serverAddr.sun_path, SOCKET_SERVER, sizeof(SOCKET_SERVER));
 if (connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(clientAddr)) < 0) {
  perror("connect error");
  exit(1);
 }
 while (true)
 {
  //收发数据
  char* bufrecv;   //用于接收客户端传来的数据
  bufrecv = new char[1024 * 1024 * 8];
  
  char* bufsend;   //用于接收客户端传来的数据
  bufsend = new char[1024 * 1024 * 8];
  memset(bufsend, 2, 1024 * 1024 * 8);
  send(clientSocket, bufsend, sizeof(bufsend), 0);
  memset(bufrecv, 0, 1024 * 1024 * 8);
  //recv也为阻塞函数
  int iLenOfRecvData = recv(clientSocket, bufrecv, 1024 * 1024 * 8, 0);
  if (iLenOfRecvData > 0)  //如果接收的数据不为空
  {
   cout << "iLenOfRecvData:" << iLenOfRecvData << endl;
  }
  else
  {
   cout << "connect error..." << endl;
   break;
  }
 }
 closesocket(clientSocket);//关闭服务器套接字
 WSACleanup();
 return 0;
}

编译服务端程序和客户端程序,先运行客户端程序,再运行服务端程序,即可实现两个进程的通信,以上代码基于vs2019编译实现。

1.3 跨编程语言进程通信

这里介绍下golang语言和c++语言,通过UNIX domain socket进行单主机不同语言通信,假设场景在边缘智能场景中,AI推理进程采用C++编写,视频抽帧进程采用golang编写,需要从视频抽帧进程传递抽帧的RGB数据到AI推理进程,此时通过UNIX domain socket进行通信,这里需要传输大数据。
其服务端代码示例,可采用上文服务端程序实例,为c++编写,这里提供golang编写的客户端实例代码,监听的socket路径为:D:\server_sock
代码如下:

package main

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

var quitSig chan bool

func main() {
 var serverAddr *net.UnixAddr //定义UNIX domain socket地址
 serverAddr, _ = net.ResolveUnixAddr("unix", "D:\\server_sock") //服务地址
 conn, _ := net.DialUnix("unix", nil, serverAddr) //"unix"表示SOCK_STREAM方式
 defer conn.Close()
 fmt.Println("connected!")

 go ClientSend(conn) //开启发送线程
 b := []byte("cleint send example\n")
 conn.Write(b)

 <-quitSig
}

func ClientSend(conn *net.UnixConn) {
 for {
  time.Sleep(time.Second)
  //创建消息缓冲区
  buffer := make([]byte, 1024*1024*8)
  buffer[0] = '1'
  t := time.Now()
  conn.Write(buffer) //发送8M数据
  elapsed := time.Since(t)
  fmt.Println("app elapsed:", elapsed)
 }
}

golang对UNIX domain socket进行了单独的封装,比c++更加简单和清晰。
运行c++服务端程序及golang的客户端程序,结果如下:

跨语言

可以看出传输8M数据,在服务端设置接收缓冲区为大于8M时,一次即可接受完,由于不经过网络协议栈,不需要考虑组包的情况,且耗时基本在毫秒级,相当于内存拷贝的速度了。

golang进程提供了很简洁的ipc socket通信方式,如果都是golang语言编写的进程,且单主机进程通信,建议采用此方式,下面给出golang语言的服务端代码实例:

package main

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

func main() {
 var servadd *net.UnixAddr

 servadd, _ = net.ResolveUnixAddr("unix", "D:\\server_sock")

 ulistener, _ := net.ListenUnix("unix", servadd)

 defer ulistener.Close()

 for {
  conn, err := ulistener.AcceptUnix()
  if err != nil {
   continue
  }

  fmt.Println("A client connected : " + conn.RemoteAddr().String())
  go unixrw(conn)
 }

}

func unixrw(conn *net.UnixConn) {
 addrStr := conn.RemoteAddr().String()
 defer func() {
  fmt.Println("disconnected :" + addrStr)
  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([]byte("service : "))
  conn.Write(b)
 }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

golang之跨语言ipc通信 的相关文章

  • UE4命令行使用,解释

    命令行在外部 从命令行运行编辑项目 1 导航到您的 LauncherInstall VersionNumber Engine Binaries Win64 目录中 2 右键单击上 UE4Editor exe 的可执行文件 并选择创建快捷方式

随机推荐

  • vscode 中使用鼠标Ctrl+滚轮放大缩小

    1 使用鼠标Ctrl 滚轮放大缩小 打开 文件 gt 首选项 gt 设置 gt 用户 gt 下面的JSON 2 3 点击在settings json中编辑 4 添加下面一行 editor mouseWheelZoom true 5 最后保存
  • MachineLearningWu_15/P70-P71_AdamAndConv

    x 1 算法参数更新 我们使用梯度下降算法来自动更新参数 但是由于学习率的不好选择性 我们有时候会下降地很快 有时候下降地很慢 我们期望有一种方式能够自动调整学习率的变化 这里引入Adaptive Moment Estimation Ada
  • eclipse汉化方式(下载,安装,中英切换)

    按照下面的步骤完成Eclipse的汉化 对初学java的小伙伴们很有帮助哦 愿大家能够学习进步 eclipse汉化的两种方式 第一种方式 第二种方式 第一种方式 一 第一步准备工作 打开eclipse点击Help About eclipse
  • Unity 3D input.GetAxis用法

    input GetAxis 用法 GetAxis Mouse X GetAxis Mouse Y GetAxis Mouse ScrollWheel GetAxis Vertical GetAxis Horizontal GetAxis 是
  • 深度特征融合---高低层(多尺度)特征融合

    目录 概述 基本概念 典型方法概述 相关工作 多尺度模型设计 Deep Feature Fusion for VHR 高分辨率图像 Remote Sensing Scene Classification DCA特征融合方法 基于神经网络的目
  • session获取与存储

    方法一 首先要明白 session是在服务器端创建并保存在服务器端 当代码中创建session时会相应地创建sessionID保存在服务器端 同时服务器向浏览器响应信息时会以cookie形式 其实就是 JSESSION sessionID
  • 什么是Java?

    刚开始学习Java时 对于老师讲述的Java的发展历程毫无兴趣 觉得只要会用就行了 直到有一天我的朋友突然问我 你们学的Java是什么呀 面对他突如其来的问题 我一下有点不知所措 刚想开口却又不知道说些什么 只能慢慢的吐出那几个字 一门编程
  • IDEA 错误: 找不到或无法加载主类

    问题 问题就是spring boot项目在IDEA中 启动类右键运行 控制台提示 错误 找不到或无法加载主类 xxx xxx xxx xxxx 启动类包名 原因 问题原因 运行启动类的时候代码没有编译到target目录下如下图 发现编译过后
  • ubuntu16.04 安装 pcl1.7

    不使用源码编译的方式 而是使用官网prebuild版本的方式安装 sudo add apt repository ppa v launchpad jochen sprickerhof de pcl sudo apt get update s
  • python指定宽度20输出_Python 以指定宽度格式化输出

    Python 以指定宽度格式化输出 2020 05 07 当对一组数据输出的时候 我们有时需要输出以指定宽度 来使数据更清晰 1 mat 20 t 28 t 32 print mat format 占4个长度 占8个长度 占12长度 2 如
  • JS关于Object.assign()和浅拷贝、深拷贝

    什么是浅拷贝和深拷贝 js中存对象都是存地址的 浅拷贝指向的是同一块内存区块 深拷贝则是另外开辟了一块区域 两个例子 var obj1 num 100 str abc var obj2 obj1 obj2 num 200 console l
  • 图像处理神器 ImageMagick 命令介绍

    安装完 ImageMagick 打开命令行 输入相应命令即可处理图像 所有命令前面都要加 magick 网上的教程都没说 也可能是我自己环境没配置吧 说明 内的命令表示是可选的 内表示是示例或者代号而已 magick convert sou
  • 每日写题分享--机器人的运动范围//DFS深度优先搜索/递归

    题目描述 题目链接戳此 解题思路 这题和上题矩阵中的路径可以对比起来看 同样也是深度优先搜索 DFS 由于机器人从 0 0 位置向下向右探索 右边的下面和下面的右边可能会重复 所以可以将走过的路径记录下来置为true防止重复走 代码实现如下
  • 电脑一体机电脑,一体机电脑哪款好_一体机电脑品牌推荐【热门机型】-太平洋IT百科...

    一体机电脑哪款好 一体机电脑将主机部分和显示器部分高度集成在一起 因此一体机电脑被厂商称为All In One AIO电脑或屏式电脑 优秀的外观设计加上良好的硬件性能 一体机电脑成为很多家庭甚至是办公用户的选择 我们不妨从一体机电脑的背景开
  • PDF如何解密?介绍几个简单小方法

    PDF格式是我们常见的文件格式 平时学习或者工作的文件都是以PDF格式来发送的 一方面因为它不能随便进行编辑 一方面是因为它还可以设置加密 但是设置加密很简单 后续需要对加密文档进行修改编辑的时候就不简单了 每次打开都需要输入密码 操作步骤
  • pinia 实战IM----注册TIM-plugin插件

    目录 初始化TIM实例 定义参数类型 扩展pinia插件 添加新的属性 添加新的选项 标注插件类型 为新的定义选项添加类型 为新的 state 添加类型 初始化pinia TIM插件 扩展 pinia属性 与 选项 仓库中使用 pina中注
  • openGauss学习笔记-59 openGauss 数据库管理-相关概念介绍

    文章目录 openGauss学习笔记 59 openGauss 数据库管理 相关概念介绍 59 1 数据库 59 2 表空间 59 3 模式 59 4 用户和角色 59 5 事务管理 openGauss学习笔记 59 openGauss 数
  • PX4log文件分析工具

    功能介绍 该工具可读取PX4存储的bin文件 自动将其进行解析 解析后可根据需求 选择需要导出的数据 将数据导出到txt文件中 几点说明 应用场景 需要将飞行数据导入到matlab中 对数据进行分析 使用原始的sdlog2pumy py文件
  • vue页面自适应屏幕宽高_Vue+Element UI 高度实时自适应

    本文章是我一个实习两个月的练习生 在项目中遇到的问题 在此记录 防治以后再踩坑 Element ui 本身使用的Container 布局容器 组件采用 flex 布局 所以用了它的布局就可以做到宽度实时自适应 当我想试试高度是否自适应时发现
  • golang之跨语言ipc通信

    1 golang之跨语言ipc通信 文章目录 1 golang之跨语言ipc通信 1 1 unix domain Socket unix域套接字 介绍 1 2 IPC SOCKET通信 1 2 1 函数及地址定义介绍 1 2 2 UNIX