进程间的实时通讯方案: local socket(解决扩展和容器应用的实时通讯问题)

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 进程间的实时通讯方案: local socket(解决扩展和容器应用的实时通讯问题)

文章目录

  • 前言
  • I 、方案案例:local socket

  • 1.1 基础知识:Socket 通讯过程
  • 1.2 serverSocket
  • 1.3 clientSocket
  • 1.4 完整demo
  • see also

前言

image.png

如果你对IPC不了解,可以先看下这篇- Inter process Communication

image.png

登录 iTunes Store 这个输入框的弹出流程是由itunesstored 控制,process:SpringBoard 进行处理,采用SBUserNotificationAlert的方式进行进程间的消息传递。

通常你会想到的方案会有以下几种(根据具体场景进行选择)

1:URL Scheme 常用的App间传值方式。常见于分享等。

2:Keychain 借助系统类 KeychainItemWrapper来使用。常见于免登陆 (同公司产品间)

3:UIPasteboard 粘贴板。淘宝的链接分享。

4:UIDocumentInteractionController 常用于文件的分享

5:local socket(本文内容)

I 、方案案例:local socket

采用Local Socket方案(TCP)创建服务端和客户端从而达到通讯效果。

  • 基于GCDAsyncSocket提供的解决方案

基于 CFSocket、GCD 进行的封装,支持 TCP 和 UDP

platform :ios, '8.0'
inhibit_all_warnings!
#use_frameworks!
target 'localScoket' do
   pod 'CocoaAsyncSocket'
end
target 'localScoket4client' do
   pod 'CocoaAsyncSocket'
end

1.1 基础知识:Socket 通讯过程

  • CFSocket(纯 C)

苹果对对底层 BSD Socket 进行轻量级的封装。

API:CFSocekt 用于建立连接,CFStream 用于读写数据。

  • tcp

image.png

  • UDP

image.png

  • TCP 的三次握手建立连接

image.png

  • TCP 的四次挥手释放连接

image.png

  • 先挥手再握手(先断开再连接)

image.png

  • 先握手再挥手(先连接再断开)

image.png

1.2 serverSocket

#import <GCDAsyncSocket.h>
@interface ViewController ()
{
    GCDAsyncSocket *_serverSocket;
}
@property(strong,nonatomic)NSMutableArray *clientSocket;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    _clientSocket = [NSMutableArray array];
    //创建服务端的socket,注意这里的是初始化的同时已经指定了delegate
    _serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    [self startChatServer];
}
-(void)startChatServer{
    //打开监听端口
    NSError *err;
    [_serverSocket acceptOnPort:12345 error:&err];
    if (!err) {
        NSLog(@"Server 服务开启成功");
    }else{
        NSLog(@"Server 服务开启失败");
    }
}
#pragma mark 有客户端建立连接的时候调用
-(void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{
    //sock为服务端的socket,服务端的socket只负责客户端的连接,不负责数据的读取。newSocket为客户端的socket    NSLog(@"服务端的socket %p 客户端的socket %p",sock,newSocket);
    //保存客户端的socket,如果不保存,服务器会自动断开与客户端的连接(客户端那边会报断开连接的log)
    NSLog(@"Server %s",__func__);
    [self.clientSocket addObject:newSocket];
    //newSocket为客户端的Socket。这里读取数据
    [newSocket readDataWithTimeout:-1 tag:100];
}
#pragma mark 服务器写数据给客户端
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
    NSLog(@"Server %s",__func__);
    [sock readDataWithTimeout:-1 tag:100];
}
#pragma mark 接收客户端传递过来的数据
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    //sock为客户端的socket
    NSLog(@"Server 客户端的socket %p",sock);
    //接收到数据
    NSString *receiverStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"Server receiverStr :%@",receiverStr);
    // 把回车和换行字符去掉,接收到的字符串有时候包括这2个,导致判断quit指令的时候判断不相等
    receiverStr = [receiverStr stringByReplacingOccurrencesOfString:@"\r" withString:@""];
    receiverStr = [receiverStr stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    //判断是登录指令还是发送聊天数据的指令。这些指令都是自定义的
    //登录指令
    if([receiverStr hasPrefix:@"iam:"]){        // 获取用户名
        NSString *user = [receiverStr componentsSeparatedByString:@":"][1];
        // 响应给客户端的数据
        NSString *respStr = [user stringByAppendingString:@"has joined"];
        [sock writeData:[respStr dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
    }
    //聊天指令
    if ([receiverStr hasPrefix:@"msg:"]) {
        //截取聊天消息
        NSString *msg = [receiverStr componentsSeparatedByString:@":"][1];
        [sock writeData:[msg dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
    }
    //quit指令
    if ([receiverStr isEqualToString:@"quit"]) {
        //断开连接
        [sock disconnect];
        //移除socket
        [self.clientSocket removeObject:sock];
    }
    NSLog(@"Server %s",__func__);
}

image.png

1.3 clientSocket

#import <GCDAsyncSocket.h>
@interface ViewController ()
{
     GCDAsyncSocket *_clientSocket;
}
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self connectToServer];
}
- (void) connectToServer{
    //1.主机与端口号
    NSString *host = @"127.0.0.1";
    int port = 12345;
    //初始化socket,这里有两种方式。分别为是主/子线程中运行socket。根据项目不同而定
    _clientSocket = [[GCDAsyncSocket alloc]
                     initWithDelegate:self delegateQueue:dispatch_get_main_queue()];//这种是在主线程中运行
    //_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; 这种是在子线程中运行
    //开始连接
    NSError *error = nil;
    if (![_clientSocket connectToHost:host onPort:port error:&error])
    {
        NSLog(@"Client Error connecting: %@", error);
    }
}
-(IBAction)login:(id)sender{
    //登录String
    NSString *loginStr = @"iam:I am login!";
    NSData *loginData = [loginStr dataUsingEncoding: NSUTF8StringEncoding];
    //发送登录指令。-1表示不超时。tag200表示这个指令的标识,很大用处
    [_clientSocket writeData: loginData withTimeout:-1 tag:200];
}
//发送聊天数据
-(IBAction) sendMsg: (id)sender{
    NSString *sendMsg = @"msg:I send message to u!";
    NSData *sendData = [sendMsg dataUsingEncoding:NSUTF8StringEncoding];
    [_clientSocket writeData:sendData withTimeout:-1 tag:201];
}
#pragma mark -socket的代理
#pragma mark 连接成功
-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
    //连接成功
    NSLog(@"Client %s",__func__);
}
#pragma mark 断开连接
-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
    if (err) {
        NSLog(@"Client 连接失败");
    }else{
        NSLog(@"Client 正常断开");
    }
}
#pragma mark 数据发送成功
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
    NSLog(@"Client %s",__func__);
    //发送完数据手动读取
    [sock readDataWithTimeout:-1 tag:tag];//不然当收到信息后不会执行读取回调方法。
}
#pragma mark 读取数据
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    NSString *receiverStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    if (tag == 200) {
        //登录指令
    }else if(tag == 201){
        //聊天数据
    }
    NSLog(@"Client %s %@",__func__,receiverStr);
}

image.png

1.4 完整demo

  • localScoket 源码
  • getIPAddress
#import <sys/types.h>
#import <sys/socket.h>
#import <ifaddrs.h>
#import <arpa/inet.h>
// Get IP Address
- (NSString *)getIPAddress {
    NSString *address = @"error";
    struct ifaddrs *interfaces = NULL;
    struct ifaddrs *temp_addr = NULL;
    int success = 0;
    // retrieve the current interfaces - returns 0 on success
    success = getifaddrs(&interfaces);
    if (success == 0) {
        // Loop through linked list of interfaces
        temp_addr = interfaces;
        while(temp_addr != NULL) {
            if(temp_addr->ifa_addr->sa_family == AF_INET) {
                // Check if interface is en0 which is the wifi connection on the iPhone
                if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) {
                    // Get NSString from C String
                    address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
                }
            }
            temp_addr = temp_addr->ifa_next;
        }
    }
    // Free memory
    freeifaddrs(interfaces);
    NSLog(@"%@",address);// IP 地址
    return address;
}

see also

  • tweak 项目 快速搭建CocoaAsyncSocket(建连、断开、重连、心跳、通用请求)
目录
打赏
0
0
0
0
5
分享
相关文章
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
针对本地存储和 PVC 这两种容器存储使用方式,我们对 ACK 的容器存储监控功能进行了全新升级。此次更新完善了对集群中不同存储类型的监控能力,不仅对之前已有的监控大盘进行了优化,还针对不同的云存储类型,上线了全新的监控大盘,确保用户能够更好地理解和管理容器业务应用的存储资源。
373 182
容器化浪潮下的AI赋能:智能化运维与创新应用
近年来,容器技术以其轻量、高效、可移植的特性成为云原生时代的基石,推动应用开发和部署方式革新。随着容器化应用规模扩大,传统运维手段逐渐力不从心。AI技术的引入为容器化生态带来新活力,实现智能监控、自动化故障诊断与修复及智能资源调度,提升运维效率和可靠性。同时,AI驱动容器化创新应用,如模型训练、边缘计算和Serverless AI服务,带来更多可能性。未来,AI与容器技术的融合将更加紧密,推动更智能、高效的运维平台和丰富的创新应用场景,助力数字化转型。
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
ACK 容器监控存储全面更新:让您的应用运行更稳定、更透明
如何在宿主主机运行容器中的php守护进程
在Docker容器中同时运行多个程序(如Nginx+PHP+Ftp)时,需用`docker exec`命令启动额外服务。首先通过`php -v`查看PHP版本,再用`which php-fpm7.4`确认PHP安装路径,通常返回`/usr/sbin/php-fpm7.4`。最后直接运行该路径启动PHP-FPM服务,确保其正常工作。
58 14
ACK容器监控存储全面更新:让您的应用运行更稳定、更透明
介绍升级之后的ACK容器监控体系,包括各大盘界面展示和概要介绍。
Docker容器内进行应用调试与故障排除的方法与技巧,包括使用日志、进入容器检查、利用监控工具及检查配置等,旨在帮助用户有效应对应用部署中的挑战,确保应用稳定运行
本文深入探讨了在Docker容器内进行应用调试与故障排除的方法与技巧,包括使用日志、进入容器检查、利用监控工具及检查配置等,旨在帮助用户有效应对应用部署中的挑战,确保应用稳定运行。
148 5
Docker 是一种容器化技术,支持开发者将应用及其依赖打包成容器,在不同平台运行而无需修改。
Docker 是一种容器化技术,支持开发者将应用及其依赖打包成容器,在不同平台运行而无需修改。本文探讨了 Docker 在多平台应用构建与部署中的作用,包括环境一致性、依赖管理、快速构建等优势,以及部署流程和注意事项,展示了 Docker 如何简化开发与部署过程,提高效率和可移植性。
112 4
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
29天前
|
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
49 5