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,代码如下所示:
- @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.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];
- //创建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
IOS网络开发(一)的更多相关文章
- IOS网络开发概述
概览 大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博.微信等,这些应用本身可能采用iOS开发,但是所有的数据支撑都是基于后台网络服务器的.如今,网络编程越来越普遍,孤立的应用通常是没有生命力 ...
- IOS网络开发(三)
1 飞机航班查询软件 1.1 问题 NSURLConnection是IOS提供的用于处理Http协议的网络请求的类,可以实现同步请求也可以实现异步请求,本案例使用NSURLConnection类实现一 ...
- IOS网络开发实战(二)
1 飞机航班查询软件 1.1 问题 NSURLConnection是IOS提供的用于处理Http协议的网络请求的类,可以实现同步请求也可以实现异步请求,本案例使用NSURLConnection类实 ...
- 初探iOS网络开发,数据解析。
通过大众点评平台开发来简单了解一下,oc的网络编程和数据解析(json) 首先我们需要到大大众点评开发者平台申请一个key.http://developer.dianping.com/app/tech ...
- IOS网络开发(二)
1 局域网群聊软件 1.1 问题 UDP协议将独立的数据包从一台计算机传输到另外一台计算机,但是并不保证接受方能够接收到该数据包,也不保证接收方所接收到的数据和发送方所发送的数据在内容和顺序上是完全一 ...
- IOS网络开发实战(一)
1 局域网群聊软件 1.1 问题 UDP协议将独立的数据包从一台计算机传输到另外一台计算机,但是并不保证接受方能够接收到该数据包,也不保证接收方所接收到的数据和发送方所发送的数据在内容和顺序上是完 ...
- iOS网络开发-打造自己的视频客户端
一.展示实现 效果 客户端: 服务器端: 二.创建表 create table CourseV ...
- ios网络开发 网络状态检查
http://www.cnblogs.com/hanjun/archive/2012/12/01/2797622.html 网络连接中用到的类: 一.Reachability 1.添加 Reachab ...
- iOS网络开发-AFNetworking请求asp.net WebService
看到园子有位朋友需要使用AFN框架请求 WebService,所以就整理了一下,demo下载链接在底部 编写WebService可以看这篇博客 http://www.cnblogs.com/linmi ...
随机推荐
- rmi远程调用
1.在服务器端程序中的spring-servlet.xml中添加 <bean id="userService" class="org.springframework ...
- ElasticSearch学习问题记录——Invalid shift value in prefixCoded bytes (is encoded value really an INT?)
最近在做一个电商项目,其中商品搜索中出现一个奇怪的现象,根据某个字段排序的时候会出现商品数量减少的情况.按照一般路要么查不出来,要么正常显示,为什么增加了按照销量排序就会出现查询结果减少的情况. 查了 ...
- JDK中的native2ascii命令详解
1.native2ascii简介: native2ascii是sun java sdk提供的一个工具.用来将别的文本类文件(比如*.txt,*.ini,*.properties,*.java等等)编码 ...
- Android 广播机制概论
Android 中的每一个应用程序都可以对自己感兴趣的广播进行注册,这样,该程序就只会接收到自己所关心的广播内容,这些广播可能是来自于系统的,也可能是来自于其他应用程序. 广播分为2种: 1.标准广播 ...
- java中获取ServletContext常见方法
http://blog.sina.com.cn/s/blog_3fe961ae0100wjnf.html 1.在javax.servlet.Filter中直接获取 ServletContext con ...
- oracle sql 优化
2. 选择最有效率的表名顺序(只在基于规则的优化器中有效) ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后的表(基础表 driving table)将被最先 ...
- Head First 设计模式 --7 适配器模式 外观模式
适配器模式:将一个类东街口转换成客户期望的另一个接口.适配器让原本接口不兼容的类可以合作无间. 适配器模式有两种,对象适配器和类的适配器.先看一下对象适配器. 还是看最开始鸭子的例子,如果此时鸭子不够 ...
- 使用File类递归列出E盘下全部文件
import java.io.File;public class FileListTest { public void tree(File file){ if(file.listFiles()!=nu ...
- Linux运行级详解
对于那些在DOS/Win9x/NT平台下的高级用户而言,Linux似乎是一个怪物.没有config.sys,没有 autoexec.bat,具有个人特色的机器配置不知道从何开始. 需要说明的是,很多人 ...
- springmvc__SimpleUrlHandlerMapping(对访问地址进行加工,以键值对的形式)
1.配置web.xml(这里配置url-pattern为/) <!-- 编码过滤器 --> <filter> <filter-name>characterEncod ...