简介

网络体系结构

网络体系结构把网络功用进行了层次拆分,不同的体系结构有不同的区分办法

iOS - 理解网络

  1. 物理层:物理层处理网络通讯物理层面的事项,比如信号的传输和设备之间的物理链接。这一层包括了电缆,衔接器和其他的硬件设备规范。
  2. 数据链路层:数据链路层供给在相同网络上不同设备之间无过错的传输,担任把数据拆成帧在物理层上传输。
  3. 网络层:网络层担任在不同网络上路由数据,它决议了网络传输中的最佳路径和拥塞操控。
  4. 传输层:供给了牢靠的设备间端到端的通讯,保证了数据无误的传输和正确的序列
  5. 会话层:会话层办理不同设备间通讯会话,担任树立,维持和停止设备间的会话
  6. 表明层:表明层供给了设备间数据交流的规范格式,担任数据紧缩,加密宽和密
  7. 应用层:供给服务给终端用户,例如email,网络阅读和文件传输。也是在这一层,各个应用跟网络交互

网络恳求进程

原生移动端干流的2种网络恳求协议,分别是HTTP恳求和WebSocket恳求

iOS - 理解网络

HTTP

WebSocket

iOS - 理解网络

网络基础知识

URL

URL是(Uniform Resource Locator)的缩写,统一资源定位器。通常Domain Name也被称作Host

iOS - 理解网络

RESTful API

RESTful中通常运用JSON作为URL指向的资源,运用HTTP Method表明处理动作增删改查

  • Resource
  • Action

iOS - 理解网络

网络协议

iOS - 理解网络

传输层

TCP

(Transmission Control Protocol)是一个供给了牢靠的,有序的,过错查看的在不同主机间的数据传输的面向链接的协议,需求运用三次握手和四次挥手来树立和断开链接,链接树立好之后才开端传输数据。TCP设计用来处理拥塞和网络过错,并且能够经过自动从头传输丢掉的数据包来保证数据传输的牢靠性。

三次握手

iOS - 理解网络

四次挥手

iOS - 理解网络

四要素

四要素对应一个链接

  • 源IP
  • 源端口
  • 目的IP
  • 目的端口

UDP

(User Datagram Protocol)是一个无链接的协议供给不牢靠和无序的数据传输。在传输数据之前不需求树立链接,并且不供给任何数据重传和数据纠错功用。

应用层

iOS - 理解网络

HTTP

简介

超文本传输协议(Hyper-Text Transfer Protocol),逾越一般文本,因为还包含了文字,图片,视频等,HTML就是常见的超文本

结构

HTTP的Request和Response结构首要由Status-Line, Header和Body或许说是Payload组成

Request

iOS - 理解网络

Response

iOS - 理解网络

Status Code

HTTP协议中运用status Code来标识response的状况

iOS - 理解网络

1.0

  • 无状况(借助Cookie/Session机制做身份认证和状况记录)
  • 链接依靠(每个恳求呼应运用一个独立的链接)
  • 无长链接(每一次恳求都需求建议新的链接)
  • 有限的header信息
  • 不支持缓存
  • 不支持管道

1.1

  • 长链接(坚持衔接,增加Connection: Keep-Alive字段,默许敞开)
  • 支持缓存
  • 分块传输编码(增加Transfer-Encoding: chunked字段)
  • 主机头(不同的网站能够保管在同一个服务器上,增加Host: :字段)
  • 管道技能

iOS - 理解网络

队头堵塞

队头堵塞是指单个(慢)目标阻止其他/后续的目标前进

因为HTTP1.1是纯文本协议,只经过Header中的Content-Length来判断资源巨细,不会进一步区分单个大块资源与其他资源。所以在一个链接上会一个一个完好的传输资源,假如前面的资源创立缓慢或许过大,会导致队头堵塞问题。这是HTTP协议导致的队头堵塞

因为前端H5场景下,需求加载的api, css, js, img等资源更多,所以做了如下优化。在移动端的场景下,首要仍是api接口,数量也较少。

  • 翻开多个并行TCP链接
  • 在多个域名上分片(sharding),如img.mysite.com, static.mysite.com等
  • CDN

2.0

  • 二进制分帧(削减音讯的尺度提高功用)
  • 多路复用(答应在单个链接上一同发送多个恳求呼应)
  • 服务端恳求(答应服务端推送资源给客户端,而不用客户端自动恳求)
  • 头部紧缩(削减Header的尺度提高功用)
  • 恳求优先级(答应客户端分解恳求的优先级)

iOS - 理解网络

HTTP1.1中导致队头堵塞是因为在资源块(resource chunks)之间没有分隔符和标识符,只能翻开多个并行链接来处理。而在HTTP2.0中在单个TCP链接上处理了队头堵塞的问题,在资源块前面加上了帧(frames)。

iOS - 理解网络

Data frame包含了2个要害的元数据

  • stream id
  • length

简单描绘的话,或许是这样拆分不同资源,混合传输,经过steam id进行组合,乃至能够加上优先级策略,决议传输的分配和次序。

队头堵塞

不过HTTP/2仍是无法处理TCP协议导致的队头堵塞。TCP基于自身的握手和数据确认机制,供给了牢靠的链接。假设在传输1,2,3数据包,接纳到1的数据包时,TCP交给数据。但是假如接纳到3数据包,2数据包丢掉时,3数据包将不会被交给,而是保存在接纳缓冲区中(receive buffer)等候2数据的重传,之后按照次序再交给这两个数据包。此刻数据包2队头堵塞了数据包3。TCP相当于时次序交给。

因为HTTP/2中单TCP链接承载了多种不同资源,所以TCP队头堵塞的情况会造成传输功用的降低。

3.0(QUIC)

  • 从TCP修正成了UDP
  • 削减了RTT
  • 改进的拥塞操控
  • 避免队头堵塞
  • 衔接迁移

队头堵塞真是实实在在推进了HTTP协议的开展,感谢队头堵塞。当然削减RTT也是推进网络协议开展的重要因素。

iOS - 理解网络

继续处理HTTP/2中TCP队头堵塞的问题,因为TCP协议的广泛运用,导致晋级TCP协议可行性低,QUIC中运用了UDP协议来处理这个问题。与HTTP/2中的数据帧类似,QUIC中增加了流帧(stream frames)分别跟踪每个流的字节范围。QUIC在出现数据包丢掉的时分,会判断流中的预期数据是否接纳到,现已接纳到预期数据就交给,反之等候丢掉的数据包重传。

QUIC数据或许不再以与发送时完全相同的次序交给,相当于在单个资源流中保留了次序,但不再夸单个流(individual streams)进行排序。

iOS - 理解网络

Stream frame

  • Stream id
  • byte <start-end>

ALPN

应用层协议洽谈(Application-Layer Protocol Negotiation),是一个传输层安全协议(TLS)的扩展,ALPN使得应用层能够洽谈在安全衔接层之上运用什么协议,比如是运用HTTP1.1仍是运用HTTP2.0,避免了额定的往返通讯。

  • NPN

NPN 是服务端发送所支持的 HTTP 协议列表,由客户端挑选;NPN 的洽谈成果是在 Change Cipher Spec 之后加密发送给服务端

  • ALPN

ALPN 是客户端发送所支持的 HTTP 协议列表,由服务端挑选;ALPN 的洽谈成果是经过 Server Hello 明文发给客户端

HTTPS

HTTPS是在HTTP协议基础加上TLS加密的完结。

传输层安全协议(Transport Layer Sercurity)以及前身安全套接层(Secure Sockets Layer,SSL),是一种安全协议。

TLS协议采用主从式架构模型,用于在两个应用程序间透过网络创立起安全的连线,防止在交流材料时受到窃听及修正。

TLS协议的优势是与高层的应用层协议(如HTTP、FTP、Telnet等)无耦合。应用层协议能透明地运行在TLS协议之上,由TLS协议进行创立加密通道需求的洽谈和认证。应用层协议传送的数据在经过TLS协议时都会被加密,然后保证通讯的私密性。

TLS 包含三个首要组件

  • 加密:隐藏从第三方传输的数据
  • 身份验证:保证交流信息的各方是他们声称的身份
  • 完好性:验证数据是否并非伪造而来或未遭篡改过

握手进程

iOS - 理解网络

TLS1.2

2RTT

iOS - 理解网络

TLS1.3

1RTT

iOS - 理解网络

OCSP Stapling

OCSP(Online Certificate Status Protocol,在线证书状况协议)是由数字证书颁发组织CA(Certificate Authority)供给,客户端经过OCSP可实时验证证书的合法性和有效性。

启用OCSP Stapling功用后,OCSP信息查询的工作将由CDN服务器完结。CDN经过低频次查询,将查询成果缓存到服务器中(默许缓存时刻60分钟)。当客户端向服务器建议TLS握手恳求时,CDN服务器将证书的OCSP信息和证书一同发送到客户端,供用户验证,无需用户再向数字证书认证组织(CA)发送查询恳求。极大地提高了TLS握手效率,节省了用户验证时刻。

iOS - 理解网络

iOS

URL Loading System

NSURLSession

iOS - 理解网络

URL Loading System运用规范协议(例如https或你创立的自定义协议)供给对URL标识的资源的拜访。加载是异步履行的,因此应用能够坚持呼应才能,并在数据或过错到达时处理它们。

你运用URLSession实例创立一个或多个URLSessionTask实例,这些URLSessionTask实例能够获取数据、下载文件或将数据和文件上传到服务器。要配置会话,请运用URLSessionConfiguration目标,该目标操控行为,例如怎么运用缓存和cookie,或许是否答应在蜂窝网络上进行衔接。

你能够重复运用一个会话来创立使命。例如,网络阅读器或许有分开的会话供惯例阅读和私家阅读运用,而私家会话不会缓存其数据。

履行流程

iOS - 理解网络

代码示例

var receivedData: Data?
func startLoad() {
    loadButton.isEnabled = false
    let url = URL(string: "https://www.example.com/")!
    receivedData = Data()
    let task = session.dataTask(with: url)
    task.resume()
}
// delegate methods
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse,
                completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
    guard let response = response as? HTTPURLResponse,
        (200...299).contains(response.statusCode),
        let mimeType = response.mimeType,
        mimeType == "text/html" else {
        completionHandler(.cancel)
        return
    }
    completionHandler(.allow)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
    self.receivedData?.append(data)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    DispatchQueue.main.async {
        self.loadButton.isEnabled = true
        if let error = error {
            handleClientError(error)
        } else if let receivedData = self.receivedData,
            let string = String(data: receivedData, encoding: .utf8) {
            self.webView.loadHTMLString(string, baseURL: task.currentRequest?.url)
        }
    }
}

CFNetwok

iOS - 理解网络

CFNetwork是Core Services结构中的一个结构,它为网络协议供给了一个笼统库。 这些笼统使履行各种网络使命变得简单,例如:

  • 运用BSD套接字
  • 运用SSL或TLS创立加密的衔接
  • 解析DNS
  • 运用HTTP,验证HTTP和HTTPS服务器
  • 运用FTP服务器
  • 发布,处理和阅读Bonjour服务

CFNetwork结构分两部分

基础API:

  • CFSorcket API:CFSocket是BSD sockets的笼统。在很少开销的一同,CFSocket几乎完结了BSD sockets的所有功用,并且集成到RunLoop中。
  • CFStream API:CFStream供给了数据读写办法,即读写流。运用它能够为内存、文件、网络(运用socket)的数据树立流。

CFNetwork API:

  • CFFTP API:CFFTP是FTP协议的笼统,运用CFFTP使得与FTP服务器通讯变得非常简单。经过运用CFFTP API,你能够创立FTP读取流(用于下载)和FTP写入流(用于上传)。
  • CFHTTP API:CFHTTP是HTTP协议的笼统,运用CFHTTP发送和接纳HTTP音讯。
  • CFHTTPAuthentication API:HTTP鉴权。

代码示例

//创立恳求
CFStringRef url = CFSTR("https://http2.pro/api/v1");
CFURLRef myURL = CFURLCreateWithString(kCFAllocatorDefault, url, NULL);
CFStringRef requestMethod = CFSTR("GET");
CFHTTPMessageRef myRequest =
CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, myURL,
kCFHTTPVersion2_0);
// 设置body
//NSData *dataToPost = [@"apptoken=-1" dataUsingEncoding:NSUTF8StringEncoding];
//CFHTTPMessageSetBody(myRequest, (__bridge CFDataRef) dataToPost);
// 设置header
//CFHTTPMessageSetHeaderFieldValue(myRequest, CFSTR("Content-Type"), CFSTR("application/x-www-form-urlencoded; charset=utf-8"));
//创立流并敞开
CFReadStreamRef requestStream = CFReadStreamCreateForHTTPRequest(NULL, myRequest);
CFReadStreamOpen(requestStream);
//接纳呼应
NSMutableData *responseBytes = [NSMutableDatadata];
CFIndex numBytesRead = 0;
do {
UInt8 buf[1024];
numBytesRead = CFReadStreamRead(requestStream, buf, sizeof(buf));
if (numBytesRead > 0) {
[responseBytes appendBytes:buf length:numBytesRead];
}
} while (numBytesRead > 0);
CFHTTPMessageRef response = (CFHTTPMessageRef) CFReadStreamCopyProperty(requestStream, kCFStreamPropertyHTTPResponseHeader);
CFHTTPMessageSetBody(response, (__bridgeCFDataRef)responseBytes);
CFReadStreamClose(requestStream);
CFRelease(requestStream);
CFAutorelease(response);
//转换为JSON
CFIndex statusCode;
statusCode = CFHTTPMessageGetResponseStatusCode(response);
CFDataRef responseDataRef = CFHTTPMessageCopyBody(response);
NSData *responseData = (__bridgeNSData *)responseDataRef;
NSMutableDictionary *jsonInfo = [NSJSONSerializationJSONObjectWithData:responseData options:NSJSONReadingAllowFragmentserror:nil];
NSLog(@"responseBody: %@", jsonInfo);

libcurl

libcurl供给了恳求网络的简单接口,用C言语完结并被广泛运用,能够跨端运用一同支持iOS和Android渠道。供给了下载文件,上传数据和经过HTTP,FTP,SMTP等协议链接远端服务器等功用

curl在macOS体系上是默许集成的,你能够经过命令来运用

curl www.baidu.com

在iOS上,libcurl有对应的framework,能够集成framework进行网络方面的开发

#import <curl/curl.h>
//初始化curl目标
CURL *curl = curl_easy_init();
//设置url
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
//设置回调
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
//履行
CURLcode res = curl_easy_perform(curl);
//处理
if (res != CURLE_OK) {
    NSLog(@"Error: %s", curl_easy_strerror(res));
} else {
    NSLog(@"Response: %s", response_data);
}
//清空
curl_easy_cleanup(curl);

数据处理

size_t write_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
    size_t realsize = size * nmemb;
    char *response = (char *)malloc(realsize + 1);
    if (response == NULL) {
        return 0;
    }
    memcpy(response, ptr, realsize);
    response[realsize] = '\0';
    char *old_data = (char *)userdata;
    char *new_data = realloc(old_data, strlen(old_data) + realsize + 1);
    if (new_data == NULL) {
        free(response);
        return 0;
    }
    strcat(new_data, response);
    free(response);
    userdata = new_data;
    return realsize;
}

Cronet

Cronet是谷歌开发的网络库,供给了一个规范的iOS网络库。Cronet跟libcurl相同,是能够做成多端统一的完结

#import <Cronet/Cronet.h>
//创立NSURLRequest
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com"]];
//创立engine
CronetEngine *engine = [Cronet getGlobalEngine];
//创立builder
CronetURLRequestBuilder *builder = [[CronetURLRequestBuilder alloc] initWithRequest:request];
//设置恳求头
[builder addHeader:@"User-Agent" value:@"My App"];
//创立Cronet URL request
CronetURLRequest *cronetRequest = [builder build];
//建议恳求并等候回应
NSError *error;
CronetURLResponse *response = [cronetRequest startWithError:&error];
//处理response
if (error) {
  NSLog(@"Error: %@", error);
} else {
  NSData *responseData = [response getBodyAsNSData];
  NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
  NSLog(@"Response: %@", responseString);
}
//清理
[cronetRequest cancel];

需求注意的是Cronet建议恳求是异步进程,需求运用闭包和回调,并且通常在URL Loading System中注册和建议恳求

WebKit

iOS中WKWebView作为WebKit的容器工具供给了用来加载和烘托web内容的高层级的API

//创立webview
let webView = WKWebView(frame: view.bounds)
view.addSubview(webView)
//创立request
let request = URLRequest(url: URL(string: "https://example.com")!)
//加载request
webView.load(request)

运用WKNavigationDelegate对应回调办法处理response

//处理response
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    webView.evaluateJavaScript("document.documentElement.outerHTML.toString()") { (html: Any?, error: Error?) in
        if let htmlString = html as? String {
            print("Response: (htmlString)")
        }
    }
}

Websocket

WebSocket 是一种在单个 TCP 衔接上进行全双工通讯的协议,iOS中能够依靠与Starscream库,这个库运用Swift对WebSocket端做了完结。

import Starscream
//创立socket目标
let socket = WebSocket(url: URL(string: "wss://example.com")!)
//设置署理
socket.delegate = self
//链接到webSocket
socket.connect()

完结WebSocketDelegate协议来处理WebSocket事件

func websocketDidConnect(socket: WebSocketClient) {
    print("WebSocket connected")
    socket.write(string: "Hello, server!")
}
func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
    print("Received message: (text)")
}
func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
    if let error = error {
        print("WebSocket disconnected with error: (error)")
    } else {
        print("WebSocket disconnected")
    }
}

发音讯

//发音讯
socket.write(string: "Hello, server!")

断开链接

//断开链接
socket.disconnect()

Socket

iOS - 理解网络

操作体系通常会为应用程序供给一组应用程序接口(API),称为套接字接口(socket API)。应用程序能够经过套接字接口,来运用网络套接字交流数据。最早的套接字接口来自于4.2 BSD,因此现代常见的套接字接口大多源自Berkeley套接字(Berkeley sockets)规范。在套接字接口中,以IP地址及端口组成套接字地址(socket address)。远程的套接字地址,以及本地的套接字地址完结连线后,再加上运用的协议(protocol),这个五元组(five-element tuple),作为套接字对(socket pairs),之后就能够彼此交流材料。

例如,在同一台核算机上,TCP协议与UDP协议能够一同运用相同的port而互不搅扰。 操作体系根据套接字地址,能够决议应该将材料送达特定的行程或线程。这就像是电话体系中,以电话号码加上分机号码,来决议通话目标一般。

socket是一种操作体系供给的进程间通讯机制,实际做的工作就是对TCP/UDP等操作笼统成了几个工作。

套接字地址结构

struct sockaddr_in {
    uint16_t sin_family;
    uint16_t sin_port;
    struct in_addr sin_addr;
    unsigned char sin_zero[8];
};
struct sockaddr {
    uint16_t sa_family;
    char sa_data[14];
}
  1. socket

客户端和服务器运用socket函数来创立一个套接字描绘符(socket descriptor)。

#import <sys/types.h>
#import <sys/socket.h>
int socket(int domain, int type, int protocol);
  1. connect

客户端经过调用connect函数来树立和服务器的链接。

#import <sys/socket.h>
int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen);
  1. bind

剩下的套接字函数,bind、listen和accept,服务器用他们来和客户端树立衔接。

#include <sys/socket>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  1. listen

客户端是建议衔接恳求的自动实体。服务器调用listen函数告诉内核,描绘符是被服务器而不是客户端运用的。

#include <sys/socket>
int listen(int sockfd, int backlog);
  1. accept

服务器经过调用accept函数来等候来自客户端的衔接恳求。

accept函数等候来自客户端的衔接恳求到达侦听描绘符listenfd,然后再addr中填写客户端你的套接字地址,并回来一个已链接描绘符(connect descriptor),这个描绘符可被用来利用Unix I/O函数与客户端通讯。

#include <sys/socket>
int accept(int listenfd, struct sockaddr *addr, int *addrlen);
  1. getaddrinfo

getaddrinfo函数将主机名、主机地址、服务名和端口号的字符串转化成套接字地址结构

  1. getnameinfo

getnameinfo函数和getaddrinfo函数时相反,将一个套接字地址结构转换成相应的主机和服务名字符串

优化

HTTP

数据源

  • 操控源数据的巨细
  • 关于API恳求,合作后端同学优化,瓶颈或许会在数据库和数据处理上
  • 关于资源恳求,运用OSS和CDN加速
  • 关于埋点等数据上报恳求,优化采样率操控,上报策略和紧缩策略

链路优化

异地多活

异地多活的计划能够增加可用性,提高功用,提高扩展性,更好的灵活性,更好的灾备和恢复才能。当前的干流云厂商都给出了自己的计划,能够参阅和布置。

DNS

IP直连

自研

HTTPDNS

IP直连运用了下发和择优机制,避开了传统DNS的进程,也避免了传统DNS统一被篡改宽和析功用欠安的问题。在树立链接的时分直接运用了ip,但是在链接的进程中证书等仍是需求对host进行验证,然后会带来一些问题

  • Cookie
  • 302重定向
  • Webview业务场景
  • SNI

TCP

树立链接是相对耗时的阶段,所以链接复用率是重要的功用指标,尽或许的削减链接的树立和耗时。

  • 晋级HTTP1.1到HTTP/2
  • 晋级HTTP/2到QUIC
  • 域名收敛

TLS

  • 晋级TLS1.2到运用TLS1.3
  • 敞开OCSP Stapling

Request

  • Task目标的缓存和调度
  • 操控Request Size

Server

  • 合作后端同学优化

Response

  • 操控Response Size

引用

URL Loading System

CFNetwork Programming Guide

TCP Connection Management

Optimizing Your App for Today’s Internet

Advances in Networking, Part 1

Advances in Networking, Part 2

Starscream In Github