#import "ViewController.h"
@interface ViewController () <NSStreamDelegate, UITextFieldDelegate, UITableViewDataSource>{ NSInputStream *_inputStream; // 输入流 NSOutputStream *_outputStream; // 输出流 NSMutableArray *_dataList;}@property (weak, nonatomic) IBOutlet UITextField *hostName;@property (weak, nonatomic) IBOutlet UITextField *portText;@property (weak, nonatomic) IBOutlet UITextField *nickNameText;@property (weak, nonatomic) IBOutlet UITextField *messageText;@property (weak, nonatomic) IBOutlet UITableView *tableView;@end@implementation ViewController/** 通过Scoket可以实现所有的网络功能:包括:GET、POST、PUT、DELETE 最主要的应用场景是:自定义的协议,编写自由的网络应用! ========================================================== Socket 的难点: 1. 因为所有的输入输出都是在一个代理方法中调用,随着自定义协议的复杂度的提高, 程序编写难度势必要大幅度提升。 2. 多线程的处理! 示例代码中,输入流合输出流都添加到了主运行循环,如果应用过于复杂,将影响主线程程序的性能 因此,需要使用另外一个运行循环,专门管理输入输出流。 代理方法的工作是对数据的输入输出流进行“解析”,解析工作同样不需要影响到主线程的工作。 多线程方面的处理,是Socket的一大难点! 可以使用第三方框架!会在XMPP项目中隆重登场! ============================================================ 程序员写程序最主要的目的是用来阅读的,捎带着把程序的功能给实现了。 SSL 安全漏洞,goto fail。 */- (void)viewDidLoad{ [super viewDidLoad]; _dataList = [NSMutableArray array];}- (BOOL)textFieldShouldReturn:(UITextField *)textField{ // 确认真的输入了文字,再发送消息给服务器 if (textField.text.length > 0) { // 发送登录消息 NSString *msg = [NSString stringWithFormat:@"msg:%@", textField.text]; // 在网络上发送的是二进制数据 NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding]; // 发送数据,直接往输入流写数据 [_outputStream write:data.bytes maxLength:data.length]; } return YES;}- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return _dataList.count;}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *ID = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; cell.textLabel.text = _dataList[indexPath.row]; return cell;}#pragma mark - 私有方法#pragma mark 连接到服务器- (void)connectToServer:(NSString *)hostName port:(NSInteger)port{ // 要进行Socket开发,以下代码都是固定的 // 设置网络 CFReadStreamRef readStream; CFWriteStreamRef writeStream; // CF框架是C语言的框架,在OC中的Socet方法,C语言部分的代码,总共就5行 // 此方法可以连接到服务器,并分配输入流和输出流的内存空间 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];}#pragma mark NSStream的代理方法/** NSStreamEventNone = 0, // 无事件 NSStreamEventOpenCompleted = 1UL << 0, // 建立连接完成 NSStreamEventHasBytesAvailable = 1UL << 1, // 有可读的字节,接收到了数据,可以读了 NSStreamEventHasSpaceAvailable = 1UL << 2, // 可以使用输出流的空间,此时可以发送数据给服务器 NSStreamEventErrorOccurred = 1UL << 3, // 发生错误 NSStreamEventEndEncountered = 1UL << 4 // 流结束事件,在此事件中负责做销毁工作 */- (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; }}#pragma mark - Action#pragma mark 登录到聊天室- (IBAction)login{ NSString *hostName = _hostName.text; NSInteger port = [_portText.text integerValue]; [self connectToServer:hostName port:port]; // 发送登录消息 NSString *msg = [NSString stringWithFormat:@"iam:%@", _nickNameText.text]; // 在网络上发送的是二进制数据 NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding]; // 发送数据,直接往输入流写数据 [_outputStream write:data.bytes maxLength:data.length];}@end