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

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 文件代码

objectivec 复制代码
//
//  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 文件代码

objectivec 复制代码
//
//  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
相关推荐
Digitally6 小时前
如何将视频从安卓设备传输到Mac?
android·macos
Rverdoser7 小时前
网站开发用什么语言好
服务器
心灵宝贝7 小时前
Mac用户安装JDK 22完整流程(Intel版dmg文件安装指南附安装包下载)
java·开发语言·macos
四时久成8 小时前
服务器认证系统
运维·服务器
徐子元竟然被占了!!8 小时前
Windows Server 2019 DateCenter搭建 FTP 服务器
运维·服务器·windows
wayuncn10 小时前
影响服务器托管费用的因素
运维·服务器·数据中心·服务器托管·物理服务器租用·服务器机柜·idc机房托管
喜欢你,还有大家10 小时前
Linux笔记10——shell编程基础-4
linux·运维·服务器·笔记
玩转以太网10 小时前
基于 W55MH32Q-EVB 实现 FatFs 文件系统+FTP 服务器
服务器·单片机·物联网
不懂机器人10 小时前
linux编程----网络通信(TCP)
linux·服务器·tcp/ip
✎﹏赤子·墨筱晗♪10 小时前
服务器初始化
运维·服务器