文章目录
- 前言
- I 、方案案例:local socket
-
- 1.1 基础知识:Socket 通讯过程
- 1.2 serverSocket
- 1.3 clientSocket
- 1.4 完整demo
- see also
前言
文章作者 | 文章标题 | 文章链接 |
---|---|---|
kunnan | 进程间的实时通讯 | blog url |
如果你对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提供的解决方案
代码语言:javascript复制基于 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
代码语言:javascript复制#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
代码语言:javascript复制#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(建连、断开、重连、心跳、通用请求)