javanli / blog

blog
0 stars 0 forks source link

知识梳理-网络基础 #39

Open javanli opened 4 years ago

javanli commented 4 years ago

基础知识

网络五层/七层

五层模型:

其中数据链路层和物理层由硬件实现,我们通常不作过多关注;传输层和网络层通常由系统基于TCP/IP协议栈实现;在此之上,所有依赖TCP/IP协议栈的应用程序都属于广义的应用层。

但是应用层会比较重,比如HTTP协议,规定了协议的格式、数据的编码、会话的管理(HTTP2.0才管会话)等等。因此标准化组织提出了OSI7层模型,这是个理论模型,把应用层拆成了:应用层+表示层+会话层,希望能够把原来的应用层协议进一步拆分,方便更进一步的复用。

(想想我们软件的分层、模块化/组件化,其实差不多的事情)

但实际上原有的应用层并没有太严重的问题,大家对这种拆分的诉求并不强烈,因此7层模型仍然还停留在理论上。

TCP & UDP

TCP和UDP都是传输层协议,TCP可靠,UDP不可靠,相对的,TCP的资源占用和耗时都会更多一些。

三次握手

TCP建立连接(三次握手):

A:请求建立连接

B:收到,同意建立连接

A:收到

关于为什么是三次而不是两次?

语义上简单理解:双方都要表达出愿意建立连接且已经收到对方建立连接的请求。

更准确的表述是,TCP的可靠性是建立在SEQ的基础上的,在建立连接阶段双方必须确认各自的initial SEQ。如果去掉第三条消息,那么B将无法知道A是否对B的initial SEQ达成共识,必须A回复一个ACK包,带上B的initial SEQ,才能完成对SEQ的确认。

四次挥手

TCP结束连接(四次挥手):

A:请求结束连接

B:收到

B:同意结束连接

A:收到

为什么是四次而不是三次?

B在收到A的结束连接请求时,可能还有任务需要处理(队列中还有数据在发送),需要处理完后再确认可以结束。

滑动窗口 & 拥塞控制

如果每个包发送后,都等到收到对方的ACK包才发送下一个包,那么发送效率会比较低,尤其是延时较高但带宽并不小的情况下。

比较直接的想法是一次性发送多个包,每确认一个包就继续发后面的,这样同时有N个包在传输中,利用率就上来了。

正常情况

如图所示,已发送未收到ACK和待发送的部分就是这里的“窗口”,当已发送的包收到ACK,窗口就会向右滑动,发送下一个包。

对端会按顺序回复ACK,那么,如果没有收到第四个包的ACK,但是收到了第五个包的ACK,也可以确认第四个包的到达。

而滑动窗口的大小取决于接收窗口和拥塞窗口的最小值,也就是说,在对方能吃得下,并且链路扛得住的情况下,尽量多发。

对方的接收窗口是对方在回包时带过来的,不深究了。

拥塞窗口则是通过拥塞控制的算法控制的,这里主要是慢启动、拥塞避免、快恢复几个概念。

img

如图,拥塞窗口初始值很小,然后指数级上升(慢启动),到达一定阶段后,线性上升(拥塞避免),出现拥塞时,将阈值减半后执行拥塞避免(快恢复)。

Scoket

socket是系统对TCP/IP协议族的封装,可以是UDP也可以是TCP的。

UDP的好理解,Server端监听端口,客户端每次发一个UDP包,也没有连接状态。

TCP的,一个TCP连接是一个四元组:(source_ip, source_port, destination_ip, destination_port),即server端的一个端口是可以同时建立很多个TCP连接的。

HTTP

HTTP,超文本传输协议,目前是基于TCP的。

HTTP请求格式

一个HTTP请求有:请求行、请求头部、空行、请求数据四部分。如:

POST /search HTTP/1.1  
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint,
application/msword, application/x-silverlight, application/x-shockwave-flash, */*  
Referer: http://www.google.cn/  
Accept-Language: zh-cn  
Accept-Encoding: gzip, deflate  
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; TheWorld)  
Host: www.google.cn
Connection: Keep-Alive  
Cookie: PREF=ID=80a06da87be9ae3c:U=f7167333e2c3b714:NW=1:TM=1261551909:LM=1261551917:S=ybYcq2wpfefs4V9g;
NID=31=ojj8d-IygaEtSxLgaJmqSjVhCspkviJrB6omjamNrSm8lZhKy_yMfO2M4QMRKcH1g0iQv9u-2hfBW7bUFwVh7pGaRUb0RnHcJU37y-
FxlRugatx63JLv7CWMD6UB_O_r  

hl=zh-CN&source=hp&q=domety

HTTP/1.1 200 OK
Date: Sat, 31 Dec 2005 23:59:59 GMT
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 122

<html>
<head>
<title>Wrox Homepage</title>
</head>
<body>
<!-- body goes here -->
</body>
</html>

另一个值得关注的点是,iOS9及以后,NSURLSession支持了http2的多路复用,用上了长连接的优势。

其中一个关注点是,新接口请求时可以指定回调的queue,这使得我们不需要线程保活了。

就平台而言,主要就是NSURLConnection到NSURLSession的演化。

我目前项目用的是socket长连接,请求大部分是protobuf格式的,也有其它自定义的二进制格式。比起HTTP的请求,性能和实时性都会更好一些。基本上都跟平台无关。

好像也没啥特别的...

iOS相关

目前业界常见的通信方式还是http+json,往往是一些对通信代价要求比较苛刻的场景才会使用pb。

当然使用起来需要定义协议结构,生成各平台协议代码,肯定是没有json方便的。

通过这种编码方式,pb格式序列化后更加紧凑,体积小,传输效率高,并且pb的序列化速度也比json快。因此很多大平台都是使用pb协议的。

对于简单数字大多使用Varint变长编码,浮点数、fixed32、fixed64等类型使用了32/64位的定长格式,而复杂类型会附带一个长度字段,这个长度字段也是Varint格式的。

Type

Meaning

Used For

0

Varint

int32, int64, uint32, uint64, sint32, sint64, bool, enum

1

64-bit

fixed64, sfixed64, double

2

Length-delimited

string, bytes, embedded messages, packed repeated fields

3

Start group

groups (deprecated)

4

End group

groups (deprecated)

5

32-bit

fixed32, sfixed32, float

可用的wire type如下:

tag部分就是通过Varint存储的一个数字,解开后低三位wire type代表后面的数据类型,而高位代表当前field id。

pb针对数字使用了Varint变长编码,简单说就是每个字节的最高位不用来表示具体数字,而是表示当前字节是否是这个数字的最后一个字节,这样虽然占用了1/8的空间,但很多比较小的数字只要1个字节就放下了。

pb的实际存储结构类似TLV(tag-length-value)。

目前最常见的数据格式是json,但这是基于文本的,在很多场景下我们会使用更高效的二进制格式如protobuf。

protobuf

中间人攻击:从上面可以看出,想要对HTTPS通信进行劫持是比较困难的,需要用户手动信任私有证书(大部分浏览器对用户会有一个很强的警告提醒,但也有少数应用或浏览器不会有明显提示,这是应用的问题而非HTTPS协议本身的问题)

HTTPS攻击

而对客户端伪装成服务端,就需要通过上面的证书有效性检测和站点身份检测。显然我们无法伪造机构签发的证书,因此需要用户手动把私有的证书安装到手机中并且设为信任证书。这样抓包工具就可以成功伪装成服务端跟客户端通信,也就是可以拿到客户端的明文,去跟服务端通信了。

对服务端伪装成客户端比较容易,因为服务端通常并不会对客户端做过多的校验;(安全性较强的金融机构可能有这步)

HTTPS抓包工具如Charles,本质是对客户端伪装成服务端,对服务端伪装成客户端。

HTTPS抓包原理

  1. 证书有效性检测

    1. 颁发者检测。证书由上级证书颁发,往上一直追溯到根证书,根证书通常是浏览器内已经内置了的。可以理解成,根证书的秘钥对签发的证书进行加密,客户端使用根证书的公钥进行解密,得到的证书内容即可保证是真实证书机构签发的。
    2. 过期检测。知道是真实签发后,证书还有可能是过期的。证书机构会提供两种接口,一种是拉取所有已过期证书,这个接口比较重,浏览器应该会做缓存;一种是只检测当前证书是否过期。
  2. 站点身份检测:证书域名和当前域名匹配。

如何保证服务端提供的证书是可信的:

加密方式的核心思路是:客户端和服务端对齐加密方法、服务端的数字证书后,客户端生成一个随机数,通过证书中的公钥加密后发给服务端,服务端通过私钥解密,这个随机数就只有两端知道,中间的窃听者是无法获知的,基于这个随机数,双方按约定的方式生成一个对话秘钥,用来加密/解密会话内容(对称加密)。

在HTTP下面提供了一层加密,可以是SSL或TLS,这两个协议区别不大,主要是加密算法的区别,我们往往笼统地称为SSL。

经过加密的HTTP。

HTTPS

进入HTTP2.0,引入了多路复用机制。HTTP2.0抽象了数据帧和流的概念,使得数据可以乱序传输,一个TCP连接可以无限制地支持多个HTTP请求。

然后HTTP1.1,keep-alive变成了默认的,并且引入了pipeline特性。pipeline特性允许客户端在同一个TCP连接中并发多个HTTP请求,但最终响应时仍需要按顺序响应,因此一个请求的阻塞会导致后续请求的阻塞。(有点像TCP的滑动窗口)。

在HTTP1.0,可以显式声明Connection: keep-alive,一定时间内,同一域名的多次请求数据,会复用之前建立的TCP连接。当并发的请求比较多时,必须等前一个请求收到响应才能发后一个请求,这会在请求比较多是造成阻塞。

最早的,每次HTTP请求就是一个单独的TCP连接。

在HTTP发展过程中,为了减少发送多个请求时多次创建TCP连接带来的性能损耗,相关处理方式进行了多次演化。

HTTP keep alive和多路复用

在这种规范下用到的HTTP方法会多一些。

新一点的REST风格的HTTP API,建议使用URL定位资源,用HTTP方法描述操作,建议的HTTP方法使用规范如下:

传统的基于HTTP的API往往只用到了GET/POST。

我们最常用的是GET/POST,通过GET向Server请求数据,通过POST向Server传递数据。主要区别是GET的参数只能编码在URL中,数据量有限;POST请求会有个专门的请求数据部分,可以编码较多的信息。

HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。

HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD方法。

HTTP请求方法

响应报文有:状态行、消息头、响应正文 三部分,如: