Android网络编程(一)传输层协议 UDP、TCP

前言

相信计算机专业的朋友在大学都学过《计算机网络》这门课程,但据我个人了解计算机专业普通大学生对计算机网络的了解浅之又浅,很多人说这门学科没用,开发的时候也用不着,其实这样想是不对的,说一下我个人的体会,之前老是听别人说OkHttp怎么这么好用,但用完之后感觉和其他框架没多大区别啊,于是就想着去专研钻研,当时差不多花了一个星期左右把OkHttp源码看了一遍,代码时看懂了,但是有些地方不知道为什么这样做,所以我就下决心把计算机网络重新学一遍,学完各种网络协议后再看OkHttp源码突然有种焕然一新的感觉。在本系列课程我会为大家讲述传输层协议UDP、TCP和应用层协议HTTP、HTTPS以及Android中优秀的网络框架的基本使用和源码解析。
参考书籍:《计算机网络-谢希仁版》
参考教程:《韩立刚视频教程》

1 UDP

1.1 概述

UDP的全称是User Date Protocal,翻译成中文是用户数据包协议,它是一种不可靠的传输协议,一般情况下一个数据包(大概64K)能完成的数据通讯使用UDP协议,比如请求DNS解析IP地址使用的就是UDP协议,因为解析IP一个数据包完全足够。还有就是文字聊天一般用的也是UDP,通常一段文字消息一个数据包就足够了,如果发送失败就再次发送,反正就一个数据包。还有一种传递大量数据包使用UDP协议的场景,就是广播,类似对讲机之类的,接收方并不一定能接收到所有的数据包。所以说UDP是一种不可靠的传输协议。

UDP的主要特点:

  • UDP是无连接的,即发送数据之前是不需要建立连接的
  • UDP使用尽最大努力交付,不保证可靠交付,同时不使用阻塞控制
  • UDP是面向报文的,UDP没有拥塞控制,很适合多媒体通信的要求
  • UDP支持一对一、一对多、多对一、多对多的交互通信
  • UDP的首部开销小,只需要8个字节

1.2 UDP首部

首先我们先用一张图来表示UDP的首部,UDP首部如图1-1


Android网络编程(一)传输层协议 UDP、TCP_第1张图片
1-1.PNG

UDP首部总共是8个字节,其中源端口、目的端口、长度、检验和各占2字节。有的同学可能要问了,你怎么没把伪首部加进去呢?这个我来讲一下,伪首部顾名思义,就是假的首部,它是不会跟随UDP数据报进行传输的,它存在的意义就是为了计算UDP首部中的检验和。
UDP首部存储的信息:

  • 源端口:即发送方的端口号,需要接收方回应时选用,不需要全为0。
  • 目的端口:接收方端口号。
  • 长度:UDP数据报长度,最小为0(只存在首部)。
  • 检验和: 检验UDP数据报在传输中是否出错,是就丢弃

UDP首部组装完毕后会将完整的数据报发送到网络层,跟IP数据报首部组成IP数据报再向上发送。

2 TCP

2.1概述

TCP全称为Transmission Control Protocol(传输控制协议),是一种可靠的面向连接传输协议,同时它也是一种client-server模式的协议,因为是可靠的传输协议,所以它比UDP要复杂的多。
首先说一下TCP具有的一些特性:

  • TCP是面向连接的传输协议
  • 每一条TCP有且只有两个端点,为一对一关系
  • TCP提供可靠交互的服务
  • TCP提供全双工通信,全双工为即可传输又可接收
  • TCP是面向字节流的

TCP的应用场景:

如果两个台主机想要在网络上传递一部1G大小的电影,需要通过什么协议进行传输呢?UDP为不可靠传输协议,传递过程中可能会出现丢包,所以UDP不行,而传输层就两个协议,一个是UDP一个是TCP,UDP传输效率高但不可靠,TCP传输效率低但它是可靠的,所以想要将传递的文件完整的到达目的地可以通过TCP协议进行传输。

2.2 TCP连接建立与断开

在2.1中介绍TCP特性的时候提到,TCP是面向连接的,即TCP在传输数据前要建立连接,数据传输完毕后要断开连接。TCP连接必须要由客户端发起。

2.2.1 TCP建立连接过程:
Android网络编程(一)传输层协议 UDP、TCP_第2张图片
2-1.PNG

如图2-1:客户端向服务端发起建立连接的请求,服务端接收到请求后告诉客户端:“我准备好了”,客户端接收到服务端的响应再给服务端一个确认,此过程总共分为三步被大家亲切的成为:“三次握手”,三次握手后一个可靠的连接就建立了,随后就可以进行数据的传输了,图中SYN、ACK这些字段我会在TCP首部中详细介绍,此处大家可以忽略。

疑点: TCP建立连接为什么是三次握手?两次握手不是已经可以建立一个连接了吗?网络上很多文章对此处大多是轻描淡写,还有的说是必须要三次握手才能建立一个可靠连接,其实这样说是不对的,当时我也因为这些不负责任的回答费解了很久。一般情况下,两次握手是可以建立一个TCP连接的,在《计算机网络-谢希仁》中大概是这样解释的:“第三次握手是为了避免服务端造成资源的浪费”,为什么这样说呢?我来给大家举一个例子:

假如TCP是两次握手:主机A向主机B发送了一个建立连接的请求x,但这个请求在半路里给堵了,主机A没有得到主机B的响应于是又发了一个建立连接的请求y,主机B收到了请求y,于是给主机A发送了一个确认,此时连接建立,数据传输完毕后断开了连接,但在断开连接后堵在半路的请求x到达了主机B,此时主机B认为主机A又给自己发送了一个建立连接的请求,于是给主机A发送了一个确认,此时主机B认为连接已经建立,处于等待状态从而导致主机B资源的浪费。但如果是三次握手就可以避免这种情况的出现,所以这才是TCP第三次握手的原因。

2.2.2 TCP断开连接过程:
Android网络编程(一)传输层协议 UDP、TCP_第3张图片
2-2.PNG

如图2-2:主机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对应图中后两次挥手。

2.3 TCP首部

首先用一张图来表示TCP首部的构造,TCP首部如图2-3


Android网络编程(一)传输层协议 UDP、TCP_第4张图片
:2-3.PNG
  • 源端口:发送方端口号
  • 目的端口:接收方端口
  • 序号:数据包的序号,以数据包第一个字节进行表示
  • 确认号:确认收到数据包的序号,同样以字节进行标识
  • 数据偏移:TCP首部长度,以字节为单位
  • 保留:保留位,总共6为,必须都为0
  • URG:紧急处理,可提升数据包发送的优先级
  • ACK:代表确认号是否有效
  • RST:将建立的连接重置
  • PSH:接收方应尽快将这个报文交给应用层
  • SYN:同步序号用来发起一个连接
  • FIN:终止一个连接

本小节只是让大家对TCP首部有一个概念性的认识,所以你可能会对首部中某些字段不太理解,没关系,在下面的文章中我还会提到这些字段。

2.3 TCP进行可靠传输

我们知道网络传输是不可靠的,可能存在丢包的现象,TCP是可靠的传输协议,那么它是怎么做到可靠传输呢?我来用几张图为大家分析TCP师怎样进行可靠传输的。

如图2-4:


Android网络编程(一)传输层协议 UDP、TCP_第5张图片
2-4.PNG
  • 情况a:A发送给B数据包M1,B收到之后进行确认,这样M1包就发送成功了,以此类推,这是无差错的情况。
  • 情况b:A发送数据包M1给B,但中途被丢弃了,如果A迟迟等不到B的响应那么A就会重新发送M1包给B。

如图2-5:


Android网络编程(一)传输层协议 UDP、TCP_第6张图片
2-5.PNG
  • 情况a:A发送数据包M1给B,B收到后给A发送一个M1的确认包但该确认包在中途被丢弃了,A迟迟未收到B的确认包就认为M1发送包或者M1确认包早中途丢失了,于是又发送了一个M1包给B,B又收到了一个M1包,此时B就可以认为M1确认包可能在中途丢失了,将重复的M1包丢弃后再给A发送一个M1确认包。
  • 情况b:A发送数据包M1给B,B收到后给A发送一个M1确认包,但该确认包选择了一个较远的传输路线或者被阻塞了,A迟迟未收到M1确认包就再给B发送一个M1包,B收到重复的M1包后将其丢弃然后再给A发送一个M1确认包,A收到M1确认后就认为M1发送成功,但此时A收到半路阻塞的那个M1确认包,而A已经确认了M1包发送成功,所以再次受到M1确认包后什么也不做。

client-server可以通过传递确认的方式来实现可靠传输,但这种传输方式有一个缺点,效率太低,因为在未收到确认包前是不可以发送下一个包的,那么我们能不能突破这一限制来提升传输效率呢?我们可以通过提升信道的利用率来提升传输效率,如图2-6


Android网络编程(一)传输层协议 UDP、TCP_第7张图片
2-6.PNG

图2-6我们可以看到,A在未收到B确认前发送了10个数据包,在这我就将10个数据包形象的编号为1-10,A一次发送10个数据包,当B收到数据包1的时候给A一个确认,A收到确认后再发送数据包11,当B收到数据包2的时候给A一个确认,A收到确认后再发送数据包12,以此类推。

图2-6中A最多一次可以连续发送10个数据包,而这个10我们可不可以理解为一个窗口呢?打个比方,现在有一个窗口共有10个格子,每个格子放一个数据包,发送的数据包放在格子里面,当收到第一个数据包的确认包后将该数据包从窗口中移出,然后将需要发送的下一个数据包放入窗口。我们这里提到的窗口就是TCP首部里面说的那个窗口,下面我结合图给大家分析一遍:


2-7.PNG

如图2-7,现在我们有12个数据包需要发送,窗口大小为5,所以最多一次可连续发送5个数据包,假设现在窗口中的五个数据包都已经发送完毕,此时收到了数据包1的确认包,那么绿色窗口就可以往右边移一个格子,如图2-8

Android网络编程(一)传输层协议 UDP、TCP_第8张图片
2-8.PNG

图2-8中数据包6被滑进格子里,此时数据包6就可以被发送,同时数据包1也可以从缓存中清除,通过这种方式可以提升信道的利用率从而提升传输效率,但这种传输方式也有一个缺点,就是接收方每收到一个数据包都要进行一次确认,这是完全没必要的,我们可不可以这样做:每收到5个数据包进行一个确认,如图2-9


Android网络编程(一)传输层协议 UDP、TCP_第9张图片
2-9.png

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个字节开始进行发送,以此类推。同时通过这种机制也可以实现断点的下载。

2.4 流量控制

流量控制:用来协调server和client两端因处理数据速度不同所带来的问题。
举个例子:

假如主机A要向主机B发送数据,如果主机A发送数据的速度比主机B处理数据的速度要快,那么很可能导致主机B崩溃,通过TCP流量控制技术可以调整主机A发送数据的速度从而解决上面的问题。

TCP是怎样实现流量控制的的?首先说明一点,前面我们描述TCP传输数据的时候提到了滑动窗口这个概念,其实不光发送方存在滑动窗口,同样接收方也存在滑动窗口,接收方收到数据包后会将数据包放入滑动窗口,对数据包操作完毕后将该数据包从滑动窗口中移出,当滑动窗口被填满时不可以再接收数据,TCP中发送发窗口和接收方窗口大小是相同的。所以,通过滑动窗口机制可以实现流量控制,如图2-10:


Android网络编程(一)传输层协议 UDP、TCP_第10张图片
2-10.PNG

图2-10中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流量控制技术。

2.5 拥塞避免

什么是拥塞呢?顾名思义就是赌了,数据被堵在半路了,那什么情况下会出现数据被堵在半路呢?举个例子:

假如现有两台主机分别是发送方主机A和接收方主机B,主机B的带宽为50M/S,也就是说主机B每秒最多能接收50M的数据,如果主机A的发送速度远低于50M/S,这种情况应该是不会出现拥塞现象的,但是如果主机A的发送速度远大于50M/S,主机B的路由器接手不了这么多数据只能进行丢弃,路由器也是有CPU、内存和自己的操作系统,当主句A发送速度越快主机B的路由器CPU和内存就要分配更多的资源去处理丢弃数据包,这样就会导致接收数据包的速度越来越低,极端的情况下可能会出现主机B接收不到数据包的现象,也就是死锁现象。

如果在进行TCP数据传输的时候不进行流量控制很容易出现死锁现象,因为网络是大家共用的,所以避免网络拥塞现象的出现需要所有计算机遵守一种特定的规则,那这种规则是怎样控制网络避免拥塞的呢?先来看图2-11


Android网络编程(一)传输层协议 UDP、TCP_第11张图片
2-11

如果不进行拥塞控制就是我们上面所说的,最终可能会出现图2-11中绿线的情况,现在我们要通过拥塞控制使网络数据传输按照图2-11中蓝线进行,下面我们来说一下如何进行拥塞控制:如图2-12

Android网络编程(一)传输层协议 UDP、TCP_第12张图片
2-12.PNG

首先将滑动窗口设置为1,然后再传输过程中逐渐以指数倍增加,当滑动窗口到达ssthresh的时候再以加法进行增加,如果出现了拥塞现象就迅速再将滑动窗口设置为1,ssthresh减小依次循环,这种方式也称为慢开始方式。但这种方式已经被废弃,因为每次出现拥塞的时候都会将滑动窗口设置为0再进行慢开始阶段,这样其实是完全没必要的,我们再来看升级版,如图2-13


Android网络编程(一)传输层协议 UDP、TCP_第13张图片
2-13.PNG

在出现拥塞的时候并不会将滑动窗口设置为1重新进行慢开始,而是将滑动窗口设置为出现拥塞时窗口的一半,然后再以加法进行增加,此过程也可称为是快恢复,这样就可以避免网络拥塞的出现。

总结

这篇文章描述了传输层协议UDP和TCP,而传输层就这两个协议,所以想要掌握传输层就必须掌握UDP和TCP,UDP为不可靠传输但传输效率高,TCP为可靠传输,是面向连接的,同时相对于UDP传输效率要低一些。

这篇文章是我Android网络编程的第一篇文章,因为写的是纯理论的文章所以一些概念需要借助图来讲解,由于笔者手比较残从生下来就没有一颗美术细胞,所以文章中大多数图都是摘自韩立刚老师计算机网络教程中,这篇文章对传输层协议描述到此为止,下篇文章Android网络编程(二)Socket编程。

你可能感兴趣的:(Android网络编程(一)传输层协议 UDP、TCP)