目录

  • 一、数据组织格式
    • 1.1 xml
    • 1.2 json
    • 1.3 protobuffer
  • 二、传输层重要协议---UDP协议
    • 2.1 UDP协议端格式
    • 2.2 校验和
  • 三、传输层重要协议---TCP协议
    • 3.1 TCP协议段格式
    • 3.2 TCP原理
      • 3.2.1 确认应答机制(安全机制)
      • 3.2.2 超时重传机制(安全机制)
      • 3.2.3 连接管理机制(安全机制)
      • 三次握手---建立连接
      • 三次握手有什么用?和可靠性有啥关系?
      • 为啥握手是三次?两次行不行?四次行不行?
      • 四次挥手---断开连接
      • 3.2.4 滑动窗口(效率机制)
      • 3.2.5 流量控制(安全机制)
      • 3.2.6 拥塞控制(安全机制)
      • 3.2.7 延时应答
      • 3.2.8 捎带应答(效率机制)
  • 四、面向字节流---粘包问题
    • 如何避免粘包问题
  • 五、TCP异常情况
    • 5.1 进程终止
    • 5.2 机器关机
    • 5.3 机器掉电/网线断开
  • TCP/UDP对比
  • 六、IP协议
    • 6.1 IP地址管理
    • 一些特殊的IP地址
    • 6.2 路由选择
  • 七、数据链路层主要协议---以太网
    • MTU
    • MSS
    • ARP
    • DNS--应用层重点协议

TCP/IP五层协议中,除应用层外的其他四层,都是操作系统驱动硬件,是实现好了的.咱们不需要管.(除非你是系统工程师,驱动开发工程师…);在应用层这里,最重要的事情,就是“设计并实现一个应用层协议"。

设计一个应用层协议,主要就是包含两个工作

  1. 明确传输的信息(根据需求)
  2. 明确数据的组织格式(参考现有的模板:xml,json,protobuffer

一、数据组织格式

当下比较流行的一些协议的模板(数据的组织格式)有:

  1. xml:可读性好,但是运行效率不高
  2. json :可读性好,但是运行效率不高
  3. protobuffer: 可读性不好,运行效率很高

1.1 xml

xml的格式非常有特点,由标签构成的:<标签名>内容</标签名>,分别是:开始标签,要表示的值,结束标签。

比如:

标签名就是key,标签值就是value。通过这些标签,就更好的体现了这个数据的可读性.尤其是哪个部分是啥意思,非常一目了然。

虽然xml提高了可读性,但是又引入了太多的辅助信息:这些标签名啥。
一个服务器程序,最贵的硬件资源,就是网络带宽!对于xml来说,因为要表示这些辅助信息,就导致传输相同数目的请求的时候,占用的网络带宽是更高的;如果带宽固定,相同时间能传输的请求个数就是更少的。因此, xml 现在很少作为应用层协议的设计模板了,现在使用xml主要是作为一些配置文件

1.2 json

json是当下最流行的一种设计应用层协议的数据格式。

json表示形式

{键:值,键:值,键:值,....
}

通过{}构成了键值对结构.一个{}中有很多个键值对,键值对之间使用逗号分割,键和值之间使用冒号分割.要求键必须是字符串类型,值允许很多种类型(数字,字符串,布尔,数组,另一个json…)。

比如:

json中表示字符串,单引号或者双引号都可以(类似于SQL),最后一个键值对后面可以有逗号,也可以没有.(标准要求是没有的,但是一般的json解析器,都不会在意这个细节)
json要求key一定是字符串,因此 key这里的引号可以省略,除非key 中包含了一些特殊符号(比如像空格,或者–…)必须要加上引号。

相比于xml, json同样能保证可读性,同时又没有xml那么繁琐(占用的带宽要更少一点)。json虽然传输效率比xml要高,但是仍然要多传递一些冗余信息,就是key的名字,这一点在表示数组的时候,尤为明显。

1.3 protobuffer

protobuffer应运而生.它是一种二进制格式的数据,在protobuffer的数据中,不再包含上面key的名字了,而是通过顺序以及一些特殊符号,来区分每个字段的含义,同时再通过一个IDL文件来描述这个数据格式(每个部分是啥意思), IDL只是起到一个辅助开发的效果,并不会真正的进行传输,传输的只是二进制的纯粹的数据。

这是一个简化的表示版本:
13\3蛋炒饭\210\21\210\3炒面\28\2\1\28 通过二进制的数据重新对这里的内容进行编排,甚至可能还会进行一些数据压缩.这样做传输效率会更高,但是也会让这个数据肉眼难以观察.调试起来就不方便了.

json 的应用范围要比 protobuffer 更广,开发效率>运行效率.

二、传输层重要协议—UDP协议

虽然传输层,是操作系统内核实现,程序猿不需要直接和传输层打交道.但是传输层对我们来说仍然意义重大,进行网络编程都要用到 socket.一旦你调用了socket代码就进入到传输层的范畴,如果一切顺利,就还好.一旦代码出现一些 bug ,为了解决、理解这些bug,传输层的一些知识就是必要的了。传输层的TCP协议,也是面试中特别爱考的协议。

2.1 UDP协议端格式


协议端格式的另一种画法:

所谓的把应用层数据报分装成UDP数据报,本质上就是在应用层数据报的基础上,添加了8个字节的报头。

2.2 校验和

校验和是用来验证网络传输的这个数据是否是正确的。网络上传递的数据本质光信号和电信号,但是,如果有一些外界干扰,磁场之类的…就可能会导致原有的一些传递的信息发生了改变。比如:正常发一组连续的高频信号,因为遇到了一个强磁场,就可能导致其中的某些高频信号变成低频。

校验和正确,不能保证数据100%就是对的.但是校验和不正确,一定认为数据是有问题的;虽然如此,校验和仍然是广泛使用,成本非常低,效果非常好。
计算校验和的时候,也不一定单纯的就是使用"个数",还可以使用数据内容参与运算.如果是基于数据内容得到的校验和,那么如果数据出错,被识别出来的概率是更高的。
现在使用的一些用来生成校验和的算法crc, md5, sha1等等,这些操作都是基于内容来生成的校验。

能不能把UDP这里的端口改成4个字节之类的呢?
改不了,此处报文长度是2个字节,范围0-65535,0-64k
64k 大小在现代的互联网程序中,非常捉襟见肘了,这是UDP使用中一个非常致命的缺陷,无法表示一个比较大的数据报,如果确实需要传一个大的数据,可以使用TCP。

三、传输层重要协议—TCP协议

3.1 TCP协议段格式


6位标志位:
URG:紧急指针是否有效;
ACK:确认号是否有效;
PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走;
RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段
SYN:请求建立连接;我们把携带SYN标识的称为同步报文段
FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段

3.2 TCP原理

TCP对数据传输提供的管控机制,主要体现在两个方面:安全和效率
这些机制和多线程的设计原则类似:保证数据传输安全的前提下,尽可能的提高传输效率。

TCP中的一些核心机制:有连接、可靠传输、面向字节流、全双工;其中有连接、面向字节流、全双工在代码中都能体现出来,而可靠传输虽然代码上不能直接体现出来,但是这个却是TCP最最核心的机制,引入TCP的关键原因就是为了保证可靠传输,因此 TCP中的很多机制,都是围绕这个可靠传输来展开:

3.2.1 确认应答机制(安全机制)

确认应答机制是确保可靠传输的核心机制。(可靠性:发送方发出去数据之后,能够知道对方有没有收到),其关键就是接收方收到消息之后,给发送方返回一个应答报文(ACK, acknowledge),表示自己已经收到了。

在正常情况下,发送方发送一条消息,接收方会应答一条消息,但是在发送方连续发送多条消息的时候,接收方在给对方返回数据的时候,在网络上,数据接收的顺序,不一定和发送的顺序完全一致,存在后发先至(网络环境非常复杂,连续发的两个包,不一定就是走同一条路)的情况,比如:

为了解决上述问题,就对消息进行编号;TCP将每个字节的数据都进行了编号,即为序列号

每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据;下一次你从哪里开始发。表示当前这个序号是针对哪个消息进行确认应答


TCP的针对消息的序号,并不是按"消息条数”来进行编号的.而是按照字节来编号

3.2.2 超时重传机制(安全机制)

相当于对确认应答进行了补充;确认应答是网络一切正常的时候,通过ACK通知发送方我收到了.如果出现了丢包的情况,超时重传机制就要起到效果了。

  1. 当发出去消息之后,就担心这个消息是不是发丢了,我就在等这个ACK,
    如果确实发丢了,对方直接就没收到,我这里肯定也没法收到ACK:

主机A发送数据给B之后,可能因为网络拥堵等原因,数据无法到达主机B;
如果主机A在一个特定时间间隔内没有收到B发来的确认应答,就会进行重发.

但是,主机A未收到B发来的确认应答,也可能是因为ACK丢失了;

因此主机B会收到很多重复数据。那么TCP协议需要能够识别出那些包是重复的包,并且把重复的丢弃掉。这时候我们可以利用前面提到的序列号,就可以很容易做到去重的效果.

TCP内部就会有一个去重操作
接收方收到的数据会先放到操作系统内核的"接收缓冲区"中,接收缓冲区可以视为是一个内存空间,并且也可以视为是一个阻塞队列;收到新的数据,TCP就会根据序号,来**检查这个数据是不是在接收缓冲区中已经存在了.如果不存在,就放进去;如果存在,直接丢弃.**保证应用程序调用socket api拿到的这个数据一定是不重复的!应用程序感知不到超时重传的过程的。

超时重传,重传的数据,一定会成功嘛?
肯定不是100%
如果只是因为网络抖动了一下,这个时候重传还是很容易成功的;如果是网络遭受了严重的伤害,可能没那么容易恢复,重传也成功不了了(比如:网线断了/没有网费…)。

重传如果失败,可能还会再尝试.也不会无休止的重传.连续几次重传都不行,就认为这个网络可能是遇到了严重的情况(网络或者对端主机出现异常),再怎么重传怕是也不行了.就只能放弃了(自动的断开TCP的连接)。重传的这个时间间隔,也不是固定的.一般来说会逐渐变大(重传的频率会逐渐降低)

操作系统具体是重传几次才放弃?操作系统超时时间每次增加多少?这些一般都是可以进行配置的。

基于上述的两个机制(确认应答机制和超时重传机制),TCP的可靠性,就得到了有效的保障。

3.2.3 连接管理机制(安全机制)

这个也是TCP保证可靠性的一个机制,在正常情况下,TCP要经过三次握手建立连接,四次挥手断开连接.

三次握手—建立连接

客户端和服务器之间,通过三次交互,完成了建立连接的过程,"握手"是一个形象的比喻。

客户端是主动发起连接请求的一方,客户端先发送一个SYN同步报文段,给服务器,如果SYN这一位为1,表示当前报文就是一个"同步报文段",主机A和主机B之间要建立连接,如果ACK这一位为1表示这个报文就是一个"确认报文段"(刚才说的确认应答报文,也是ACK1);


这个过程就像表白,双向奔赴的过程:双方都像对方表白,这个才算是正式确立关系。

中间的这两次,是一定会合二为一的。每次要传输的数据,都要经过一系列的封装和分用,才能完成传输,封装两次肯定不如封装一次来的更高效.(这就像是我在某个淘宝店铺上,买了两件衣服,肯定是打一个包裹发给我更好的)。

TCP的状态:

TCP的状态有很多(比线程的状态多多了)这些状态,也不太需要去研究。
LISTEN:表示服务器启动成功,端口绑定成功,随时可以有客户端来建立连接.(手机开机,信号良好,随时可以有人给我打电话);
ESTABLISHED:表示客户端已经连接成功**,随时可以进行通信了**.(有人给我打电话,我接听了,接下来就可以说话了);

三次握手有什么用?和可靠性有啥关系?

三次握手,相当于是"投石问路",检查一下当前这个网络的情况是否满足可靠传输的基本条件,让双方能够协商一些必要的信息,如果网络本身就效果非常差,强行进行TCP传输,也会涉及到大量丢包。更具体的说,可以认为,三次握手其实也是在检测通信双方,发送能力和接收能力是否都正常

比如:我和一个同学进行语音通话:

TCP通信过程中,需要客户端和服务器之间,有一些共同的信息的,因此在三次握手的同时,也会相互之间交互一些必要的内容

为啥握手是三次?两次行不行?四次行不行?

四次行!但是没必要!分开传输降低效率,不如合在一起好!
两次不行!意味着缺少最后一次.此时客户端这边关于发送接收能力正常的,情报是完整的,但是服务器这边是残缺的.服务器不知道自己的发送能力是否ok,也不知道客户端的接收能力是否ok ,此时此刻,服务器对于当下能否满足可靠传输,心里是没底的,这第三次交互,就是为了给服务器吃一个定心丸。(就比如上面打电话的例子)。

四次挥手—断开连接

三次握手,就让客户端和服务器之间建立好了连接,其实建立好连接之后,操作系统内核中,就需要使用一定的数据结构来保存连接相关的信息,保存的信息其实最重要就是前面说的"五元组",而且客户端服务器都得保存五元组:源IP,源端口,目的IP,目的端口,TCP;既然保存了信息,就需要占用系统资源(内存),如果有朝一日,连接断开了(不复存在了),此时之前保存了连接信息就没意义了,对应的空间也就可以释放了,挥手要把之前的记录的东西也就销毁

双方各自向对方发送了FIN(1就表示是结束报文段)请求,并且各自给对方一个ACK确认报文:

  • 三次握手,一定是客户端主动发起的(主动发起的一方才叫客户端);
  • 四次挥手,可能是客户端主动发起,也可能是服务器主动发起
  • 三次握手,中间两次能合并:三次握手中,B发送的ACK和SYN同一时机,就能够合并,此处的B给A发送的ACK和SYN都是操作系统内核负责进行的
  • 四次挥手,中间两次有时候合并不了(有时候是能合并)。
    不能合并的原因,在于B发送ACK和B发送FIN的时机是不同的;四次挥手中,B给A发的ACK是内核负责的,B给A发的FIN是用户代码负责(B的代码中调用了socket.close()方法,才会触发FIN)。如果这两操作之间的时间差比较大,就不能合并了;如果时间差比较小,这是可能会合并的(延时应答和捎带应答)。

TCP的状态:

CLOSE_WAIT:四次挥手挥了两次之后出现的状态.这个状态就是在等待代码中调用socket.close方法,来进行后续的挥手过程,正常情况下,一个服务器上不应该存在大量的CLOSE_WAIT.如果存在,说明大概率是代码bug , close没有被执行到。

TIME_WAIT:谁主动发起FIN,谁就进入TIME_WAIT.起到的效果就是给最后一次ACK提供重传机会,表面上看起来,A发送完ACK之后,就没有A的啥事了,按理说,A就应该销毁连接,释放资源了,但是并没有直接释放,而是会进入TIME_WAIT状态等待一段时间,一段时间之后再来释放连接,等这一会,就是怕最后一个ACK丢包!如果最后一个ACK丢包了就意味着B过一会就会重传FIN。

TIME_WAIT应该持续多久呢?设定的时间是2*MSLMSL表示网络上任意两点之间,传输需要的最大时间。这个时间也是系统上可以配置的参数,一个典型的设置就是60 s(经验值)。

TCP虽然可靠性是最高的机制,但是TCP也会尽可能的提高传输效率,这时候就有了滑动窗口.

3.2.4 滑动窗口(效率机制)

前面讨论了确认应答策略,对每一个发送的数据段,都要给一个ACK确认应答。收到ACK后再发送下一个数据段。这样做有一个比较大的缺点,就是性能较差。尤其是数据往返的时间较长的时候。滑动窗口就是在确保可靠性的前提下,尽量提高传输效率

滑动窗口,本质就是在"批量的发送数据",一次发一波数据,然后一起等一波ACK。

  • 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值(也就是批量发送的数据大小)。上图的窗口大小就是4000个字节(四个段)。
  • 发送前四个段的时候,不需要等待任何ACK,直接发送;
  • 收到第一个ACK后,滑动窗口向后移动,继续发送第五个段的数据;依次类推;"滑动”的意思并不用把4组数据的ACK都等到了,才继续往下发送,而是收到一个ACK,就继续往下发送一组。
  • 操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答;只有确认应答过的数据,才能从缓冲区删掉;
  • 窗口越大,则网络的吞吐率就越高,可以认为就是传输速度就越快,窗口大了,同一份时间内等待的ACK就更多,总的等待ACK的时间就少了。


随着ACK接连到来,就会发送新的数据,此时这个"窗口”(要等待ACK的数据)就再逐渐往后"滑动"。

丢包的两种情况:

  1. 情况一:数据包已经抵达,ACK被丢了


这种情况下,部分ACK丢了并不要紧,因为可以通过后续的ACK进行确认;

  1. 情况2:数据包丢了


由于1001 - 2000这个数据丢了~所以B就再反复索要1001这个数据,
即使A给B已经往后发了,这个时候仍然是在索要1001,当索要若干次之后,A就明白了,就触发了重传。

在A重传1001-2000之前,B的接收缓冲区如下图:

当A重传了1001-2000之后,B的接收缓冲区,就把缺口给补上了,后续的2001-7000这些数据都是已经传输过了,这些数据就不必再重传.接下来B就向A索要7001开始的数据;
这里的重传只是需要把丢了的那一块数据给重传了即可.其他已经到了的数据就不必再重传了,整体的重传效率还是比较高的.这种机制被称为 “高速重发控制”(也叫 “快重传”)。

3.2.5 流量控制(安全机制)

流量控制,是滑动窗口的延伸,目的也是为了保证可靠性。
接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。因此TCP支持根据接收端的处理能力,来决定发送端的发送速度。这个机制就叫做流量控制
流量控制的关键就是得能够衡量接收方的处理速度,此处使用接收方接收缓冲区的剩余空间大小来衡量当前的处理能力。

这样的数据传输过程,可以理解成"生产者消费者模型"
A就是生产者,B的应用程序就是消费者,B的接收缓冲区,就是交易场所。
接收缓冲区肯定有一个总大小,随着A发送数据,接收缓冲区里就会逐渐放入一些数据剩余空间就会逐渐缩小.如果剩余空间比较大,就认为B的处理能力是比较强.就可以让A发的快点;如果剩余空间比较小,就认为B的处理能力是比较弱,就可以让A发的慢点

接收方如何告诉发送方剩余空间大小?

接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段,通过ACK端通知发送端。通过这个16位窗口大小,来衡量当前接收方剩余空间的大小,发送方收到这个数据之后,就会灵活的调整发送速度(调整窗口大小),虽然这里是16位,实际上这里的窗口大小不止是64k,还可以更大。实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是窗口字段的值左移M位;

如果接收端缓冲区满了,就会将窗口置为0;这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端。

探测报文不传输实际的数据,只是为了触发ACK,只是为了知道当前的窗口大小是多少。

3.2.6 拥塞控制(安全机制)

拥塞控制也是滑动窗口的延伸,也是限制滑动窗口发送的速率
拥塞控制衡量的是发送方到接收方这整个链路之间拥堵情况(处理能力)

虽然TCP有了滑动窗口这个大杀器,能够高效可靠的发送大量的数据。但是如果在刚开始阶段就发送大量的数据,仍然可能引发问题。因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵。在不清楚当前网络状态下,贸然发
送大量的数据,是很有可能引起雪上加霜的。
TCP引入 慢启动 机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据;

A开始的时候以一个比较小的窗口来发送数据.如果数据很流畅的就到达了,逐渐加大窗口大小,如果加大到一定程度之后,出现了丢包(丢包就意味着通信链路出现拥堵了),这个时候再减小窗口;通过反复的增大/减小过程逐渐就摸到了一个合适的范围。拥塞窗口就在这个范围中不断变化,达到"动态平衡"。

此处引入一个概念为拥塞窗口;发送开始的时候,定义拥塞窗口大小为1;
每次收到一个ACK应答,拥塞窗口加1;每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口。


“慢启动” 只是指初使时慢,但是增长速度非常快。为了不增长的那么快,因此不能使拥塞窗口单纯的加倍。此处引入一个叫做慢启动的阈值,当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长

当TCP开始启动的时候,慢启动阈值等于窗口最大值;在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回1(直接让窗口回归初始值一个主要的目的是网络的情况是复杂,不稳定的,如果出现丢包,很可能你光把速度降下来一点,不能解决问题的;如果降的太慢,就会出现持续性的丢包,就对网络通信质量带来了很大的影响,一下让窗口变的很小,就是期望这次传输,一定能成功);少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络拥塞;当TCP通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降。

期望的最理想的效果窗口大小在阈值和丢包窗口之间

3.2.7 延时应答

相当于流量控制的延伸。流量控制是踩了下刹车,使发送方发的不要太快.延时应答就想在这个基础上,能够尽量的再让窗口更大一些。

如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小。

假设一个蓄水池注水的同时也在出水.每次注入一波水,就会询问当前水池里剩余空间有多少。
采取的策略就是不立即回答,而是稍微晚一会回答,迟一点回答,意味着在这个延时间里,就会出更多的水。
如果立即回答,可能回答一个剩余20吨.如果延时一会回答,就可以说,剩余10吨(延时这个时间里,出了10吨水),这个操作就是在有限的情况下,又尽可能的提高了一点传输速度。
窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况
下尽量提高传输效率

窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率。

3.2.8 捎带应答(效率机制)

在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 “一发一收” 的。意味着客户端给服务器说了 “How are you”,服务器也会给客户端回一个 “Fine, thank you”;那么这个时候ACK就可以搭顺风车,和服务器回应的 “Fine,thank you” 一起回给客户端。
因为延时应答的存在,导致ACK不一定是立即返回的,如果当前的延时应答,导致ACK的返回时机(是内核响应的)和应用代码中返回的响应(应用程序返回的)时机重合了,就可以把这个ACK和响应数据,合二为一。

客户端和服务器之间的通信,有以下几种模型:

  1. 一问一答.客户端发一个请求,服务器返回一个对应的响应;
  2. 多问一答.上传文件;
  3. 一问多答.下载文件;
  4. 多问多答.直播,串流…

四、面向字节流—粘包问题

(不仅仅TCP存在粘包,其他的面向字节流的机制也存在,比如读文件)

  • 首先要明确,粘包问题中的 “包” ,是指的应用层的数据包。在TCP接收缓冲区中,若干个应用层数据包混在一起了,分不出来谁是谁了。
  • 在TCP的协议头中,没有如同UDP一样的 “报文长度” 这样的字段,但是有一个序号这样的字段。
  • 站在传输层的角度,TCP是一个一个报文过来的。按照序号排好序放在缓冲区中。
  • 站在应用层的角度,看到的只是一串连续的字节数据。那么应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始到哪个部分,是一个完整的应用层数据包。

比如A向B发送数据报;

这些数据报到达B之后,就会进行分用,分用意味着就把TCP数据进行解析取出其中的应用层数据放到接收缓冲区中,以备应用程序来取。

假设在接收缓冲区的存储形式如下:

B的应用程序就需要通过read方法来从接收缓冲区中取数据出来,因为TCP是面向字节流的.B取的时候就是取的若干个字节,从哪里取到哪里是一个完整的应用层数据报呢,这个时候各数据报混在一起,就分不出来谁是谁了。

如何避免粘包问题

归根结底就是一句话,明确两个包之间的边界

  • 对于定长的包,保证每次都按固定大小读取即可;
  • 对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置;
  • 对于变长的包,还可以在包和包之间使用明确的分隔符(应用层协议,是程序猿自己来定的,只要保证分隔符不和正文冲突即可,如上面的例子中就可以约定每个包以;结尾,这个时候,只要能够按照;来进行切分,就可以区分出当前从哪到哪是一个完整的应用层数据了)。

对于UDP协议来说,是否也存在 “粘包问题” 呢?不会

  • 对于UDP,如果还没有上层交付数据,UDP的报文长度仍然在。同时,UDP是一个一个把数据交付给应用层。就有很明确的数据边界。
  • 站在应用层的角度,使用UDP的时候,要么收到完整的UDP报文,要么不收,不会出现"半个"的情况。

五、TCP异常情况

5.1 进程终止

进程终止会释放文件描述符,仍然可以发送FIN。和正常关闭没有什么区别。

TCP连接,是通过socket 来建立的。socket本质上是进程打开的一个文件.文件其实就存在于进程的PCB里面有个文件描述符表每次打开一个文件(包括socket),都在文件描述符表里,增加一项;每次关闭一个文件,都在文件描述符表里,进行删除一项.如果直接杀死进程, PCB也就没了.里面的文件描述符表也就没了,此处的文件相当于’自动关闭’了.这个过程其实和手动调用socket.close()一样,都会触发4次挥手。

5.2 机器关机

和进程终止的情况相同。按照操作系统约定的正常流程关机,会让操作系统,杀死所有进程.然后再关机。

5.3 机器掉电/网线断开

  1. 一种可能是接收方断电:


如果B断电,意味着A发送的数据不再有ACK了.A进入超时重传逻辑,重传几次之后,A认为这个连接已经出现严重故障了.尝试重新建立连接,重连失败之后就会放弃连接(A主动释放曾经和B相关的连接信息)。

接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就
会进行reset。即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在。如果对方不在,也会把连接释放。

  1. 一种情况是发送方断电:

如果A断电,B就不知道当前是A挂了,还是A休息会再继续,B就会时不时的给A发送一个小的报文(不带有实际的数据,只是为了触发ACK),通过探测报文,发现A不再返回ACK了,因此B就认为A出现了问题。

另外,应用层的某些协议,也有一些这样的检测机制。例如HTTP长连接中,也会定期检测对方的状态。例如QQ,在QQ断线之后,也会定期尝试重新连接。

TCP/UDP对比

  • TCP用于可靠传输的情况,应用于文件传输,重要状态更新等场景;
  • UDP用于对高速传输(对于效率要求比较高)和实时性要求较高的通信领域,例如,早期的QQ,视频传输、机房内部的主机之间通信、分布式系统中等等。另外UDP可以用于广播;

六、IP协议

IP地址(Internet Protocol Address)是指互联网协议地址,又译为网际协议地址。
作用
IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
格式
IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也就是4个字节),如:01100100.00000100.00000101.00000110
通常用“点分十进制”的方式来表示,即 a.b.c.d 的形式(a,b,c,d都是0~255之间的十进制整数)。如:100.4.5.6

IP协议报头

  • 4位版本: IP协议的版本号.当前只有两个取值,4和6(0100、0110),当前主要讨论的是IPv4;IPv4数量=2^32,大约43亿左右,而TCP/IP协议规定,每个主机都需要有一个IP地址。对于全世界计算机来说,这个数量是不够的,所以后来推出了IPv6(长度128位,是IPv4的4倍)。但因为目前IPv4还广泛的使用,且可以使用其他技术来解决IP地址不足的问题,所以IPv6也就没有普及。
  • 4位首部长度:IP的报头和TCP类似,都是可变的.带有选项4位的取值范围0-15,这里的单位也是4字节.如果取值是1111=>15,实际表示的首部长度就是60字节.
  • 8位服务类型TOS:TOS说是8位,其实只有4位是有效的。4位TOS分别表示:最小延时,最大吞吐量,最高可靠性,最小成本。(同一时刻,只能取一种状态)。
  • 16位总长度:UDP好像也有类似的情况,16位(最大长度64k)。因此,单个IP数据报最大长度确实不能超过64k,如果要构造一个更长的数据报(比如搭载的载荷部分已经超过64k了),IP协议自身实现了分包和组包这样的操作便可实现.
  • 16位标识、3位标志、13位片偏移:这三个字段就是用来进行分包和组包的。

  • 在切分的第二个包中没有TCP报头:对于IP数据包来说,根本不关心载荷里是啥,只是单纯的对数据进行了切分,也不知道这里有没有TCP报头。
  • 如何区分这多个IP包,是从同一个数据拆分来的呢?
    多个被拆出的IP数据报的16位标识的值是相同的,即可辨认这些包是否是从同一个包中拆分出来的。
  • 通过这个13位片偏移,来描述这多个包谁先谁后;片偏移准确来说也不是一个单纯的1,2,3,4,而是可以基于片偏移来算出中间差多少空间。
  • 3位标志中只有一位是好使的:0表示还有后续,1表示这是最后一个包了.(结束标记)。

如何基于UDP实现分包组包?解决方案就是照抄IP的作业。

  1. 8位生存时间(TTL):表示一个IP数据报,在网络上还能存在多久;这里的单位不是s或者ms,而是转发次数;IР数据报被发送的时候,会有一个初始的TTL(比如常见的取值,128或者64),IP数据报每次经过了一个路由器,TTL就会-1,如果TTL减到0了,此时收到这个包的路由器就会把这个包给丢弃。主要就是有些包里面的IP地址,可能是永远也到不了,像这样的包,不可能在网络上无休止的转发(占用硬件资源太多了),正常的IP数据报都会在既定的TTL内来到达。
  2. 8位协议:传输层使用的是哪种协议.TCP 或者是UDP都有不同的取值。
  3. 16位首部检验和:用来校验数据是否正确。
  4. 32位源IP地址,32位目的P地址:源IP表示发件人地址,目的IP表示收件人地址.对于IPv4来说,一个IP地址本质上是32位的整数,通常会使用"点分十进制"这样的方式来表示这个IP地址:三个点,把32位整数分成4个部分,每个部分1个字节.每个部分的取值就是0-255。(给人看的IP通常就是点分十进制表示的,给机器存储的IP,在底层仍然是按照4个字节整数来表示)

6.1 IP地址管理

IP地址分为两个部分:网络号和主机号
网络号:标识网段,描述当前的网段信息(局域网的标识),保证相互连接的两个网段具有不同的标识;
主机号:标识主机,(区分了局域网内部的主机)同一网段内,主机之间具有相同的网络号,但是必须有不同的主机号;两个相邻的局域网(同一个路由器连接的),网络号也是不同的.

到底前多少个bit位是网络号?多少个bit位是网络号?咋规定的呢?是固定3个字节嘛?其实是不固定的。

引入了一个"子网掩码"这样的概念来表示:

子网掩码也是一个32位,点分十进制表示的整数,子网掩码的左侧都是1,右侧都是0(不会1、0混着排列),左边的这些1就表示哪些位是网络号,剩下0就是表示哪些位是主机号

如:

IP地址中的后8个bit位就是主机号,前24个bit位就是网络号
255 => 1111 1111 1111 1111 1111 1111 0000 0000

一般家用的场景中,一个局域网设备很少(不会超过255)常见的子网掩码就是255.255.255.0,如果一个局域网设备多了,子网掩码就会出现一些其他值。

需要根据实际需要来确定网段如何划分(每个设备的IP都是可配置的,包括子网掩码啥的,都是可以修改的)。

IP地址网段划分通过子网掩码的方式划分的:
在历史上(有子网掩码之前),是简单粗暴的通过"分类"的方式来划分的.把IP地址分成了A,B,C,D,E这五类,每一类分别都有几位是网络号,几位是主机号…(但是后来淹没在历史长河中了)。

一些特殊的IP地址

  1. 如果IP的主机号为全0,该IP就表示网络号.(局域网里的一个正常的设备,主机号不能设为0);
  2. 如果IP的主机号为全1(255),该IP就表示"广播地址".往这个广播地址上发的消息,整个局域网中都能收到;
  3. IP地址是127开头的,该IP都表示"环回IP",表示主机自己,127.0.0.1 (环回IP中的典型代表);
  4. IР地址是10开头,192.168开头,172.16-172.31开头,表示该IP地址是一个局域网内部的IP(内网IP)除此之外,剩下的IP称为外网IP(直接在广域网上使用的IP),要求外网IP一定是唯一的,每个外网IP都会对应到唯一的一个设备.内网IP只是在当前局域网中是唯一的.不同的局域网里,可以有相同的内网IP的设备。

预期IP就表示网络上的唯一位置,为什么同一个IP可以表示不同的设备?

当前IPv4协议,使用的IP地址是32位的整数,32位能表示的数据范围42亿9千万。如果给每个设备都分配一个唯一的IP地址,意味着世界上的设备就不能超过42亿9千万。随着网络的发展,现在世界上的设备越来越多,已经超过了42亿9千万.
让每个设备都有唯一的IP地址,不现实(尤其是现在移动互联网的兴起)。如何解决这样的问题?

  1. 动态分配IP地址:让每个设备连上网的时候,才有IP,不联网的时候就没IP(这个IP就可以给别人用),但是这个方案不能从根本上解决问题(设备没有减少, IP地址也没有增加…)治标不治本。
  2. NAT机制:让多个设备共用同一个IP(外网IP)[当前网络环境]

运营商的路由器这里就会修改IP数据报,从内网发出去的源IP改成外网IP,带有这样功能的路由器设备也称为NAT设备

把网络分成了内网(局域网)和外网(广域网),要求外网IP必须表示唯一的设备.
同时内网中的若干个设备,可以共用同一个外网IP 。这个时候每个外网IP都可能表示着几千个,甚至上万个设备.IP地址的压力就缓解了很多了。

NAT机制,把IP分成了内网和外网,也就隐含了一个重要的结论:
对于一个外网IP,可以在互联网的任意位置都能访问到;
对于一个内网IP,只能在当前局域网内部访问.局域网1的设备,不能使用内网IP访问局域网2的设备;(内网IP可以重复出现,只有在同一个局域网中才是唯一的)。
NAT IP转换过程:

  • NAT路由器将源地址从10.0.0.10替换成全局的IP 202.244.174.37;
  • NAT路由器收到外部的数据时,又会把目标IP从202.244.174.37替换回10.0.0.10;
  • 在NAT路由器内部,有一张自动生成的,用于地址转换的表;当 10.0.0.10 第一次向 163.221.120.9 发送数据时就会生成表中的映射关系;同时NAT会记录这个映射关系.保证服务器返回的数据,再按照这个映射关系给还原回去。

例:

这个源端口是系统随机分配的.

NAT只是续命了一波,是不能从根本上解决问题的,IPv6才是从根本上解决了IP地址不够用的问题:
lPv6在报头中使用了一个更长的字段来表示IP地址:16个字节,128位(2* 128)
(2^32 * 2^32 * 2^32 * 2^32),号称IPv6可以给地球上的每个沙子都分配一个IP地址。

每个数字都是一个十六进制的数字(4bit),每个冒号分割了2个字节。

既然IPv6这么香,为啥现在还是在用IPv4 + NAT呢?
这里最大的问题在于,IPv6和IPv4是不兼容的。对于一个设备来说,支持IPv4和IPv6需要两个截然不同的机制,现有的大量的网络设备(路由器…)很可能都是只支持IPv4,不支持IPv6,因此,要想升级到IPv6,就得更换设备,换设备就得加钱!

6.2 路由选择

路由选择也就是规划路径.两个设备之间,要找出一条通道能够完成传输的过程。
要想找出通道,前提是,得先认识路。IP协议的路由选择也是类似的.
IР数据报中的目的地址就表示了这个包要发到哪里去,这个目的地址如果当前路由器直接认识,就直接告诉你路了,如果当前路由器不认识,就会告诉你一个大概的方向,让你走到下一个路由器的时候再来问问,依次往后走,其实也是在离目标越来越近.这个时候就总会遇到一个认识这个地址的路由器,于是就可以具体的转发过去了。
有的时候,不光遇到了一个认识这个地址的路由器,并且他还认识多个路,就可以选一个更合适的路了。

什么路由器"认识”这个IP地址?
在路由器内部维护了一个数据结构—路由表(由一系列专门的路由表生成算法自动生成一波,还可以手动配置)。路由表里面就记录了一些网段信息(网络号),(目的IP就再这些网络号中匹配),以及每个网络号对应的网络接口(网络接口其实就对应到路由器里面具体的端口)。

七、数据链路层主要协议—以太网

以太网这个协议不仅仅规定了数据链路层的内容,也规定了物理层的内容

以太网数据帧格式:

通过6个字节来表示源地址和目的地址(此处的源地址/目的地址使用mac地址/物理地址),这个就要比IPv4更长,长了6w多倍,这里的地址称为“mac地址",mac地址做到了每个设备都是唯一的(每个网卡都是唯一的),是在网卡出厂的时候就写死了。
物理地址,也不是完全不能改,有些网卡是支持手动配置的。

已经有IP地址了为啥还要物理地址?

当年网络层协议和数据链路层协议,是各自独立研发出来的!
mac地址和IP地址,就有点重复了.(按理来说一套地址就够了)。
现在的现状,就是当前mac地址和IP地址同时使用.表示不同的功能:
IP用来表示一次传输过程中的起点和终点.(不考虑NAT的情况,一个IP数据报中的源IP和目的IP是固定的);
mac用来表示传输过程中,任意两个相邻节点之间的地址(一个以太网数据帧,在每次转发过程中,源mac和目的 mac都会改变);

CRC:帧尾就是一个基于CRC算法的校验和.(循环冗余算法)把数据的每个字节依次进行累加.(溢出就溢出)

MTU

MTU:一个以太网数据帧能够承载的数据范围。这个范围取决于硬件设备 ,以太网是和硬件也密切相关的.不同的硬件设备对应的数据链路层协议,可能又不一样,MTU也不相同 。数据链路层考虑的是相邻节点之间的数据传输 ,考虑这个细节的时候就需要关注到交通工具是啥 ,不同的交通工具,能够搭载的数据量就不太相同.

如果数据报超过了MTU咋办?
IP层能够分包,IP层的分包其实不是给IP的报头64k 准备的,更多的是为了适应数据链路层的MTU。

MSS

MSS: TCP中在IP不分包的前提下,能搭载多少载荷。
MTU:也取决于TCP和IP的报头(报头都是变长的)

MSS分包的开销还是有的.MSS 对于TCP起到一定的提示效果.当当前传输TCP的数据长度不超过MSS的时候,是属于最高效的状态。

ARP

ARP报文并不是用来传输数据的.只是起到一个辅助的效果.路由器这样的设备在转发数据的时候,首先拿到的是一个IP地址(目的 IP),通过IP地址来决定接下来这个数据咋走(从哪个端口出去,发到哪个设备上),因此就得决定接下来封装的以太网数据帧,目的mac是啥.需要根据ARP协议建立起IP-> mac这样的映射关系。

当设备启动的时候,就会向局域网中广播ARP报文,每个设备收到之后都会给出一个应答.应答的信息中就包含了自己的IP和mac,发起广播的那一方,就可以根据这些回应,建立起这个映射表了。

DNS–应用层重点协议

DNS,即Domain Name System,域名系统。DNS是一整套从域名映射到IP的系统。
TCP/IP中使用IP地址来确定网络上的一台主机,但是IP地址不方便记忆,且不能表达地址组织信息,于是人们发明了域名,并通过域名系统来映射域名和IP地址www.sogou.com就是域名,域名和IP地址之间,是一个——对应的关系

网络通信发送数据时,如果使用目的主机的域名,需要先通过域名解析查找到对应的IP地址:

  • 域名解析的过程,可以简单的理解为:发送端主机作为域名系统树形结构的一个子节点,通过域名信息,从下到上查找对应IP地址的过程。如果到根节点(根域名服务器)还找不到,即找不到该主机。
  • 域名解析使用DNS协议来传输数据。DNS协议是应用层协议,基于传输层UDP或TCP协议来实现。

网络原理:TCP/UDP相关推荐

  1. 网络原理TCP/UDP

    文章目录 TCP TCP协议段格式 可靠机制 确认应答机制 超时重传机制 连接管理机制 建立连接--三次握手 断开连接--四次挥手 常见问题 效率机制 滑动窗口 流量控制机制 拥塞控制 延迟应答 捎带 ...

  2. 网络编程(Tcp/Udp实现聊天、文件上传)

    网络编程 1.1 概述 计算机网络是指将位置不同的多台[计算机 通过通信线路连接起来,实现资源共享和信息传递的计算机系统 1.2 网络通信的要素 ip和端口 网络通信协议(tcp/udp) 1.3 I ...

  3. 网络编程---TCP/UDP套接字编程原理

    本篇介绍的是Linux下的网络编程,故有些接口是不适用于Windows的,但是具体概念和实现方法是大体一致的 本篇重在讲解原理,具体实现请戳这里->UDP套接字编程实现 介绍 网络编程套接字(s ...

  4. 【Java】网络编程——TCP/UDP网络对讲机

    目录 前言 OSI概述 传输层 端口 Socket InetAddress类 UDP协议间的通信 UDP协议相关类 UDP 发送端 UDP 接收端 + 发送端 UDP 聊天 + 全局广播 UDP发送大 ...

  5. 网络编程-tcp/udp

    Java网络编程 计算机网络就是通过传输介质.通信设施和网络协议,把分散在不同地点的计算设备互连起来,实现资 源共享和数据传输的系统. TCP/IP协议簇 TCP/IP协议栈是一系列网络协议的总和,是 ...

  6. 网络编程 TCP/UDP

    网络编程 打电话–连接–接了–通话->TCP连接 发短信------发送了就完事了---->UDP连接 网络编程的目的: 传播交流信息,数据交换.资源共享.通信 想要打到这个效果需要什么: ...

  7. 网络调试工具TCP/UDP socket的使用

    TCP/UDP socket调试工具的使用,可以在想服务器发送命令,或者查看服务器返回的数据. 打开界面后选择左边客户端还是服务端,使用TCP还是udp,点击创建相应的模拟环境.

  8. 【网络原理】UDP和TCP协议重点知识汇总

    目录 1.UDP协议: 2.TCP协议: 1.UDP协议: UDP协议的特点:无连接.不可靠传输.面向数据报和全双工. UDP报文最大长度是2个字节,2个字节表示的范围就是0~65535,也就是64k ...

  9. 网络原理 | TCP/IP中的连接管理机制 重要协议与核心机制

    应用层.传输层.网络层.数据链路层.物理层 一.应用层协议 应用层 是程序猿最最经常打交道的一层 其他四层,都是操作系统.驱动.硬件,实现好了的,咱们不需要管 (除非你是系统工程师,驱动开发工程师-- ...

  10. [windows网络编程]tcp/udp编程初步详解-转

    #pragma comment (lib,"ws2_32.lib") #include <Winsock2.h> #include <stdio.h> 如你 ...

最新文章

  1. 图森未来完成2.15亿美元D轮融资,将拓展无人驾驶运输服务
  2. python3 url 提取 ip 端口 协议
  3. 商业领袖摘下帽子才能炼成极致
  4. Winform中使用zxing和Graphics实现自定义绘制二维码布局
  5. Drupal 7 主题模板概述
  6. java完成键盘动作_java实现鼠标和键盘动作后台监听
  7. VS Code 的 python 扩展发布,绘图功能更强大
  8. Android源码大放送(实战开发必备)
  9. python直方图规定化_OpenCV 直方图处理:直方图均衡和规定化(匹配)
  10. proteus——采用 LM317 的稳压电源
  11. Word如何在表格里面插入行或者列
  12. Python使用quote、unquote、urlencode、urldecode对url编码解码
  13. 算法快学笔记(九):红黑二叉树
  14. 30 行代码实现蚂蚁森林自动收能量(附送源码)
  15. 中国互联网20周年谈GITC 2014
  16. JavaScript的DOM知识点总结
  17. Grammarly使用说明
  18. 【人物访谈·01期】关于对数据分析前辈的一次行业探讨
  19. Android 获取手机IP
  20. java考了80多分,八省联考成绩出炉,学生们表示“有些崩溃”,你考了多少分

热门文章

  1. windows中合并磁盘
  2. Css中路径data:image/png;base64的用法详解 (转载)
  3. DDoS攻击费用和防御费用对比
  4. python编程认证找工作时有什么作用?
  5. Excel动态图制作
  6. Linux笔记 No.17---(磁盘分区fdisk,gdisk,parted、格式化mkfs,mke2fs、挂载mount、卸载umount、df、du、lsof命令)
  7. 11月区块链行业大事件盘点:政策利好与监管趋严并举,你想了解的都在这
  8. 低成本快速上链 智臻链开放联盟网络正式对外开放
  9. 职称计算机的考题整理分享,是2011年的,支持自学成才的人
  10. blacklist regions:NGS测序数据中的黑名单