专栏
iOS开发之网络通信(1)—— 计算机网络
iOS开发之网络通信(2)—— HTTP(S)
iOS开发之网络通信(3)—— XML & JSON
iOS开发之网络通信(4)—— socket
iOS开发之网络通信(5)—— CocoaAsyncSocket
iOS开发之网络通信(6)—— AFNetworking & Alamofire
一. HTTP发展史
HTTP版本 | 年份 | 新增命令 | 连接方式 | 摘要 |
---|---|---|---|---|
0.9 | 1991 | GET | 短连接 | 初版 |
1.0 | 1996 | POST、HEAD | 短连接 (非标准长连接Connection: keep-alive) | 1. 可以传输文字,还能传输图像、视频、二进制文件。 2. 加入头信息、状态码、多字符集支持、多部分发送、权限、缓存、内容编码等。 |
1.1 | 1999 | PUT、PATCH、TRACE、OPTIONS、DELETE | 默认持久连接 | 1. 管道机制: 一个连接可同时发送多个请求(服务器端需要按顺序返回结果)。 2. 增加Host字段,指定服务器的域名,这样服务器上支持了虚拟主机,即一台机器多个站点。 |
2 | 2015 | - | 默认持久连接 | 1. 无论是header还是body都是二进制数据(打包成帧frame)。 2. 在一个连接里,客户端和服务端同时发送多个请求,为了区分它们就需要对数据做标记。 3. 支持header信息索引。 4. 支持服务端主动推送功能。 |
二. HTTP简介
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写, 是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
HTTP基于TCP/IP通信协议来传递数据 (HTML文件, 图片文件, 查询结果等), 默认端口号为80。
HTTP使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。
请求报文结构
客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。
请求方法
根据 HTTP 标准,HTTP 请求可以使用多种请求方法。
HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD方法。
HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。
序号 | 方法 | 描述 |
---|---|---|
1 | GET | 请求指定的页面信息,并返回实体主体。 |
2 | HEAD | 类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头 |
3 | POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。 |
4 | PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 |
5 | DELETE | 请求服务器删除指定的页面。 |
6 | CONNECT | HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 |
7 | OPTIONS | 允许客户端查看服务器的性能。 |
8 | TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
9 | PATCH | 是对 PUT 方法的补充,用来对已知资源进行局部更新。 |
三. HTTPS简介
HTTPS(Hypertext Transfer Protocol Secure:超文本传输安全协议)是一种透过计算机网络进行安全通信的传输协议。HTTPS 经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包。HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。
HTTPS 默认工作在 TCP 协议443端口.
工作原理:
四. HTTP与HTTPS区别
- HTTP工作在80端口; HTTPS工作在443端口。
- HTTP 明文传输,数据都是未加密的,安全性较差; HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。
- 使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy 和 GlobalSign 等。
- HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
- HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,HTTPS 比 HTTP 要更耗费服务器资源。
五. 代码部分
Apple默认使用HTTPS,如需开启HTTP,需在info.plist添加如下配置:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
1. 简单请求
// 创建URLRequest
let url = URL(string: "http://www.baidu.com")
var request = URLRequest.init(url: url!) // 默认 cachePolicy = .useProtocolCachePolicy,timeoutInterval = 60.0
request.httpMethod = "GET" // or HEAD、POST、PUT...
// 创建URLSession
let session = URLSession.shared
// 创建请求任务
let dataTask = session.dataTask(with: request) { (data, response, error) in
NSLog("data:\(String(describing: data)), response:\(String(describing: response)), error:\(String(describing: error))")
}
// 发起请求
dataTask.resume()
以上请求最终会转换成如下真正的请求结构。使用Charles抓到raw数据:
GET / HTTP/1.1
Host: www.baidu.com
Accept: */*
User-Agent: KKHttpDemo/1 CFNetwork/1220.1 Darwin/20.3.0
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: keep-alive
上面提到了三个要点:URLRequest、URLSession 和 会话任务。
URLRequest
URLRequest封装了加载请求的两个基本属性:要加载的URL和用于加载该请求的策略。此外,对于HTTP和HTTPS请求,URLRequest包括HTTP方法(GET,POST等)和HTTP标头。
URLRequest除了可设置URL及请求方法,还可定制缓存策略cachePolicy、设置请求超时timeoutInterval等。
URLSession
URLSession提供了一系列API用于执行数据传输任务。每个App创建一个或多个URLSession实例,每个实例协调一组相关的数据传输任务。
URLSession有四种类型:
1. shared单例会话。单例会话没有会话配置对象(URLSessionConfiguration),即会话不可自定义。通常用于简单的请求。
2. default默认会话。默认会话与单例会话非常相似,但是您可以对其进行配置。您还可以将委托分配给默认会话以增量获取数据。
3. ephemeral临时会话。临时会话类似于单例会话,但是不会将缓存,cookie或凭据写入磁盘。例如应用于无痕浏览,你懂的。😏
4. background后台会话。后台会话使您可以在应用未运行时在后台执行内容的上载和下载。
URLSessionConfiguration
既然介绍了URLSession,那么也应该来了解下URLSessionConfiguration。URLSessionConfiguration作为URLSession的配置属性,可以进行一些自定义配置。上面提到的default、ephemeral及background都用到了URLSessionConfiguration。
使用URLSessionConfiguration的allowsCellularAccess属性还可以设置会话是否能通过蜂窝网络进行连接。
会话任务
会话任务(URL Session Tasks)有以下四种,上面例子只是用到的其中一种(dataTask)。
1. Data tasks。使用NSData对象发送和接收数据。数据任务旨在向服务器发出简短的,经常是交互式的请求。
2. Upload tasks。与数据任务相似,但是它们还发送数据(通常以文件的形式),并在应用程序不运行时支持后台上传。
3. Download tasks。以文件形式检索数据,并在应用程序不运行时支持后台下载和上传。
4. WebSocket tasks。使用RFC 6455中定义的WebSocket协议通过TCP和TLS交换消息。
2. 上传数据
// 要上传的数据
let uploadData = "test123".data(using: .utf8)
// 配置URL请求
let url = URL(string: "http://httpbin.org/post")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
// 使用单例会话
let session = URLSession.shared
// 启动一个上传请求
let task = session.uploadTask(with: request, from: uploadData) { data, response, error in
if let error = error {
print ("error: \(error)")
return
}
guard let response = response as? HTTPURLResponse,
(200...299).contains(response.statusCode) else {
print ("server error")
return
}
if let mimeType = response.mimeType,
mimeType == "application/json",
let data = data,
let dataString = String(data: data, encoding: .utf8) {
print ("got data: \(dataString)")
}
}
task.resume()
httpbin.org是一个测试HTTP请求的GitHub开源网站
log:
got data: {
"args": {},
"data": "test123",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "en-us",
"Content-Length": "7",
"Content-Type": "text/plain",
"Host": "httpbin.org",
"User-Agent": "KKHttpDemo/1 CFNetwork/1220.1 Darwin/20.3.0",
"X-Amzn-Trace-Id": "Root=1-603317a5-511395fa2f7c282800345ec2"
},
"json": null,
"origin": "113.90.245.35",
"url": "http://httpbin.org/post"
}
3. 下载数据
let url = URL(string: "http://httpbin.org/image/jpeg")!
let downloadTask = URLSession.shared.downloadTask(with: url) {
urlOrNil, responseOrNil, errorOrNil in
if let error = errorOrNil {
print ("error: \(error)")
return
}
guard let fileURL = urlOrNil else { return }
do {
let documentsURL = try
FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
var savedURL = documentsURL.appendingPathComponent(fileURL.lastPathComponent)
// 修改后缀名
savedURL.deletePathExtension()
savedURL.appendPathExtension("jpeg")
// 从tmp文件夹下移动到documents文件夹下
try FileManager.default.moveItem(at: fileURL, to: savedURL)
// 生成image
let imageData = try Data.init(contentsOf: savedURL)
let image = UIImage(data: imageData)
if image != nil {
// 刷新UI
DispatchQueue.main.async {
self.imageView?.image = image
}
}
} catch {
print ("file error: \(error)")
}
}
downloadTask.resume()
4. 后台下载
AppDelegate.swift
var backgroundCompletionHandler: (() -> Void)?
// 下载完成后调用:App在后台还是被系统杀死,都会调用。手动杀死App或在前台时不调用
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
backgroundCompletionHandler = completionHandler
}
ViewController.swift
// 后台下载
func backgroundDownload() {
// 创建后台URLSession
let config = URLSessionConfiguration.background(withIdentifier: "com.Kang.downloadSession")
config.isDiscretionary = true
config.sessionSendsLaunchEvents = true
let urlSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)
// 开启下载任务
let url = URL(string: "http://httpbin.org/image/jpeg")!
let backgroundTask = urlSession.downloadTask(with: url)
backgroundTask.earliestBeginDate = Date().addingTimeInterval(5) // 延迟5秒下载
backgroundTask.countOfBytesClientExpectsToSend = 200 // 最大上传200Byte
backgroundTask.countOfBytesClientExpectsToReceive = 500 * 1024 // 最大下载500KB
backgroundTask.resume()
}
//MARK: - URLSessionDelegate
// 任务完成
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
print(#function, "后台下载完成")
DispatchQueue.main.async {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let backgroundCompletionHandler = appDelegate.backgroundCompletionHandler else {
return
}
backgroundCompletionHandler() // 告诉系统,已经处理完成
}
}
//MARK: - URLSessionDownloadDelegate
// 下载完成
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print(#function, "下载完成")
let fileURL = location
do {
let documentsURL = try
FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
var savedURL = documentsURL.appendingPathComponent(fileURL.lastPathComponent)
// 修改后缀名
savedURL.deletePathExtension()
savedURL.appendPathExtension("jpeg")
// 从tmp文件夹下移动到documents文件夹下
try FileManager.default.moveItem(at: fileURL, to: savedURL)
// 生成image
let imageData = try Data.init(contentsOf: savedURL)
let image = UIImage(data: imageData)
if image != nil {
// 刷新UI
DispatchQueue.main.async {
self.imageView?.image = image
}
}
} catch {
print ("file error: \(error)")
}
}
// 下载进度
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
// 可以将字节转化为我们需要的Kb或者Mb
let receiveSize = ByteCountFormatter.string(fromByteCount: totalBytesWritten, countStyle: .file)
let totalSize = ByteCountFormatter.string(fromByteCount: totalBytesExpectedToWrite, countStyle: .file)
let status = receiveSize + " / " + totalSize
print("process: \(status)")
// 刷新UI
DispatchQueue.main.async {
self.statusLabel?.text = status
}
}