文章目录
- 前言
- I 、方案案例:local socket
- 1.1 基础知识:Socket 通讯过程
- 1.2 serverSocket
- 1.3 clientSocket
- 1.4 完整demo
- see also
前言
如果你对IPC不了解,可以先看下这篇- Inter process Communication
登录 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
- UDP
- TCP 的三次握手建立连接
- TCP 的四次
挥手
释放连接
- 先挥手再握手(先断开再连接)
- 先握手再挥手(先连接再断开)
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__); }
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); }
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(建连、断开、重连、心跳、通用请求)