HTTP与HTTPS网络传输协议
HTTP和HTTPS都是基于TCP的,TCP经常用于处理文本和非实时的音视频。
HTTP
- HTTP是一个网络传输协议,现在几乎也不再使用HTTP了,因为是明文传输的数据,没有任何加密机制,数据在传输过程中容易被监听、篡改或伪造。一些敏感信息比如账号、密码容易被盗。
- HTTP默认使用端口 80 来进行信息传输。
- HTTP的URL以**http://**开头
- HTTP没有解密和加密的过程,因此速度通常较快。
- HTTP被认为不安全,在一些搜索引擎会被标记为不安全。
- HTTP不需要任何证书
HTTPS
- HTTPS是使用SSL/TLS对数据进行加密的,在信息传输过程中提供了一层安全防护。HTTPS使得数据在传输中是安全的,能够防止中间人攻击、数据篡改和伪装。
- 默认使用端口 443 进行加密信息的传输。
- HTTPS的URL以https:// 开头
- HTTPS要通过SSL/TLS加密,因此速度通常较快。
- HTTPS必须安装SSL/TLS证书才能使用
HTTPS的请求方式
GET
明文请求,几乎不传参数,只传类似page、index这种可以公开给大家知道的明文参数,参数直接拼在URL里面。
POST
同样使用URL,但并不传递明文参数,参数全部放在包体里面,需要解包
json
POST https://example.com/api/users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com"
练习:使用TCP协议中的POST进行网络请求。
同样建立一个新的文件夹和页面来进行网络请求管理, 命名为RequestBase
设置服务器,导入需要的库。
swift
#if DEBUG
var baseUrl : String = "测试服务器url" //测试服务器
#else
var baseUrl : String = "正式服务器url" //正式服务器
#endif
let baseResouceUrl = "资源服务器URL" //资源服务器
import SystemConfiguration
import Foundation
定义一个闭包来回调来处理请求的响应,这个请求是异步的,函数会在发起请求后立刻返回。当请求完成的时候,这个回调会被调用,总之就是用来处理错误信息的。
swift
class RequestBase {
typealias Completion = (Any?) -> ( )
//这是个闭包传入的可以是任何数据类型
}
然后定义一个函数postRequest来定义请求头、请求体等内容。
swift
func postRequest(jsonStr: String?, url: String, dataKey: String = "data", completion: @escaping Completion) {
}
这个函数需要一些传入参数来配置网络请求。
这个jsonStr是请求体的内容
url是用于创建url的字符串
dataKey表示将数据放入JSON结构中的键的名称,它默认是data
@escaping 表示这个闭包会在函数 postRequest 执行完后仍然被调用。因为网络请求是异步的,所以闭包的执行会在之后的某个时间点,函数可能早就返回了。
*注意,网络请求都是异步的。
swift
func postRequest(jsonStr: String?, url: String, dataKey: String = "data", completion: @escaping Completion) {
var req = URLRequest(url: URL(string: url)!, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 15)
}
URLRequest()类包含了发送请求到服务器所需的基本信息,如请求的URL、HTTP方法,请求体,请求头等...为了配置这些内容,需要创建一个 URLRequest 实例,配置请求的 URL、缓存策略和超时时间。
使用这个类中的三个参数,分别是url,cachePolicy,timeoutInterval
URL(string: )!是一个传字符串自动生成url的方法,把生成的结果传进url。
**cachePolicy**是用于指定请求的缓存策略的参数,.userProtocolCachePolicy表示请求将使用 URL 协议所定义的缓存规则。
timeoutInterval 是用于设置请求超时时间的参数,现在网速快,一般就是15,10,30等等。以秒为单位。这里设置为15秒。
swift
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpBody = jsonStr?.data(using: .utf8)
req.httpMethod 设置请求的HTTP方法为 "POST"
req.setValue设置请求头中的 Content-Type 字段,表明请求体的数据格式是 JSON。这其实就是一个请求体的标签。
req.httpBody设置请求体内容,将要发送的数据转换为UTF-8编码的Data对象。使用?来安全转换。这个请求体是JSON格式的,但由于苹果不支持JSON格式的数据传递,所以jsonStr带有的数据在传进来的时候不能是JSON,但在用到这一句的时候又必须是JSON,现在先不处理这里的问题,把处理参数的任务交给另一页。
*注:这部分的具体内容由后端的API文档提供
接下来要发起网络请求
URLSession.shared.dataTask(with: req) 是 Swift 编程语言中用于发起网络请求的一个方法,用let dataTask来获取发起网络请求后获得的data,response,error.
swift
let dataTask = URLSession.shared.dataTask(with: req) { data, response, error in {}
创建一个网络请求数据的任务
并定义一个闭包(closure)用于处理请求完成后的回调,处理三个参数,分别是数据data,请求response,错误error,在任务设定好之后,使用dataTask.resume()来执行任务。
swift
let dataTask = URLSession.shared.dataTask(with: req) { data, response, error in {
if let data = data {
let str = String(data: data, encoding: .utf8)
}
completion(nil)
}
dataTask.resume()
只在存在数据的时候执行将请求到的data转为字符串的操作(if let data = data),如果data不存在,就返回completion(nil)。
这里的data的类型是Data()是原始数据,就是一堆字节。要使用这些数据必须把它转成字符串。
使用String(data: , encoding: )将数据转为字符串,encoding即为字符编码格式,这里使用.utf8,还有这两种。.ascii: ASCII 编码.utf16: UTF-16 编码
然后我们打印str看看这是什么(当然这里其实还没传jsonStr和url参数,为了方便查看这里是传了之后的结果):
swift
{\"id\":32,\"createtime\":1744677590,\"updatetime\":1758552771,\"deletetime\":null,\"result_str\":\"INFJ\
发现这是一个含有很多转义字符"\"的,类似JSON结构的字符串。
它不是一个标准的JSON,因为苹果不能直接处理JSON结构的字符串,接下来我们要将得到的字符串转换为字典再处理。
使用一个函数stringValueDic传入的参数是字符串,返回的结果是一个存储字典的数组。
思路:先把这个不标准的JSON转成标准的JSON,再把它转成苹果能识别的字典。
swift
func stringValueDic(_ str: String) -> [String : Any]? {
let data = str.data(using: String.Encoding.utf8)
//把传入的字符串参数转为Data类型,使用UTF-8编码,出来的是Data?()
if let dict = try? JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String: Any] {
return dict
}
//将data解析为JSON格式的数据,再将其转换为字典数组形式
return nil
}
将字符串转换为 UTF-8 编码的 Data,它的数据类型是Data()?
options: JSONSerialization.ReadingOptions.mutableContainers:指定 JSON 的解析选项,这里使用 mutableContainers,表示解析出的对象(如字典或数组)是可变的。
swift
if let data = data {
let str = String(data: data, encoding: .utf8)
print(str)
let dic = stringValueDic(str ?? "")
print(dic)
返回网络请求处,现在我们获得了字符串str,打印出来看看,发现显示的是汉字内容。
接着调用函数stringValueDic,将str传入。
打印dic出来看看转换完的结果是这样的:
({...updatetime = 1758442771 } ), "code": 1]就是一个[ String: Any ]的形式,证明转换成功了。
这里有非常多种类的内容,我们需要根据传入的dataKey去匹配相应的内容,这里dataKey默认是"data"。
swift
let dataTask = URLSession.shared.dataTask(with: req) { data, response, error in
if let data = data {
let str = String(data: data, encoding: .utf8)
print(str)
let dic = stringValueDic(str ?? "")
print(dic)
if let dataDic = dic?[dataKey] {
completion(dataDic)
return
}
根据传入的键去字典中获取需要的内容:
dic?[dataKey] 尝试从 dic 字典中以 dataKey为键获取值。如果成功获取到值,则将其赋值给 dataDic ,这里的dataKey是传入的参数。
然后再把dataDic传入闭包(参数)**completion**中,这个completion用来接收请求,并转换为对应的字典,再从中获取的数据,以便后续调用。因要在函数外面调用这个completion,所以它其实是一个逃逸闭包,需要用@escaping修饰。
写好要怎么处理获取网络请求并处理请求到的数据了,为了传参数进去,现在我们要对参数进行处理之后,再请求网络上的数据。
回到这个函数,dataKey有默认值,completion是一个回调都不需要传实参,需要jsonStr和url。
swift
func postRequest(jsonStr: String?, url: String, dataKey: String = "data", completion: @escaping Completion) {
}
新开一页用来管理传入的参数,让这个类继承RequestBase以便直接调用里面的方法。
配置URL,拼接需要请求的URL,并创建单例模式,以便在需要使用的ViewController里调用。
swift
class RequestDetailReport: RequestBase {
static let DetailBaseUrl = baseUrl + "需要用到的URL后缀"
static let shared = RequestDetailReport()
}
用一个函数去获取具体的URL,现在就是有参数版的了。
req.httpBody = jsonStr?.data(using: .utf8)
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
现在我们还剩下参数jsonstr没有处理,这个参数用于设置请求体,因为前面设置了请求体的数据格式是JSON(大部分请求体的格式都是JSON),而苹果不能直接传入JSON格式的数据。所以数据传进来的时候不能是JSON。
创建一个执行具体网络请求的函数,我们选择把jsonStr需要用到的数据以字典数组的形式传进来。
swift
func getDetailRequest() {
let dic = ["sys_lan": "1", "result_str": "INFJ"]
}
然后我们要把dic转成JSON的格式,在RequestBase里编写一个字典转JSON的函数实现。
要用到**JSONSerialization类中的data(withJSONObject:options:)**:方法,它会将给定的对象(通常是字典或数组)转换为 JSON 格式的数据。
withJSONObject:传入一个遵循NSObject的对象(如字典或数组)options:这是第二个参数用于处理 JSON 数据,[]表示不处理。
swift
//传入的是数组字典(参数),返回的是JSON字符串
func dicValueString(_ dic: [String : Any]) -> String? {
let data = try? JSONSerialization.data(withJSONObject: dic, options: [])
}
这时候data还不是正常可识别的数据(包含了很多Unicode),需要再为它指定编码方式如.utf8。
用String(data:, encoding)方法来转换成正常可识别的JSON数据
swift
func dicValueString(_ dic: [String : Any]) -> String? {
let data = try? JSONSerialization.data(withJSONObject: dic, options: [])
let str = String(data: data!, encoding: String.Encoding.utf8)
return str
}
现在有了字典转JSON的方法,返回RequestDetailReport页面继续处理参数jsonStr
swift
let jsonStr = dicValueString(dic as[ String : Any])
虽然知道我们传进去的就是字典数组形式,但是为了避免错误,还是将dic强行转为该形式。
最后就可以调用发起网络请求函数postRequest了。
swift
func getDetailRequest() {
let dic = ["sys_lan": "1", "result_str": "INFJ"]
let jsonStr = dicValueString(dic as[ String : Any])
postRequest(jsonStr: jsonStr, url: RequestDetailReport.DetailBaseUrl) { dic in }
}
{ dic in }这里的dic是随便取的,表示网络请求执行完我们得到的东西。暂时还用不上。
最后在需要进行网络请求的页面使用单例模式去调用这个具体网络请求的函数getDetailRequest
这是一个ViewController的初始化函数:
swift
override func viewDidLoad() {
super.viewDidLoad()
RequestDetailReport.shared.getDetailRequest()
// Do any additional setup after loading the view.
}
完成。
TCP协议和UDP协议
TCP协议
- TCP拥有完整的序号,缺包要补,就比如说聊天记录或者网络请求。平时聊天的时候,有些消息先发后到,就很有可能是丢包补的。
- TCP协议通常用在一些需要数据完整性和可靠性的场景,如FTP(文件传输)、HTTP/HTTTPS(网络浏览)、SMTP/IMAP(电子邮件)。
- TCP实现流量控制盒拥塞控制,这些机制通过调整数据发送速度来确保网络的健康状态。
- 由于较为复杂,TCP通常比UDP更慢。
TCP的三次握手,四次挥手(面试):
三次握手:在客户端和服务器之间建立一个TCP连接
**第一次握手(SYN包):**是客户端发送一个SYN包给服务器,请求建立连接,此时,客户端处于SYN_SEND状态。这个包内包含一个客户端的初始序列号。
**第二次握手(SYN+ACK包):**服务器接收到SYN包后,回复一个SYN+ACK包,表示同意连接,并返回一个确认号。
**第三次握手(ACK包):**客户端在收到SYN+ACK包之后,会发送一个ACK包给服务器,表示确认。连接此时建立成功,双方现在都处于ESTABLISHED(已连接)状态。
*SYN的意思就是同步序列编号包,ACK的意思就是同步确认包。
四次挥手:在TCP连接关闭时,安全地断开服务器和客户端之间的连接。
第一次挥手(FIN): 客户端向服务器发送一个FIN(结束)包,请求关闭连接。此时客户端进入FIN_WAIT_1状态。
第二次挥手(ACK): 服务器收到FIN包之后,发送一个ACK包给客户端,确认接收。此时,服务器进入CLOSE_WAIT状态。
第三次挥手(FIN): 服务器准备关闭连接时候,向客户端发送FIN包,请求关闭连接。服务器进入LAST_ACK状态。
第四次挥手(ACK):客户端收到服务器的FIN包后,发送ACK包给服务器,确认接收。此时客户端进入TIME_WAIT状态,等待确认最终的ACK包被接受。之后,客户端在超时后进入CLOSED状态, 连接完全关闭。
UDP协议
- UDP遇到丢包的时候,丢了就丢了。
- UDP是没有顺序的。
- UDP不提供流量控制盒拥塞控制,发送方可以以任意速率发送数据包。
- UDP的速度较快,通常应用于视频会议、在线游戏、直播等领域。RTC(直播协议)是基于UDP的,UDP经常处理实时的音视频。
总结:
TCP是一个严谨但慢的协议,处理同样的文本,它是不如UDP轻便的,所以TCP常常用于很严谨的场景,比如聊天、网页,APP的后台协议等等。而UDP则只有轻便,所以常常用来做音视频的直播,或者实时语音,在线游戏,视频会议等等。