本节重点 • 理解传输层的作⽤,深⼊理解TCP的各项特性和机制 • 对整个TCP/IP协议有系统的理解 • 对TCP/IP协议体系下的其他重要协议和技术有⼀定的了解

1.应用层

我们之前编写完了基本的 java socket ,要知道,我们之前所写的所有代码都在应⽤层,都是为了 完成某项业务,如翻译等。关于应⽤层,我们在讲解完毕基本的 TCP/IP 协议之后,我们会单独来进⾏讲解(详情可见上篇文章)

2.传输层

负责数据能够从发送端传输接收端

.2.1再谈端⼝号

端⼝号(Port)标识了⼀个主机上进⾏通信的不同的应⽤程序;

在TCP/IP协议中,⽤"源IP","源端⼝号","⽬的IP","⽬的端⼝号","协议号"这样⼀个五元组来标识⼀个 通信(可以通过netstat-n查看);

端⼝号范围划分 • 0-1023:知名端⼝号,HTTP,FTP,SSH等这些⼴为使⽤的应⽤层协议,他们的端⼝号都是固定的.

• 1024-65535:操作系统动态分配的端⼝号.客⼾端程序的端⼝号,就是由操作系统从这个范围分配 的.

认识知名端⼝号(Well-Know Port Number) 有些服务器是⾮常常⽤的,为了使⽤⽅便,⼈们约定⼀些常⽤的服务器,都是⽤以下这些固定的端⼝号: • ssh服务器,使⽤22端⼝ • ftp服务器,使⽤21端⼝ • telnet服务器,使⽤23端⼝ • http服务器,使⽤80端⼝ • https服务器,使⽤443 我们⾃⼰写⼀个程序使⽤端⼝号时,要避开这些知名端⼝号.

两个问题 1. ⼀个进程是否可以bind多个端⼝号.    // 可以 2. ⼀个端⼝号是否可以被多个进程bind.    //不可以

2.2UDP协议

UDP协议端格式

• 16位UDP⻓度,表⽰整个数据报(UDP⾸部+UDP数据)的最⼤⻓度; • 如果校验和出错,就会直接丢弃;( 因为数据传输过程中,是二进制传输,有可能1变成0,也有可能0变成1(比特翻转),所以就需要校验,但是校验并不能百分百,因为udp的校验机制,并不能完全避免,比如如果一个1变成了0,一个0变成了1.通过udp的校验机制,这个结果就会判定为数据没有改变,这个是漏洞)

除了CRC校验以外,还有一些精准更高的(例如md5/sha1);

这个东西的背后涉及一系列的公式计算,不再是像CRC一个公式的计算,无论是多长的原始数据,算出来的结果 都是一样的长度.常见的md5有16位(2字节),32位(4字节),64位(8字节).

尽管只是变了1比特,但是通过计算,变化也是非常大.并且,如果给一个源字符串很容易计算结果,但是如果给一个结果,就很难算出源字符串.保密性很高,有时也会被当成加密使用

我们都说UDP数据能放64kb数据,其实是64kb-8字节.但是由于8字节太小,所以我们忽略了

UDP的特点 UDP传输的过程类似于寄信. • ⽆连接:知道对端的IP和端⼝号就直接进⾏传输,不需要建⽴连接;

• 不可靠:没有确认机制,没有重传机制;如果因为⽹络故障该段⽆法发到对⽅,UDP协议层也不会给应⽤层返回任何错误信息; • ⾯向数据报:不能够灵活的控制读写数据的次数和数量;

理解UDP的"不可靠"

⾯向数据报 应⽤层交给UDP多⻓的报⽂,UDP原样发送,既不会拆分,也不会合并; ⽤UDP传输100个字节的数据: • 如果发送端调⽤⼀次sendto,发送100个字节,那么接收端也必须调⽤对应的⼀次recvfrom,接收100个字节;⽽不能循环调⽤10次recvfrom,每次接收10个字节;

UDP使⽤注意事项 我们注意到,UDP协议⾸部中有⼀个16位的最⼤⻓度.也就是说⼀个UDP能传输的数据最⼤⻓度是 64K(包含UDP⾸部). 然⽽64K在当今的互联⽹环境下,是⼀个⾮常⼩的数字. 如果我们需要传输的数据超过64K,就需要在应⽤层⼿动的分包,多次发送,并在接收端⼿动拼装;

2.3TCP协议

TCP全称为"传输控制协议(Transmission Control Protocol").⼈如其名,要对数据的传输进⾏⼀个详细的控制;

TCP协议段格式

保留位4bit,15个数字,这里的数字的单位是字节,不是bit.所以头部长度是4*15字节=60字节.

这里 不像UDP报头是8字节固定的.TCP只有前面20个字节是固定的,保留位置(由于UDP受到了2比特位的限制,不能扩大,所以TCp就涨了心眼,所以,就设置了保留位,用的时候直接用,不用的时候直接不用就好,这样就可以避免到时候扩展不兼容的问题)

• 源/⽬的端⼝号:表⽰数据是从哪个进程来,到哪个进程去; • 32位序号/32位确认号:下面会详细介绍 • 4位TCP报头⻓度:表⽰该TCP头部有4位bit(有多少个4字节);所以TCP头部最⼤⻓度是15*4=60 • 6位标志位:; URG:紧急指针是否有效 ACK:确认号是否有效 PSH:提⽰接收端应⽤程序⽴刻从TCP缓冲区把数据读⾛ RST:对⽅要求重新建⽴连接;我们把携带RST标识的称为复位报⽂段 SYN:请求建⽴连接;我们把携带SYN标识的称为同步报⽂段 FIN:通知对⽅,本端要关闭了,我们称携带FIN标识的为结束报⽂段 • 16位窗⼝⼤⼩:下面会有详细介绍 • 16位校验和:发送端填充,CRC校验.接收端校验不通过,则认为数据有问题.此处的检验和不光包 TCP⾸部,也包含TCP数据部分. • 16位紧急指针:标识哪部分数据是紧急数据; • 40字节头部选项:选项部分可以有一个,或者多个,也可以一个也没有

确认应答

TCP最重要的就是可靠传输,怎么才能算可靠传输呢?

你发信息过来,我回你信息说收到了,意味着就是真的收到了

例如:

我发送信息过去的时候,我也不知道对方收到了没有,但是如果对方给我回消息说收到,那我就知道对方收到了(已读不回不算哈哈哈哈哈啊哈哈啊哈)

但是如果有多条消息呢??

女神的本意是这样的

可是我们知道网络是经过多地方传输的,会出现后发先到的情况(就好像我们导航到一个地方有很多方法和路线,每条路线的情况都不相同,所以时间是不可预测的)就会出现下面这种情况

可是女神本意不是这样的,所以我们应该怎么避免这种问题呢??

为了解决这个问题,我们就引入了序号和确认序号.在应答的报文里面就会说我这次应答的 是哪个数据.

大家可能有疑问,发送一次,传回一次,才会继续发送.那女神刚刚那样的情况是给女神发送了多次,为什么会出现这种情况呢??是因为网络条件复杂,断电等等都有可能,我们为了确保每次都能正确,所以引入确认序号

这里总结一下:TCP的可靠传输是因为有这样的应答机制,不是因为"三次握手",网上有些说是因为"三次握手"所以才可靠是错误的."等下会说三次握手"

应答报文中,通过应答告诉 发送方表示我收到了.称为"ack",全称acknowledge的缩写.

超时重传

超时重传是应答报文的补充

如果传输一切顺利,发送方的数据正常传给接收方,接收方就可以发送ack报文给发送方表示 收到.可是网络传输会有丢"丢包"的行为

这种时候就需要重新传输了.

TCP的可靠性就是表现在这种时候,及时丢包了,就可以重新发送,让接收方仍然能够接收到数据.

这个就不是UDP能都走到的.UDP即没有接收应答也没有重新发送.数据怎么样了都不知道.

发送方发了数据后就会等一下(有上限的,不是傻等),如果没有收到ack就会认为"丢包",然后就会重新发送.

(上面的说法有一点点错误,不止是发送的数据会丢,发送回来的ack也会丢,但是总的不变,只要发送方没有收到ack,就当做是发送的数据丢了)

如何解决这个问题呢??

TCP在内核中有一片缓存区,发送方的数据都需要放到缓冲区,然后应用的时候通过read和Scanner.next都是通过缓冲区读取数据.

1.数据还在缓冲区里,没有被读走

拿新收到的数据的序列拿出来和已有的序列比一比,如果一样的就会被丢弃

2.数据在缓冲区里,已经被读走了

此时序列不能直接在缓冲区里读到,可是缓冲区里的序号是按照顺序排列的1-1000,2-2000,3-3000.....(可以把缓冲区想象成阻塞的缓存队列),此时 scock 的api就会记录最后一个被read走的序列号,如果是3000,就代表之前的都被读走了,所以新到的这个1000肯定是重复的,就被丢弃了

上面谈到的ack的重传,保证顺序,还有去重.都是ack内置的,我们使用TCP的api的时候,我们只需要调用一下outputStream.write()这个方法就好了,省了我们很多时间(当然正确的使用他们的前提得是我们需要真正的了解他们)

如果我们使用UDP就需要我们自己好好斟酌了

超时重传是会重传.但是怎么重传呢??

1.重传是有上限的,如果超出长限,就会重置连接,如果重置失败,就会直接放弃.

2.重传的时间间隔也是有说法的,随着重传的次数的增大,重传的间隔时间越久(频率就会变低)

连接管理(三次握手,四次挥手)

建立连接,断开连接

内核是怎么建立上诉的连接的 呢??

三次握手

客户端是主动的一方,第一次交互,一定是客户端主动发起的.

所谓的syn是一个特殊的TCP数据报

1.没有载荷,不会携带应用层数据

2.六个标志位中 的第五位,为1

虽然syn不带有应用层载荷,但是也是会有IP报头/以太网数据帧头..更会有TCP报头.

TCP报头就包含了客户端自己的端口.IP报头就包含了客户端自己的IP.(这个过程也是客户端在告诉服务器,我是谁)

但是接下来就有两种可能性:

1.服务器同意了,服务器表示我也愿意和你建立连接

2.服务器没同意(一般来说这种情况很少见,就是出现服务器负载极高的情况下,服务器无法响应了,客户端太多了)就没有下文了.

只要服务器有空闲都会得到一个肯定的响应!!

服务器收到syn之后,会返回ack(确认应答),语义:收到

接下俩服务器还会返回syn,这个syn的意思就是我接受你的连接,我也愿意和你建立连接.

虽然都是叫做同步,实际上是不同的含义.同一个术语在不同的语境下面有不同的意思

握手过程中,确认应答/超时重传也会存在.

为啥要握手??意义是什么?

1.三次握手,可以先针对通讯路径进行探路,初步确认通信链路是否畅通(可靠性的条件)

2.三次握手,也是在验证双方通信,发送能力和接受能力是否正常(一定要是两端都没问题)

至于四次握手可以吗?两次握手可以吗?

四次握手(可以但没必要)

两次握手(不可以)

如果少了最后一次握手,服务器并不知道自己的发送是否完好,因为客户端收到了服务器的请求,但是并没有告诉服务器,服务器会认为自己的发送是坏的.

TCP中也是有很多参数需要进行协商,往往是以"选项"部分来体现

序号和确认序号的作用

不仅仅是确认顺序,还可以确认是不是同一次建立的连接中发送的信息

第一次连接的时候,传输的数据有一个在路上堵车了,等到终于到了的时候,已经改朝换代了,老的连接已经荒废,现在是新的连接,此时,这份数据就该丢掉,但是数据报是按照ip+端口进行识别的,第一个连接,是用客户端A来连接,第二个连接还是是用客户端A来连接的,服务器两次都是B(恰好是同一个端口的话,客户端概率比较小,但是服务器这方面概率还是挺大的)

如何识别是不是前朝的数据呢?这里就需要序列来区分,即使有时丢包,但是序号差别不大,但是如果是别的朝代的包,序列差别很大,一眼就看的出来.(序号不是随机的,背后有一套序号分配机制)

四次挥手

scock.close()触发fin数据报

通信双方各自给对方发送fin,再各自给对方返回syn表示和平分开

那既然四次挥手和三次握手这么像,三次握手也是合并才成为三次的,那四次挥手可不可以也成为三次挥手呢??

答案是:有时候能合并,有时候不能合并.不像三次握手是100%.四次挥手只是有可能.所以不能称为三次挥手.

如果实际通信中,第一个ack和第二个fin间隔时间比较长,那就不可以合并了.就需要分成两次来运输

如果当前时间间隔比较小,是有可能合并.TCP还有延时应答和捎带应答.(下面会一一到来)

状态转换

TCP的状态和"线程"的状态的概念有点类似

TCP服务器和客户端都有数据结构来保存这个连接的信息,在这个数据结构中就有一个属性叫做"状态",操作系统根据目前的状态,就知道要做什么,就不会混乱.

我们只需要记住几个重要的状态,其余的需要的时候再查即可

LISTEN状态表示服务器这边ServerScock已经建立完毕,并且端口号已经写好了(比如:我手机已经开机好了,随时可以给我打电话了)

ESTABLISHED已确立的,客户端和服务器已经建立联系(三次握手完了)(比如:有人打电话给我,我接通了,我们可以正式通话了)

CLOSE_WAIT状态表示接下来的代码需要调用close(),来发送fin给对方.(收到对方的fin进入这个状态)

TIME_WAIT状态表示,本端先发用fin,对方也给我发了fin后,此时本端进入TIME_WAIT状态,给最后一个ack重传留有时间

TIME_WAIT的意义是:防止ack丢包

TIME_WAIT也不是无休止等待,而是有时间限制的.

滑动窗口(TCP非常有特色的)

由于TCP的可靠运输,超时重传,确认应答,耗费了太多的时间.所以TCP就引入了滑动窗口.

在应答机制下,每次发送方接收到了ack以后才会发送下一个数据,导致大量的时间都浪费在了ack上面了,希望在保证可靠传输的情况下,可以节约一点等待时间的花费.

滑动窗口就是为了解决上诉的这些问题,滑动窗口解决的是时间的损耗,节约等待的时间,而不是发送的速度.(虽然这个机制还是没有UDP这种不可靠传输的快,但是也是比啥都不做要好)

上诉发送和返回ack都很快,所以就像一个滑动的窗口,一直往后面滑动.

滑动窗口要确保可靠性,可是如果ack丢了,或者是数据在路上丢了,没有传过去怎么办呢?

1.ack丢了

2.数据丢了

解决这个问题的关键在于:这里返回的是ack是1001.而不是3001.说明1001-2000这个数据是丢了的.不然就是返回3001了.所以主机B就会发送三次重复的确认应答给主机A.主机A收到了同样的应答之后就知道是丢了哪里的数据,就会进行重发.主机B收到了1001-2000的数据后.就会直接返回ack7001,因为这些数据早早的就已经到位了.只是由于2000的数据丢了,所以ack一直发不出去.当2000这个数据到了的时候.就代表着都有了.就会直接返回7001.

在上诉重传的时候.总体的效率是非常高的.这里的重传是针对性的重传.丢了哪一个就传哪一个

如果同时丢了1001和2001.主机B就是返回1001.然后就按照刚刚的逻辑继续走.等到1001的数据重新发送以后.主机B收到了.就会发送2001这个ack三次.

确认应答-超时重传   ========  滑动窗口-快速重传 并不是冲突的.而是同时存在的

1.如果当前传输是按照滑动窗口来传输(短时间内传输大量的数据),就按照快速重传来保证可靠性,判断丢包的条件就是返回多次一样的ack

2.如果当前传输不是按照滑动窗口(没有传输很多数据),就按照超时重传来保证可靠性,此时丢包的判定就是超过规定时间,还没有收到ack.

滑动窗口也有确认应答,只是是稍作调整,批量的确认应答,批量的前提是短时间有很多数据,如果没有很多数据,窗口滑动不起来,也就会退化成确认应答.

流量控制(流控)

通过滑动窗口提高效率,窗口大小越大,更多的数据复用同一块空间等待,效率就越高(批量传数据,不需要等待ack,此时数据的量就是窗口的大小).

窗口的大小能无限大吗?当然不能,提升效率的前提是不影响TCP的可靠性

发送的速度不能无限大,这时候就需要考虑一些情况,从而保证可靠性.

如果你发送太快,接收方方处理不过来,此时就会丢包.(缓冲区满了.应用层read读取速度跟不上)

很明显,如果此时缓冲区满了,丢包了,就算重传也没用,反而会浪费硬件资源.

与其等到接收方满了你不发,不如你提前感知到快满了,就减慢速度.让发送方的发送速度和接受方的接收速度能步调一致.就是让接受方 的速度反过来影响发送方的速度.(这个就叫流量控制)

TCP的窗口中还包含了一个参数,叫做扩展因子,实际上.要设置的窗口大小为16位窗口大小 * 2 ^ 窗口因子

拥塞控制

拥塞控制是站在发送方角度限制发送方发送的速度

流量控制是站在接收方的角度来制约发送方速度的.

就有了以下的综合的合理的方法;

延时应答

也是基于滑动窗口,尽可能再提高一点效率,结合滑动窗口,以及流量控制,把反馈的窗口搞大一些 

举个例子:

捎带应答

基于延时应答引出的,能够提升传输效率的 机制

修改窗口大小确实是提升速度的机制,捎带应答就是另外一种机制.

尽可能把能合并打包一起带走的打包在一起,从而提高效率

ack延时的这段时间里,刚好响应的数据包也准备好了,此时就可以把ack和应答的数据放在一个tcp数据报里.一起发送,

面向字节流

此处重点讨论字节流中一个非常重要的问题   "粘包问题"

此处的包是TCP载荷中的数据包.

在tcp传输到了接收方以后,接收方通过read的的,叫做应用层数据包,由于整个read非常灵活,不知道从哪到哪是一个完整的数据包

粘包问题是tcp引起的.但是需要程序员自己去解决(写应用底层逻辑的时候)

异常情况

3.网络层

3.1 IP协议

3.1.1 地址管理

基本概念 • 主机:配有IP地址,但是不进⾏路由控制的设备; • 路由器:即配有IP地址,⼜能进⾏路由控制; • 节点:主机和路由器的统称;

协议头格式

• 4位版本号(version):指定IP协议的版本,对于IPv4来说,就是4. • 4位头部⻓度(header length):IP头部的⻓度是多少个32bit,也就是length*4的字节数.4bit表⽰最⼤的数字是15,因此IP头部最⼤⻓度是60字节. • 8位服务类型(Type Of Service):3位优先权字段(已经弃⽤),4位TOS字段,和1位保留字段(必须置为0).4位TOS分别表⽰:最⼩延时,最⼤吞吐量,最⾼可靠性,最⼩成本.这四者相互冲突,只能选择⼀个. 对于ssh/telnet这样的应⽤程序,最⼩延时⽐较重要;对于ftp这样的程序,最⼤吞吐量⽐较重要.

• 16位总⻓度(total length):IP数据报整体占多少个字节.

• 16位标识(id):唯⼀的标识主机发送的报⽂.如果IP报⽂在数据链路层被分⽚了,那么每⼀个⽚⾥⾯的这个id都是相同的.

• 3位标志字段:第⼀位保留(保留的意思是现在不⽤,但是还没想好说不定以后要⽤到).第⼆位置为1表⽰禁⽌分⽚,这时候如果报⽂⻓度超过MTU,IP模块就会丢弃报⽂.第三位表⽰"更多分⽚",如果分⽚了的话,最后⼀个分⽚置为1,其他是0.类似于⼀个结束标记. • 13位分⽚偏移(framegament offset):是分⽚相对于原始IP报⽂开始处的偏移.其实就是在表⽰当前分⽚在原报⽂中处在哪个位置.实际偏移的字节数是这个值*8得到的.因此,除了最后⼀个报⽂之外,其他报⽂的⻓度必须是8的整数倍(否则报⽂就不连续了).

• 8位⽣存时间(Time To Live, TTL):数据报到达⽬的地的最⼤报⽂跳数.⼀般是64.每次经过⼀个路由,TTL-=1,⼀直减到0还没到达,那么就丢弃了.这个字段主要是⽤来防⽌出现路由循环

• 8位协议:表⽰上层协议的类型

• 16位头部校验和:使⽤CRC进⾏校验,来鉴别头部是否损坏.

• 32位源地址和32位⽬标地址:表⽰发送端和接收端.

• 选项字段(不定⻓,最多40字节):略

要求公网上的设备,对应的公网IP,都必须是唯一的.

但是私网上的设备,使用私网Ip,只要确保局域网内部IP不重复即可,不同的局域网IP允许重复

由于上述设定,就有一个重要的限制: 1.公网设备访问公网设备, 没有任何问题,直接访问即可

2.局域网设备访问局域网设备(同一个局域网中),也没有任何问题,

3.局域网设备访问局域网设备(不同局域网中),不允许访问. 4.局域网设备访问公网设备就需要对局域网设备的 IP 进行 地址转换

5.公网设备访问局域网设备,不允许主动访问

如果刚好这两个设备的端口又都一样呢??就是这么凑齐

此时路由器就会对端口进行映射

由于在路由器局部IP要转换成公网IP,所以这个时候,如果刚好同一个局部网内的设备访问同一个网址,并且,设备都是相同的端口号,就会一样,所以就需要不同的端口号来区分

广播信息都是UDP,因为TCP不支持

3.1.2 路由选择

4.数据链路层

以太网(横跨数据链路层和物理层)

上面内容介绍完毕,再来介绍一下DNS

制作不易,太多细节和核心需要解剖了.

有收获的朋友不妨一键三连!!!!

---------------------------------------------------------------------------------------------------------------------------------

推荐阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: