Socket
*********************************************
简单理解Socket 就是网络连接,可以实现两个点之间的数据通讯。
•Socket允许使用长连接,允许应用程序运行在异步模式(提高效率),只有在需要的时候才接收数据
•使用Socket,可以只传送数据本身而不用进行XML封装,大大降低数据传输的开销 在(JSON)之前出现的
iOS中常用的两种Socket类型
Ø流式Socket(SOCK_STREAM):流式是一种面向连接的Socket,针对于面向连接的TCP服务应用
Ø数据报式Socket(SOCK_DGRAM):数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用
•在iOS中以NSStream(流)来发送和接收数据
开发步骤
{
网络连接设置
1.设置网络连接,绑定到主机和端口
2.设置输入流和输出流的代理,监听数据流的状态
3.将输入输出流添加至运行循环
4.打开输入流和输出流
发送消息给服务器
有可读取字节时,读取服务器返回的内容
到达流末尾时,关闭流,同时并从主运行循环中删除
}
通过Scoket可以实现所有的网络功能:包括:GET、POST、PUT、DELETE
文件读取、写入(I/O)方式是以(二进制)流的方式读取的
最主要的应用场景是:自定义的协议,编写自由的网络应用!
Socket 的难点:
1. 因为所有的(I/O)输入输出都是在一个代理方法中调用,随着自定义协议的复杂度的提高,
程序编写难度势必要大幅度提升。
2. 多线程的处理!
输入流和输出流都添加到了主运行循环,如果应用过于复杂,将影响主线程程序的性能
因此,需要使用另外一个运行循环,专门管理输入输出流。
代理方法的工作是对数据的输入输出流进行“解析”,解析工作同样不需要影响到主线程的工作。
3.多线程方面的处理,是Socket的一大难点!可以使用第三方框架GCDAsyncSocket来解决多线程问题。
连接服务器
#pragma mark 连接到服务器 - (void)connectToServer:(NSString *)hostName port:(NSInteger)port { // 设置网络 CFReadStreamRef readStream; CFWriteStreamRef writeStream; // CF框架是C语言的框架 // 此方法可以连接到服务器,并分配输入流和输出流的内存空间 CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)hostName, port, &readStream, &writeStream); // 记录已经分配的输入流和输出流 _inputStream = (__bridge NSInputStream *)readStream; _outputStream = (__bridge NSOutputStream *)writeStream; // 设置代理,监听输入流和输出流中的变化 _inputStream.delegate = self; _outputStream.delegate = self; // Scoket是建立的长连接,需要将输入输出流添加到主运行循环 // 如果不将流加入主运行循环,delegate拒绝工作 [_inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; [_outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; // 打开输入流和输出流,准备开始文件读写操作 [_inputStream open]; [_outputStream open]; }
登录到聊天室
1 #pragma mark 登录到聊天室 2 - (IBAction)login 3 { 4 NSString *hostName = _hostName.text; 5 NSInteger port = [_portText.text integerValue]; 6 7 [self connectToServer:hostName port:port]; 8 9 // 发送登录消息 10 NSString *msg = [NSString stringWithFormat:@"iam:%@", _nickNameText.text]; 11 // 在网络上发送的是二进制数据 12 NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding]; 13 14 // 发送数据,直接往输入流写数据 15 [_outputStream write:data.bytes maxLength:data.length]; 16 }
NSStream的代理方法
/**
NSStreamEventNone = 0, // 无事件
NSStreamEventOpenCompleted = 1UL << 0, // 建立连接完成
NSStreamEventHasBytesAvailable = 1UL << 1, // 有可读的字节,接收到了数据,可以读了
NSStreamEventHasSpaceAvailable = 1UL << 2, // 可以使用输出流的空间,此时可以发送数据给服务器
NSStreamEventErrorOccurred = 1UL << 3, // 发生错误
NSStreamEventEndEncountered = 1UL << 4 // 流结束事件,在此事件中负责做销毁工作
*/
#pragma mark NSStream的代理方法 - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { NSLog(@"%d", eventCode); switch (eventCode) { case NSStreamEventOpenCompleted: NSLog(@"连接完成"); break; case NSStreamEventHasBytesAvailable: NSLog(@"有可读字节"); // 读从服务器接收到得数据,从输入流中读取 // 先开辟一段缓冲区以读取数据,用空间来换取程序的简单 uint8_t buffer[1024]; // read返回的是输入流缓冲区中实际存储的字节数 NSInteger len = [_inputStream read:buffer maxLength:sizeof(buffer)]; if (len > 0) { // 读到数据 // 将buffer中的数据,转换成字符串,输出 NSString *str = [[NSString alloc] initWithBytes:buffer length:len encoding:NSUTF8StringEncoding]; // 将接收到的内容添加到数组 [_dataList addObject:str]; // 刷新表格 [_tableView reloadData]; } break; case NSStreamEventHasSpaceAvailable: NSLog(@"可以写入数据"); break; case NSStreamEventErrorOccurred: NSLog(@"发生错误"); break; case NSStreamEventEndEncountered: NSLog(@"流结束"); // 做善后工作 // 关闭流的同时,将流从主运行循环中删除 [aStream close]; [aStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; default: break; } }
GCDAsyncSocket(异步Socket)
需要导入Security.framework & CFNetwork.framework框架

// 1. Socket通讯的第一件事情——先创建一个长连接 _socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];

1 #pragma mark - Socket代理方法 2 #pragma mark 连接到主机 3 - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port 4 { 5 NSLog(@"连接建立 %@ %@", host, [NSThread currentThread]); 6 7 // 长连接建立完成后,给服务器发送 iam:昵称 通知服务器用户登录 8 NSString *str = [NSString stringWithFormat:@"iam:%@", _nickNameText.text]; 9 // 网络通讯中,所有的数据都是以二进制流的模式传输的 10 NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding]; 11 12 // 将数据写入到输出流,告诉服务器用户登录 13 [_socket writeData:data withTimeout:-1 tag:100]; 14 } 15 16 #pragma mark 读数据 17 - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag 18 { 19 NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 20 21 if (tag == 100) { 22 NSLog(@"登录消息 %@", str); 23 } else { 24 NSLog(@"聊天消息 %@->%ld", str, tag); 25 } 26 27 NSLog(@"%@", [NSThread currentThread]); 28 29 // 剩下的工作 绑定表格数据 30 [_dataList addObject:str]; 31 32 // 在主线程刷新表格 33 dispatch_async(dispatch_get_main_queue(), ^{ 34 [_tableView reloadData]; 35 }); 36 } 37 38 #pragma mark 写数据 39 #pragma mark Socket已经把数据写给了服务器 40 - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag 41 { 42 // 通过Log,我们发现在给服务器写入数据时,如果指定了tag,根据tag就知道发送(写给)的是哪一类的数据 43 // 发现了读数据的代理方法没有被触发 44 NSLog(@"写数据 %ld", tag); 45 46 // 尝试让socket读一下数据,读取服务器返回的内容 47 [_socket readDataWithTimeout:-1 tag:tag]; 48 }

1 #pragma mark - Action 2 - (IBAction)connect:(id)sender 3 { 4 NSString *hostName = _hostName.text; 5 int port = [[_portText text] intValue]; 6 7 // 连接到主机 8 NSError *error = nil; 9 if (![_socket connectToHost:hostName onPort:port error:&error]) { 10 NSLog(@"%@", error.localizedDescription); 11 } else { 12 NSLog(@"OK"); 13 } 14 }