什么是 MTU ?


我们通常意义上的说的以太网 MTU 是指不包含以太网 Header 和 FCS (帧检验序列:Frame Check Sequence)的以太网 Payload (有效载荷)部分,IEEE802 规定了大小为0~1500字节。这个长度不包含链路层,MTU是链路层对上层网络层的设计。所以,二层以太网帧长应该为这个长度加上18 bytes,该18 bytes 由 6 bytes的目的MAC地址-DA、6bytes的源MAC地址-SA和 2 bytes的上层协议类型以及 4 bytes 的FCS组成。FCS 由硬件计算,被添加到帧的最后,帧校验可以判断数据包是否由于噪声而被破坏出现了乱码位。根据上述,一个以太网数据帧,最大可达到1500 + 18 = 1518 字节。

这里还有一个 MSS 的概念:TCP 有一个最大分段大小,用来在网络传输中通知传输双方每个分段中能发送的最大 TCP 数据量。我们可以从上图中清晰的看到 MSS 表示的数据。通常网络情况下,TCP MSS = MTU - IP Header 长度 - TCP Header 长度

MTU 的大小也跟我们上网的方式有关系,不同的上网方式支持不同的 MTU。

比如一般的EtherNet(以太网,默认值):1500

PPPoE(PPP Over Ethernet在以太网上承载PPP协议)/ADSL:1492

Dial Up/Modem:576

定义 MTU 的意义

MTU约束了 MAC 帧中数据部分(payload)的大小也就是说限定了 IP 封包的大小,这个大小包括 IP 封包的包头;而最终 IP 包是要放进 MAC 帧中的。

所以我们可以认为MTU = IP封包的大小

IP报文的分片和重组

路径 MTU 发现

在因特网协议中,一条因特网传输路径的“路径最大传输单元”被定义为从源地址到目标地址所经过路径上到所有 IP 跳到最大传输单元到最小值。举个例子,一个很长的水管,由不同粗细的水管组成,那么单位时间内通过该长水管的最大水量必定由最细的水管来决定。

我们结合上图说下,路径 MTU 发现的工作原理:
1.在发送端主机发送 IP 数据报时将首部的分片禁止标志位设置为1。(Don’t fragment = 1,我们可以从 Wireshark中很容易看到该设置)。由于设置为了1,表明途中遇到的路由器不能私自将巨型包分片,而是直接将包丢弃。

2.丢弃之后,通过 ICMP 消息将数据链路上的 MTU 值发给发送主机。

3.发送端主机将从 ICMP 消息中获得的 MTU 值设定为当前 MTU,然后根据当前 MTU 对数据报进行重新发送。

Internet网络层的设计有10大原则,其中一个原则就是『避免静态选项和参数』:如果不可避免要使用参数的话,比如最大数据包的长度,最好的办法就是让发送方和接收方协商一个数值,而不是直接使用定义好的固定的值。路径 MTU 发现要求发送端和接收端中间的网络路径是那个的设备都要支持 MTU 协商,如果有其中一台不支持,就无法协商到最小的 MTU。有时候也因为安全等原因,ICMP 消息被禁用导致路径 MTU 发现也不能生效。

注意

出于安全的考虑,比如防范 DDOS 攻击,有些网络域禁止了 ICMP 的传输,导致路径 MTU 发现功能无法正常运行,会造成用户的使用网络出现故障。常见的一个表现就是,在低数据流量低情况下可以正常工作,但在有大量数据发送的时候,网络就会没有响应。

MTU值设置不当会引起什么问题

MTU值引起的常见现象:

  • 部分网站无法访问
  • 上网速度很慢
  • 部分网络应用无法使用
  • 图片不能上传等等

如何确定 MTU 值?

在 OSX 系统中,我们可以通过 ping 命令来发送特定大小的 ICMP 消息包。如上图所示,我刚开始使用来 packet size 为 1465 bytes 大小来 ping,数据包直接是被 100% 丢失的,而使用来 1464 bytes 来 ping,便发送接受正常。我家里使用的 PPPoE 网络,所以根据我们上面计算方式,1464 + ICMP Header(8 bytes)+ IP Header(20 bytes)= 1492,这便是 IP 封包的大小,也就是 MTU 的大小。跟 1500 bytes 相差的便是 PPPoE 网络带来的 8 bytes,由PPPoE Header(6 bytes)+ PPPID(2 bytes)得来。

一起学习 DNS

什么是 DNS ?

网域名称系统(英文:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS使用TCP和UDP端口53。当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。

一方面,DNS能够帮助我们省去记忆和输入 IP 地址的麻烦。另一方面,对于在不同ip的主机开发部署有着很重要的意义:如果我们在访问一个ip地址为115.159.231.171的一个Web主页,假设如果公司将这个Web主页移动到了另外一个不同的ip主机上,那是不是要通知到每一个使用原ip地址访问服务的用户?这显然行不通。所以域名到网络ip地址的映射具有非常重要的意义。

从域名到IP地址的一个翻译过程,我们俗称“DNS解析”。在DNS解析之后,便拿到了ip地址,有了ip地址,应用程序便可以与目标主机建立一个TCP连接或者发送UDP数据包。

DNS 解析

我们以查找www.zhuanzhuan.com为例子:

0.查询Local DNS服务器

我们不得不提到这个Local DNS 服务器。Local DNS包括网络服务提供商(ISP)分配给我们的DNS(一般为两个),或者是其他组织提供的公共DNS,比如谷歌的8.8.8.8,国内114DNS的114.114.114.114。因为这些DNS的地址填写在我们的本地电脑或者移动设备上,所以也称为Local DNS。有时候我们看到电脑或者移动设备上,DNS的地址是路由器的地址,而路由器本身会将DNS请求转发到ISP的DNS服务器上。这个小细节需要注意下。

1.查询DNS根服务器

DNS根服务器是一个超级大管家,掌管着所有顶级域名服务器,比如商用的.com,非赢利组织使用的.org,教育机构使用的.edu,政府部门使用的.gov等等,世界上的顶级域名大概有250多个。我们例子的顶级域名是.com,于是DNS根服务器会将查询请求转发给.com顶级域名服务器。

2.查询.com顶级域名服务器

.com顶级域名服务器掌管着.com域,我们的目标是zhuanzhuan.com,于是下一步将请求转发给zhuanzhuan.com域名服务器

3.查询zhuanzhuan.com域名服务器

zhuanzhuan.com域名服务器掌管着zhuanzhuan.com域下所有的子域和主机。它当然知道www.zhuanzhuan.com的IP地址。

4.访问www.zhuanzhuan.com对应的web服务器

根据上一步返回的IP地址进行访问。这里例子中,我们描绘的场景是一个最坏的情况,发生在DNS服务器没有缓存任何有用信息时。而在实际情况中,由于DNS服务器的缓存机制,在进行DNS解析道时候,实际的查询路径可能会更短,甚至没有,直接从本地DNS服务器缓存命中返回。

DNS的查询和响应使用的传输层协议是UDP协议。DNS消息通过UDP数据包发送,如果在一个限定时间内没有收到响应的UDP数据包,那么DNS客户端则会重复查询请求,如果重复一定次数仍然失败,则会尝试域内另一台DNS服务器。

DNS 查询:递归/迭代

递归查询:服务器必需回答域名与目标IP的映射关系,对这个关系的描述必须是完整的。
迭代查询:服务器收到一次迭代查询回复一次结果,这个结果不一定是目标IP与域名的映射关系,也可以是其它DNS服务器的地址。

如文中图所示从客户端到本地DNS服务器是属于递归查询,而DNS服务器之间就是的交互查询就是迭代查询。一次域名解析可以涉及到递归和迭代两种机制。

DNS劫持

DNS劫持都谁干的好事?运营商。中国的电信运营商包括了三大运营商中国电信、中国移动、中国联通,还有一些小的运营商,包括长城宽带、歌华有线宽带等。我们可以谷歌搜索下,关于DNS劫持的问题,很多人都曾经遇到过。DNS劫持是网络运营商作恶的方式之一。电信运营商将原本网Web的IP地址故意解析为包含广告的IP地址,这样用户在访问网站时就会看到电信运营商那些无耻的广告。

之前转转的用户就遇到过这样的问题。我们发现用户所在的地方,优品服务的一个域名被电信运营商给劫持了,跳转到了联通移动网络域名纠错导航页面。当用户使用WIFI时,我们可以修改网络的高级设置中DNS,尝试修改为谷歌的8.8.8.8,114DNS的114.114.114.114, 或者阿里DNS 223.5.5.5, 223.6.6.6等来解决。

HTTPS解决的是HTTP劫持,并不能解决DNS劫持问题。更多阅读可以阅读文末的推荐文章运营商的勾当:DNS劫持和HTTP劫持有什么区别

iOS清除DNS CACHE

iOS设备对域名访问时并不是每次访问都需要向DNS服务器发出请求,一般来说当解析工作完成一次后,该解析结果会保存在设备的DNS缓存列表中,如果这时DNS解析出现更改变动的话,由于DNS缓存列表信息没有改变,所以仍旧会按照之前缓存的结果来解析,便会出现DNS解析故障。这时候我们要清除本地的DNS缓存,一般有几个办法:

  1. 开启飞行模式 -> 修改DNS -> 关闭飞行模式
  2. 开关机
  3. 重置网络设置

修改HOST是什么鬼?

不知道读者有没有修改过电脑host文件的经历,我们修改有时候只是为了屏蔽不想访问的网站让破解软件注册时不向自己的服务器发送信息,比如发送到127.0.0.1本机地址上,有时候是为了测试,让线上的域名映射到开发环境的ip,有时候为了加速网站的解析速速。修改HOST给了我们很多方便的可能。

iOS非越狱设备我们是无法修改HOST文件的,越狱之后,就另当别论了。

域名解析到常用命令

域名解析中常用的命令有三个:dig、nslookup、host,关于这三个命令的一些使用,读者可以移步到阮一峰的DNS原理入门

更多参考



前言

早上8点左右的时候看到了微信群里有一条分享,点开进去后是一个视频,是网易新闻的一个关于圣诞节的一个推广视频,刚好就坐在电脑旁边,便用 Charles 抓包看了下。

因为以前对视频加载这块接触的不多,便想了解下获取视频的请求和普通的请求有什么不通,如何播放就不谈了,主要从HTTP协议的角度研究学习下。



刚打开网页的时候只有前3个请求,此时视频是暂停的,通过视频上方中的播放按钮开始了播放,这时候开始发出了第4个请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
HTTP-Trace-Version: 1.0  // Charles的http trace版本
Generator: Charles/4.0.1 // Charles版本

// 请求
Method: GET
Protocol-Version: HTTP/1.1
Protocol: http
Host: file.ws.126.net
File: /3g/activities/xmas2016.mp4
Remote-Address: 58.59.19.125
Client-Address: 192.168.1.104
Start-Time: 2016-12-24T08:20:44.528+08:00
DNS-Duration: 9
Connect-Duration: 22
Request-Begin-Time: 2016-12-24T08:20:44.560+08:00
Request-Time: 2016-12-24T08:20:44.561+08:00
Response-Time: 2016-12-24T08:20:44.586+08:00
End-Time: 2016-12-24T08:20:44.588+08:00
Request-Header-Size: 504
Response-Header-Size: 424
Request-Body-Size: 0
Response-Body-Size: 2
Request-Body-Decoded: false
Response-Body-Decoded: false
Request-Header:<<--EOF-1482539973400-
GET /3g/activities/xmas2016.mp4 HTTP/1.1
Host: file.ws.126.net
Accept-Language: zh-cn
X-Playback-Session-Id: 843D959A-32AD-49C4-A2D7-32065A6292D9
Range: bytes=0-1
Accept: */*
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 10_2 like Mac OS X) AppleWebKit/602.3.12 (KHTML, like Gecko) Mobile/14C92 MicroMessenger/6.5.2 NetType/WIFI Language/zh_CN
Referer: http://c.m.163.com/nc/qa/activity/20161202/index.html?from=groupmessage&isappinstalled=1
Accept-Encoding: identity
Connection: keep-alive


--EOF-1482539973400-
// 响应
Response-Header:<<--EOF-1482539973400-
HTTP/1.1 206 Partial Content
Date: Sat, 24 Dec 2016 00:20:44 GMT
Expires: Sun, 25 Dec 2016 00:20:44 GMT
Content-Length: 2
Accept-Ranges: bytes
Content-Range: bytes 0-1/4300047
Content-Type: text/plain; charset=GBK
Last-Modified: Thu, 22 Dec 2016 02:04:16 GMT
Cache-Control: max-age=86400
Server: nginx
Fw-Cache-Status: hit
Fw-Via: DISK HIT from 61.130.28.99, DISK HIT from 58.59.19.104
Proxy-Connection: Keep-alive

这是第一个请求的一些详细信息,其余3个请求我就不贴了,比较占篇幅,值得注意的是,这4个请求中请求头的range每一个都不同,依次如下:

  • bytes=0-1
  • bytes=0-4300046
  • bytes=4276224-4300046
  • bytes=8640-4276223

Range的诞生

Range,是在 HTTP/1.1里新增的一个请求头字段域。我们使用的迅雷等支持多线程下载以及断点下载的核心也是基于此重要特性。

HTTP/1.1规范的 Range 的约定

如果Server支持 Range,首先就要告诉客户端,服务器会在响应头中添加Accept-Ranges: bytes来表示支持 Range 的请求,之后客户端才可能发起带 Range 的请求。不支持的话,用Accept-Ranges: none告知客户端,对不起,我不支持。

Server通过请求头中的Range: bytes=0-xxx来判断是否是做 Range 请求,如果这个值存在而且有效,则只发回请求的那部分文件内容,响应的状态码变成206,表示Partial Content,并设置Content-Range。如果无效,则返回416状态码,表明Request Range Not Satisfiable。如果请求头中不带 Range,那么 Server则正常响应,也不会设置 Content-Range 等。

Value Description
206 Partial Content
416 Range Not Satisfiable

Range的格式为:

Range:(unit=first byte pos)-[last byte pos],即Range: 单位(如bytes)= 开始字节位置-结束字节位置

我们再来看个例子。假设我们要开启多线程下载,需要把一个大文件分割成多个部分进行下载,比如4个部分,然后创建4个线程,每个线程负责下载一个部分,如果文件大小为 5000 个 byte(随意的数字),那么我们可以划分为

  • Range: bytes=0-1199 头1200个字节
  • Range: bytes=1200-2399 第二个1200字节
  • Range: bytes=2400-3599 第三个1200字节
  • Range: bytes=3600-5000 最后的1400字节

服务器给出响应:

// 第1个响应

  • Content-Length:1200
  • Content-Range:bytes 0-1199/5000

// 第2个响应

  • Content-Length:1200
  • Content-Range:bytes 1200-2399/5000

// 第3个响应

  • Content-Length:1200
  • Content-Range:bytes 2400-3599/5000

// 第4个响应

  • Content-Length:1400
  • Content-Range:bytes 3600-5000/5000

如果每个请求都成功了,服务器返回的response头中有一个 Content-Range 的字段域,Content-Range 用于响应头,告诉了客户端发送了多少数据,它描述了响应覆盖的范围和整个实体长度。一般格式:

Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity length]Content-Range:字节 开始字节位置-结束字节位置/文件大小

小结

HTTP协议博大精深,设计有很多巧妙的地方,Range也许就是一处吧。

更多阅读

SPDY 即“Speedy”发音,有速度之意,从名字便可知谷歌当年提出的这个方案的目标就是为了解决 HTTP 的性能瓶颈,缩短 web 页面的 load 时间。SPDY 是一个二进制协议,并不像 HTTP 那样直观易读。

SPDY 的设计

SPDY 解决的问题

  • 多路复用:这名字听起来很高大上。其实理解起来,就是在一个 TCP 连接中,分出了多个 SPDY Stream 流通道,每个通道是并行的,不用相互等待,且各自负责一个请求和响应。这无疑会降低延迟和提升带宽的使用率。之前 HTTP/1.1 就面临着 head-of-line blocking 队首阻塞问题,也就是一个 response 响应如果迟迟没有回来,会阻塞住后续的 response 响应。后面我们用 WWDC 的一些截图来说明问题。
  • 请求优先级:SPDY 允许为通道中的请求设置优先级,使得高优先级的请求能够优先得到回复响应。
  • 请求头部压缩:我们知道,HTTP 协议是无状态的,无法对之前的请求和响应状态进行管理,而 Cookie 的引入正是为了解决这个问题。服务端通过客户端发送的 Cookie 来知晓是哪个客户端发送的 HTTP 请求,从而进行后续的操作。在 HTTP 请求的头部,还有一个 User Agent,这个字段携带着客户端的一些信息,如 iOS/Android 系统版本等信息。显而易见的是,Cookie 以及 User Agent 在每次的请求中会被携带进去,会耗费一定的流量。SPDY能够对 HTTP 请求头部进行压缩,会一定程度上节省不少流量。
  • 服务器推送:HTTP 请求一般由客户端主动发送,服务端进行响应。而 SPDY 可以让服务端将客户端需要的内容 push 过去,不需要被动的等待客户端发送请求。有点像我们目前的 socket 连接下的服务器推送。

关于多路复用

我们使用【WWDC2014 707 Session whats_new_in_foundation_networking】中的几张图来说明下问题:

图1: HTTP/1.1 未使用 pipelining


图2: HTTP/1.1 使用 pipelining


图3: SPDY 多路复用
  • 图1: HTTP/1.1 未使用 pipelining

    style.cssdata.xml的请求必须在收到image.jpg的 response 之后才能依次发送出去,可想而知,这种方式会造成高延迟和低带宽使用率。

  • 图2: HTTP/1.1 使用 pipelining

    style.cssdata.xml的请求不必等到image.jpg响应回来之后才进行发送,而是在image.jpg请求发出去之后就依次发送了出去,而style.cssdata.xml的响应(图中深色部分)必须要在image.jpg的响应之后。如果image.jpg请求的响应数据量比较大(图片较大),加之网络延迟严重等因素,会造成style.cssdata.xml的响应迟迟不能回来,而对于网页渲染而言,资源的优先级却是data.xml为最高优先级,style.css资源为次优先级,image.jpg为最低,最高优先级的资源却被最低优先级的资源请求影响,也就是受到了image.jpg响应的阻塞,即head-of-line blocking队首阻塞。

  • 图3: SPDY 多路复用

    我们从图中看到,SPDY的多路复用,从请求的发送时序上和HTTP/1.1使用pipelining是一样的,关键不同的地方在于响应返回时序上。在这个 TCP 连接中,分出了3个SPDY Stream流通道,即imagestyle.css, data.xml3个资源对应的流通道,每个通道各自负责一个请求和响应,并行且不用相互等待。

我该使用 SPDY 么?

  • 更好的用户体验
    • 对于长连接的延迟的减少可以明显改善用户体验。
    • 苹果发现:在某些情况下,SPDY速度要比 HTTP/1.1 快25%。
  • 额外的获益
    • 减少 CPU 的使用率(一次SSL握手过程取代多次)
    • 相同的服务器架构支持更多的客户端连接

SPDY 并不是总优于 HTTP/1.1:

  1. HTTP/1.1使用多个并行TCP连接要比SPDY的单个TCP连接要快。
  2. 许多SPDY的实现因为 CRIME 漏洞的影响禁用了头部压缩,所以头部压缩并不能带来很大的收益。

iOS8启用SPDY

苹果于 iOS8 和 OSX Yosemite 后支持了SPDY,NSURLSession默认就支持了SPDY,支持的版本范围有 SPDY/2,SPDY/3,SPDY/3.1。

1
NSURL *url = [NSURL URLWithString:@"https://www.example.com/"];
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
                              dataTaskWithURL:url
                              completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
                              
                              }];
[task resume];

将会默认启用SPDY,原来的代码不用做任何的改变。

HTTP全名HyperText Transfer Protocol,意为“超文本传输协议”,它是一个简单的请求-响应协议。

HTTP的缺点

HTTP并不是个加密的协议,会天然的存在几个缺点:

1.明文通信,内容很容易被窃听

互联网上的任何一个角落都存在通信内容被窃听的风险,被广泛使用的抓包工具如Wireshark,Charles等,都可以获取HTTP协议的请求(Request)和响应(Response)的内容,并对其处理。

如何解决呢?

通过HTTPS或者简单点对通信的内容进行加密。对通信内容加密要求客户端对HTTP报文主体内容进行加密,然后发送给服务器,为了保证这点,要求客户端和服务器同时具备加密和解密机制。该方式不同于SSL/TLS对通信线路的加密处理,所以内容仍然有可能被篡改。

2.不验证通信双方身份,因此有可能遭遇黑客伪装客户端或者服务器的情况

在 HTTP 协议通信中,因为不存在确认通信双方身份的步骤,对于请求发送者来说,所以任何人都可以发起请求;对于服务器来说,只要接收到请求,对于不在IP和端口不在限制黑名单的发送者都会返回一个响应。

不验证通信双方身份,会带来什么隐患呢?

  • 通信的服务器可能是伪装的服务器
  • 通信的客户端可能是伪装的客户端
  • 无法抵挡DoS攻击(Denial of Service,拒绝服务器攻击)

如何解决呢?

使用SSL/TSL证书确认机制来确认双方身份。对于 iOS 开发而言,我们可以在 App 内预埋证书,在SSL/TSL握手阶段完成与服务器双向证书的认证。关于这点,我会在后面的文章中详细提及。

3.无法验证报文的完整性,因此可能会接收到被篡改的报文。

在 HTTP 协议通信中,常常会遇到中间人攻击(Man-In-The-Middle-Attack,MITMA),通信的报文会被中间人篡改,那么怎么防止这种情况?

通常业内用的是 MD5 或 SHA-1 等散列值校验的方式,结合用来确认文件的数字签名方法。

HTTP协议版本

目前主流的 HTTP 协议版本是 HTTP/1.1,而1.1版本却是1997年发布的。在1.1之前还有几个版本,但1.1解决了一个重要的问题。我们来看下先辈们是如何解决网络传输问题的。

在 HTTP/1.0 版本的时候,每进行一次 HTTP 通信就要断开一次 TCP 连接。

在1.0版本的时候,浏览器在打开 web 页面的时候,会使用“并行连接”(parallel connection)的方式来同时打开多个 TCP 连接,这么做会带来什么影响?

  • 我们从图中可以看到,每次 TCP 连接的建立,需要三次握手(1.客户端:你好,我给你讲个笑话;(SYN)2.服务端:好的(ACK),你讲吧(SYN);3.客户端:好的,我准备开始讲了(ACK)),这样会带来1.5个RTT(round-trip-time)往返延迟,这种情况在高延迟的情况下比较明显;
  • 每次 TCP 的连接开始阶段,会有一个慢启动的过程,这个过程是用来了解网络路径行为,所以如果能重用要比不重用要好一些;
  • 并行连接会抢夺资源,会造成一定的丢包率。

而 HTTP/1.1 使用“持久连接”(persistent connection)的机制解决了这个问题。这种策略也被成为“连接重用”(connection reuse)或者“keep-alive”。通过一次 TCP 的建立,可以进行多次 HTTP 请求、响应,这种做法无疑会降低 TCP 建立、断开释放带来的开销。持续连接的机制使得 HTTP 请求可以以“管线化”(pipelining)的方式进行发送,不过 pipelining 只适用于幂等请求(如GET/HEAD等方法),非幂等请求(如PUT/POST等方法)不能使用,因为非幂等请求之间可能有前后依赖。在1.1以前的版本中,每一个 HTTP 请求都需要等待收到上一个请求响应后才能发送。管线化使得 HTTP 请求可以并行发送。1.1版本这里的进步无疑是巨大的。

基于 HTTP/1.1 或者 HTTP/2.0 的 web 通信,我们看到,connection 都是 keep-alive 的方式。为了连接重用,很少见到显式将 connection 置为 close 的。那我们想一下,什么时候会断开 TCP 连接呢?事实上,客户端和服务器通常会持久连接保持打开状态,当它们已经闲置一小段时间或者当存在大量的连接时,一些 TCP 连接会被关闭。

那 HTTP/1.1 这么好,为什么互联网工程任务组(IETF)还要开发 HTTP/2.0 版本?2.0解决的问题正是 1.1 版本的弊端所在。包括了几个重要的改进:

  • 多路复用
  • 优先级请求
  • 请求头部压缩
  • 服务器推送

事实上,HTTP/2.0 版本正是基于谷歌2012年提出的 SPDY 方案为原型,所以我们下一节来一起了解学习下 SPDY。

转转iOS App计划着今明年能够切换到HTTPS通信,特此开这个《iOS HTTPS迁移之路》系列,记录下我在学习和研究过程中遇到的问题以及经历。如果朋友们在阅读的时候有疑问,非常欢迎留言。

关于HTTPS,业内我们都知道像Google、Facebook、Twitter这些巨头公司早已经实现全站HTTPS,但似乎国内的网站使用的并不是特别多。百度在实现全站HTTPS方面,走的比较靠前,除下技术驱动的因素外,经常遭到底层运行商的流量劫持也是一个不容忽视的原因。作为电商老大的淘宝目前也是全站HTTPS。而京东似乎只是在注册登陆的时候使用了HTTPS,其他仍然使用的是HTTP。新浪目前也是在着手全站HTTPS。但启用全站HTTPS并非易事,像天猫、淘宝这种体量巨大,系统复杂,改造历时数月之久。

不过切换到HTTPS,一切都是值得的。

我想从以下几个方面展开,其中涉及到了很多网络方面的知识,一来是能够让自己更全面的了解为什么从HTTP迁移到HTTPS,其中涉及到了哪些细节,二来也是对其中某部分知识查漏补缺的记录。通过这次笔记系列,我对网络安全有了一些认识,当然比较浅显,得持续学习下去。

  1. HTTP是什么?HTTP/1、HTTP/2与HTTPS有何区别与联系?

  2. 为什么要引入HTTPS?

  3. 启用HTTPS要解决的难题

  4. AFNetworking3.0 HTTPS

  5. HTTPS性能优化

Block的常见几个用途

1.简化枚举

1
2
3
4
NSArray *array = ...
[array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
NSLog(@"Object at index %lu is %@", idx, obj);
}];

2.简化并发任务

  • 使用于OperationQueue
1
2
3
4
5
6
7
8
9
10
11
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
...
}];

// schedule task on main queue:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:operation];

// schedule task on background queue:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
  • 使用于GCD
1
2
3
4
5
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
NSLog(@"Block for asynchronous execution");
});

循环引用的问题

让我们先来看一段代码。

1
2
3
4
@interface MyClass ()
@property (readonly) int val;
@property (strong) dispatch_block_t work;
@end

Capturing self strongly in this block is likely to lead to a retain cycle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@interface BlockTestView2 ()

@property (readonly) int val;
@property (nonatomic, strong) id obj;

@property (strong) dispatch_block_t work;

@end

@implementation BlockTestView2

- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_obj = @1;
[self setup];
}

return self;
}

- (void)setup {
_val = 5;

// retain cycle
self.work = ^{
NSLog(@"BlockTestView2 %d", _val);
};
}

// 1. Scoping
// Avoid capturing self
- (void)setup1 {
int local = self.val;
self.work = ^{
NSLog(@"BlockTestView2 %d", local);
};
}

// 2. Programmatically
// nil the block property
- (void)setup2 {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
self.work = ^{
NSLog(@"BlockTestView2 %d", _val);
};
#pragma clang diagnostic pop

// nil the block property somewhere, please ignore
// this call at this point
[self cancel];
}
- (void)cancel {
self.work = nil;
}

// 3. Attributes
// Use __weak
- (void)setup3 {
__weak __typeof(self) weakSelf = self;
self.work = ^{
__strong __typeof(self) strongSelf = weakSelf;
NSLog(@"BlockTestView2 %@", strongSelf.obj);
};
}

@end

如何声明、定义一个Block

  • 作为局部变量
    returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
    例如:
    int (^addByOneBlock)(int) = ^int(int number) { return number + 1; }

  • 作为属性
    @property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);
    例如:
    @property (nonatomic, assign) int (^addByOneBlock)(int)

  • 作为方法参数(声明)
    (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;
    例如:
    (void)someMethod:(int (^)(int))addByOneBlock;

  • 方法调用的参数(调用)
    [someObject someMethodThatTakesABlock:^returnType (parameters) {...}];
    例如:
    [someObject someMethod:^int (int number) { return number + 1; }];

  • typedef

    1
    2
    typedef returnType (^TypeName)(parameterTypes);
    TypeName blockName = ^returnType(parameters) {...};

例如:

1
2
3
4
5
typedef void(^SDWebImageQueryCompletedBlock)(UIImage *image, SDImageCacheType cacheType);

typedef void(^SDWebImageCheckCacheCompletionBlock)(BOOL isInCache);

typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize);

1
^ 返回值类型 (参数列表) { 表达式 }
          |
          | 省略 返回值类型
          |
^ (参数列表) { 表达式 }
          |
          | 当不使用参数时,可以省略 (参数列表)
          |
^ { 表达式 }

-------------------
Example:

^int (int count) { return count + 1; }
          |
          |
          |
^(int count) { return count + 1; }
          |
          |
          |
^ { NSLog(@"HelloWorld!"); }