本文原文由作者“zskingking”发表于:jianshu.com/p/271b1c57bb0b,本次收录有改动。 1、点评互联网发展至今已经高度发达,而对于互联网应用(尤其即时通讯网专注的即时通讯技术这一块)的开发者来说,网络编程是基础中的基础,只有更好地理解相关基础知识,对于应用层的开发才能做到游刃有余。 对于Android程序员来说,如果您觉得本文内容稍显枯燥,可以看看即时通讯网之前整理过的一篇类似文章《迈向高阶:优秀Android程序员必知必会的网络基础》,该文内容更偏向于知识点的概括。 如果您希望更系统地学习网络编程方面的知识,可以读一读以下专为初学者整理的系列文章或资料:
2、前言相信计算机专业的朋友在大学都学过《计算机网络》这门课程,但据我个人了解计算机专业普通大学生对计算机网络的了解浅之又浅,很多人说这门学科没用,开发的时候也用不着,其实这样想是不对的。 说一下我个人的体会,之前老是听别人说OkHttp怎么这么好用,但用完之后感觉和其他框架没多大区别啊,于是就想着去专研钻研,当时差不多花了一个星期左右把OkHttp源码看了一遍,代码时看懂了,但是有些地方不知道为什么这样做,所以我就下决心把计算机网络重新学一遍,学完各种网络协议后再看OkHttp源码突然有种焕然一新的感觉。 在本篇文章里,会为大家讲述作为Android程序员的我,对于网络通信传输层协议UDP、TCP的理解,希望能给你带来启发。 参考书籍:《计算机网络-谢希仁版》 参考教程:《韩立刚视频教程》 3、关于作者网名:zskingking,博客地址:https://www.jianshu.com/u/274367e58472 4、UDP协议4.1概述UDP的全称是User Date Protocal,翻译成中文是用户数据包协议,它是一种不可靠的传输协议,一般情况下一个数据包(大概64K)能完成的数据通讯使用UDP协议,比如请求DNS解析IP地址使用的就是UDP协议,因为解析IP一个数据包完全足够。还有就是文字聊天一般用的也是UDP,通常一段文字消息一个数据包就足够了,如果发送失败就再次发送,反正就一个数据包。还有一种传递大量数据包使用UDP协议的场景,就是广播,类似对讲机之类的,接收方并不一定能接收到所有的数据包。所以说UDP是一种不可靠的传输协议。 UDP的主要特点:
4.2UDP首部首先我们先用一张图来表示UDP的首部,UDP首部如下图: <ignore_js_op> UDP首部总共是8个字节,其中源端口、目的端口、长度、检验和各占2字节。有的同学可能要问了,你怎么没把伪首部加进去呢?这个我来讲一下,伪首部顾名思义,就是假的首部,它是不会跟随UDP数据报进行传输的,它存在的意义就是为了计算UDP首部中的检验和。 UDP首部存储的信息:
UDP首部组装完毕后会将完整的数据报发送到网络层,跟IP数据报首部组成IP数据报再向上发送。 5、TCP协议5.1概述TCP全称为Transmission Control Protocol(传输控制协议),是一种可靠的面向连接传输协议,同时它也是一种client-server模式的协议,因为是可靠的传输协议,所以它比UDP要复杂的多。 首先说一下TCP具有的一些特性:
TCP的应用场景: 如果两个台主机想要在网络上传递一部1G大小的电影,需要通过什么协议进行传输呢?UDP为不可靠传输协议,传递过程中可能会出现丢包,所以UDP不行,而传输层就两个协议,一个是UDP一个是TCP,UDP传输效率高但不可靠,TCP传输效率低但它是可靠的,所以想要将传递的文件完整的到达目的地可以通过TCP协议进行传输。 5.2TCP连接建立与断开在5.1中介绍TCP特性的时候提到,TCP是面向连接的,即TCP在传输数据前要建立连接,数据传输完毕后要断开连接。TCP连接必须要由客户端发起。 【5.2.1】TCP建立连接过程: <ignore_js_op> 如上图2所示:客户端向服务端发起建立连接的请求,服务端接收到请求后告诉客户端:“我准备好了”,客户端接收到服务端的响应再给服务端一个确认,此过程总共分为三步被大家亲切的成为:“三次握手”,三次握手后一个可靠的连接就建立了,随后就可以进行数据的传输了,图中SYN、ACK这些字段我会在TCP首部中详细介绍,此处大家可以忽略。 疑点: TCP建立连接为什么是三次握手?两次握手不是已经可以建立一个连接了吗?网络上很多文章对此处的描述大多是轻描淡写,还有的说是必须要三次握手才能建立一个可靠连接,其实这样说是不对的,当时我也因为这些不负责任的回答费解了很久。一般情况下,两次握手是可以建立一个TCP连接的,在《计算机网络-谢希仁》中大概是这样解释的:“第三次握手是为了避免服务端造成资源的浪费”,为什么这样说呢?我来给大家举一个例子: 假如TCP是两次握手:主机A向主机B发送了一个建立连接的请求x,但这个请求在半路里给堵了,主机A没有得到主机B的响应于是又发了一个建立连接的请求y,主机B收到了请求y,于是给主机A发送了一个确认,此时连接建立,数据传输完毕后断开了连接,但在断开连接后堵在半路的请求x到达了主机B,此时主机B认为主机A又给自己发送了一个建立连接的请求,于是给主机A发送了一个确认,此时主机B认为连接已经建立,处于等待状态从而导致主机B资源的浪费。但如果是三次握手就可以避免这种情况的出现,所以这才是TCP第三次握手的原因。 【5.2.2】TCP断开连接过程: <ignore_js_op> 如上图所示:主机A至主机B数据传输结束后主机A会向主机B发送一个断开连接请求,主机B收到后给主机A一个确认,A就不可向B传输数据了,但此时主机B仍然可以往主机A传输数据,等B->A数据传输结束后主机B向主机A发送一个断开连接请求。主机A收到后给予一个确认,这样就成功断开一个TCP连接,过程分四步,也被大家亲切的称为:“四次挥手”。 疑点:断开连接为什么是四次挥手?两次不就可以了吗?下面我来用一个形象的例子来个大家解除疑点 TCP是全双工的:即client和server都可以进行传输和接收,假设主机A和主机B建立了一个TCP连接,主机A可以往主机B发送数据同时主机B也可以往主机A发送数据,现在我将主机A->主机B描述成一根水管x,水管x只能由A流到B,主机B->主机A为水管y,水管y只能由B流到A,现在水管x已经完成的它的输送水源工作,此时就可以将水管x切除,对应图中前两次挥手,但此时水管y还在工作,必须要等水管y工作完成后才能够将其切除,切除水管y对应图中后两次挥手。 5.3TCP首部首先用一张图来表示TCP首部的构造,TCP首部如下图所示: <ignore_js_op> TCP首部的各项内容解释:
本小节只是让大家对TCP首部有一个概念性的认识,所以你可能会对首部中某些字段不太理解,没关系,在下面的文章中我还会提到这些字段。 5.4TCP进行可靠传输我们知道网络传输是不可靠的,可能存在丢包的现象,TCP是可靠的传输协议,那么它是怎么做到可靠传输呢?我来用几张图为大家分析TCP师怎样进行可靠传输的。 如下图所示: <ignore_js_op>
如下图所示: <ignore_js_op>
client-server可以通过传递确认的方式来实现可靠传输,但这种传输方式有一个缺点,效率太低,因为在未收到确认包前是不可以发送下一个包的,那么我们能不能突破这一限制来提升传输效率呢?我们可以通过提升信道的利用率来提升传输效率,如下图所示: <ignore_js_op> 从上图我们可以看到,A在未收到B确认前发送了10个数据包,在这我就将10个数据包形象的编号为1-10,A一次发送10个数据包,当B收到数据包1的时候给A一个确认,A收到确认后再发送数据包11,当B收到数据包2的时候给A一个确认,A收到确认后再发送数据包12,以此类推。 上图中A最多一次可以连续发送10个数据包,而这个10我们可不可以理解为一个窗口呢?打个比方,现在有一个窗口共有10个格子,每个格子放一个数据包,发送的数据包放在格子里面,当收到第一个数据包的确认包后将该数据包从窗口中移出,然后将需要发送的下一个数据包放入窗口。 我们这里提到的窗口就是TCP首部里面说的那个窗口,下面我结合图给大家分析一遍: <ignore_js_op> 上图中,现在我们有12个数据包需要发送,窗口大小为5,所以最多一次可连续发送5个数据包,假设现在窗口中的五个数据包都已经发送完毕,此时收到了数据包1的确认包,那么绿色窗口就可以往右边移一个格子,如下图: <ignore_js_op> 上图中,数据包6被滑进格子里,此时数据包6就可以被发送,同时数据包1也可以从缓存中清除,通过这种方式可以提升信道的利用率从而提升传输效率,但这种传输方式也有一个缺点,就是接收方每收到一个数据包都要进行一次确认,这是完全没必要的,我们可不可以这样做:每收到5个数据包进行一个确认,如下图: <ignore_js_op> A一次给B发送了5个数据包,B确认5个数据包都收到了,给A回复一个6,代表B已经收到了前5个数据包让A下次从第6个数据包开始发送,通过累积响应这种方式又进一步提升了传输效率,但这是理想情况下,如果说A发送完5个数据包,B只收到了1、2、4、5,数据包3丢了,怎么办?是直接给A回复一个3吗?是的话4、5都要进行重传,这样就得不偿失了,而编写TCP协议那位老哥也想到了这种情况,所以就指定了相应的策略,接着刚刚说,如果B确定数据包3丢了或者被阻塞了,那么它会立刻连续发送3个3,A收到连续的3个3后就认为数据包3丢了,然后就会只补传数据包3。 注意点: 上面的内容中我为了方便讲解都是把数据包编成编号进行描述,其实真正的数据包编号不是这样的,TCP协议是面向字节流的,所以说序号和确认号应以字节为标准,比如:A现在向B发送了5和数据包共100个字节,B收到这5个数据包后会给A回复一个101,此时A就会从第101个字节开始进行发送,以此类推。同时通过这种机制也可以实现断点的下载。 5.5流量控制流量控制:用来协调server和client两端因处理数据速度不同所带来的问题。 举个例子: 假如主机A要向主机B发送数据,如果主机A发送数据的速度比主机B处理数据的速度要快,那么很可能导致主机B崩溃,通过TCP流量控制技术可以调整主机A发送数据的速度从而解决上面的问题。 TCP是怎样实现流量控制的的?首先说明一点,前面我们描述TCP传输数据的时候提到了滑动窗口这个概念,其实不光发送方存在滑动窗口,同样接收方也存在滑动窗口,接收方收到数据包后会将数据包放入滑动窗口,对数据包操作完毕后将该数据包从滑动窗口中移出,当滑动窗口被填满时不可以再接收数据,TCP中发送发窗口和接收方窗口大小是相同的。 所以,通过滑动窗口机制可以实现流量控制,如下图所示: <ignore_js_op> 上图中,B为发送方A为接收方,当B与A建立连接的时候首先会明确自己滑动窗口的大小,假如是10,B就会将rwnd(滑动窗口)设置为10,A收到后也会将滑动窗口设置为10,连接建立成功会A开始向B发送数据,我们知道滑动窗口越大发送的速度越快,假如rwnd=10时B处理数据包的速度小于接收数据包的速度那么滑动窗口会逐渐被填满,这样会导致主机B中未处理的数据包越来越多最终可能会崩溃,为了避免这种情况的出现,B可以在滑动窗口被填满了之后给A发送一个rwnd=6,A接收到rwnd=6后会将滑动窗口调整为6进而降低发送数据的速度,同样B如果觉得A的发送速度过慢也可以通过设置rwnd的值来调整A的发送速度,B动态的设置A的滑动窗口就称作为TCP流量控制技术。 5.6拥塞避免什么是拥塞呢?顾名思义就是赌了,数据被堵在半路了,那什么情况下会出现数据被堵在半路呢? 举个例子: 假如现有两台主机分别是发送方主机A和接收方主机B,主机B的带宽为50M/S,也就是说主机B每秒最多能接收50M的数据,如果主机A的发送速度远低于50M/S,这种情况应该是不会出现拥塞现象的,但是如果主机A的发送速度远大于50M/S,主机B的路由器接手不了这么多数据只能进行丢弃,路由器也是有CPU、内存和自己的操作系统,当主句A发送速度越快主机B的路由器CPU和内存就要分配更多的资源去处理丢弃数据包,这样就会导致接收数据包的速度越来越低,极端的情况下可能会出现主机B接收不到数据包的现象,也就是死锁现象。 如果在进行TCP数据传输的时候不进行流量控制很容易出现死锁现象,因为网络是大家共用的,所以避免网络拥塞现象的出现需要所有计算机遵守一种特定的规则,那这种规则是怎样控制网络避免拥塞的呢? 先来看下面这张图: <ignore_js_op> 如果不进行拥塞控制就是我们上面所说的,最终可能会出现上图中绿线的情况,现在我们要通过拥塞控制使网络数据传输按照上图中蓝线进行。 下面我们来说一下如何进行拥塞控制: <ignore_js_op> 首先将滑动窗口设置为1,然后再传输过程中逐渐以指数倍增加,当滑动窗口到达ssthresh的时候再以加法进行增加,如果出现了拥塞现象就迅速再将滑动窗口设置为1,ssthresh减小依次循环,这种方式也称为慢开始方式。但这种方式已经被废弃,因为每次出现拥塞的时候都会将滑动窗口设置为0再进行慢开始阶段,这样其实是完全没必要的。 我们再来看升级版,如下图: <ignore_js_op> 在出现拥塞的时候并不会将滑动窗口设置为1重新进行慢开始,而是将滑动窗口设置为出现拥塞时窗口的一半,然后再以加法进行增加,此过程也可称为是快恢复,这样就可以避免网络拥塞的出现。 附录:更多网络编程文章《技术往事:改变世界的TCP/IP协议(珍贵多图、手机慎点)》 《通俗易懂-深入理解TCP协议(上):理论基础》 《通俗易懂-深入理解TCP协议(下):RTT、滑动窗口、拥塞处理》 《理论经典:TCP协议的3次握手与4次挥手过程详解》 《理论联系实际:Wireshark抓包分析TCP 3次握手、4次挥手过程》 《计算机网络通讯协议关系图(中文珍藏版)》 《UDP中一个包的大小最大能多大?》 《P2P技术详解(一):NAT详解——详细原理、P2P简介》 《P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解》 《P2P技术详解(三):P2P技术之STUN、TURN、ICE详解》 《通俗易懂:快速理解P2P技术中的NAT穿透原理》 《高性能网络编程(一):单台服务器并发TCP连接数到底可以有多少》 《高性能网络编程(二):上一个10年,著名的C10K并发连接问题》 《高性能网络编程(三):下一个10年,是时候考虑C10M并发问题了》 《高性能网络编程(四):从C10K到C10M高性能网络应用的理论探索》 《高性能网络编程(五):一文读懂高性能网络编程中的I/O模型》 《高性能网络编程(六):一文读懂高性能网络编程中的线程模型》 《不为人知的网络编程(一):浅析TCP协议中的疑难杂症(上篇)》 《不为人知的网络编程(二):浅析TCP协议中的疑难杂症(下篇)》 《不为人知的网络编程(三):关闭TCP连接时为什么会TIME_WAIT、CLOSE_WAIT》 《不为人知的网络编程(四):深入研究分析TCP的异常关闭》 《不为人知的网络编程(五):UDP的连接性和负载均衡》 《不为人知的网络编程(六):深入地理解UDP协议并用好它》 《不为人知的网络编程(七):如何让不可靠的UDP变的可靠?》 《技术扫盲:新一代基于UDP的低延时网络传输层协议——QUIC详解》 《让互联网更快:新一代QUIC协议在腾讯的技术实践分享》 《现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障》 《聊聊iOS中网络编程长连接的那些事》 《移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”》 《移动端IM开发者必读(二):史上最全移动弱网络优化方法总结》 《IPv6技术详解:基本概念、应用现状、技术实践(上篇)》 《IPv6技术详解:基本概念、应用现状、技术实践(下篇)》 《从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路》 《以网游服务端的网络接入层设计为例,理解实时通信的技术挑战》 《迈向高阶:优秀Android程序员必知必会的网络基础》 《全面了解移动端DNS域名劫持等杂症:技术原理、问题根源、解决方案等》 《美图App的移动端DNS优化实践:HTTPS请求耗时减小近半》 《Android程序员必知必会的网络通信传输层协议——UDP和TCP》 >> 更多同类文章 …… |