iOS从零开始学习socket编程——HTTP1.0客户端
在开始socket编程之前,首先需要明确几个概念:
1.网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
2.socket中文名为“套接字”,是基于TCP/IP协议通信机制。
3.客户端的socket连接需要指定主机的ip地址和端口,ip地址类似于家庭地址,用于唯一确认一台主机,而端口类似于门牌号,用于唯一确认主机上的某一个程序。
我们模拟一次HTTP的请求。首先在终端中输入
telnet 202.118.1.7 80
我们会得到这样的提示
Trying 202.118.1.7...
Connected to www.neu.edu.cn.
Escape character is ‘^]‘.
这时候表示已经和服务器建立了socket连接,接下来就是传递参数。
输入:
GET / HTTP/1.0
HOST: www.neu.edu.cn
两次回车后得到这样的数据
HTTP/1.1 200 OK
Date: Thu, 16 Apr 2015 13:58:33 GMT
Content-Type: text/html
Content-Length: 9710
Last-Modified: Thu, 16 Apr 2015 08:51:04 GMT
Connection: close
Vary: Accept-Encoding
ETag: "552f77f8-25ee"
Server: Apache/2.4.12 (FreeBSD)
Accept-Ranges: bytes
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
…………
这表示已经请求成功,其中空白的那一行上面的是请求返回的头部,浏览器需要解析这段数据,对我们有用的是Content-Type字段和空白行下面的内容。Content-Type字段告诉我们接下来的数据应该以何种方式被保存。
HTTP协议是基于TCP/IP协议的,所以显然可以利用socket模拟一次HTTP请求。iOS提供了基于C的socket编程接口CFSocket以及输入输出流CFReadStreamRef、CFWriteStreamRef,但是其实现方法比较复杂,这里我们使用著名的AsyncSocket开源框架进行socket编程。使用AsyncSocket并不影响我们对socket的理解,同时也被QQ等知名软件用来实现即时通讯、文件传输等功能。
AsyncSocket下载地址:https://github.com/robbiehanson/CocoaAsyncSocket
下载后把RunLoop文件夹下的AsyncSocket.h和AsyncSocket.m拷贝到工程文件中即可使用。
需要说明的是,AsyncSocket基于Runloop,可以异步调用方法,并不需要多开一个线程。
我们新建一个SocketDemoViewController类,因为是简单的HTTP客户端,所以所有的操作、视图将会在这个类中实现。在这个简单的客户端中,我们将会模拟一个HTTP的访问,将返回头部显示在屏幕上,并且保存获取到的图片和html文件等。
//SocketDemoViewController.h
#import <UIKit/UIKit.h>
#import "AsyncSocket.h"
#define HOST_IP @"202.118.1.7"
#define HOST_PORT 80
@interface SocketDemoViewController : UIViewController <UITextViewDelegate>
@property (nonatomic, strong) AsyncSocket *client;
@property (nonatomic, strong) UITextView *inputMsg;
@property (nonatomic, strong) UITextView *outputMsg;
@property (nonatomic, strong) NSString *fileName;
@property (nonatomic, strong) NSMutableData *receiveData;
- (int) connectServer: (NSString *) hostIP port:(int) hostPort;
- (void) sendMsg;
- (void) reConnect;
HOST_IP和HOST_PORT我选择了东北大学首页www.neu.edu.cn。
我们创建了一个AsyncSocket类的对象client,两个textview用于显示输入和输出内容。connectServer方法与服务器进行连接,reConnect重新连接,sendMsg方法向服务器发送数据。
接下来是SocketDemoViewController.m的代码,有点长,慢慢分析。
#import "SocketDemoViewController.h"
@implementation SocketDemoViewController
@synthesize inputMsg, outputMsg,fileName,receiveData;
@synthesize client;
- (void)viewDidLoad {
[super viewDidLoad];
//UI部分的实现
inputMsg = [[UITextView alloc] initWithFrame: CGRectMake( 10.0f, 40.0f, 355.0f, 100.0f)];
inputMsg.delegate = self;
inputMsg.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview: inputMsg];
outputMsg = [[UITextView alloc] initWithFrame: CGRectMake( 10.0f, 180.0f, 355.0f, 150.0f)];
outputMsg.textColor = [UIColor redColor];
outputMsg.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview: outputMsg];
UIButton *btnSend = [UIButton buttonWithType: UIButtonTypeRoundedRect];
btnSend.frame = CGRectMake(150.0f, 350.0f, 75.0f, 30.0f);
[btnSend setTitle: @"发送" forState: UIControlStateNormal];
[btnSend addTarget: self action: @selector(sendMsg) forControlEvents: UIControlEventTouchUpInside];
[self.view addSubview: btnSend];
UIButton *reConnect = [UIButton buttonWithType: UIButtonTypeRoundedRect];
reConnect.frame = CGRectMake(150.0f, 400.0f, 75.0f, 30.0f);
[reConnect setTitle: @"reConnect" forState: UIControlStateNormal];
[reConnect addTarget: self action: @selector(reConnect) forControlEvents: UIControlEventTouchUpInside];
[self.view addSubview: reConnect];
//与服务器进行连接
[self connectServer:HOST_IP port:HOST_PORT];
}
//链接server
- (int) connectServer: (NSString *) hostIP port:(int) hostPort{
if (client == nil)
{
client = [[AsyncSocket alloc] initWithDelegate:self];//初始化client,记得设置代理
NSError *err = nil;
if (![client connectToHost:hostIP onPort:hostPort error:&err])
{
//连接失败
return 2;
}
else
{
NSLog(@"连接成功");
return 1;
}
}
else
{
[client readDataWithTimeout:-1 tag:0];
return 0;
}
}
- (void) reConnect{
int stat = [self connectServer:HOST_IP port:HOST_PORT];
}
- (void)sendMsg{
//把inputMsg的内容发送给服务器
NSString *inputMsgStr = self.inputMsg.text;
NSData *data = [inputMsgStr dataUsingEncoding:NSUTF8StringEncoding];
[client writeData:data withTimeout:-1 tag:0];
}
#pragma mark -
#pragma mark close Keyboard
- (void)textFieldDidEndEditing:(UITextField *)textField
{
[inputMsg resignFirstResponder];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[inputMsg resignFirstResponder];
return YES;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[inputMsg resignFirstResponder];
}
#pragma mark socket delegate
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port{
//成功连接到服务器执行的回调函数
[client readDataWithTimeout:-1 tag:0];
}
- (void)onSocketDidDisconnect:(AsyncSocket *)sock
{
client = nil;//置空client
}
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
//收到数据的处理
if (receiveData == nil) {
receiveData = [[NSMutableData alloc] init];
}
[receiveData appendData:data];
NSString* aStr = [[NSString alloc] initWithData:receiveData encoding:NSUTF8StringEncoding];
NSRange endRange = [aStr rangeOfString:@"\r"];
if (endRange.location != NSNotFound) {
NSRange range = [aStr rangeOfString:@"\r\n\r\n"];
if (range.location != NSNotFound) {
NSString *requestHeader = [aStr substringToIndex:(unsigned long)range.location];
self.outputMsg.text = requestHeader;
}
else{
self.outputMsg.text = aStr;
}
//获取真正的请求到的数据
NSData *contentData = [self getContentDataWithData:data];
NSRange htmlRange = [aStr rangeOfString:@"text/html"];
if (htmlRange.location != NSNotFound) {
NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *documentsDirectory =[paths objectAtIndex:0];
NSString *ducumentPlistPath = [documentsDirectory stringByAppendingPathComponent:@"get.html"];//plist文件位置
[[NSFileManager defaultManager] createFileAtPath:ducumentPlistPath contents:contentData attributes:nil];
}
UIImageView *imgv = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 50, 50)];
imgv.image = [UIImage imageWithData:contentData];
if (imgv.image != nil) {
[self.view addSubview:imgv];
UIImageWriteToSavedPhotosAlbum(imgv.image, self, @selector(imageSavedToPhotosAlbum:didFinishSavingWithError:contextInfo:), nil);
}
receiveData = nil;
}
[client readDataWithTimeout:-1 tag:0];
}
- (NSData *)getContentDataWithData:(NSData *)data{
//将数据块和返回头部区分开来,返回NSData类型的请求到的数据
NSString *toSearch = @"\r\n\r\n";
NSData *target = [toSearch dataUsingEncoding:NSUTF8StringEncoding];
NSRange doubleChangeLine = [data rangeOfData:target options:0 range:NSMakeRange(0, [data length])];
if (doubleChangeLine.location != NSNotFound) {
NSData *content = [data subdataWithRange:NSMakeRange(doubleChangeLine.location + 4, [data length] - doubleChangeLine.location - 4)];
return content;
}
else{
return nil;
}
}
- (void)imageSavedToPhotosAlbum:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{
//把image保存到本地相册中
if (!error) {
NSLog(@"成功保存到相册");
}
}
@end
运行程序后,只要在输入框中,输入之前命令行中的那一段代码(记得要换行,参见截图中光标的位置),即可模拟HTTP请求。
在onSocket:(AsyncSocket )sock didReadData:(NSData )data withTag:(long)tag方法中,我们根据Content-Type字段来选择处理数据的方式,并且将HTML文件保存在沙盒documents目录下的get.html方法中。讲可能存在的图片保存在本地相册中。
对于详细的didReadData解析,参见上一篇文章:《AsyncSocket didReadData函数详解》
http://blog.csdn.net/abc649395594/article/details/45046871
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。