Open javanli opened 4 years ago
五层模型:
应用层
传输层
网络层
数据链路层
物理层
其中数据链路层和物理层由硬件实现,我们通常不作过多关注;传输层和网络层通常由系统基于TCP/IP协议栈实现;在此之上,所有依赖TCP/IP协议栈的应用程序都属于广义的应用层。
但是应用层会比较重,比如HTTP协议,规定了协议的格式、数据的编码、会话的管理(HTTP2.0才管会话)等等。因此标准化组织提出了OSI7层模型,这是个理论模型,把应用层拆成了:应用层+表示层+会话层,希望能够把原来的应用层协议进一步拆分,方便更进一步的复用。
(想想我们软件的分层、模块化/组件化,其实差不多的事情)
但实际上原有的应用层并没有太严重的问题,大家对这种拆分的诉求并不强烈,因此7层模型仍然还停留在理论上。
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:同意结束连接
为什么是四次而不是三次?
B在收到A的结束连接请求时,可能还有任务需要处理(队列中还有数据在发送),需要处理完后再确认可以结束。
如果每个包发送后,都等到收到对方的ACK包才发送下一个包,那么发送效率会比较低,尤其是延时较高但带宽并不小的情况下。
比较直接的想法是一次性发送多个包,每确认一个包就继续发后面的,这样同时有N个包在传输中,利用率就上来了。
如图所示,已发送未收到ACK和待发送的部分就是这里的“窗口”,当已发送的包收到ACK,窗口就会向右滑动,发送下一个包。
对端会按顺序回复ACK,那么,如果没有收到第四个包的ACK,但是收到了第五个包的ACK,也可以确认第四个包的到达。
而滑动窗口的大小取决于接收窗口和拥塞窗口的最小值,也就是说,在对方能吃得下,并且链路扛得住的情况下,尽量多发。
对方的接收窗口是对方在回包时带过来的,不深究了。
拥塞窗口则是通过拥塞控制的算法控制的,这里主要是慢启动、拥塞避免、快恢复几个概念。
如图,拥塞窗口初始值很小,然后指数级上升(慢启动),到达一定阶段后,线性上升(拥塞避免),出现拥塞时,将阈值减半后执行拥塞避免(快恢复)。
socket是系统对TCP/IP协议族的封装,可以是UDP也可以是TCP的。
UDP的好理解,Server端监听端口,客户端每次发一个UDP包,也没有连接状态。
TCP的,一个TCP连接是一个四元组:(source_ip, source_port, destination_ip, destination_port),即server端的一个端口是可以同时建立很多个TCP连接的。
HTTP,超文本传输协议,目前是基于TCP的。
一个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的请求,性能和实时性都会更好一些。基本上都跟平台无关。
好像也没啥特别的...
目前业界常见的通信方式还是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
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。
中间人攻击:从上面可以看出,想要对HTTPS通信进行劫持是比较困难的,需要用户手动信任私有证书(大部分浏览器对用户会有一个很强的警告提醒,但也有少数应用或浏览器不会有明显提示,这是应用的问题而非HTTPS协议本身的问题)
而对客户端伪装成服务端,就需要通过上面的证书有效性检测和站点身份检测。显然我们无法伪造机构签发的证书,因此需要用户手动把私有的证书安装到手机中并且设为信任证书。这样抓包工具就可以成功伪装成服务端跟客户端通信,也就是可以拿到客户端的明文,去跟服务端通信了。
对服务端伪装成客户端比较容易,因为服务端通常并不会对客户端做过多的校验;(安全性较强的金融机构可能有这步)
HTTPS抓包工具如Charles,本质是对客户端伪装成服务端,对服务端伪装成客户端。
证书有效性检测
如何保证服务端提供的证书是可信的:
加密方式的核心思路是:客户端和服务端对齐加密方法、服务端的数字证书后,客户端生成一个随机数,通过证书中的公钥加密后发给服务端,服务端通过私钥解密,这个随机数就只有两端知道,中间的窃听者是无法获知的,基于这个随机数,双方按约定的方式生成一个对话秘钥,用来加密/解密会话内容(对称加密)。
在HTTP下面提供了一层加密,可以是SSL或TLS,这两个协议区别不大,主要是加密算法的区别,我们往往笼统地称为SSL。
经过加密的HTTP。
进入HTTP2.0,引入了多路复用机制。HTTP2.0抽象了数据帧和流的概念,使得数据可以乱序传输,一个TCP连接可以无限制地支持多个HTTP请求。
然后HTTP1.1,keep-alive变成了默认的,并且引入了pipeline特性。pipeline特性允许客户端在同一个TCP连接中并发多个HTTP请求,但最终响应时仍需要按顺序响应,因此一个请求的阻塞会导致后续请求的阻塞。(有点像TCP的滑动窗口)。
在HTTP1.0,可以显式声明Connection: keep-alive,一定时间内,同一域名的多次请求数据,会复用之前建立的TCP连接。当并发的请求比较多时,必须等前一个请求收到响应才能发后一个请求,这会在请求比较多是造成阻塞。
Connection: keep-alive
最早的,每次HTTP请求就是一个单独的TCP连接。
在HTTP发展过程中,为了减少发送多个请求时多次创建TCP连接带来的性能损耗,相关处理方式进行了多次演化。
在这种规范下用到的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方法。
响应报文有:状态行、消息头、响应正文 三部分,如:
基础知识
网络五层/七层
五层模型:
应用层
传输层
网络层
数据链路层
物理层
其中数据链路层和物理层由硬件实现,我们通常不作过多关注;传输层和网络层通常由系统基于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,也可以确认第四个包的到达。
而滑动窗口的大小取决于接收窗口和拥塞窗口的最小值,也就是说,在对方能吃得下,并且链路扛得住的情况下,尽量多发。
对方的接收窗口是对方在回包时带过来的,不深究了。
拥塞窗口则是通过拥塞控制的算法控制的,这里主要是慢启动、拥塞避免、快恢复几个概念。
如图,拥塞窗口初始值很小,然后指数级上升(慢启动),到达一定阶段后,线性上升(拥塞避免),出现拥塞时,将阈值减半后执行拥塞避免(快恢复)。
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抓包原理
证书有效性检测
如何保证服务端提供的证书是可信的:
加密方式的核心思路是:客户端和服务端对齐加密方法、服务端的数字证书后,客户端生成一个随机数,通过证书中的公钥加密后发给服务端,服务端通过私钥解密,这个随机数就只有两端知道,中间的窃听者是无法获知的,基于这个随机数,双方按约定的方式生成一个对话秘钥,用来加密/解密会话内容(对称加密)。
在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请求方法
响应报文有:状态行、消息头、响应正文 三部分,如: