在上篇中已经建立了基本的登录和服务器的连接,接下来在此基础之上实现聊天室数据的发送:
新建工程,实现步骤具体如下:
0
1
2
3
4
5
6
7
8
9
10
11
12
13
具体代码如下所示:
<span style="font-size:18px;"><span style="font-size:18px;">// // ViewController.m // 聊天室 // // Created by apple on 15/11/4. // Copyright (c) 2015年 LiuXun. All rights reserved. // #import "ViewController.h" @interface ViewController ()<NSStreamDelegate,UITextFieldDelegate,UITableViewDataSource,UITableViewDelegate> { NSInputStream *_inputStream; // 对应输入流 NSOutputStream *_outputStream; // 对应输出流 } @property (weak, nonatomic) IBOutlet NSLayoutConstraint *inputViewConstraint; @property (weak, nonatomic) IBOutlet UITableView *tableView; @property(nonatomic, strong) NSMutableArray *chatMsgs; // 聊天消息数组 @end @implementation ViewController -(NSMutableArray *)chatMsgs { if (_chatMsgs == nil) { _chatMsgs = [NSMutableArray array]; } return _chatMsgs; } - (void)viewDidLoad { [super viewDidLoad]; // 收发数据 // 做一个聊天 // 1. 用户登录 // 2. 登录成功后即可收发数据 // 监听键盘 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kbFrmWillChange:) name:UIKeyboardWillChangeFrameNotification object:nil]; } -(void)kbFrmWillChange:(NSNotification *)notification { NSLog(@"%@", notification.userInfo); // 获取窗口的高度 CGFloat windowH = [UIScreen mainScreen] .bounds.size.height; // 键盘结束时的Frame CGRect kbEndFrm = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; // 获取键盘结束的y值 CGFloat kbEndY = kbEndFrm.origin.y; self.inputViewConstraint.constant = windowH - kbEndY; // 键盘的高度 } #pragma mark OC输入输出流协议中的方法 -(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { NSLog(@"%@", [NSThread currentThread]); /* NSStreamEventOpenCompleted = 1UL << 0, // 输入输出流打开完成 NSStreamEventHasBytesAvailable = 1UL << 1, // 表示有字节可读(说明服务器已经返回数据到客户端) NSStreamEventHasSpaceAvailable = 1UL << 2, // 可以发送字节 NSStreamEventErrorOccurred = 1UL << 3, // 连接出现错误 NSStreamEventEndEncountered = 1UL << 4 // 连接结束 */ switch (eventCode) { case NSStreamEventOpenCompleted: NSLog(@"输入输出流打开完成"); break; case NSStreamEventHasBytesAvailable: NSLog(@"有字节可读"); [self readData]; break; case NSStreamEventHasSpaceAvailable: NSLog(@"可以发送字节"); break; case NSStreamEventErrorOccurred: NSLog(@"连接出现错误"); break; case NSStreamEventEndEncountered: NSLog(@"连接结束"); // 关闭输入输出流 [_inputStream close]; [_outputStream close]; // 从主运行循环移除 [_inputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; [_outputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; break; default : break; } } #pragma mark 连接服务器主机 - (IBAction)connectToHost:(id)sender { // 1. 第一步建立连接 /* 点击参数——>在编辑界面右侧就有此参数的描述 第一个参数CFAllocatorRef alloc:用于为输入输出流分配空间,如果传为NULL就会使用默认的大小分配 第二个参数CFStringRef host:所要连接到主机的IP地址。即要连接到哪台主机。 第三个参数port:所要连接到主机的具体端口号。 */ NSString *host = @"127.0.0.1"; // 因为是C语言函数所以要进行桥接转换为C语言字符串 int port = 12345; // 定义输入输出流 CFReadStreamRef readStream; CFWriteStreamRef writeStream; CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)(host), port, &readStream, &writeStream); // 将C语言的输入输出流转化为OC对象 // 如果要想知道连接成功还是失败,可以通过OC中的代理进行实现。所以需要将C语言输入输出流转化为OC对象。 _inputStream = (__bridge NSInputStream *)(readStream); _outputStream = (__bridge NSOutputStream *)(writeStream); // 设置代理 _inputStream.delegate = self; _outputStream.delegate = self; // 把输入输出流添加到主运行循环 // 不添加到主运行循环,代理有可能不工作 /** 以下情况需要添加到主运行循环:一是在子线程开启计时器。二是设置网络代理。 */ [_inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; [_outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; // 打开输入输出流——必须打开输入输出流,才可以建立连接,双方才可以发送数据 [_inputStream open]; [_outputStream open]; } #pragma mark 用户登录 - (IBAction)loginBtnClick:(id)sender { // 登录 // 发送用户名和密码 // 在这里做的时候,只发用户名,密码就不用发送 // 如果要登录,发送的数据格式为 "iam:zhangsan" // 如果要发送聊天消息,数据格式为"msg:did you have dinner" // uint8_t typedef —> unsigned char uint8_t; // (const uint8_t *) 意味着要传入一个字节数组 // maxLength: 表示发送字节数组中的前多少个字节 // 登录的指令 NSString *loginStr = @"iam:zhangsanXXXXXXX"; // 把Str转化为NSData NSData *data = [loginStr dataUsingEncoding:NSUTF8StringEncoding]; [_outputStream write:data.bytes maxLength:data.length]; } #pragma mark - 读取服服务器返回的数据 -(void)readData { // 建立缓冲区,可以存放1024个字节 uint8_t buf[1024]; // 返回实际装的字节 NSInteger len = [_inputStream read:buf maxLength:sizeof(buf)]; // 把字节数组转化为字符串 NSData *data = [NSData dataWithBytes:buf length:len]; // 从服务器接收到的数据 NSString *reciveStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"%@", reciveStr); [self reloadDataWithText:reciveStr]; } #pragma mark 实现TextField的Return键的方法 -(BOOL)textFieldShouldReturn:(UITextField *)textField { NSString *text = textField.text; NSLog(@"%@", text); // 已知:如果要发送聊天消息,数据格式为"msg:did you have dinner" // 按服务器指定的发送内容的格式拼接字符串 // 聊天信息 NSString *msgStr = [NSString stringWithFormat:@"msg:%@", text]; // 将msgStr转化为NSData NSData *data = [msgStr dataUsingEncoding:NSUTF8StringEncoding]; // 刷新表格 [self reloadDataWithText:msgStr]; // 发送数据 [_outputStream write:data.bytes maxLength:data.length]; // 发送完数据后,清空textField textField.text = nil; return YES; } #pragma mark 表格刷新数据 -(void)reloadDataWithText:(NSString *)text { [self.chatMsgs addObject:text]; [self.tableView reloadData]; // 数据多的时候表格应该向上滚动 // 获取表格的最后一行 NSIndexPath *lastPath = [NSIndexPath indexPathForRow:self.chatMsgs.count-1 inSection:0]; // 让表格滚动到最后一行底部 [self.tableView scrollToRowAtIndexPath:lastPath atScrollPosition:UITableViewScrollPositionBottom animated:YES]; } #pragma mark 表格的数据源 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.chatMsgs.count; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *ID = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID]; } cell.textLabel.text = self.chatMsgs[indexPath.row]; return cell; } #pragma mark - 表格滚动时让输入控件收回 -(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { [self.view endEditing:YES]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end</span></span>运行结果如下:
开发步骤如下:
步骤一:建立网络连接。连接服务器主机,需要服务器主机的IP地址、服务器文件内指定的连接端口号、C语言格式的输入输出流。C语言格式的输入输出流为CFReadStreamRef和CFWriteStreamRef,需要注意的是服务器的IP地址为NSString类型需要桥接转化为C语言类型。可以点击错误圆点—>点击错误提示的第一项自动bridge转换。要使用C语言的CFStreamCreatePairWithSocketTOHost语句进行连接。具体如下:
// 1.第一步建立连接
/* 点击参数——>在编辑界面右侧就有此参数的描述
第一个参数CFAllocatorRef alloc:用于为输入输出流分配空间,如果传为NULL就会使用默认的大小分配
第二个参数CFStringRef host:所要连接到主机的IP地址。即要连接到哪台主机。
第三个参数port:所要连接到主机的具体端口号。
*/
NSString *host =@"127.0.0.1"; //因为是C语言函数所以要进行桥接转换为C语言字符串
int port =12345;
//定义输入输出流
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridgeCFStringRef)(host), port, &readStream, &writeStream);
步骤二:将C语言的输入输出流转化为OC的输入输出流对象。因为要使用到OC中NSStreamDelegate协议中的方法。
具体实现如下:
// 将C语言的输入输出流转化为OC对象
// 如果要想知道连接成功还是失败,可以通过OC中的代理进行实现。所以需要将C语言输入输出流转化为OC对象。
_inputStream = (__bridgeNSInputStream *)(readStream);
_outputStream = (__bridgeNSOutputStream *)(writeStream);
步骤三:设置为全局的输入输出流设置代理,并让当前控制器遵守NSStreamDelegate协议。如下所示:......步骤比较多不过都在代码中添加了注释,在此不再奥数。
不过应当掌握的几个其它知识点还是很重要的:
知识点一:监听键盘,修改容器的约束值。
知识点二:让UItabView组件滚动到指定的位置。