iOS一个简单聊天工具的实现
2016-04-05 14:04
399 查看
Top
Socket的英文原义是孔或者插座的意思,通常也称作套接字,用于描述IP地址和端口,是一个通信链的句柄,本案例使用第三方Socket编程框架AsyncSocket框架实现一个简易的聊天工具,并且能够进行文件传输,由于没有服务器本案例将服务器端和客户端写在一个程序中,如图-1所示:
图-1
首先创建一个SingleViewApplication应用,导入AsyncSocket框架。在Storyboard中搭建聊天界面,上方的Textfield控件用于输入接受端IP地址,中间TextView控件用于展示聊天记录,下方的TextView用于接受用户输入的聊天内容,右下角有一个发送按钮。将这三个控件分别关联成ViewController的输出口属性IPTF、chatRecordTV、chatTV。
接下来首先实现聊天功能,在ViewController中定义三个属性severSocket、clientSocket以及myNewSocket。在viewDidLoad方法中创建服务器端severSocket,将端口号设置为8000,委托对象设置为self。将发送按钮关联成viewController的动作方法send:,实现send:方法,创建客户端对象,获取聊天输入框的内容转化成NSData类型的数据发送出去。
然后ViewController遵守AsyncSocketDelegate协议,分别实现关于socket连接,数据传输以及数据读取的协议方法,更新显示聊天记录内容。
最后实现传输文件功能,传输文件时为了确定所传输文件的类型需要拼接一个消息头,将传输文件的类型、名称和大小保存到消息头里,通常传输数据的开始的100个字节是消息头。在sender:方法中增加拼接消息头的代码。
接受端在接收到文件数据时首先对消息头进行解析,获取到接收文件的类型、大小以及名称,然后再持续接受文件数据,接受完成后将文件保存到本地路径。
实现此案例需要按照如下步骤进行。
步骤一:搭建聊天界面
首先创建一个SingleViewApplication应用,导入AsyncSocket框架。在Storyboard中搭建聊天界面,上方的Textfield控件用于输入接受端IP地址,中间TextView控件用于展示聊天记录,下方的TextView用于接受用户输入的聊天内容,如图-2所示:
图-2
然后将这三个控件分别关联成ViewController的输出口属性IPTF、chatRecordTV、chatTV,代码如下所示:
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *IPTF;
@property (weak, nonatomic) IBOutlet UITextView *chatRecordTV;
@property (weak, nonatomic) IBOutlet UITextView *chatTV;
@end
步骤二:实现聊天功能
首先在ViewController中定义三个属性severSocket、clientSocket以及myNewSocket,代码如下所示:
@interface ViewController ()
@property (nonatomic, strong)AsyncSocket *serverSocket;
@property (nonatomic, strong)AsyncSocket *clientSocket;
@property (nonatomic, strong)AsyncSocket *myNewSocket;
@end
其次在viewDidLoad方法中创建服务器端severSocket,将端口号设置为8000,委托对象设置为self,代码如下所示:
- (void)viewDidLoad {
[super viewDidLoad];
self.serverSocket = [[AsyncSocket alloc]initWithDelegate:self];
[self.serverSocket acceptOnPort:8000 error:nil];
}
将发送按钮关联成viewController的动作方法send:,实现send:方法,创建客户端对象,获取聊天输入框的内容转化成NSData类型的数据发送出去,代码如下所示:
- (IBAction)send:(UIButton *)sender {
[self.chatTV resignFirstResponder];
//创建Socket客户端
self.clientSocket = [[AsyncSocket alloc]initWithDelegate:self];
[self.clientSocket connectToHost:self.IPTF.text onPort:8000 withTimeout:-1 error:nil];
//将聊天输入框的内容转化为NSData
NSData *data = [self.chatTV.text dataUsingEncoding:NSUTF8StringEncoding];
//发送数据
[self.clientSocket writeData:data withTimeout:-1 tag:0];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@",self.chatRecordTV.text,self.chatTV.text];
}
运行程序发现,点击聊天输入框弹出的键盘会挡住聊天输入框,因此需要在接受用户输入的时候屏幕界面上移,viewController遵守UITextFieldDelegate和UITextViewDelegate协议,当进入输入状况的时候屏幕界面上移,代码如下所示:
-(BOOL)textViewShouldBeginEditing:(UITextView *)textView {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:0.2];
self.view.center = CGPointMake(160, self.v
20000
iew.center.y-200);
[UIView commitAnimations];
return YES;
}
添加一个单击手势。当单击屏幕或者点击发送按钮时键盘收回,屏幕界面恢复正常位置,代码如下所示:
//点击发送按钮是
- (IBAction)send:(UIButton *)sender {
[self.chatTV resignFirstResponder];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:0.2];
self.view.center = CGPointMake(160, point.y);
[UIView commitAnimations];
//创建Socket客户端
self.clientSocket = [[AsyncSocket alloc]initWithDelegate:self];
[self.clientSocket connectToHost:self.IPTF.text onPort:8000 withTimeout:-1 error:nil];
//将聊天输入框的内容转化为NSData
NSData *data = [self.chatTV.text dataUsingEncoding:NSUTF8StringEncoding];
//发送数据
[self.clientSocket writeData:data withTimeout:-1 tag:0];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@",self.chatRecordTV.text,self.chatTV.text];
}
//单击屏幕时
- (IBAction)resign:(UITapGestureRecognizer *)sender {
if (self.view.center.y!=point.y) {
[self.chatTV resignFirstResponder];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:0.2];
self.view.center = point;
[UIView commitAnimations];
}
}
最后ViewController遵守AsyncSocketDelegate协议,分别实现关于socket连接,数据传输以及数据读取的协议方法,代码如下所示:
//当Socket接受一个连接的时候被调用
-(void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket{
//将新接受的socket持有
self.myNewSocket = newSocket;
}
//当Socket连接并准备读和写调用
-(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
//持续读取数据
[self.myNewSocket readDataWithTimeout:-1 tag:0];
}
//当Socket读取数据时调用
-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSString *chatStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n对方说:%@",self.chatRecordTV.text,chatStr];
[self.myNewSocket readDataWithTimeout:-1 tag:0];
}
//持续发送数据
-(void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag {
NSLog(@"发送成功");
[self.myNewSocket readDataWithTimeout:-1 tag:0];
}
运行程序,聊天效果如图-3所示:
图-3
步骤三:实现文件传输功能
首先定义三个属性用于记录传输文件的数据、名称和大小,代码如下所示:
@property (strong,nonatomic) NSMutableData *allData;
@property (nonatomic, copy)NSString *reciveFileName;
@property (nonatomic, assign)int reciveFileLength;
文件传输需要拼接头文件,将传输文件的类型、大小和名称放进消息头里面,因此在sender:方法中增加拼接消息头的代码,本案例以路径开头区分是发送文件还是聊天,代码如下所示:
- (IBAction)send:(UIButton *)sender {
[self.chatTV resignFirstResponder];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:0.2];
self.view.center = CGPointMake(160, point.y);
[UIView commitAnimations];
self.clientSocket = [[AsyncSocket alloc]initWithDelegate:self];
[self.clientSocket connectToHost:self.IPTF.text onPort:8000 withTimeout:-1 error:nil];
if (![self.chatTV.text hasPrefix:@"/Users"]) {
NSData *data = [self.chatTV.text dataUsingEncoding:NSUTF8StringEncoding];
[self.clientSocket writeData:data withTimeout:-1 tag:0];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@",self.chatRecordTV.text,self.chatTV.text];
}else {
NSString *filePath = self.chatTV.text;
NSData *fileData = [NSData dataWithContentsOfFile:filePath];
//把头信息添加到文件Data的前面
NSString *header = [NSString stringWithFormat:@"file&&%@&&%d",[filePath lastPathComponent],fileData.length];
//把头字符串转成data
NSData *headerData = [header dataUsingEncoding:NSUTF8StringEncoding];
//把头替换进100个字节的data里面
NSMutableData *sendAllData = [NSMutableData dataWithLength:100];
[sendAllData replaceBytesInRange:NSMakeRange(0, headerData.length) withBytes:headerData.bytes];
[sendAllData appendData:fileData];
NSLog(@"%@",header);
NSLog(@"SendLength = %d",sendAllData.length);
[self.clientSocket writeData:sendAllData withTimeout:-1 tag:0];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@正在发送",self.chatRecordTV.text,self.chatTV.text];
}
}
在读取数据的方法中首先获取消息头信息,如果传递过来的是文件则获取到传输文件的类型、大小和名称,持续读取文件数据,将文件保存到本地路径,接受完成更新聊天记录,代码如下所示:
-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
if (data.length>=100) {
NSData *headerData = [data subdataWithRange:NSMakeRange(0, 100)];
NSString *headerStr = [[NSString alloc]initWithData:headerData encoding:NSUTF8StringEncoding];
if (headerStr&& [headerStr componentsSeparatedByString:@"&&"].count==3) {
isFile = YES;
}
}
if (isFile == NO) {
NSString *chatStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n对方说:%@",self.chatRecordTV.text,chatStr];
}
if (isFile==YES) {
if (data.length>=100) {
NSData *headerData = [data subdataWithRange:NSMakeRange(0, 100)];
NSString *headerStr = [[NSString alloc]initWithData:headerData encoding:NSUTF8StringEncoding];
if (headerStr&& [headerStr componentsSeparatedByString:@"&&"].count==3) {
NSArray *headers = [headerStr componentsSeparatedByString:@"&&"];
NSString *type = [headers objectAtIndex:0];
if ([type isEqualToString:@"file"]) {
self.reciveFileName = [headers objectAtIndex:1];
self.reciveFileLength = [[headers objectAtIndex:2] intValue];
NSData *subFileData = [data subdataWithRange:NSMakeRange(100, data.length-100)];
if (!self.allData) {
self.allData = [NSMutableData data];
}
[self.allData appendData:subFileData];
}
}else {//没有消息头的情况下
[self.allData appendData:data];
}
}else {//没有消息头的情况下
[self.allData appendData:data];
}
NSLog(@"%d,%d",self.allData.length,self.reciveFileLength);
if (self.allData.length == self.reciveFileLength ) {
NSLog(@"接受完成");
NSString *filePath = [@"/Users/Vivian/Documents" stringByAppendingPathComponent:self.reciveFileName];
[self.allData writeToFile:filePath atomically:YES];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n对方说:成功接收文件%@",self.chatRecordTV.text,self.reciveFileName];
self.allData = nil;
isFile = NO;
}
if (isFile == YES) {
//如果有数据能够持续接受
[self.myNewSocket readDataWithTimeout:-1 tag:0];
}
}
}
运行程序,传输文件效果如图-4所示:
图-4
本案例中,ViewController.m文件中的完整代码如下所示:
#import "ViewController.h"
@interface ViewController () <AsyncSocketDelegate,UITextFieldDelegate,UITextViewDelegate> {
BOOL isFile;
CGPoint point;
}
@property (weak, nonatomic) IBOutlet UITextField *IPTF;
@property (weak, nonatomic) IBOutlet UITextView *chatRecordTV;
@property (weak, nonatomic) IBOutlet UITextView *chatTV;
@property (nonatomic, strong)AsyncSocket *serverSocket;
@property (nonatomic, strong)AsyncSocket *clientSocket;
@property (nonatomic, strong)AsyncSocket *myNewSocket;
@property (strong,nonatomic) NSMutableData *allData;
@property (nonatomic, copy)NSString *reciveFileName;
@property (nonatomic, assign)int reciveFileLength;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.serverSocket = [[AsyncSocket alloc]initWithDelegate:self];
[self.serverSocket acceptOnPort:8000 error:nil];
point = self.view.center;
self.allData = [NSMutableData data];
isFile = NO;
}
-(void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket{
//将新接受的socket持有
self.myNewSocket = newSocket;
}
-(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
[self.myNewSocket readDataWithTimeout:-1 tag:0];
}
-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
if (data.length>=100) {
NSData *headerData = [data subdataWithRange:NSMakeRange(0, 100)];
NSString *headerStr = [[NSString alloc]initWithData:headerData encoding:NSUTF8StringEncoding];
if (headerStr&& [headerStr componentsSeparatedByString:@"&&"].count==3) {
isFile = YES;
}
}
if (isFile == NO) {
NSString *chatStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n对方说:%@",self.chatRecordTV.text,chatStr];
}
if (isFile==YES) {
if (data.length>=100) {
NSData *headerData = [data subdataWithRange:NSMakeRange(0, 100)];
NSString *headerStr = [[NSString alloc]initWithData:headerData encoding:NSUTF8StringEncoding];
if (headerStr&& [headerStr componentsSeparatedByString:@"&&"].count==3) {
NSArray *headers = [headerStr componentsSeparatedByString:@"&&"];
NSString *type = [headers objectAtIndex:0];
if ([type isEqualToString:@"file"]) {
self.reciveFileName = [headers objectAtIndex:1];
self.reciveFileLength = [[headers objectAtIndex:2] intValue];
NSData *subFileData = [data subdataWithRange:NSMakeRange(100, data.length-100)];
if (!self.allData) {
self.allData = [NSMutableData data];
}
[self.allData appendData:subFileData];
}
}else {//没有消息头的情况下
[self.allData appendData:data];
}
}else {//没有消息头的情况下
[self.allData appendData:data];
}
NSLog(@"%d,%d",self.allData.length,self.reciveFileLength);
if (self.allData.length == self.reciveFileLength ) {
NSLog(@"接受完成");
NSString *filePath = [@"/Users/Vivian/Documents" stringByAppendingPathComponent:self.reciveFileName];
[self.allData writeToFile:filePath atomically:YES];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n对方说:成功接收文件%@",self.chatRecordTV.text,self.reciveFileName];
self.allData = nil;
isFile = NO;
}
if (isFile == YES) {
//如果有数据能够持续接受
[self.myNewSocket readDataWithTimeout:-1 tag:0];
}
}
}
-(void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag {
NSLog(@"发送成功");
[self.myNewSocket readDataWithTimeout:-1 tag:0];
}
-(BOOL)textViewShouldBeginEditing:(UITextView *)textView {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:0.2];
self.view.center = CGPointMake(160, self.view.center.y-200);
[UIView commitAnimations];
return YES;
}
- (IBAction)send:(UIButton *)sender {
[self.chatTV resignFirstResponder];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:0.2];
self.view.center = CGPointMake(160, point.y);
[UIView commitAnimations];
self.clientSocket = [[AsyncSocket alloc]initWithDelegate:self];
[self.clientSocket connectToHost:self.IPTF.text onPort:8000 withTimeout:-1 error:nil];
if (![self.chatTV.text hasPrefix:@"/Users"]) {
NSData *data = [self.chatTV.text dataUsingEncoding:NSUTF8StringEncoding];
[self.clientSocket writeData:data withTimeout:-1 tag:0];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@",self.chatRecordTV.text,self.chatTV.text];
}else {
NSString *filePath = self.chatTV.text;
NSData *fileData = [NSData dataWithContentsOfFile:filePath];
//把头信息添加到文件Data的前面
NSString *header = [NSString stringWithFormat:@"file&&%@&&%d",[filePath lastPathComponent],fileData.length];
//把头字符串转成data
NSData *headerData = [header dataUsingEncoding:NSUTF8StringEncoding];
//把头替换进100个字节的data里面
NSMutableData *sendAllData = [NSMutableData dataWithLength:100];
[sendAllData replaceBytesInRange:NSMakeRange(0, headerData.length) withBytes:headerData.bytes];
[sendAllData appendData:fileData];
NSLog(@"%@",header);
NSLog(@"SendLength = %d",sendAllData.length);
[self.clientSocket writeData:sendAllData withTimeout:-1 tag:0];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@正在发送",self.chatRecordTV.text,self.chatTV.text];
}
}
- (IBAction)done:(UITextField *)sender {
[self.IPTF resignFirstResponder];
}
- (IBAction)resign:(UITapGestureRecognizer *)sender {
if (self.view.center.y!=point.y) {
[self.chatTV resignFirstResponder];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:0.2];
self.view.center = point;
[UIView commitAnimations];
}
}
@end
简易的聊天工具
1.1 问题
Socket的英文原义是孔或者插座的意思,通常也称作套接字,用于描述IP地址和端口,是一个通信链的句柄,本案例使用第三方Socket编程框架AsyncSocket框架实现一个简易的聊天工具,并且能够进行文件传输,由于没有服务器本案例将服务器端和客户端写在一个程序中,如图-1所示:图-1
1.2 方案
首先创建一个SingleViewApplication应用,导入AsyncSocket框架。在Storyboard中搭建聊天界面,上方的Textfield控件用于输入接受端IP地址,中间TextView控件用于展示聊天记录,下方的TextView用于接受用户输入的聊天内容,右下角有一个发送按钮。将这三个控件分别关联成ViewController的输出口属性IPTF、chatRecordTV、chatTV。接下来首先实现聊天功能,在ViewController中定义三个属性severSocket、clientSocket以及myNewSocket。在viewDidLoad方法中创建服务器端severSocket,将端口号设置为8000,委托对象设置为self。将发送按钮关联成viewController的动作方法send:,实现send:方法,创建客户端对象,获取聊天输入框的内容转化成NSData类型的数据发送出去。
然后ViewController遵守AsyncSocketDelegate协议,分别实现关于socket连接,数据传输以及数据读取的协议方法,更新显示聊天记录内容。
最后实现传输文件功能,传输文件时为了确定所传输文件的类型需要拼接一个消息头,将传输文件的类型、名称和大小保存到消息头里,通常传输数据的开始的100个字节是消息头。在sender:方法中增加拼接消息头的代码。
接受端在接收到文件数据时首先对消息头进行解析,获取到接收文件的类型、大小以及名称,然后再持续接受文件数据,接受完成后将文件保存到本地路径。
1.3 步骤
实现此案例需要按照如下步骤进行。步骤一:搭建聊天界面
首先创建一个SingleViewApplication应用,导入AsyncSocket框架。在Storyboard中搭建聊天界面,上方的Textfield控件用于输入接受端IP地址,中间TextView控件用于展示聊天记录,下方的TextView用于接受用户输入的聊天内容,如图-2所示:
图-2
然后将这三个控件分别关联成ViewController的输出口属性IPTF、chatRecordTV、chatTV,代码如下所示:
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *IPTF;
@property (weak, nonatomic) IBOutlet UITextView *chatRecordTV;
@property (weak, nonatomic) IBOutlet UITextView *chatTV;
@end
步骤二:实现聊天功能
首先在ViewController中定义三个属性severSocket、clientSocket以及myNewSocket,代码如下所示:
@interface ViewController ()
@property (nonatomic, strong)AsyncSocket *serverSocket;
@property (nonatomic, strong)AsyncSocket *clientSocket;
@property (nonatomic, strong)AsyncSocket *myNewSocket;
@end
其次在viewDidLoad方法中创建服务器端severSocket,将端口号设置为8000,委托对象设置为self,代码如下所示:
- (void)viewDidLoad {
[super viewDidLoad];
self.serverSocket = [[AsyncSocket alloc]initWithDelegate:self];
[self.serverSocket acceptOnPort:8000 error:nil];
}
将发送按钮关联成viewController的动作方法send:,实现send:方法,创建客户端对象,获取聊天输入框的内容转化成NSData类型的数据发送出去,代码如下所示:
- (IBAction)send:(UIButton *)sender {
[self.chatTV resignFirstResponder];
//创建Socket客户端
self.clientSocket = [[AsyncSocket alloc]initWithDelegate:self];
[self.clientSocket connectToHost:self.IPTF.text onPort:8000 withTimeout:-1 error:nil];
//将聊天输入框的内容转化为NSData
NSData *data = [self.chatTV.text dataUsingEncoding:NSUTF8StringEncoding];
//发送数据
[self.clientSocket writeData:data withTimeout:-1 tag:0];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@",self.chatRecordTV.text,self.chatTV.text];
}
运行程序发现,点击聊天输入框弹出的键盘会挡住聊天输入框,因此需要在接受用户输入的时候屏幕界面上移,viewController遵守UITextFieldDelegate和UITextViewDelegate协议,当进入输入状况的时候屏幕界面上移,代码如下所示:
-(BOOL)textViewShouldBeginEditing:(UITextView *)textView {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:0.2];
self.view.center = CGPointMake(160, self.v
20000
iew.center.y-200);
[UIView commitAnimations];
return YES;
}
添加一个单击手势。当单击屏幕或者点击发送按钮时键盘收回,屏幕界面恢复正常位置,代码如下所示:
//点击发送按钮是
- (IBAction)send:(UIButton *)sender {
[self.chatTV resignFirstResponder];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:0.2];
self.view.center = CGPointMake(160, point.y);
[UIView commitAnimations];
//创建Socket客户端
self.clientSocket = [[AsyncSocket alloc]initWithDelegate:self];
[self.clientSocket connectToHost:self.IPTF.text onPort:8000 withTimeout:-1 error:nil];
//将聊天输入框的内容转化为NSData
NSData *data = [self.chatTV.text dataUsingEncoding:NSUTF8StringEncoding];
//发送数据
[self.clientSocket writeData:data withTimeout:-1 tag:0];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@",self.chatRecordTV.text,self.chatTV.text];
}
//单击屏幕时
- (IBAction)resign:(UITapGestureRecognizer *)sender {
if (self.view.center.y!=point.y) {
[self.chatTV resignFirstResponder];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:0.2];
self.view.center = point;
[UIView commitAnimations];
}
}
最后ViewController遵守AsyncSocketDelegate协议,分别实现关于socket连接,数据传输以及数据读取的协议方法,代码如下所示:
//当Socket接受一个连接的时候被调用
-(void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket{
//将新接受的socket持有
self.myNewSocket = newSocket;
}
//当Socket连接并准备读和写调用
-(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
//持续读取数据
[self.myNewSocket readDataWithTimeout:-1 tag:0];
}
//当Socket读取数据时调用
-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSString *chatStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n对方说:%@",self.chatRecordTV.text,chatStr];
[self.myNewSocket readDataWithTimeout:-1 tag:0];
}
//持续发送数据
-(void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag {
NSLog(@"发送成功");
[self.myNewSocket readDataWithTimeout:-1 tag:0];
}
运行程序,聊天效果如图-3所示:
图-3
步骤三:实现文件传输功能
首先定义三个属性用于记录传输文件的数据、名称和大小,代码如下所示:
@property (strong,nonatomic) NSMutableData *allData;
@property (nonatomic, copy)NSString *reciveFileName;
@property (nonatomic, assign)int reciveFileLength;
文件传输需要拼接头文件,将传输文件的类型、大小和名称放进消息头里面,因此在sender:方法中增加拼接消息头的代码,本案例以路径开头区分是发送文件还是聊天,代码如下所示:
- (IBAction)send:(UIButton *)sender {
[self.chatTV resignFirstResponder];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:0.2];
self.view.center = CGPointMake(160, point.y);
[UIView commitAnimations];
self.clientSocket = [[AsyncSocket alloc]initWithDelegate:self];
[self.clientSocket connectToHost:self.IPTF.text onPort:8000 withTimeout:-1 error:nil];
if (![self.chatTV.text hasPrefix:@"/Users"]) {
NSData *data = [self.chatTV.text dataUsingEncoding:NSUTF8StringEncoding];
[self.clientSocket writeData:data withTimeout:-1 tag:0];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@",self.chatRecordTV.text,self.chatTV.text];
}else {
NSString *filePath = self.chatTV.text;
NSData *fileData = [NSData dataWithContentsOfFile:filePath];
//把头信息添加到文件Data的前面
NSString *header = [NSString stringWithFormat:@"file&&%@&&%d",[filePath lastPathComponent],fileData.length];
//把头字符串转成data
NSData *headerData = [header dataUsingEncoding:NSUTF8StringEncoding];
//把头替换进100个字节的data里面
NSMutableData *sendAllData = [NSMutableData dataWithLength:100];
[sendAllData replaceBytesInRange:NSMakeRange(0, headerData.length) withBytes:headerData.bytes];
[sendAllData appendData:fileData];
NSLog(@"%@",header);
NSLog(@"SendLength = %d",sendAllData.length);
[self.clientSocket writeData:sendAllData withTimeout:-1 tag:0];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@正在发送",self.chatRecordTV.text,self.chatTV.text];
}
}
在读取数据的方法中首先获取消息头信息,如果传递过来的是文件则获取到传输文件的类型、大小和名称,持续读取文件数据,将文件保存到本地路径,接受完成更新聊天记录,代码如下所示:
-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
if (data.length>=100) {
NSData *headerData = [data subdataWithRange:NSMakeRange(0, 100)];
NSString *headerStr = [[NSString alloc]initWithData:headerData encoding:NSUTF8StringEncoding];
if (headerStr&& [headerStr componentsSeparatedByString:@"&&"].count==3) {
isFile = YES;
}
}
if (isFile == NO) {
NSString *chatStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n对方说:%@",self.chatRecordTV.text,chatStr];
}
if (isFile==YES) {
if (data.length>=100) {
NSData *headerData = [data subdataWithRange:NSMakeRange(0, 100)];
NSString *headerStr = [[NSString alloc]initWithData:headerData encoding:NSUTF8StringEncoding];
if (headerStr&& [headerStr componentsSeparatedByString:@"&&"].count==3) {
NSArray *headers = [headerStr componentsSeparatedByString:@"&&"];
NSString *type = [headers objectAtIndex:0];
if ([type isEqualToString:@"file"]) {
self.reciveFileName = [headers objectAtIndex:1];
self.reciveFileLength = [[headers objectAtIndex:2] intValue];
NSData *subFileData = [data subdataWithRange:NSMakeRange(100, data.length-100)];
if (!self.allData) {
self.allData = [NSMutableData data];
}
[self.allData appendData:subFileData];
}
}else {//没有消息头的情况下
[self.allData appendData:data];
}
}else {//没有消息头的情况下
[self.allData appendData:data];
}
NSLog(@"%d,%d",self.allData.length,self.reciveFileLength);
if (self.allData.length == self.reciveFileLength ) {
NSLog(@"接受完成");
NSString *filePath = [@"/Users/Vivian/Documents" stringByAppendingPathComponent:self.reciveFileName];
[self.allData writeToFile:filePath atomically:YES];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n对方说:成功接收文件%@",self.chatRecordTV.text,self.reciveFileName];
self.allData = nil;
isFile = NO;
}
if (isFile == YES) {
//如果有数据能够持续接受
[self.myNewSocket readDataWithTimeout:-1 tag:0];
}
}
}
运行程序,传输文件效果如图-4所示:
图-4
1.4 完整代码
本案例中,ViewController.m文件中的完整代码如下所示:#import "ViewController.h"
@interface ViewController () <AsyncSocketDelegate,UITextFieldDelegate,UITextViewDelegate> {
BOOL isFile;
CGPoint point;
}
@property (weak, nonatomic) IBOutlet UITextField *IPTF;
@property (weak, nonatomic) IBOutlet UITextView *chatRecordTV;
@property (weak, nonatomic) IBOutlet UITextView *chatTV;
@property (nonatomic, strong)AsyncSocket *serverSocket;
@property (nonatomic, strong)AsyncSocket *clientSocket;
@property (nonatomic, strong)AsyncSocket *myNewSocket;
@property (strong,nonatomic) NSMutableData *allData;
@property (nonatomic, copy)NSString *reciveFileName;
@property (nonatomic, assign)int reciveFileLength;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.serverSocket = [[AsyncSocket alloc]initWithDelegate:self];
[self.serverSocket acceptOnPort:8000 error:nil];
point = self.view.center;
self.allData = [NSMutableData data];
isFile = NO;
}
-(void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket{
//将新接受的socket持有
self.myNewSocket = newSocket;
}
-(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
[self.myNewSocket readDataWithTimeout:-1 tag:0];
}
-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
if (data.length>=100) {
NSData *headerData = [data subdataWithRange:NSMakeRange(0, 100)];
NSString *headerStr = [[NSString alloc]initWithData:headerData encoding:NSUTF8StringEncoding];
if (headerStr&& [headerStr componentsSeparatedByString:@"&&"].count==3) {
isFile = YES;
}
}
if (isFile == NO) {
NSString *chatStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n对方说:%@",self.chatRecordTV.text,chatStr];
}
if (isFile==YES) {
if (data.length>=100) {
NSData *headerData = [data subdataWithRange:NSMakeRange(0, 100)];
NSString *headerStr = [[NSString alloc]initWithData:headerData encoding:NSUTF8StringEncoding];
if (headerStr&& [headerStr componentsSeparatedByString:@"&&"].count==3) {
NSArray *headers = [headerStr componentsSeparatedByString:@"&&"];
NSString *type = [headers objectAtIndex:0];
if ([type isEqualToString:@"file"]) {
self.reciveFileName = [headers objectAtIndex:1];
self.reciveFileLength = [[headers objectAtIndex:2] intValue];
NSData *subFileData = [data subdataWithRange:NSMakeRange(100, data.length-100)];
if (!self.allData) {
self.allData = [NSMutableData data];
}
[self.allData appendData:subFileData];
}
}else {//没有消息头的情况下
[self.allData appendData:data];
}
}else {//没有消息头的情况下
[self.allData appendData:data];
}
NSLog(@"%d,%d",self.allData.length,self.reciveFileLength);
if (self.allData.length == self.reciveFileLength ) {
NSLog(@"接受完成");
NSString *filePath = [@"/Users/Vivian/Documents" stringByAppendingPathComponent:self.reciveFileName];
[self.allData writeToFile:filePath atomically:YES];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n对方说:成功接收文件%@",self.chatRecordTV.text,self.reciveFileName];
self.allData = nil;
isFile = NO;
}
if (isFile == YES) {
//如果有数据能够持续接受
[self.myNewSocket readDataWithTimeout:-1 tag:0];
}
}
}
-(void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag {
NSLog(@"发送成功");
[self.myNewSocket readDataWithTimeout:-1 tag:0];
}
-(BOOL)textViewShouldBeginEditing:(UITextView *)textView {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:0.2];
self.view.center = CGPointMake(160, self.view.center.y-200);
[UIView commitAnimations];
return YES;
}
- (IBAction)send:(UIButton *)sender {
[self.chatTV resignFirstResponder];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:0.2];
self.view.center = CGPointMake(160, point.y);
[UIView commitAnimations];
self.clientSocket = [[AsyncSocket alloc]initWithDelegate:self];
[self.clientSocket connectToHost:self.IPTF.text onPort:8000 withTimeout:-1 error:nil];
if (![self.chatTV.text hasPrefix:@"/Users"]) {
NSData *data = [self.chatTV.text dataUsingEncoding:NSUTF8StringEncoding];
[self.clientSocket writeData:data withTimeout:-1 tag:0];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@",self.chatRecordTV.text,self.chatTV.text];
}else {
NSString *filePath = self.chatTV.text;
NSData *fileData = [NSData dataWithContentsOfFile:filePath];
//把头信息添加到文件Data的前面
NSString *header = [NSString stringWithFormat:@"file&&%@&&%d",[filePath lastPathComponent],fileData.length];
//把头字符串转成data
NSData *headerData = [header dataUsingEncoding:NSUTF8StringEncoding];
//把头替换进100个字节的data里面
NSMutableData *sendAllData = [NSMutableData dataWithLength:100];
[sendAllData replaceBytesInRange:NSMakeRange(0, headerData.length) withBytes:headerData.bytes];
[sendAllData appendData:fileData];
NSLog(@"%@",header);
NSLog(@"SendLength = %d",sendAllData.length);
[self.clientSocket writeData:sendAllData withTimeout:-1 tag:0];
self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@正在发送",self.chatRecordTV.text,self.chatTV.text];
}
}
- (IBAction)done:(UITextField *)sender {
[self.IPTF resignFirstResponder];
}
- (IBAction)resign:(UITapGestureRecognizer *)sender {
if (self.view.center.y!=point.y) {
[self.chatTV resignFirstResponder];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:0.2];
self.view.center = point;
[UIView commitAnimations];
}
}
@end
隐藏
相关文章推荐
- java-模拟tomcat服务器
- Linux socket 初步
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 不可修补的 iOS 漏洞可能导致 iPhone 4s 到 iPhone X 永久越狱
- iOS 12.4 系统遭黑客破解,漏洞危及数百万用户
- 每日安全资讯:NSO,一家专业入侵 iPhone 的神秘公司
- [转][源代码]Comex公布JailbreakMe 3.0源代码
- java socket 注意的地方
- java socket 注意的地方
- C#基于socket模拟http请求的方法
- 简单的Ruby中的Socket编程教程
- Socket不能选择本地IP连接问题如何解决
- C#之Socket操作类实例解析
- 使用C#来编写一个异步的Socket服务器
- C#使用Socket快速判断数据库连接是否正常的方法
- 科学知识:理解socket
- Android聊天工具基于socket实现
- php与flash as3 socket通信传送文件实现代码