一、基本知识
- 客户端(Client):移动应用(iOS、Android)
- 服务器(Server):为客户端提供服务、提供数据、提供资源的机器
- 请求(Request):客户端向服务器索要数据的一种行为
- 响应(Response):服务器对客户端的请求作出的反应,一般指返回数据给客户端
二、HTTP基本介绍
- 客户端通过URL找到想要连接的服务器。
- URL的全称是Uniform Resource Locator(统一资源定位符)
- URL的基本格式 = 协议://主机地址/路径
- URL就是资源的地址、位置,互联网上的每个资源都有一个唯一的URL
- 协议:不同的协议,代表着不同的资源查找方式、资源传输方式。
- 主机地址:存放资源的主机(服务器)的IP地址(域名)
- 路径:资源在主机(服务器)中的具体位置
- 常见的协议:HTTP(超文本传输协议)、file(访问本地计算机)、mailto(访问电子邮件)、FTP(访问共享主机的文件资源)。
- HTTP协议简介
- 全称是Hypertext Transfer Protocol,超文本传输协议
- 规定了客户端和服务器之间的数据传输格式,让客户端和服务器能有效的进行数据沟通。
- HTTP协议的特点:
- 简单快速:因为HTTP协议简单,所以HTTP服务器的程序规模小,因而通信速度很快。
- 灵活:HTTP允许传输各种各样的数据
三、HTTP请求
- 在HTTP/1.1 协议中,定义了8种发送http请求的方法:GET(查)、POST(改)、OPTIONS、HEAD、PUT(增)、DELETE(删)、TRACE、CONNECT、PATCH。
- 最常用的GET和POST(实际上GET和POST都能够办到增删改查)。 GET请求:
- 在请求URL后面以?的形式跟上给服务器的参数,多个参数之间用&隔开,比如http://www.test.com/login?username=123&password=123456
- 由于流浪器和服务器对URL长度有限制,因此在URL后面附带的参数是有限制的,通常不能超过1KB。
- 发给服务器的参数全部都放在请求体中
- 理论上,POST传递数据是没有限制(具体还得看服务器处理能力)
- GET和POST的选择
- 如果要传递大量的数据,比如文件上传,只能用POST请求。
- GET的安全性比POST要差些,如果包含机密、敏感信息,建议用POST。
- 如果仅仅是索取数据(数据的查询),建议使用GET。
- 如果是增加、修改、删除数据,建议使用POST。
四、iOS中发送HTTP请求的方案
- 苹果原生:
- NSURLConnection:用法简单,最古老最经典最直接的一种方案(坑比较多)。
- NSURLSession:功能比NSURLConnection更加强大,iOS7开始使用。
- CFNetwork:NSURL* 的底层,纯C语言。
- 第三方框架
- AFNetworking:简单易用,多人维护迭代。
五、HTTP通信
- HTTP协议规定:一个完整的由客户端发给服务器的HTTP 请求 包含以下内容
- 请求头:包含了对客户端的环境描述、客户端请求信息等。
- GET /test.png HTTP/1.1 // 包含了请求方法、请求资源路径、HTTP协议版本
- Host:168.110.117.36:8081 // 客户端访问的服务器主机地址
- User-Agent:Mozilla/5.0 // 客户端的类型,客户端的软件环境
- Accept:text/html,*/* // 客户端所能接收的数据类型
- Accept-Language:zh-cn // 客户端的语言环境
- Accept-Encoding:gzip // 客户端支持的数据压缩格式
- 请求体:客户端发给服务器的具体数据,即参数。
- 请求头:包含了对客户端的环境描述、客户端请求信息等。
- HTTP协议规定:一个完整的HTTP 响应 中包含以下内容
- 响应头:包含了对服务器的描述、对返回数据的描述
- HTTP/1.1 200 OK // 包含了HTTP协议版本、状态码、状态英文名称
- Server:Apache-Coyote/1.1 // 服务器的类型
- Content-Type:image、jpeg // 返回数据类型
- Data:Mon,23 Jun 2014 12:54:52 GMT // 响应时间
- 响应体:服务器返回给客户端的具体数据。
- 响应头:包含了对服务器的描述、对返回数据的描述
六、NSURLConnection
- 常用类:
- NSURLConnetion:负责发送请求,建立客户端和服务器的连接,并收集来自服务器的响应数据。
- NSURLRequest、NSMutableURLRequest:一个对象代表一个请求,它包含一个NSURL对象、请求方法、请求头、请求体、请求超时时间等。
- NSURL:请求地址。
- 使用步骤:
- 创建一个NSURL对象,设置请求的路径。
- 创建NSURLRequest对象,设置请求头和请求体以及请求方法等。(GET请求可以默认不设置请求方法)
- 使用NSURLConnection发送请求。
- 具体使用实例
-
同步请求
NSURL *url = [NSURL URLWithString:@"http://news.k618.cn/pic/cnxhy/201504/W020150424355233105959.jpg"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLResponse *response = nil; NSError *error = nil; /** 1.同步请求 2.此方法9.0之后被弃用 */ NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; UIImage *img = [UIImage imageWithData:data]; self.imgV.image = img;
- 异步请求
NSURL *url = [NSURL URLWithString:@"http://news.k618.cn/pic/cnxhy/201504/W020150424355233105959.jpg"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; /** 1.异步请求 2.queue: 表示返回block执行与哪个线程中 2.此方法9.0之后被弃用 */ [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc]init] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) { // 刷新界面要放在主线程(因为界面的创建就在主线程) [[NSOperationQueue mainQueue] addOperationWithBlock:^{ UIImage *img = [UIImage imageWithData:data]; self.imgV.image = img; }]; NSLog(@"------data=%@-----response=%@----%@", data, response, [NSThread currentThread]); }];
- 代理请求
/** 根据文档可知,delegate运行的环境需要开启了RunLoop,所以,当下列方法放入子线程中时,要开启子线程的RunLoop,否则delegate的方法不会调用。 */ NSOperationQueue *queue = [[NSOperationQueue alloc]init]; [queue addOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:@"http://t1.mmonly.cc/uploads/tu/zyf/tt/20160316/b3ivxec3bqv.jpg"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLConnection *connetion = [[NSURLConnection alloc] initWithRequest:request delegate:self]; // 可以设置代理方法在哪个线程中执行(暂时设置在主线程,默认主线程) [connetion setDelegateQueue:[NSOperationQueue mainQueue]]; [[NSRunLoop currentRunLoop] run]; // 开启子线程的的RunLoop }]; #pragma mark - 代理方法 // 开始接收到服务器的响应时调用 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { } // 接收到服务器返回的数据时调用(服务器返回的数据比较大时会调用多次) - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { } // 服务器返回的数据完全接收完毕后调用 - (void)connectionDidFinishLoading:(NSURLConnection *)connection { } // 请求出错时调用(比如请求超时) - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { NSLog(@"------%s------", __FUNCTION__); }
- POST和GET请求
// POST请求 NSURL *url = [NSURL URLWithString:[@"http://www.iqiyi.com/v_19rrkowa6k.html?list=19rrmo4fl6" stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];; // 设置请求配置,需要用可变的NSMutableURLRequest设置 NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:url]; mutableRequest.HTTPMethod = @"POST"; // 设置请求方法,默认为GET请求 mutableRequest.timeoutInterval = 20.0; // 请求超时时间,默认60s NSString *paramStr = @"list=19rrmo4fl6"; mutableRequest.HTTPBody = [paramStr dataUsingEncoding:NSUTF8StringEncoding]; // 设置请求体 // 设置请求头 // [mutableRequest setValue:<#(nullable NSString *)#> forHTTPHeaderField:<#(nonnull NSString *)#>]; [NSURLConnection sendAsynchronousRequest:mutableRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) { NSHTTPURLResponse *re = (NSHTTPURLResponse *)response; NSLog(@"------response=%@-------statusCode=%d-----", response, re.statusCode); }]; // GET请求 NSURL *url = [NSURL URLWithString:[@"http://www.iqiyi.com/v_19rrkowa6k.html?list=19rrmo4fl6" stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];; // 设置请求配置,需要用可变的NSMutableURLRequest设置 NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:url]; mutableRequest.timeoutInterval = 20.0; // 请求超时时间,默认60s // 设置请求头 // [mutableRequest setValue:<#(nullable NSString *)#> forHTTPHeaderField:<#(nonnull NSString *)#>]; [NSURLConnection sendAsynchronousRequest:mutableRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) { NSHTTPURLResponse *re = (NSHTTPURLResponse *)response; NSLog(@"------response=%@-------statusCode=%d-----", response, re.statusCode); }];
七、NSURLSession
- 常用类:NSURLSessionTask(抽象类)、NSURLSessionDataTask(一些列请求)、NSURLSessionDownloadTask(下载)、NSURLSessionUploadTask(上传)、NSURLSessionConfiguration(session配置类)。
- 使用步骤:
- 通过NSURLSession创建task
- 启动task(resume)
- 具体实例:
- 网络请求
#pragma mark - 代理方法做网络请求 - (void)urlSessionWithDelegate { // 网络请求的的一些配置,例如:超时时间、网络情况等 NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; // delegateQueue:表示代理方法运行在哪个线程中 NSURLSession *delegateSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[[NSOperationQueue alloc] init]]; NSURLSessionDataTask *task = [delegateSession dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://p3.pstatp.com/origin/11fd000255db5f64b772"]]]; [task resume]; } #pragma mark - NSURLSessionDataDelegate // 接收到服务器的响应 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { NSLog(@"-----%s", __FUNCTION__); // 需要确认以什么样的形式去接收服务器的数据或者不接收。 completionHandler(NSURLSessionResponseAllow); _data = [NSMutableData data]; } // 接收服务器数据,此方法可能多次调用 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [_data appendData:data]; NSLog(@"-----%s---%@", __FUNCTION__, [NSThread currentThread]); } // 网络请求完成 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { dispatch_sync(dispatch_get_main_queue(), ^{ self.imgV.image = [UIImage imageWithData:_data]; }); NSLog(@"-----%s", __FUNCTION__); } #pragma mark - 快速创建session,做网络请求 - (void)urlSession { // 创建session对象 NSURLSession *session = [NSURLSession sharedSession]; // 通过session创建task NSURLSessionTask *task = [session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://t1.mmonly.cc/uploads/tu/201510/126/1.jpg"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { dispatch_sync(dispatch_get_main_queue(), ^{ self.imgV.image = [UIImage imageWithData:data]; }); }]; // 启动task [task resume]; }
- 下载请求
#pragma mark - session快速下载 - (void)downTask { NSURLSession *session = [NSURLSession sharedSession]; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://p9.pstatp.com/origin/134900068f9a107994f4"]]; // location: 临时文件的位置(*.tmp) NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"--------------%@", location); NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:response.suggestedFilename]; NSURL *destinationURL = [NSURL fileURLWithPath:path]; [fileManager moveItemAtURL:location toURL:destinationURL error:nil]; dispatch_sync(dispatch_get_main_queue(), ^{ self.imgV.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:destinationURL]]; }); NSLog(@"--------------location:%@-------destinationURL:%@-----", location,destinationURL); }]; [task resume]; } #pragma mark - session代理下载 - (void)downloadTaskWithDelegate { /** * 注意点:下载的代理方法中没有response的代理方法,不需要回调去调是否执行接收服务器数据的方法。 */ NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[[NSOperationQueue alloc] init]]; NSURLSessionDownloadTask *task = [session downloadTaskWithURL:[NSURL URLWithString:@"http://p3.pstatp.com/origin/13510001b63985024add"]]; [task resume]; } #pragma mark - NSURLSessionDownloadDelegate // 接收服务器的下载的数据 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { NSLog(@"---bytesWritten:%ld------totalBytesWritten:%ld------totalBytesExpectedToWrite:%ld------", (long)bytesWritten, (long)totalBytesWritten, (long)totalBytesExpectedToWrite); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.progressView.progress = (totalBytesWritten / totalBytesExpectedToWrite); }]; } // 下载的数据接收完毕 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { NSLog(@"---%s", __FUNCTION__); NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:downloadTask.response.suggestedFilename]; NSURL *destinationURL = [NSURL fileURLWithPath:path]; [fileManager moveItemAtURL:location toURL:destinationURL error:nil]; dispatch_sync(dispatch_get_main_queue(), ^{ self.imgV.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:destinationURL]]; }); } // 网络请求完成 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { NSLog(@"---%s", __FUNCTION__); }
- 断点下载:断点下载说白了就是下载中途停止了,下次再启动下载。对于此类情形,分两种情况:
- 在应用内暂定(未关闭应用):这种情况可以直接调用 suspend 的方法暂定下载,再通过resume方法恢复下载。
- 暂定下载后,退出应用:这种情况,采用 cancelByProducingResumeData:(void (^)(NSData * _Nullable resumeData))completionHandler 方法暂停下载,并本地保存resumeData,再通过 downloadTaskWithResumeData:(NSData *)resumeData 方法恢复下载。
-
// 方法一:暂停下载(仅适合应用不关闭) [_task suspend]; // 不仅可以启动任务,还可以唤醒suspend状态的任务 [_task resume]; // 方法二:取消下载,获得恢复记录数据resumeData(保存本地记录) [self.task cancelByProducingResumeData:^(NSData *resumeData) { self.resumeData = resumeData; }]; // 恢复下载时接过保存的恢复数据 self.task = [self.session downloadTaskWithResumeData:self.resumeData]; // 启动任务 [self.task resume]; // 下载失败时,可以在这方法中获取暂停下载的数据记录resumeData - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { // 保存恢复数据 self.resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData]; } 补充: // 取消任务,既可以向resume的状态发送取消任务消息,也可以向suspend的状态发送任务消息,但是此方法一当调用,则下载任务就不能恢复了。 [_task cancel];
- 上传
#pragma mark - 文件上传 - (void)uploading { NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionUploadTask *task = [session uploadTaskWithRequest:request fromData:bodydData completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { }]; [task resume]; } #pragma mark - 代理方法文件上传 - (void)uploadingWithDelegate { /** * 1.注意:没有和downloading相似的uploading的代理方法,上传遵循是NSURLSessionDataDelegate代理方法。 * 2. bodyData: 这上传bodyData比较复杂,大多数人都会采用AFNetworking去解决,具体bodyData的格式请自行百度。 */ NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]]; NSURLSessionUploadTask *task = [session uploadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.guojinbao.com"]] fromData:bodyData]; [task resume]; } #pragma mark - 上传代理---NSURLSessionDataDelegate // 接到服务器的响应 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { NSLog(@"-------%s-----", __FUNCTION__); } // 此方法可以跟踪上传进度,可能多次调用 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { NSLog(@"-------%s-----", __FUNCTION__); } // task结束,可能上传成功结束,error为nil,也有可能是上传失败,error有值。 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error { NSLog(@"-------%s-----", __FUNCTION__); } // 其他补充: // 这种方法请求,是PUT上传文件,需要服务器支持PUT请求,fileURL:要上传文件的url。(此方法上传不安全) - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
- 网络请求