「这是我参加2022首次更文应战的第7天,活动详情查看:2022首次更文应战」。

iOS进程通讯

说到iOS进程之间的通讯,咱们能够想到有哪些方法呢?

在这儿,我总结了以下几种常用的方法:

  • APP Group
  • UIPasteBoard
  • URL scheme
  • LocalSocket
  • Keychain

看到这儿,是不是觉得有些方法很熟悉,比方说:UIPasteBoardURL scheme常常做一些跨运用之前的处理(从WX张贴分享案牍到某宝、某多、某东等),APP Group是苹果用来解决运用之间数据传输(在前面屏幕同享这篇文章有讲过),而LocalSocket这种方法基本上很少用到,那么本篇文章咱们就讲讲怎样运用LocalSocket。

LocalSocket

首先咱们需求知道客户端、服务端的差异;

  • 服务端:需求在本地端口进行TCP的绑定、监听
  • 客户端:获取服务端同一个端口,进行衔接

当两头树立衔接后,接着就可以去发送音讯了;

这儿还是以Broadcast Upload Extension为例;(运用OC三方库GCDAsyncSocket

咱们需求将系统屏幕同享的数据回传给主运用,所以在主程序中,咱们是以服务端去敞开socket,在扩展程序中,咱们是以客户端去衔接socket,并向主程序宣布通讯。

MainApp 中 socket 的设置

在主程序中,设置socket的端口号、地址。

敞开socket服务

// 敞开socket服务
- (void)startSocketService {
    if (self.serverSocket != nil) {
        return;
    }
    self.serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    self.serverSocket.autoDisconnectOnClosedReadStream = YES;
    __autoreleasing NSError *error = nil;
    BOOL result = [self.serverSocket acceptOnInterface:@"localhost" port:8080 error:&error];
    if(error || !result){
        NSLog(@"server socket result:%@ error:%@", error, @(result));
    }else {
        NSLog(@"server socket:%@ listen", self.serverSocket);
    }
}

中止socket服务

// 中止socket服务
- (void)stopSocketService {
    if (self.serverSocket != nil) {
        [self.serverSocket disconnect];
    }
    self.serverSocket = nil;
}

Extension 中 socket 的设置

在扩展程序中衔接socket,并发送数据包。

衔接socket

// 衔接socket
- (void)socketConnect {
    if (self.videoSocket != nil) {
        self.videoSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:[self.class SEND_VIDEO_SERIAL_QUEUE]];
    }
    if (!self.videoSocket.isConnected) {
        NSError *error;
        [self.videoSocket connectToHost:@"localHost" onPort:8080 error:&error];
        [self.videoSocket readDataWithTimeout:-1 tag:10086];
    }
}

断开socket

// 断开socket
- (void)socketDisConnect {
    [self.videoSocket disconnect];
    self.videoSocket = nil;
}

socket写入数据

// 发送数据
- (void)sendVideoSampleBuffer:(nonnull CMSampleBufferRef)sampleBuffer {
    CFTimeInterval currentTime = CACurrentMediaTime();
    if (currentTime - self.lastTimeInterval < self.frameInterval) {
        return;
    }
    self.lastTimeInterval = currentTime;
    @autoreleasepool {
        NSNumber *orientation = nil;
        if (@available(iOS 11.0, *)) {
            CFStringRef orientationKey = (__bridge CFStringRef)RPVideoSampleOrientationKey;
            orientation = (NSNumber *)CMGetAttachment(sampleBuffer,orientationKey,NULL);
        }
        size_t length = 0;
        void *sendData = [TScreenShareBroadcasterTool sampleBufferToData:sampleBuffer targetSize:self.options.targetFrameSize orientation:orientation length:&length];
        dispatch_async([[self class] SEND_VIDEO_SERIAL_QUEUE], ^{
            NSData *data = [NSData dataWithBytes:sendData length:length];
            [self.videoSocket writeData:data withTimeout:5 tag:10086];
            free(sendData);
        });
    }
}

socket回调

#pragma mark - GCDAsyncSocketDelegate
//现已衔接到服务器
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(nonnull NSString *)host port:(uint16_t)port{
    NSLog(@"衔接成功 : %@---%d",host,port);
    //衔接成功或许收到音讯,必须开端read,否则将无法收到音讯,
    //不read的话,缓存区将会被封闭
    // -1 表明无限时长 ,永久不失效
    [sock readDataWithTimeout:-1 tag:10086];
}
// 衔接断开
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
    NSLog(@"断开 socket衔接 原因:%@",err);
    [self.videoSocket disconnect];
    self.videoSocket = nil;
    [self socketConnect];
}
//现已接收服务器返回来的数据
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    NSLog(@"接收到tag = %ld : %ld 长度的数据",tag,data.length);
    //衔接成功或许收到音讯,必须开端read,否则将无法收到音讯
    //不read的话,缓存区将会被封闭
    // -1 表明无限时长 , tag
    [sock readDataWithTimeout:-1 tag:10086];
}
//音讯发送成功 署理函数 向服务器 发送音讯
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
    NSLog(@"%ld 发送数据成功",tag);
}

运用socket一般都需求处理粘包的情况。目前粘包解决方案基本上都是通用的。

粘包解决方案

一般解决方案就是将数据包分为两部分:Header + Data

`Header`: 包括`Data`length,有自己固定的length`Data`: 实践的数据包;

Header中包括Data长度,这样接收到数据后,通过读取Header的长度字段,便知道每一个Data的实践长度,然后依据实践的长度去读取对应的长度的数据,这样便可以获取正确的数据;具体做法如下:

  • 封包:给每个包添加包头,包头包括数据的长度
  • 拆包:将数据包塞入环形缓冲区,比较缓存区的长度是否大于总长度,大于则阐明Data完整,此刻取出Header获取数据的长度,并依据长度取出正确的数据,处理完后将处理后的数据移出缓冲区;

写在最终

咱们应该都知道socket是实时的传输数据,虽然有或许存在丢包的情况,可是如果咱们需求运用到实时性较高的场景,运用socket无疑是一种不错的挑选。