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

news2024/12/23 8:39:27

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.coloradmin.cn/o/1406656.html

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

相关文章

(2)(2.4) CRSF/ELRS Telemetry

文章目录 前言 1 ArduPilot 参数编辑器 前言 &#xff01;Note ELRS&#xff08;ExpressLRS&#xff09;遥控系统使用穿越火线协议&#xff0c;连接方式类似。不过&#xff0c;它不像穿越火线那样提供双向遥测。 TBS CRSF 接收机与 ArduPilot 的接口中包含遥测和遥控信息。…

【K8S 云原生】K8S之HPA自动扩缩容、命名空间资源限制、容器抓包

目录 一、HPA概述 1、概念 2、两个重要的组件&#xff1a; 3、HPA的规则&#xff1a; 4、pod的副本数扩容有两种方式&#xff1a; 4.1、手动扩缩容&#xff0c;修改副本数&#xff1a; 4.2、自动扩缩容HPA 二、实验部署&#xff1a; 1、部署HPA 2、实现自动扩缩容 三…

Studio One 6 mac 6.5.2 激活版 数字音乐编曲创作

PreSonus Studio One是PreSonus出品的一款功能强大的音乐创作软件。主要为用户提供音乐创作、录音、编辑、制作等功能。它可以让你创造音乐&#xff0c;无限的轨道&#xff0c;无限的MIDI和乐器轨道&#xff0c;虚拟乐器和效果通道&#xff0c;这些都是强大和完美的。 软件下载…

JVM工作原理与实战(二十五):堆的垃圾回收-垃圾回收算法

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、垃圾回收算法介绍 1.垃圾回收算法的历史和分类 2.垃圾回收算法的评价标准 二、垃圾回收算法详解 1.标记清除算法 2.复制算法 3.标记整理算法 4.分代垃圾回收算法 总结 前言…

打开json文件,读取里边的每一行数据,每一行数据是一个字典,使用matplotlib画图

这段代码的目的是读取 JSON 文件&#xff0c;提取关键信息&#xff0c;然后使用 Matplotlib 绘制四个子图&#xff0c;分别显示不同的指标随着 iter 变化的情况。这种图形化分析有助于直观地了解模型的性能。 画图结果如下&#xff1a; json文件格式如下&#xff1a;下面只粘贴…

Nacos源码下载与运行

早先在linux环境下搭建过nacos环境 即Centos安装部署nacos实战&#xff0c;本次是从官网上下载源码&#xff0c;本地运行看看&#xff0c;记录过程&#xff0c;方便备查。 第一步、Nacos源码下载 推荐到nacos官网下载 Github地址&#xff0c;本次选择最新版&#xff0c;1.4.7…

计算机毕业设计 基于SpringBoot的民宿租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

解决vue 2.6通过花生壳ddsn(frp内网穿透)实时开发报错Invalid Host header和websocket

请先核对自己的vue版本&#xff0c;我的是2.6.14&#xff0c;其他版本未测试 起因 这两天在维护一个基于高德显示多个目标&#xff08;门店&#xff09;位置的项目&#xff0c;由于高德要求定位必须使用https服务&#xff0c;遂在本地无法获取到定位坐标信息&#xff0c;于是…

数藏潮玩开启元宇宙新空间(定制开发)

元宇宙给我们带来了很多的可能性&#xff0c;对于一个品牌或者是平台来说&#xff0c;越早抓住数藏也就越早抓住了元宇宙的早起红利&#xff0c;就能在未来式这个超空间里面占住商机。 而数藏潮玩的想象空间是巨大的&#xff0c;所以能创造的生态也是无限大。在通过对数藏平台…

一文读懂:D3.js的前世今生,以及与echarts的对比

D3.js&#xff08;Data-Driven Documents&#xff09;是一种用于创建动态、交互式数据可视化的JavaScript库。它通过使用HTML、CSS和SVG等Web标准&#xff0c;将数据与文档结合&#xff0c;使得数据可以以一种直观和易于理解的方式进行呈现。D3.js的重要性在于它赋予了开发者更…

SSM:Spring + Spring MVC + MyBatis 的整合

SSM 前言整合 前言 在完成 Spring 、Spring MVC 与 MyBatis 基础知识的学习后&#xff0c;下面简单介绍 SSM 框架的整合使用。 整合 SSM&#xff0c;是 Java 开发中常用的一个 Web 框架组合&#xff0c;用于构建基于 Spring 和 MyBatis 的 Web 应用&#xff08; Spring MVC …

桌面型物联网智能机器人设计(预告)

相关资料 桌面级群控机器人CoCube探索-2022--CSDN博客 视频&#xff1a; 能&#xff01;有&#xff01;多&#xff01;酷&#xff01;CoCube桌面级群控机器人 让我看看谁在SJTU里划水… 简要介绍 设计一个桌面型物联网智能机器人&#xff0c;以ESP32芯片为核心&#xff0c;配…

Spring Security 6 学习-1

什么是 Spring Security Spring Security文档 Spring Security中文文档 Spring Security 是 Spring 家族中的安全型开发框架&#xff0c;主要解决三大方面问题&#xff1a;认证&#xff08;你是谁&#xff09;、授权&#xff08;你能干什么&#xff09;、常见攻击保护&#xff…

vue项目中使用Element多个Form表单同时验证

一、项目需求 在项目中一个页面中需要实现多个Form表单&#xff0c;并在页面提交时需要对多个Form表单进行校验&#xff0c;多个表单都校验成功时才能提交。 二、实现效果 三、多个表单验证 注意项&#xff1a;多个form表单&#xff0c;每个表单上都设置单独的model和ref&am…

GPT4+Python近红外光谱数据分析及机器学习与深度学习建模

详情点击链接&#xff1a;GPT4Python近红外光谱数据分析及机器学习与深度学习建模 第一&#xff1a;GPT4入门基础 1、ChatGPT概述&#xff08;GPT-1、GPT-2、GPT-3、GPT-3.5、GPT-4模型的演变&#xff09; 2、ChatGPT对话初体验&#xff08;注册与充值、购买方法&#xff09…

写Shell以交互方式变更Ubuntu的主机名

以下是一个简单的 Bash 脚本&#xff0c;用于以交互方式更改 Ubuntu 20 系统的主机名&#xff1a; 1#!/bin/bash 2 3# 提示用户输入新的主机名 4read -p "请输入新的系统名称&#xff08;主机名&#xff09;: " new_hostname 5 6# 检查是否输入了新的主机名 7if [ -…

Parallels Desktop 18 for Mac(pd虚拟机) 激活版

Parallels Desktop 18是一款功能强大的虚拟机软件&#xff0c;可以在Mac操作系统上同时运行多种操作系统&#xff0c;包括Windows、Linux、Android等。该软件提供了多种高级功能&#xff0c;如支持DirectX 11游戏、3D图形和OpenGL应用程序&#xff0c;以及运行Windows和Mac应用…

OpenCV书签 #余弦相似度的原理与相似图片/相似文件搜索实验

1. 介绍 余弦相似度&#xff08;Cosine Similarity&#xff09;&#xff0c;又称为余弦相似性&#xff0c;是通过计算两个向量的夹角余弦值来评估他们的相似度。余弦相似度仅仅与向量的指向方向相关&#xff0c;与向量的长度无关&#xff0c;它将向量根据坐标值绘制到向量空间…

网络协议与攻击模拟_07UDP协议

一、简单概念 1、UDP协议简介 UDP&#xff08;用户数据报&#xff09;协议&#xff0c;是传输层的协议。不需要建立连接&#xff0c;直接发送数据&#xff0c;不会重新排序&#xff0c;不需要确认。 2、UDP报文字段 源端口目的端口UDP长度UDP校验和 3、常见的UDP端口号 5…

Springboot+vue的医院后台管理系统(有报告),Javaee项目,springboot vue前后端分离项目

演示视频&#xff1a; Springbootvue的医院后台管理系统&#xff08;有报告&#xff09;&#xff0c;Javaee项目&#xff0c;springboot vue前后端分离项目 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的医院后台管理系统&#xff0c;采用M&#xff08…