IOS网络开发(一)

1 简易的聊天工具

1.1 问题

Socket的英文原义是孔或者插座的意思,通常也称作套接字,用于描述IP地址和端口,是一个通信链的句柄,本案例使用第三方Socket编程框架AsyncSocket框架实现一个简易的聊天工具,并且能够进行文件传输,由于没有服务器本案例将服务器端和客户端写在一个程序中,如图-1所示:

IOS网络开发(一)

图-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所示:

IOS网络开发(一)

图-2

然后将这三个控件分别关联成ViewController的输出口属性IPTF、chatRecordTV、chatTV,代码如下所示:

  1. @interface ViewController ()
  2. @property (weak, nonatomic) IBOutlet UITextField *IPTF;
  3. @property (weak, nonatomic) IBOutlet UITextView *chatRecordTV;
  4. @property (weak, nonatomic) IBOutlet UITextView *chatTV;
  5. @end

步骤二:实现聊天功能

首先在ViewController中定义三个属性severSocket、clientSocket以及myNewSocket,代码如下所示:

  1. @interface ViewController ()
  2. @property (nonatomic, strong)AsyncSocket *serverSocket;
  3. @property (nonatomic, strong)AsyncSocket *clientSocket;
  4. @property (nonatomic, strong)AsyncSocket *myNewSocket;
  5. @end

其次在viewDidLoad方法中创建服务器端severSocket,将端口号设置为8000,委托对象设置为self,代码如下所示:

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. self.serverSocket = [[AsyncSocket alloc]initWithDelegate:self];
  4. [self.serverSocket acceptOnPort:8000 error:nil];
  5. }

将发送按钮关联成viewController的动作方法send:,实现send:方法,创建客户端对象,获取聊天输入框的内容转化成NSData类型的数据发送出去,代码如下所示:

 
  1. - (IBAction)send:(UIButton *)sender {
  2. [self.chatTV resignFirstResponder];
  3. //创建Socket客户端
  4. self.clientSocket = [[AsyncSocket alloc]initWithDelegate:self];
  5. [self.clientSocket connectToHost:self.IPTF.text onPort:8000 withTimeout:-1 error:nil];
  6. //将聊天输入框的内容转化为NSData
  7. NSData *data = [self.chatTV.text dataUsingEncoding:NSUTF8StringEncoding];
  8. //发送数据
  9. [self.clientSocket writeData:data withTimeout:-1 tag:0];
  10. self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@",self.chatRecordTV.text,self.chatTV.text];
  11. }

运行程序发现,点击聊天输入框弹出的键盘会挡住聊天输入框,因此需要在接受用户输入的时候屏幕界面上移,viewController遵守UITextFieldDelegate和UITextViewDelegate协议,当进入输入状况的时候屏幕界面上移,代码如下所示:

  1. -(BOOL)textViewShouldBeginEditing:(UITextView *)textView {
  2. [UIView beginAnimations:nil context:nil];
  3. [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
  4. [UIView setAnimationDuration:0.2];
  5. self.view.center = CGPointMake(160, self.view.center.y-200);
  6. [UIView commitAnimations];
  7. return YES;
  8. }

添加一个单击手势。当单击屏幕或者点击发送按钮时键盘收回,屏幕界面恢复正常位置,代码如下所示:

  1. //点击发送按钮是
  2. - (IBAction)send:(UIButton *)sender {
  3. [self.chatTV resignFirstResponder];
  4. [UIView beginAnimations:nil context:nil];
  5. [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
  6. [UIView setAnimationDuration:0.2];
  7. self.view.center = CGPointMake(160, point.y);
  8. [UIView commitAnimations];
  9. //创建Socket客户端
  10. self.clientSocket = [[AsyncSocket alloc]initWithDelegate:self];
  11. [self.clientSocket connectToHost:self.IPTF.text onPort:8000 withTimeout:-1 error:nil];
  12. //将聊天输入框的内容转化为NSData
  13. NSData *data = [self.chatTV.text dataUsingEncoding:NSUTF8StringEncoding];
  14. //发送数据
  15. [self.clientSocket writeData:data withTimeout:-1 tag:0];
  16. self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@",self.chatRecordTV.text,self.chatTV.text];
  17. }
  18. //单击屏幕时
  19. - (IBAction)resign:(UITapGestureRecognizer *)sender {
  20. if (self.view.center.y!=point.y) {
  21. [self.chatTV resignFirstResponder];
  22. [UIView beginAnimations:nil context:nil];
  23. [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
  24. [UIView setAnimationDuration:0.2];
  25. self.view.center = point;
  26. [UIView commitAnimations];
  27. }
  28. }

最后ViewController遵守AsyncSocketDelegate协议,分别实现关于socket连接,数据传输以及数据读取的协议方法,代码如下所示:

  1. //当Socket接受一个连接的时候被调用
  2. -(void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket{
  3. //将新接受的socket持有
  4. self.myNewSocket = newSocket;
  5. }
  6. //当Socket连接并准备读和写调用
  7. -(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
  8. //持续读取数据
  9. [self.myNewSocket readDataWithTimeout:-1 tag:0];
  10. }
  11. //当Socket读取数据时调用
  12. -(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
  13. NSString *chatStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
  14. self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n对方说:%@",self.chatRecordTV.text,chatStr];
  15. [self.myNewSocket readDataWithTimeout:-1 tag:0];
  16. }
  17. //持续发送数据
  18. -(void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag {
  19. NSLog(@"发送成功");
  20. [self.myNewSocket readDataWithTimeout:-1 tag:0];
  21. }

运行程序,聊天效果如图-3所示:

IOS网络开发(一)

图-3

步骤三:实现文件传输功能

首先定义三个属性用于记录传输文件的数据、名称和大小,代码如下所示:

  1. @property (strong,nonatomic) NSMutableData *allData;
  2. @property (nonatomic, copy)NSString *reciveFileName;
  3. @property (nonatomic, assign)int reciveFileLength;

文件传输需要拼接头文件,将传输文件的类型、大小和名称放进消息头里面,因此在sender:方法中增加拼接消息头的代码,本案例以路径开头区分是发送文件还是聊天,代码如下所示:

  1. - (IBAction)send:(UIButton *)sender {
  2. [self.chatTV resignFirstResponder];
  3. [UIView beginAnimations:nil context:nil];
  4. [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
  5. [UIView setAnimationDuration:0.2];
  6. self.view.center = CGPointMake(160, point.y);
  7. [UIView commitAnimations];
  8. self.clientSocket = [[AsyncSocket alloc]initWithDelegate:self];
  9. [self.clientSocket connectToHost:self.IPTF.text onPort:8000 withTimeout:-1 error:nil];
  10. if (![self.chatTV.text hasPrefix:@"/Users"]) {
  11. NSData *data = [self.chatTV.text dataUsingEncoding:NSUTF8StringEncoding];
  12. [self.clientSocket writeData:data withTimeout:-1 tag:0];
  13. self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@",self.chatRecordTV.text,self.chatTV.text];
  14. }else {
  15. NSString *filePath = self.chatTV.text;
  16. NSData *fileData = [NSData dataWithContentsOfFile:filePath];
  17. //把头信息添加到文件Data的前面
  18. NSString *header = [NSString stringWithFormat:@"file&&%@&&%d",[filePath lastPathComponent],fileData.length];
  19. //把头字符串转成data
  20. NSData *headerData = [header dataUsingEncoding:NSUTF8StringEncoding];
  21. //把头替换进100个字节的data里面
  22. NSMutableData *sendAllData = [NSMutableData dataWithLength:100];
  23. [sendAllData replaceBytesInRange:NSMakeRange(0, headerData.length) withBytes:headerData.bytes];
  24. [sendAllData appendData:fileData];
  25. NSLog(@"%@",header);
  26. NSLog(@"SendLength = %d",sendAllData.length);
  27. [self.clientSocket writeData:sendAllData withTimeout:-1 tag:0];
  28. self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@正在发送",self.chatRecordTV.text,self.chatTV.text];
  29. }
  30. }

在读取数据的方法中首先获取消息头信息,如果传递过来的是文件则获取到传输文件的类型、大小和名称,持续读取文件数据,将文件保存到本地路径,接受完成更新聊天记录,代码如下所示:

  1. -(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
  2. if (data.length>=100) {
  3. NSData *headerData = [data subdataWithRange:NSMakeRange(0, 100)];
  4. NSString *headerStr = [[NSString alloc]initWithData:headerData encoding:NSUTF8StringEncoding];
  5. if (headerStr&& [headerStr componentsSeparatedByString:@"&&"].count==3) {
  6. isFile = YES;
  7. }
  8. }
  9. if (isFile == NO) {
  10. NSString *chatStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
  11. self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n对方说:%@",self.chatRecordTV.text,chatStr];
  12. }
  13. if (isFile==YES) {
  14. if (data.length>=100) {
  15. NSData *headerData = [data subdataWithRange:NSMakeRange(0, 100)];
  16. NSString *headerStr = [[NSString alloc]initWithData:headerData encoding:NSUTF8StringEncoding];
  17. if (headerStr&& [headerStr componentsSeparatedByString:@"&&"].count==3) {
  18. NSArray *headers = [headerStr componentsSeparatedByString:@"&&"];
  19. NSString *type = [headers objectAtIndex:0];
  20. if ([type isEqualToString:@"file"]) {
  21. self.reciveFileName = [headers objectAtIndex:1];
  22. self.reciveFileLength = [[headers objectAtIndex:2] intValue];
  23. NSData *subFileData = [data subdataWithRange:NSMakeRange(100, data.length-100)];
  24. if (!self.allData) {
  25. self.allData = [NSMutableData data];
  26. }
  27. [self.allData appendData:subFileData];
  28. }
  29. }else {//没有消息头的情况下
  30. [self.allData appendData:data];
  31. }
  32. }else {//没有消息头的情况下
  33. [self.allData appendData:data];
  34. }
  35. NSLog(@"%d,%d",self.allData.length,self.reciveFileLength);
  36. if (self.allData.length == self.reciveFileLength ) {
  37. NSLog(@"接受完成");
  38. NSString *filePath = [@"/Users/Vivian/Documents" stringByAppendingPathComponent:self.reciveFileName];
  39. [self.allData writeToFile:filePath atomically:YES];
  40. self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n对方说:成功接收文件%@",self.chatRecordTV.text,self.reciveFileName];
  41. self.allData = nil;
  42. isFile = NO;
  43. }
  44. if (isFile == YES) {
  45. //如果有数据能够持续接受
  46. [self.myNewSocket readDataWithTimeout:-1 tag:0];
  47. }
  48. }
  49. }

运行程序,传输文件效果如图-4所示:

IOS网络开发(一)

图-4

1.4 完整代码

本案例中,ViewController.m文件中的完整代码如下所示:

 
  1. #import "ViewController.h"
  2. @interface ViewController () <AsyncSocketDelegate,UITextFieldDelegate,UITextViewDelegate> {
  3. BOOL isFile;
  4. CGPoint point;
  5. }
  6. @property (weak, nonatomic) IBOutlet UITextField *IPTF;
  7. @property (weak, nonatomic) IBOutlet UITextView *chatRecordTV;
  8. @property (weak, nonatomic) IBOutlet UITextView *chatTV;
  9. @property (nonatomic, strong)AsyncSocket *serverSocket;
  10. @property (nonatomic, strong)AsyncSocket *clientSocket;
  11. @property (nonatomic, strong)AsyncSocket *myNewSocket;
  12. @property (strong,nonatomic) NSMutableData *allData;
  13. @property (nonatomic, copy)NSString *reciveFileName;
  14. @property (nonatomic, assign)int reciveFileLength;
  15. @end
  16. @implementation ViewController
  17. - (void)viewDidLoad {
  18. [super viewDidLoad];
  19. self.serverSocket = [[AsyncSocket alloc]initWithDelegate:self];
  20. [self.serverSocket acceptOnPort:8000 error:nil];
  21. point = self.view.center;
  22. self.allData = [NSMutableData data];
  23. isFile = NO;
  24. }
  25. -(void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket{
  26. //将新接受的socket持有
  27. self.myNewSocket = newSocket;
  28. }
  29. -(void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
  30. [self.myNewSocket readDataWithTimeout:-1 tag:0];
  31. }
  32. -(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
  33. if (data.length>=100) {
  34. NSData *headerData = [data subdataWithRange:NSMakeRange(0, 100)];
  35. NSString *headerStr = [[NSString alloc]initWithData:headerData encoding:NSUTF8StringEncoding];
  36. if (headerStr&& [headerStr componentsSeparatedByString:@"&&"].count==3) {
  37. isFile = YES;
  38. }
  39. }
  40. if (isFile == NO) {
  41. NSString *chatStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
  42. self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n对方说:%@",self.chatRecordTV.text,chatStr];
  43. }
  44. if (isFile==YES) {
  45. if (data.length>=100) {
  46. NSData *headerData = [data subdataWithRange:NSMakeRange(0, 100)];
  47. NSString *headerStr = [[NSString alloc]initWithData:headerData encoding:NSUTF8StringEncoding];
  48. if (headerStr&& [headerStr componentsSeparatedByString:@"&&"].count==3) {
  49. NSArray *headers = [headerStr componentsSeparatedByString:@"&&"];
  50. NSString *type = [headers objectAtIndex:0];
  51. if ([type isEqualToString:@"file"]) {
  52. self.reciveFileName = [headers objectAtIndex:1];
  53. self.reciveFileLength = [[headers objectAtIndex:2] intValue];
  54. NSData *subFileData = [data subdataWithRange:NSMakeRange(100, data.length-100)];
  55. if (!self.allData) {
  56. self.allData = [NSMutableData data];
  57. }
  58. [self.allData appendData:subFileData];
  59. }
  60. }else {//没有消息头的情况下
  61. [self.allData appendData:data];
  62. }
  63. }else {//没有消息头的情况下
  64. [self.allData appendData:data];
  65. }
  66. NSLog(@"%d,%d",self.allData.length,self.reciveFileLength);
  67. if (self.allData.length == self.reciveFileLength ) {
  68. NSLog(@"接受完成");
  69. NSString *filePath = [@"/Users/Vivian/Documents" stringByAppendingPathComponent:self.reciveFileName];
  70. [self.allData writeToFile:filePath atomically:YES];
  71. self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n对方说:成功接收文件%@",self.chatRecordTV.text,self.reciveFileName];
  72. self.allData = nil;
  73. isFile = NO;
  74. }
  75. if (isFile == YES) {
  76. //如果有数据能够持续接受
  77. [self.myNewSocket readDataWithTimeout:-1 tag:0];
  78. }
  79. }
  80. }
  81. -(void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag {
  82. NSLog(@"发送成功");
  83. [self.myNewSocket readDataWithTimeout:-1 tag:0];
  84. }
  85. -(BOOL)textViewShouldBeginEditing:(UITextView *)textView {
  86. [UIView beginAnimations:nil context:nil];
  87. [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
  88. [UIView setAnimationDuration:0.2];
  89. self.view.center = CGPointMake(160, self.view.center.y-200);
  90. [UIView commitAnimations];
  91. return YES;
  92. }
  93. - (IBAction)send:(UIButton *)sender {
  94. [self.chatTV resignFirstResponder];
  95. [UIView beginAnimations:nil context:nil];
  96. [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
  97. [UIView setAnimationDuration:0.2];
  98. self.view.center = CGPointMake(160, point.y);
  99. [UIView commitAnimations];
  100. self.clientSocket = [[AsyncSocket alloc]initWithDelegate:self];
  101. [self.clientSocket connectToHost:self.IPTF.text onPort:8000 withTimeout:-1 error:nil];
  102. if (![self.chatTV.text hasPrefix:@"/Users"]) {
  103. NSData *data = [self.chatTV.text dataUsingEncoding:NSUTF8StringEncoding];
  104. [self.clientSocket writeData:data withTimeout:-1 tag:0];
  105. self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@",self.chatRecordTV.text,self.chatTV.text];
  106. }else {
  107. NSString *filePath = self.chatTV.text;
  108. NSData *fileData = [NSData dataWithContentsOfFile:filePath];
  109. //把头信息添加到文件Data的前面
  110. NSString *header = [NSString stringWithFormat:@"file&&%@&&%d",[filePath lastPathComponent],fileData.length];
  111. //把头字符串转成data
  112. NSData *headerData = [header dataUsingEncoding:NSUTF8StringEncoding];
  113. //把头替换进100个字节的data里面
  114. NSMutableData *sendAllData = [NSMutableData dataWithLength:100];
  115. [sendAllData replaceBytesInRange:NSMakeRange(0, headerData.length) withBytes:headerData.bytes];
  116. [sendAllData appendData:fileData];
  117. NSLog(@"%@",header);
  118. NSLog(@"SendLength = %d",sendAllData.length);
  119. [self.clientSocket writeData:sendAllData withTimeout:-1 tag:0];
  120. self.chatRecordTV.text = [NSString stringWithFormat:@"%@\n我说:%@正在发送",self.chatRecordTV.text,self.chatTV.text];
  121. }
  122. }
  123. - (IBAction)done:(UITextField *)sender {
  124. [self.IPTF resignFirstResponder];
  125. }
  126. - (IBAction)resign:(UITapGestureRecognizer *)sender {
  127. if (self.view.center.y!=point.y) {
  128. [self.chatTV resignFirstResponder];
  129. [UIView beginAnimations:nil context:nil];
  130. [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
  131. [UIView setAnimationDuration:0.2];
  132. self.view.center = point;
  133. [UIView commitAnimations];
  134. }
  135. }
  136. @end

上一篇:2018.2.28 PHP中使用jQuery+Ajax实现分页查询多功能如何操作


下一篇:深入理解php内核