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

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

一: 简介

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

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

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


在终端使用 netstat -nta -p tcp | grep 8766
可以查看 8766 端口的连接情况,可以看到 已经有 62106端口的客户端连接成功。
在这里插入图片描述

二:主要函数

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

  • domain 选择 AF_INET, /* internetwork: UDP, TCP, etc. */
  • type. 选择SOCK_STREAM, 代表Tcp。 如果是udp的话,需要使用 SOCK_DGRAM
  • protocol 填0, 由系统选择

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

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

  • sockfd 前面返回的描述符
  • myaddr 包含有ip和port的struct 对象
  • addrlen前一个stuct的长度

3. int listen(int sockfd, int backlog)

调用后,本地socket端口的状态变更为:LISTEN, 可以使用netstat -nta -p tcp | grep 8766在终端查看

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

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

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

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

tcp的接收客户端发来的数据

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

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

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

关闭连接

三:demo代码

如下图,创建了两个进程,分别为服务器(app), 客户端为 命令行程序(客户端的代码可以直接移植到win)
在这里插入图片描述

1. 服务器端主要逻辑

  • 主要创建了socket一个 internetwork (AF_INET)和 TCP (SOCK_STREAM)组合的socket
  • 绑定任何ip的8766端口(代表任意ip的客户端都可以连接过来)
  • listen() 开始监听
  • 启动子线程,在线程内 阻塞等待客户端连接(accept),和接收客户端消息(read)
  • 启动客户端命令行进程。(这里可以在本地其他地方或者局域网内的其他电脑启动命令行)
  • 点击ui上的发送按钮,往客户端发送消息

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

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

@interface ViewController ()
@property (weak) IBOutlet NSTextField *textLabel;
@property (nonatomic, assign) int listenFd;
@property (nonatomic, assign) int currListenFd;
@end

static void subThreadWorker(ViewController *vc);

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    int listenFd;
    
    listenFd = socket(AF_INET, SOCK_STREAM, 0);
    self.listenFd = listenFd;
    if (listenFd == -1) {
        perror("socket create failed!");
        return;
    }
    //    INADDR_ANY是ANY,是绑定地址0.0.0.0上的监听, 能收到任意一块网卡的连接;
    //    INADDR_LOOPBACK, 也就是绑定地址LOOPBAC, 往往是127.0.0.1, 只能收到127.0.0.1上面的连接请求
    /**
     serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
     $ netstat -nta -p tcp | grep LISTEN
     tcp4       0      0  *.8765                 *.*                    LISTEN
     
     ------
     serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
     //tcp4       0      0  127.0.0.1.8766         *.*                    LISTEN
     */
    struct sockaddr_in serverAddr = {0};
    serverAddr.sin_family = AF_INET;
//    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
//    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_addr.s_addr = INADDR_ANY; //接受任意ip
//    serverAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    serverAddr.sin_port = htons(8766); //监听端口号8766
    
    
    int ret = bind(listenFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
    if (ret == -1) {
        perror("socket bind failed!");
        return;
    }
    
    //5代表正在等待完成相应的TCP三次握手过程的队列长度
    ret = listen(listenFd, 5);
    if (ret == -1) {
        printf("socket listen failed! error: %s(errno: %d)\n",strerror(errno),errno);
        perror("socket listen failed!");
        return;
    }
    
    std::thread(subThreadWorker, self).detach();
    printf("创建成功!\n");
    
    //启动子进程
     NSURL *subAppURL = [NSURL fileURLWithPath:
     [NSString stringWithFormat:@"%@/Socket_TCP_ClientApp", 
     [NSBundle mainBundle].executablePath.stringByDeletingLastPathComponent]];
     [[NSWorkspace sharedWorkspace] openURL:subAppURL configuration:
     [NSWorkspaceOpenConfiguration configuration] completionHandler:nil];
}

static void subThreadWorker(ViewController *vc) {
    //单独线程监听服务器发回来的消息
    ssize_t numRed = 0;
    static const int buffer_size = 4096;
    char buff[buffer_size];
    
    int tmpListenFd = vc.listenFd;
    
    while (tmpListenFd != -1) {
        printf("服务器等待客户端  %i  连接...\n", vc.listenFd);
        tmpListenFd = accept(tmpListenFd, NULL, NULL);
        printf("收到客户端连接。 sfd:%i\n", vc.listenFd);
        if (vc.listenFd == -1) {
            printf("socket accept failed! error: %s(errno: %d)\n",strerror(errno),errno);
            perror("这是一个无效的连接!");
            break;
        }
        vc.currListenFd = tmpListenFd;
        
        //循环读取 client 发来的消息
        while ((numRed = read(tmpListenFd, buff, buffer_size)) > 0) {
            printf("服务器收到客户端发的数据: %s\n", buff);
        }
        if (numRed == -1) {
            perror("numRed == -1!");
            break;
        }
        if (close(tmpListenFd) == -1) {
            perror("close faild!");
            break;
        }
        tmpListenFd = vc.listenFd;
        printf("for over!\n");
    }
}

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

- (void)dealloc {
}

@end

2. 客户端主要逻辑

  • 如果是windows,需要首先调用 WSAStartup(...)
  • 同样创建socket 连接,tcp 类型的。socket(AF_INET, SOCK_STREAM, 0);
  • 连接到服务器,地址为: "10.34.133.46:8766"(如果本机的话可以使用127.0.0.1), connect(...)
  • 向服务器发送一条消息, send(client_fd, buf, sizeof(buf), 0)
  • 收到一次消息,就退出程序. recv(client_fd, buf, sizeof(buf), 0)
  • 处理退出前的清理工作。

主要代码: main.cpp 文件代码

//
//  main.cpp
//  Socket_TCP_ClientApp
//
//  Created by jimbo on 2024/1/22.
//

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

#ifdef _WIN32
#include <system_error>
#include <WS2tcpip.h>
#pragma warning(disable:4996)
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#endif // _WIN32
#pragma comment(lib, "ws2_32.lib")


#ifdef _WIN32
class  SocketInit
{
public:
    SocketInit() {
        //Win 必须在socket 使用前调用 WSAStartup()
        WSADATA data;
        int ret = WSAStartup(MAKEWORD(2, 1), &data);
        printf("WSAStartup val:%d\n", ret);
    }
    ~SocketInit() {
        printf("~SocketInit\n");
        WSACleanup();
    }
};
const SocketInit sInit;
#endif // _WIN32


int main(int argc, char* argv[]) {

    printf("\n\n我是Client\n\n");
    int client_fd;
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8766); //端口
    server_addr.sin_addr.s_addr = inet_addr("10.34.133.46"); //ip

    //创建连接
    client_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_fd < 0) {
#ifdef _WIN32
        char buf[1024];
        strerror_s(buf, 1024, errno);
#else
        char *buf = strerror(errno);
#endif // _WIN32
        printf("socket create failed. %s(errno:%d)\n", buf, errno);
        exit(EXIT_FAILURE);
    }

    //连接到服务器
    if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("socket connect failed");
        exit(EXIT_FAILURE);
    }
    printf("socket connect  success!\n");

    char buf[1024] = "hello socket from client!";
    //向服务器发送一条消息
    if (send(client_fd, buf, sizeof(buf), 0) < 0) {
        perror("socket send failed");
        exit(EXIT_FAILURE);
    }
    
    printf("socket send  success!\n");

    //收到一次消息,就退出程序
    if (recv(client_fd, buf, sizeof(buf), 0) < 0) {
        perror("socket recv failed");
        exit(EXIT_FAILURE);
    }

    printf("client recv response: [%s]\n", buf);

#ifdef _WIN32
    /* 关闭socket */
    closesocket(client_fd);
#else
    /* 关闭socket */
    close(client_fd);
#endif // _WIN32

    printf("client ended successfully\n");
    exit(EXIT_SUCCESS);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

东莞UG逆向建模设计汽车内外饰出stp图抄数3d造型建模代画图服务

汽车内外饰三维扫描及逆向建模是一项复杂且技术性强的工程。它涉及到使用高精度的三维扫描仪对汽车内外饰进行全面、细致的扫描&#xff0c;获取其精确的三维数据。这个过程中&#xff0c;需要确保扫描的环境、光线、角度等因素对扫描结果的影响最小化&#xff0c;以保证获取的…

【测试开发八股文】算法

1、栈和队列 1&#xff09;栈&#xff1a;先进后出&#xff1b;队列&#xff1a;先进先出 2&#xff09;如何用栈实现队列&#xff1f; 分析&#xff1a;两个栈&#xff0c;栈1负责入队&#xff0c;栈2负责出队 改进点&#xff1a;出队时&#xff0c;栈2出队后&#xff0c;可不…

python基础——线程

线程的使用 from threading import Thread import time def func(i):print("start{}".format(i))time.sleep(1)print("end{}".format(i)) for i in range(10):Thread(targetfunc,args(i,)).start() 守护线程的使用 主进程结束时&#xff0c;守护线程不会…

常用芯片学习——HC244芯片

HC573 三态输出八路缓冲器|线路驱动器 使用说明 SNx4HC244 八路缓冲器和线路驱动器专门设计用于提高三态存储器地址驱动器、时钟驱动器以及总线导向接收器和发送器的性能和密度。SNx4HC244 器件配备两个具有独立输出使能 (OE) 输入的 4 位缓冲器和驱动器。当 OE 为低电平时&a…

1. Matplotlib的Figure基础概念

1. Matplotlib的Figure基础概念 一 **角色和作用**二 **类比&#xff1a;**三 **基本使用示例** Matplotlib是一个用于绘制二维图形的Python库&#xff0c;广泛应用于数据可视化领域。其灵活性和强大的功能使得用户能够轻松创建各种类型的图表&#xff0c;包括折线图、散点图、…

node 第二十二天 mongoDB最新版7.x安装教程

学习服务端其实就是学习数据库, 就web这一条线而言, 客户端的学习就是学习浏览器, 而服务端的学习就是学习数据库(当然还有服务器) 为什么学习mongoDB mongoDB是非关系型数据库(not only sql) 基本上补全了mysql的缺陷, 当然也缺失了部分mysql的优势. 当然, 非大型应用的业务场…

linux中文件锁定--flock命令

在Linux操作系统中&#xff0c;flock是一个用于文件锁定的命令。文件锁定是一种机制&#xff0c;用于在多任务和多用户环境中管理对共享资源&#xff08;如文件&#xff09;的访问。flock允许你在代码中设置锁&#xff0c;以确保在任何给定时刻只有一个进程可以访问被锁定的文件…

leetcode—— 腐烂的橘子

腐烂的橘子 在给定的 m x n 网格 grid 中&#xff0c;每个单元格可以有以下三个值之一&#xff1a; 值 0 代表空单元格&#xff1b;值 1 代表新鲜橘子&#xff1b;值 2 代表腐烂的橘子。 每分钟&#xff0c;腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。 返回 直到…

Leetcode刷题(二十八)

找出字符串中第一个匹配项的下标&#xff08;Easy&#xff09; 给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不是 haystack 的一部分&#xff0c;则返…

数据链路层流量控制与传输层流量控制对比

目录 一.数据链路层 1.停止等待协议 无差错情况&#xff1a; 有差错情况&#xff1a; 2.滑动窗口协议 &#xff08;1&#xff09;后退N帧协议&#xff08;GBN&#xff09; &#xff08;2&#xff09;选择重传协议&#xff08;SR&#xff09; 二.传输层 1.传输层的可靠…

Github2024-01-23 开源项目日报 Top9

根据Github Trendings的统计&#xff0c;今日(2024-01-23统计)共有9个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目3Go项目2TypeScript项目2Dart项目1Jupyter Notebook项目1 gpt4free 语言模型集合改进计划 创建周期…

基于 LangChain 框架,向量数据库如何创建、读取、更新、删除(CRUD)

RAG是目前大语言模型从工具走向生产力实践的最热门的方式&#xff0c;它可以实现从海量的文本数据中检索相关的信息&#xff0c;并用于生成高质量的文本输出。 而聊到RAG&#xff0c;我们就很难避开使用RAG的基础设施-向量数据库 今天我将带领大家&#xff0c;以最为基础的CRU…

安装miniconda、tensorflow、libcudnn

目录 安装miniconda 安装tensorflow 安装 libcudnn 安装miniconda wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && bash Miniconda3-latest-Linux-x86_64.sh 安装tensorflow tensorflow官网&#xff0c;查看版本对应 https:…

Linux——进程程序替换

进程程序替换 文章目录 进程程序替换1. 进程程序替换的基本概念2. exec系列函数2.1 是否带p2.1.1 execl()2.1.2 execlp() 2.2 是否带e2.2.1 execle() 2.3 l或v2.3.1 execvp() 2.4 返回值 3. 注意点 本章思维导图&#xff1a; 注&#xff1a;本章思维导图对应的 .xmind和 .png…

万界星空科技注塑行业MES解决方案

注塑行业是一个具有发展潜力的行业&#xff0c;随着人们对物质生活的质量要求越来越高&#xff0c;日用品、医疗保健、汽车工业以及建筑等行业对注塑制品的需求量日益增长。注塑企业提供的多种多样的塑料产品已深入到经济生活的各个领域&#xff0c;为国家经济的各个部门包括轻…

yolov8 opencv dnn部署自己的模型

源码地址 本人使用的opencv c github代码,代码作者非本人 使用github源码结合自己导出的onnx模型推理自己的视频 推理条件 windows 10 Visual Studio 2019 Nvidia GeForce GTX 1070 opencv4.7.0 (opencv4.5.5在别的地方看到不支持yolov8的推理&#xff0c;所以只使用opencv…

四、Flask学习之JavaScript

四、Flask学习之JavaScript JavaScript&#xff0c;作为一种前端脚本语言&#xff0c;赋予网页生动的交互性和动态性。通过它&#xff0c;开发者能够操作DOM&#xff08;文档对象模型&#xff09;实现页面元素的动态改变、响应用户事件&#xff0c;并借助AJAX技术实现异步数据…

【极数系列】Flink项目入门搭建(03)

【极数系列】Flink项目入门搭建&#xff08;03&#xff09; 引言 gitee地址&#xff1a;https://gitee.com/shawsongyue/aurora.git 源码直接下载可运行&#xff0c;模块&#xff1a;aurora_flink Flink 版本&#xff1a;1.18.0 Jdk 版本&#xff1a;11 1.创建mavenx项目 2.…

基于taro搭建小程序多项目框架

前言 为什么需要这样一个框架&#xff0c;以及这个框架带来的好处是什么&#xff1f; 从字面意思上理解&#xff1a;该框架可以用来同时管理多个小程序&#xff0c;并且可以抽离公用组件或业务逻辑供各个小程序使用。当你工作中面临这种同时维护多个小程序的业务场景时&#xf…

Unity中UGUI在Mask剪裁粒子特效的实现

在Unity使用Mask是剪裁不了粒子特效的&#xff0c;之前有想过RenderTexture来实现&#xff0c;不过使用RenderTexture不适合用于很多个特效&#xff0c;因为RenderTexture依赖Camera的照射&#xff0c;如果在背包中每种道具都有不同的特效&#xff0c;那使用RenderTexture则需要…
最新文章