计算机网络知识随记之基础篇

原文连接
本篇主要讲解网络的基础知识,从tcp/ip协议栈讲起,不会讲解的太深,权做本身的笔记,有问题的地方但愿你们能够留言指出。
php

协议栈

这里主要讲解TCP/IP协议栈,再也不讲解其与OSI协议栈的区别。html

TCP/IP协议分为4层,由上至下为应用层、传输层、网络层和网络接口层。java

  • 应用层:定义上层应用能够直接使用的高级协议,如http、ftp等。
  • 传输层:定义控制数据传输的协议,用以保证数据的可靠性和顺序到达性等,如tcp、udp协议。
  • 网络层:定义不一样网络类型间通讯的协议,如IP协议用于实现网际路由,ICMP协议用于检测网络的畅通性,ARP协议用于获取设备MAC地址等。
  • 网络接口层:定义网络介质上的传输协议,和电气相关,如Ethernet协议、802.3协议等,主要由操做系统的网卡驱动程序实现。

四层由上到下逐层依赖,用户数据也在每一层被添加进不一样的头部,以便进行传输和解析,封装过程大体如图:
network-protocol-all.png
图1linux

下面我自上而下详细讲解下每一层的做用和数据封装内容。算法

应用层

该层主要涉及一些高级协议,像你们耳熟能详的http协议、ftp协议等等,每个协议都能单独开几篇文章来说了,这里就不赘述了。编程

传输层

该层主要是对应用层的数据进行分割和重装,提供端到端的服务。主要有UDP和TCP两种协议,下面分别讲解这两种协议。缓存

UDP

UDP即User Datagram,用户数据报协议,从名字上会联系到IP Datagram,即Ip数据报,二者也确实有关联。在UDP被并入TCP/UDP协议簇以前,是做为IP协议的上层抽象存在的,它的名字也是源于IP Datagram,前面加上User便有了端到端的意味。图2展现了UDP的包结构。安全

network-udp-protocol.png
图2服务器

UDP的首部只占用8个字节,顶部的12个字节的伪首部是用于计算UDP首部检验和。所谓“伪”是指这个首部只是拿来用下,并不出如今实际传送的数据中。UDP能够依据伪首部计算出来的检验和来判断数据包是否正确到达目的地,即若是传输过程当中该数据包中伪首部设计的数据发生变化,那么该数据包会被视为非法包,被丢弃。cookie

IP地址不是在IP层获取的吗?而它对上层的UDP不是应该透明吗?
分层只是一个逻辑上的理想化模型,在实际实现中,为了效率等,UDP的检验和其实就是经过IP层来计算获取的。

UDP是面向报文的,没有可靠性控制,没有拥塞控制,无链接,因此其开销小,但网络环境差或者发送数据过大时致使ip分片过多,致使发送率下降,影响程序的使用。为了提升UDP的发送率,应该尽可能使得UDP可使用一个IP数据报就能发送出去,这里就涉及到MTU(下文会详细讲解)。

UDP推荐传送的数据大小为1500(以太网的MTU)-20(ip数据报首部)-8(udp首部)=1472字节。

TCP

TCP即Transportation Control Protocol,传输控制协议,提供了一种可靠的面向链接的字节流传输层服务。TCP协议相对复杂,主要知识点有三次握手创建链接、四次握手关闭链接、滑动窗口协议、拥塞控制策略、Nagle算法等等,这里只作简单介绍,基本一带而过,感兴趣的能够去详细研究。图3展现了tcp报文段(segment)的格式。
network-tcp-protocol.png
图3

源端口号和目的端口号是用于区分进程用的,序号和确认号是用于握手的凭证。4bit的首位长度(单位是4Byte)代表tcp首部最大为15*4Byte=60byte。后面的标志位有几个要讲解下:

  • ACK: ACK=1表示该报文段中有确认号须要处理。
  • PSH: PSH=1表示该报文段中有数据须要处理。
  • RST: RST=1表示到目的端的链接出问题了,须要上层作出处理,如从新创建链接等。
  • SYN: SYN=1 ACK=0代表是创建链接请求报文段,SYN=1 ACK=1代表赞成创建链接报文。
  • FIN: FIN=1表示对端的数据已经发送完毕,要求释放链接。

窗口大小是用来实现滑动窗口协议的,用来加快tcp数据的传输。

下面咱们来看看创建链接的示意图。

network-tcp-connection.png

三次握手的过程上图已经详细地描述了,这里说一下第三次握手的必要性,即发送方在收到接收方的ack后又主动发送了一次ack给接收方。缘由是为了不一种异常状况:在网络不稳定的状况下,发送方发出的一个链接请求通过在某个网络中间节点滞留,等其到达接收端时正常的通讯早已结束,但接收方不知道,因此它会马上发送一个ack给发送方,若是此时没有第三次握手的确认,那么服务端会认为该链接有效,形成资源的浪费。

下面再看一下关闭连接的示意图。
network-tcp-fin.png

四次握手的过程上图已经详细的描述了,这里说一下TIME_WAIT这个状态,为何会有一个2MSL的时间存在?MSL 即Max Segment LifeTime,一个报文段的最长生存时间。2MSL即是用来保证A发送的最后一个ack能够到达B,若是没有到达B,B会超时重发ACK和FIN报文,此时A也能够收到该报文,而后从新发送ack,以保证四次握手的完整性。固然关于TIME_WAIT是许多服务器运维人员的心头痛,由于它会占据着一个端口不释放,浪费资源,后面再写文章专门聊下TIME_WAIT吧。

TCP的状态迁移见下图。

network-tcp-status.png

上面这张图基本阐述了每一个状态的迁移时机,这是一个有限状态机表现方式。看懂了这个图,tcp也算是入门了。

关于传输层想了解更多的话,能够好好阅读下这篇文章

网络层

网络层也常叫IP层,由于这一层最重要的协议就是IP协议,用于网际路由,提供不可靠无链接的数据报传送服务,该层的数据传送单位为IP数据报(IP datagram)

其协议格式以下图所示。
network-ip-protocol.png

这里简单介绍几个部分。

  • 首部长度同tcp同样,最大表示15*4Byte=60Byte。
  • 总长度(16bit)是指整个ip数据报的长度,表示最大值为64K,也就是说IP报是有最大传输限定值的。
  • 标识字段用于惟一的表示一次数据传输。
  • 标志位(3bit)用于表示是否能够分片或者是否有其余分片。
  • 片偏移(13bit)用于切片时的片偏移。

这里详细讲解下IP分片。IP数据报下一层要经过数据链路层封装成帧发送出去,但帧大小受网络设备电气属性影响是有限制的,即MTU,不一样的网络拓扑和设备的MTU也不一样,如以太网是1500字节。若是IP数据报大于MTU,那么它必须分片才能实现数据的传送,分片发生在各个网络设备上,在目的主机参照标识字段、标志位和片偏移来实现重组。值得注意的是,ip分片传输后,标识字段都被复制到每个分片上,而总长度也变为该片的长度。优势是IP数据报能够穿过复杂多变的网络环境,缺点是一个分片丢失,该数据报就发送失败,增大了丢包的几率。

网络层另外一个重要的协议是ICMP(Internet Control Message Protocol)协议,其提供了一套查找网络故障的机制,有差错报文和控制报文,可用于检查主机不可达、中间路由出问题、网络拥塞等问题,不一样的问题会返回不一样的错误类型。ICMP的功能只是报告问题而不能纠正错误。ping命令使用控制报文中的回显请求和应答来实现的,traceroute是使用差错报文中的TTL超时报文和目标不可达报文实现的。

网络层还有ARP和RARP协议用于在IP和MAC地址之间转换,这里就不详细讲解了。

另外这篇文章详细地讲述了IP数据报分片是如何在局域网和广域网中传递的,感兴趣的能够去详细看下。

网络接口层

tcp/ip的网络接口层对应OSI模型中的链路层和物理层,其传输的数据单位是帧(Frame)。在上层要发送的数据包的首部和尾部添加相关数据后封装成帧后发送出去, 其主要有以下三个做用:

  1. 为网络层接收和发送ip数据报(IP datagram)。
  2. 为arp发送请求和接收数据。
  3. 为rarp发送请求和接收数据。

网络接口层协议有Ethernet协议、802.3等等,以下图展现了这两种协议的组成部分。
network-link-protocol.png

该层首部一般包含目的和源地址,就是设备的mac地址,尾部是一个CRC校验码,用于保证数据的准确性。详细介绍能够去参考这篇文章

细心的同窗应该观察到中间传输的数据域是有长度限制的,如802.3是从38到1492,ethernet协议是从46到1500。这里数据长度的限制是由传输介质的物理特性决定的,若是传输的数据长度不在该范围内,则该帧会被丢弃。这里就是MTU的出处了。

MTU(Maximal Transmission Unit),最大传输单位值,便可以一次性传输数据的最大值,该值每每受传输介质约束。在以太网协议中,MTU值为1500.一个帧的构成为7字节前导同步吗+1字节帧开始定界符+6字节的目的MAC+6字节的源MAC+2字节的帧类型+1500+4字节的FCS(校验码),最大值为1526。其实经过抓包工具获取的一个帧最大倒是1514,缘由在于数据帧到达网卡后,在物理层网卡会先去掉前导同步码和帧开始定界符,以后根据FCS进行验证,若是失败则丢弃该帧,不然就检查目的MAC地址是否符合本身的接收条件(MAC地址和本身的匹配、广播地址等等),若是符合,将该帧交设备驱动程序作进一步的处理,这个时候抓包工具才能抓到包,此时的帧也被去除了校验码,因此最终抓到的帧大小为6+6+2+1500=1514。传输数据有最大值也有最小值,当上层传输的数据小于最小值时(ethernet时46),好比tcp三次握手时的ack返回仅有20(tcp头部)+20(ip头部)=40字节,小于最小值46字节。对于此种状况,网卡驱动程序会进行自动填充,但若是抓包工具若是先于驱动程序抓到该帧,那么其大小就要取决于抓包工具自己的显示,wireshark只是显示原大小。

上面对于链路层中的包长度进行了简单地说明,感兴趣的同窗能够去查看这篇文章

至此,TCP/IP的网络协议栈大体简单地进行了讲解,下面就再以问答的形式讲解几个知识点。

MTU是什么?MSS是什么?二者有什么关系吗?

答:MTU即Maximal Transmission Unit,最大传输单元,是指网络接口层中因为受传输介质的物理特性制约一次能够传送的最大字节数,如以太网中MTU为1500Byte。MTU能够由人为修改,从而达到最优网络传输效率。
MSS即Maximal Segment Size,最大段长度,是传输层中TCP报文段中数据段的最大长度,默认是536Byte。其存在的意义以下:

  • 防止传输数据太小形成资源浪费。好比发送数据每次都是1Byte,在每一层都会添加首部(20Byte的TCP首部和20Byte的IP首部),以后才会完成发送。也就是说传送的41Byte中,只有1Byte是有效数据,这明显形成了资源的浪费。
  • 防止传输数据过大形成传输效率下降。若是发送的数据过大,超过了MTU的值,那么在IP层就会出现分片现象,而接收方也要耗费更多的资源和时间来处理分片,若是在传输过程当中发生丢片,也会进一步增大网络开销。

有了MSS,TCP每次传输的数据都能被控制在一个合理的范围内,避免IP分片的发生,增大传输效率,最大化利用资源。

下图描述了MTU和MSS的关系,能够简单地理解为MTU=IP Header + TCP Header + MSS。
network-mtu-mss.jpg

下面这张图描述了TCP是如何在三次握手创建链接的过程当中和对方协商MSS大小的。注意MSS只会出如今SYN包中。

network-tcp-mss.jpg


什么是半链接?SYN Flood是怎么一回事?
答:所谓半链接,顾名思义就是尚未完成三次握手创建链接。具体是指服务端在收到客户端的SYN包后,会据今生成一个半链接的对象,并将其存储在一个半链接队列(SYN Queue)中进行维护。一旦收到客户端的ACK包后会将该对象从半链接队列(SYN Queue)转移到已链接队列(Accept Queue)中等待accept系统调用。一经accept调用,数据的传输和接收才会正式开始。队列确定是不能无限长的,由于每一个对象都会占据存储资源,内核配置中有两个参数对应这两个队列的长度。

  • /proc/sys/net/ipv4/tcp_max_syn_backlog,该参数指定了半链接队列的最大长度,队列达到该值时,后续的SYN请求会被拒绝。能够经过sysctl进行配置。
  • sk_max_ack_backlog,该参数指定了已链接队列的最大长度,队列达到该长度时,后续的链接请求会被拒绝。该参数通常是在listen系统调用中指定的,如listen(int sockfd,int backlog) 中第二个参数。

SYN FLood攻击即是利用了半链接队列的长度限制来完成攻击的。攻击策略为伪造大量SYN包发送给服务端,但不返回ACK包,致使服务端半链接队列被迅速占满,正常的链接被抛弃。服务端会有大量处于SYN_RECV状态的链接,其会尝试重发ACK包给实际不存在的客户端,致使CPU满负载,内存耗尽,从而达到攻击效果。

通常能够经过修改net.ipv4.tcp_synack_retries 来减小重试发送ack的次数、开启 net.ipv4.tcp_syncookies、调大net.ipv4.tcp_max_syn_backlog来进行防护。

下面两篇文章对半链接都进行了很详细的讲解,感兴趣的能够去查看:文章一文章二
阿里云在2014年春节期间遭遇过SYN Flood攻击,感兴趣的也能够去看下相关介绍


什么是粘包?怎么解决?
答:粘包是指服务端收到的数据出现不完整、混乱等状况。

粘包出现的缘由是TCP数据的“流”特性。所谓“流”是指tcp传输的数据并不会在逻辑上进行划分,好比客户端给服务端发送了A和B两份数据,tcp发送的时候并非分两次先发A数据,再发B数据,而是可能出现下面的状况:

  1. 先发送A和B的一部分,再发送B剩余部分
  1. 先发送A的一部分,再发送A剩余部分和B

可能有人会问,咱们在编程的时候不是直接调用了send(byte[])了吗?为何数据会出现这种混乱的发送状况,缘由有两个:

  1. 若是开启了Nagle算法,即tcp_no_delay是false,那么即使调用了send方法,tcp也不必定会当即将数据发送出去,而是会等待其余数据一同发送,这样作是为了最大化利用网络资源。
  1. 若是发送的数据过大,超过了MSS的大小,tcp会对数据进行分段发送,此时也不可能一次性将数据发送完毕。

这个时候服务端接收数据时该如何把A和B区分开呢?若是只是单纯地将一次接收的数据做为完整数据(A或B)处理,很容易出错。这里就引出了封包和拆包的概念。

所谓封包,就是将要传送的数据按照一个可辨识的结构传输,好比客户端和服务端约定传递的数据都听从 字节长度(4Byte)+实际数据 的规则,那么服务端在解析数据(即拆包)的时候,就能够先读取4Byte,获取到一个完整数据的实际长度,而后再读取该长度的数据,以后再交由上层处理。
关于封包和拆包,感兴趣的能够去看下面这篇文章


socket的close和shutdown有区别吗?

答: 有区别。

这个涉及到tcp关闭链接的方式,主要由两种:

  1. 一种是正常的经过4次握手关闭,这是优雅的方式,能够保证双方的数据都被接受。
  1. 一种是经过发送RST包的方式跳过4次握手快速关闭链接,这是暴力的方式,不安全。

close会直接关闭socket的文件描述符,包括输入和输出流,但shutdown只是在己方的输入输出流上作了一些人为的处理,好比丢弃了输入流中的数据,直接返回EOF结束符和向输出流写入东西时报错等等,但并不会关闭socket,因此对方的链接依然能够正常读写,要关闭仍是要调用close。

另外close只会关闭本进程的socket文件描述符,若是其还被其余进程调用,则实际不会被关闭。但shutdown却会影响全部使用了该socket的进程。无特殊状况,使用close关闭socket便可。
在shutdownOutput后,再写入时会报broken pipe的错误。对执行了close的socket读入时会报错:socket closed。详情能够参考这篇文章


flush有必要吗?

答:基本无必要。

咱们在使用文件流的时候,都会强调一下flush函数,有时若是不主动调用,会致使写入的数据没有写到磁盘上,形成数据丢失。那么socket编程中是否要注意flush呢?

实际上当咱们socketOutputStream.write(byte[])(java接口)的时候,数据已经发送出去,与flush无关。固然若是你在outputstream的外层套一层buffer,好比在SocketOutPutStream外层包一个BufferedOutPutStream,其内部会有本身的缓存,这个时候flush就有必要了,由于buffer至关于在jvm层作了一层缓存,直到调用flush函数,数据才会被写出。


进程在乎外中止的时候,其关联的socket会关闭吗?

答:会关闭。

在linux系统中,是回这样的。当进程意外停止或者被kill的时候,系统会直接调用相关socket的close函数,单方面关闭socket连接而且不等待对方关闭,会直接给对方发送RST包。能够参考这篇文章

相关文章
相关标签/搜索