基础知识网络五层/七层五层模型:
其中数据链路层和物理层由硬件实现,我们通常不作过多关注;传输层和网络层通常由系统基于TCP/IP协议栈实现;在此之上,所有依赖TCP/IP协议栈的应用程序都属于广义的应用层。 但是应用层会比较重,比如HTTP协议,规定了协议的格式、数据的编码、会话的管理(HTTP2.0才管会话)等等。因此标准化组织提出了OSI7层模型,这是个理论模型,把应用层拆成了:应用层+表示层+会话层,希望能够把原来的应用层协议进一步拆分,方便更进一步的复用。 (想想我们软件的分层、模块化/组件化,其实差不多的事情) 但实际上原有的应用层并没有太严重的问题,大家对这种拆分的诉求并不强烈,因此7层模型仍然还停留在理论上。 TCP & UDPTCP和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,也可以确认第四个包的到达。 而滑动窗口的大小取决于接收窗口和拥塞窗口的最小值,也就是说,在对方能吃得下,并且链路扛得住的情况下,尽量多发。 对方的接收窗口是对方在回包时带过来的,不深究了。 拥塞窗口则是通过拥塞控制的算法控制的,这里主要是慢启动、拥塞避免、快恢复几个概念。 如图,拥塞窗口初始值很小,然后指数级上升(慢启动),到达一定阶段后,线性上升(拥塞避免),出现拥塞时,将阈值减半后执行拥塞避免(快恢复)。 Scoketsocket是系统对TCP/IP协议族的封装,可以是UDP也可以是TCP的。 UDP的好理解,Server端监听端口,客户端每次发一个UDP包,也没有连接状态。 TCP的,一个TCP连接是一个四元组:(source_ip, source_port, destination_ip, destination_port),即server端的一个端口是可以同时建立很多个TCP连接的。 HTTPHTTP,超文本传输协议,目前是基于TCP的。 HTTP请求格式一个HTTP请求有:请求行、请求头部、空行、请求数据四部分。如: POST /search HTTP/1.1 HTTP/1.1 200 OK 另一个值得关注的点是,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,可以显式声明 最早的,每次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请求方法响应报文有:状态行、消息头、响应正文 三部分,如: |
|