macOS跨进程通信: Unix Domain Socket 创建实例

news2024/12/24 13:54:59

macOS跨进程通信: Unix Domain Socket 创建实例

一: 简介

Socket 是 网络传输的抽象概念。
一般我们常用的有Tcp SocketUDP Scoket, 和类Unix 系统(包括Mac)独有的 Unix Domain Socket(UDX)。

  • Tcp Socket 能够跨电脑进行通信,即使是在同一个电脑下的多进程间通信,也会通过网卡进行数据传输,如果本地网卡的环回网络被禁用, 则会导致通信失败。
  • Unix Domain Socket,使用的是Liunx 系统中万物皆文件的概念,和有名管道的操作差不多,都是在文本创建一个特有的文件,用来在两个进程间通信,两个进程分别写入和读取文件流中的数据,达到传输的目的。 和Tcp Socket不一样的是不用借助网卡通信,限制比较小,传输的效率高。

这里主要针对 Unix Domain Socket进行研究.


在终端使用 ls -ll /tmp/
可以看到红圈中我们demo创建的Unix Domain Socket 文件。
Unix Domain Socket 会在 在第一列将会显示类型 s
这里还有其他类型的文件。其中p表示命名管道文件,d表示目录文件,l表示符号连接文件,-表示普通文件,s表示socket文件,c表示字符设备文件,b表示块设备文件。
在这里插入图片描述

二:主要函数

1. int socket (int domain, int type, int protocol) 创建socket 对象

  • domain 选择 AF_UNIX, 代表 unix domain socket
  • type. 选择SOCK_STREAM, socket 流
  • protocol 填0, 由系统选择

2. int bind(int sockfd, const struct sockaddr* myaddr, socklen_t addrlen)

将socket 绑定到对应 ip 和 端口上

  • sockfd 前面返回的描述符
  • myaddr 包含 通信 对象 路径的struct, 这里创建的是 /tmp/jimbo_udx_server.sock
  • addrlen前一个stuct的长度

3. int listen(int sockfd, int backlog)

调用后,本地socket 文件的状态变更

  • sockfd 前面返回的描述符
  • backlog 此socket 接收的客户端的数量

4. int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen)

阻塞式等待客户端接入,客户端接入后返回。
传入serversockfd,返回接入后的sockfd.
后面两个参数代表接口客户端的地址及struct长度

5. int read(int sockfd, void *buf, int len, unsigned int flags)

接收客户端发来的数据

6. int write(int sockfd, const void *msg, int len, int flags)

服务器 往 客户端/服务器发送数据

7. int close(int sockfd) 或 Windows的 7. int closesocket(int sockfd)

关闭连接

三:demo代码

如下图,创建了两个进程,分别为服务器App, 客户端App.
UI 上点击发送按钮。 收到消息后可以在 控制台查看 输出。
在这里插入图片描述

1. 服务器端主要逻辑

  • 主要创建了socket一个 AF_UNIXSOCK_STREAM 组合的socket

  • remove(...) 删除以前的sock 文件

  • bind 将文件路径和 socket 对象绑定在一起

  • listen() 开始监听

  • 启动子线程,在线程内 阻塞等待客户端连接(accept),和接收客户端消息(read)

  • 启动客户端进程。 客户端内进行连接到这个服务器

  • 点击ui上的发送按钮,往客户端发送消息

    主要代码: ViewController.mm 文件代码

//
//  ViewController.m
//  Sockct_UDX_MainApp
//
//  Created by jimbo on 2024/1/5.
//

#import "ViewController.h"
#include <sys/socket.h>

const char * s_sock_path = "/tmp/jimbo_udx_server.sock";


@interface ViewController ()
@property (weak) IBOutlet NSTextField *textLabel;

@property (nonatomic, assign) int sfd;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    /**
     int socket(int domain, int type, int protocol)
     AF_UNIX VS AF_INET(ipv4 tcp)
     SOCK_STREAM VS SOCK_DGRAM
     当protocol为0时,会自动选择type类型对应的默认协议。
     */
    int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
    self.sfd = sfd;
    
    if (sfd == -1) {
        perror("socket create failed!");
        return;
    }
    // 删除所有与路径名一致的既有文件,这样才能将 socket 绑定到这个路径名上
      if (remove(s_sock_path) == -1 && errno != ENOENT){
          perror("remove failed");
          return;
      }
    
    struct sockaddr_un addr  = {0};
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, s_sock_path);
    
    //bind 的时候会在文件路径创建响应的s_sock_path文件. (UI app 需要关闭沙盒才能有权限访问对应的路径)
    //当使用ls –ll列出时,UNIX domain socket 在第一列将会显示类型 s
    //扩展一下,这个位置还可以有其他几种选项:p、d、l、s、c、b和-:
    //其中p表示命名管道文件,d表示目录文件,l表示符号连接文件,-表示普通文件,s表示socket文件,c表示字符设备文件,b表示块设备文件。
    int ret = bind(sfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un));
    if (ret == -1) {
        perror("bind faild!");
        return;
    }
    
    ret = listen(sfd, 5);
    if (ret == -1) {
        perror("listen failed!");
        return;
    }
    
    NSThread *th = [[NSThread alloc] initWithTarget:self selector:@selector(subThreadWorker) object:nil];
    [th setName:@"udx thread"];
    [th start];
    NSLog(@"---start");
    
    
    //启动子进程 app client
    
       //启动子进程
    NSURL *subAppURL = [[NSBundle mainBundle] URLForResource:@"Sockct_UDX_SubApp" withExtension:@"app"];
    [[NSWorkspace sharedWorkspace] openURL:subAppURL configuration:[NSWorkspaceOpenConfiguration configuration] completionHandler:nil];
}


- (void)subThreadWorker {
    NSLog(@"---subThreadWorker");
    
    ssize_t numRed = 0;
    static const int buffer_size = 100;
    char buffer[buffer_size];
    
    while (self.sfd != -1) {
        printf("服务器等待客户端%i连接...\n", self.sfd);
        //接受新链接, 并得到新的id
        self.sfd = accept(self.sfd, NULL, NULL);
        printf("收到客户端连接。 sfd:%i\n", self.sfd);
        
        if (self.sfd == -1) {
            perror("这是一个无效的连接!");
            break;
        }
        
        while ((numRed = read(self.sfd, buffer, buffer_size)) > 0) {
            printf("服务器收到客户端发的数据: %s\n", buffer);
        }
        if (numRed == -1) {
            perror("numRed == -1!");
            break;
        }
        if (close(self.sfd) == -1) {
            perror("close faild!");
            break;
        }
        printf("for over!\n");
    }
    
    printf("sub thread over!\n");
    
//    exit(0);
}

- (IBAction)sendMsgToClient:(id)sender {
    
    const  char *backBuffer = [self.textLabel.stringValue UTF8String];
    ssize_t sendLen =  write(self.sfd, backBuffer, strlen(backBuffer)+1);
    if (sendLen < 0) {
        printf("error:%i\n", errno);
        perror("服务器发送给客户端失败!reason:");
    } else {
        printf("服务器发送给客户端成功!len:%zi\n", sendLen);
    }
}


@end

2. 客户端主要逻辑

  • 主要创建了socket一个 AF_UNIXSOCK_STREAM 组合的socket
  • connect(...) 使用带服务器创建的sock 路径/tmp/jimbo_udx_server.sock 的结构体,和 socket 对象进行连接。 这样双方通信就建立了
  • read(...)在子线程 阻塞式 等待服务器的消息.
  • write(..) UI 按钮点击后,往服务器发消息

主要代码: ViewController.mm 文件代码

//
//  ViewController.m
//  Sockct_UDX_SubApp
//
//  Created by jimbo on 2024/1/5.
//

#import "ViewController.h"
#include <sys/socket.h>

const char * s_sock_path = "/tmp/jimbo_udx_server.sock";


@interface ViewController ()
@property (weak) IBOutlet NSTextField *textLabel;

@property (nonatomic, assign) int sfd;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
    self.sfd = sfd;
    
    if (sfd == -1) {
        perror("socket create failed!");
        return;
    }
    
    /*
    // client 也可以同事绑定一个路径。自己又当客户端又当服务器
    
    const char * s_sock_path_client = "/tmp/jimbo_udx_client.sock";
     
    // 删除所有与路径名一致的既有文件,这样才能将 socket 绑定到这个路径名上
    if (remove(s_sock_path_client) == -1 && errno != ENOENT){
        perror("remove jimbo_udx_client.sock failed");
        return;
    }
    
    struct sockaddr_un addr_client  = {0};
    addr_client.sun_family = AF_UNIX;
    strcpy(addr_client.sun_path, s_sock_path_client);
    
    //bind 的时候会在文件路径创建响应的s_sock_path文件. (UI app 需要关闭沙盒才能有权限访问对应的路径)
    //当使用ls –l列出时,UNIX domain socket 在第一列将会显示类型 s
    //扩展一下,这个位置还可以有其他几种选项:p、d、l、s、c、b和-:
    //其中p表示命名管道文件,d表示目录文件,l表示符号连接文件,-表示普通文件,s表示socket文件,c表示字符设备文件,b表示块设备文件。
    int ret = bind(sfd, (struct sockaddr *)&addr_client, sizeof(struct sockaddr_un));
    if (ret == -1) {
        perror("bind  addr_client faild!");
        return;
    }
    */
    struct sockaddr_un addr = {0};
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, s_sock_path);
    
    NSLog(@"准备connect");
    int ret = connect(self.sfd, (const struct sockaddr *)&addr, sizeof(addr));
    if (ret == -1) {
        perror("connect faild!");
        return;
    }
    NSLog(@"connect成功");
    
    
    [NSThread detachNewThreadWithBlock:^{
        //单独线程监听服务器发回来的消息
        static const int buffer_size = 100;
        char buffer[buffer_size];
        while (self.sfd != -1) {
            printf("等待服务的回调...\n");
            ssize_t len = read(self.sfd, buffer, buffer_size);
            printf("收到的服务器回馈长度:%zi\n", len);
            if (len <= 0) {
                printf("read error:%i\n", errno);
                perror("read failed");
//                assert(false); //需要判断是否服务器已经断开了的情况。
                exit(0);
            }else {
                printf("服务器返回的数据:%s\n", buffer);
            }
        }
        printf("等待服务器回调线程结束!\n");
    }];
    
}

- (IBAction)sendMegToServer:(id)sender {
    //发送消息
    const char *buf = [self.textLabel.stringValue UTF8String];
    size_t numWrite = strlen(buf) + 1;
    
    ssize_t writeSize = write(self.sfd, buf, numWrite);
    printf("numWrite: %zu writeSize:%zi\n", numWrite, writeSize);
    if (writeSize == -1) {
        perror("write failed!");
        return;
    }
}

- (void)dealloc {
    if (self.sfd > 0) {
        NSLog(@"关闭 sfd");
        close(self.sfd);
        self.sfd = -1;
    }
}

@end

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1406780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SpringBoot Vue家政服务预约平台系统

大家好✌&#xff01;我是Dwzun。很高兴你能来阅读我&#xff0c;我会陆续更新Java后端、前端、数据库、项目案例等相关知识点总结&#xff0c;还为大家分享优质的实战项目&#xff0c;本人在Java项目开发领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#x…

Midjourney 提示词入门 | 提示词格式 特点如何写好自己的提示词?进阶技巧

文章目录 1 Prompt格式2 文本提示词的基本要求3 好的文本提示词的特点 上一节我们初步了解了Midjourney的使用 那么在使用过程中最重要的是通过Prompt告知Midjourney怎么画 因而高效写Prompt非常重要~ 先来了解一下Prompt基本格式 1 Prompt格式 /imagine Text_prompt如下图…

【数据结构与算法】4.自主实现单链表的增删查改

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有限&#xff0c;欢迎各位大佬指点&…

开始学习Vue2(脚手架,组件化开发)

一、单页面应用程序 单页面应用程序&#xff08;英文名&#xff1a;Single Page Application&#xff09;简 称 SPA&#xff0c;顾名思义&#xff0c;指的是一个 Web 网站中只有唯一的 一个 HTML 页面&#xff0c;所有的功能与交互都在这唯一的一个页面内完成。 二、vue-cli …

JVM虚拟机面试题

一.JVM组成 1.JVM是什么 2.什么是程序计数器 3.java堆 4.虚拟机栈 5.方法区 6.直接内存 二.类加载器 1.什么是类加载器,类加载器有哪些 2.双亲委派模型 3.类装载的执行过程 三.垃圾回收 1.对象什么时候可以被垃圾器回收 2.JVM垃圾回收算法 3.JVM分代回收 4.JVM有哪些垃圾回收…

vcenter7.0

Vcenter7.0简易详细安装图解 环境&#xff1a;Windows server 2016 镜像&#xff1a;VMware-VCSA-all-7.0.3-20395099.iso 1.前提配置一个静态IP地址&#xff08;192.168.80.120&#xff09;和关闭防火墙 2.配置一个dns&#xff08;这里做的是一个不加域的&#xff09; 第一步…

数据仓库-相关概念

简介 数据仓库是一个用于集成、存储和管理大量数据的系统。它用于支持企业决策制定过程中的数据分析和报告需求。数据仓库从多个来源收集和整合数据&#xff0c;并将其组织成易于查询和分析的结构。 数据仓库的主要目标是提供高性能的数据访问和分析能力&#xff0c;以便…

不就业,纯兴趣,应该自学C#还是JAVA?

不就业&#xff0c;纯兴趣&#xff0c;应该自学C#还是JAVA? 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「JAVA的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff…

OpenHarmony 鸿蒙使用指南——概述

简介 OpenHarmony采用多内核&#xff08;Linux内核或者LiteOS&#xff09;设计&#xff0c;支持系统在不同资源容量的设备部署。当相同的硬件部署不同内核时&#xff0c;如何能够让设备驱动程序在不同内核间平滑迁移&#xff0c;消除驱动代码移植适配和维护的负担&#xff0c;…

2024年游泳骨传导耳机该怎么选?什么牌子的游泳耳机好?

游泳是一项非常有趣的运动&#xff0c;但是如果你想在水中听音乐或者收听其他音频内容&#xff0c;就需要一款专业的游泳骨传导耳机。那么&#xff0c;我们应该如何选择游泳骨传导耳机呢&#xff1f;接下来跟我一起看看这四款性能不错的游泳耳机吧。 1. 南卡骨传导游泳耳机 推…

【赠书第18期】人工智能B2B落地实战:基于云和Python的商用解决方案

文章目录 前言 1 方案概述 2 方案实施 2.1 云平台选择 2.2 Python环境搭建 2.3 应用开发与部署 2.4 应用管理 2.5 安全性与隐私保护 3 方案优势与效益 4 推荐图书 5 粉丝福利 前言 随着云计算技术的快速发展&#xff0c;越来越多的企业开始将业务迁移至云端&#x…

spring mvc Rest风格

南城余的Java学习 专栏收录该内容 70 篇文章0 订阅 我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xf…

数据结构----线性表、顺序表、模拟实现顺序表

文章目录 1. 线性表2. 顺序表3. 模拟实现顺序表 1. 线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;指具有相同数据类型的元素按照一定的顺序排列的数据结构&#xff0c;其中每…

Python笔记12-多线程、网络编程、正则表达式

文章目录 多线程网络编程正则表达式 多线程 现代操作系统比如Mac OS X&#xff0c;UNIX&#xff0c;Linux&#xff0c;Windows等&#xff0c;都是支持“多任务”的操作系统。 进程&#xff1a; 就是一个程序&#xff0c;运行在系统之上&#xff0c;那么便称之这个程序为一个运…

17β-Estradiol high sensitivity ELISA kit

高灵敏ELISA试剂盒&#xff0c;可检测到低至14 pg/ml的17β-雌二醇 雌二醇(estradiol) 是由卵巢内卵泡的颗粒细胞分泌的类固醇激素&#xff0c;是主要的雌激素&#xff0c;负责调节女性特征、附属性器官的成熟和月经-排卵周期&#xff0c;促进乳腺导管系统的产生&#xff0c;有…

全双工通信协议:WebSocket

全双工通信协议&#xff1a;WebSockets 前言何时使用WebSocketsWebSocket APITextWebSocketHandlerWebSocketConfigurerWebSocket握手配置服务器允许的来源心跳包Java WebSocket API案例一&#xff1a;前端发送消息并接收后端响应案例二&#xff1a;模拟后端向前端推送消息案例…

【DeepLearning-2】预归一化(Pre-Normalization)策略

2.1层归一化&#xff08;Layer Normalization&#xff09;在 PreNorm 类中的数学原理&#xff1a; 2.2代码实现&#xff1a; class PreNorm(nn.Module):def __init__(self, dim, fn):super().__init__()self.norm nn.LayerNorm(dim)self.fn fn def forward(self, x, **kwar…

SpringBoot-多数据源切换和事物处理(免费)

作者原始文章: SpringBoot-多数据源切换和事物处理 最新内容和改动请看上面的文章 安装 <dependency><groupId>com.gitee.huanminabc</groupId><artifactId>dynamic-datasource</artifactId><version>1.0.3-RELEASE</version> <…

初识SQL注入

目录 注入攻击 SQL注入 手工注入 Information_schema数据库 自动注入 介绍一下这款工具&#xff1a;sqlmap 半自动注入 前面给大家通过学习练习的方式将XSS攻击的几种形式和一些简单的靶场和例题的演示&#xff0c;从本篇开始我将和小伙伴们通过边复习、边练习的方式来进…

MongoDB系列之一文总结索引

概述 分类 索引的分类&#xff1a; 按照索引包含的字段数量&#xff0c;可分为单键索引&#xff08;单字段索引&#xff09;和组合索引&#xff08;联合索引、复合索引&#xff09;按照索引字段的类型&#xff0c;可以分为主键索引和非主键索引按照索引节点与物理记录的对应…