zoukankan      html  css  js  c++  java
  • IOS网络开发(一)

     

    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,代码如下所示:

    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:@"%@ 我说:%@",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:@"%@ 我说:%@",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:@"%@ 对方说:%@",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所示:

    图-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:@"%@ 我说:%@",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:@"%@ 我说:%@正在发送",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(0100)];
    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:@"%@ 对方说:%@",self.chatRecordTV.text,chatStr];
    12. }
    13. if (isFile==YES{
    14. if (data.length>=100{
    15. NSData *headerData [data subdataWithRange:NSMakeRange(0100)];
    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:@"%@ 对方说:成功接收文件%@",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所示:

    图-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(0100)];
    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:@"%@ 对方说:%@",self.chatRecordTV.text,chatStr];
    43. }
    44. if (isFile==YES{
    45. if (data.length>=100{
    46. NSData *headerData [data subdataWithRange:NSMakeRange(0100)];
    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:@"%@ 对方说:成功接收文件%@",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:@"%@ 我说:%@",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:@"%@ 我说:%@正在发送",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
  • 相关阅读:
    【BigData】Java基础_ArrayList的使用
    【BigData】Java基础_构造方法的使用
    【BigData】Java基础_类和对象的基本使用
    【BigData】Java基础_冒泡排序
    【BigData】Java基础_数组
    【BigData】Java基础_循环
    【BigData】Java基础_终端输入2个数字并求和
    Navicat自动断开连接处理方式
    名字修饰约定extern "C"与extern "C++"浅析
    qt study 元对象,属性和反射编程
  • 原文地址:https://www.cnblogs.com/hytx/p/5051934.html
Copyright © 2011-2022 走看看