iOS進程通信
在iOS中,進程通信的方式有很多種;下面先列舉下常用的幾種方式:
- URL scheme
- Keychain
- APP Group
- UIPasteBoard
- LocalSocket
此篇著重了解LocalSocket,首先我們需要知道客戶端、服務端的區(qū)別;
- 服務端:需要在本地端口進行TCP的綁定、監(jiān)聽
- 客戶端:獲取服務端同一個端口,進行連接
當兩端建立連接后,接著就可以去發(fā)送消息了;
以Broadcast Upload Extension為例;(這里使用OC的第三方庫GCDAsyncSocket)
- **在主程序中,我們是以服務端去設置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
- (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寫入數(shù)據(jù)
// 發(fā)送數(shù)據(jù)
- (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
//已經(jīng)連接到服務器
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(nonnull NSString *)host port:(uint16_t)port{
NSLog(@"連接成功 : %@---%d",host,port);
//連接成功或者收到消息,必須開始read,否則將無法收到消息,
//不read的話,緩存區(qū)將會被關閉
// -1 表示無限時長 ,永久不失效
[sock readDataWithTimeout:-1 tag:10086];
}
// 連接斷開
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
NSLog(@"斷開 socket連接 原因:%@",err);
[self.videoSocket disconnect];
self.videoSocket = nil;
[self socketConnect];
}
//已經(jīng)接收服務器返回來的數(shù)據(jù)
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
NSLog(@"接收到tag = %ld : %ld 長度的數(shù)據(jù)",tag,data.length);
//連接成功或者收到消息,必須開始read,否則將無法收到消息
//不read的話,緩存區(qū)將會被關閉
// -1 表示無限時長 , tag
[sock readDataWithTimeout:-1 tag:10086];
}
//消息發(fā)送成功 代理函數(shù) 向服務器 發(fā)送消息
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
NSLog(@"%ld 發(fā)送數(shù)據(jù)成功",tag);
}
由于使用socket,所以需要處理粘包的情況。目前粘包解決方案基本上都是通用的。
粘包解決方案:
給每個數(shù)據(jù)包添加頭部,頭部中包含數(shù)據(jù)包長度,這樣接收到數(shù)據(jù)后,通過讀取頭部的長度字段,便知道每一個數(shù)據(jù)包的實際長度,然后根據(jù)實際的長度去讀取對應的長度的數(shù)據(jù),這樣便可以獲取正確的數(shù)據(jù);
具體做法如下:
封包:給每個包添加包頭,包頭包含數(shù)據(jù)的長度
拆包:將數(shù)據(jù)包塞入環(huán)形緩沖區(qū),比較緩存區(qū)的長度是否大于總長度,大于則取出包頭獲取數(shù)據(jù)的長度,并根據(jù)長度取出正確的數(shù)據(jù),處理完后將處理后的數(shù)據(jù)移出緩沖區(qū);