网络-这次讲清楚

149 阅读21分钟

之前写过关于的Http 相关的使用,最近想巩固下网络相关的知识,那就在这次讲清楚

计算机网络


利用通信线路将地理上分散具有独立功能计算机和通信设备以不同的形式链接起来,以完善的软件和相关协议进行数据共享和信息传递计算机网络从覆盖范围上划分可以分为三类:

  1. 局域网: LAN(作用范围一般为几米到几十公里)
  2. 城域网: 界于 WAN 与 LAN 之间
  3. 广域网: 作用范围一般为几十到几千公里

当然计算机网络划分不止这一种分类方式,可以按拓扑结构分类(总线型、环型、星型、网状)、还可 以按按信息的交换方式(电路交换、报文交换、报文分组交换)来分等等方式;

计算机网络体系结构

OSI 七层模型

开放系统互连参考模型 (Open System Interconnect 简称 OSI)是国际标准化 组织(ISO)和国际电报电话咨询委员会(CCITT)联合制定的开放系统互连参考模型, 为开放式互连信息系统提供了一种功能结构的框架。其目的是为异种计算机互连 提供一个共同的基础和标准框架,并为保持相关标准的一致性和兼容性提供共同 的参考。这里所说的开放系统,实质上指的是遵循 OSI 参考模型和相关协议能够 实现互连的具有各种应用目的的计算机系统。 OSI 采用了分层的结构化技术,共分七层,物理层、数据链路层、网络层、 传输层、会话层、表示层、应用层。

TCP/IP 模型

OSI 模型比较复杂且学术化,所以我们实际使用的 TCP/IP 模型,共分 4 层, 链路层、网络层、传输层、应用层。两个模型之间的对应关系如图所示:

image.png

tcp和ip 网络传输的数据

每个分层中,都会对所发送的数据附加一个首部,在这个首部中包含了该层 必要的信息,如发送的目标地址以及协议相关信息。通常,为协议提供的信息为 包首部,所要发送的内容为数据。在下一层的角度看,从上一层收到的包全部都 被认为是本层的数据。网络中传输的数据包由两部分组成:一部分是协议所要用到的首部,另一部 分是上一层传过来的数据。首部的结构由协议的具体规范详细定义。在数据包的 首部,明确标明了协议应该如何读取数据。反过来说,看到首部,也就能够了解 该协议必要的信息以及所要处理的数据

image.png

  • 应用程序处理(应用层) 首先应用程序会进行编码处理,这些编码相当于 OSI 的表示层功能; 编码转化后,邮件不一定马上被发送出去,这种何时建立通信连接何时发送数据的管理功能,相当于 OSI 的会话层功能。

  • TCP 模块的处理(传输层) TCP 根据应用的指示,负责建立连接、发送数据以及断开连接。TCP 提供将应用层发来的数据顺利发送至对端的可靠传输。为了实现这一功能,需要在应用层数据的前端附加一个 TCP 首部。

  • IP 模块的处理)(网络层)

IP 将 TCP 传过来的 TCP 首部和 TCP 数据合起来当做自己的数据,并在 TCP 首部的前端加上自己的 IP 首部。IP 包生成后,参考路由控制表决定接受此 IP 包的路由或主机。

  • 网络接口(以太网驱动)的处理(链路层)

从 IP 传过来的 IP 包对于以太网来说就是数据。给这些数据附加上以太网首部并进行发送处理,生成的以太网数据包将通过物理层传输给接收端。

  • 网络接口(以太网驱动)的处理(链路层)

主机收到以太网包后,首先从以太网包首部找到 MAC 地址判断是否为发送给自己的包,若不是则丢弃数据。

如果是发送给自己的包,则从以太网包首部中的类型确定数据类型,再传给相应的模块,如 IP、ARP 等。这里的例子则是 IP 。

  • IP 模块的处理(网络层)

IP 模块接收到 数据后也做类似的处理。从包首部中判断此 IP 地址是否与自己的 IP 地址匹配,如果匹配则根据首部的协议类型将数据发送给对应的模块,如 TCP、UDP。这里的例子则是 TCP。

另外吗,对于有路由器的情况,接收端地址往往不是自己的地址,此时,需要借助路由控制表,在调查应该送往的主机或路由器之后再进行转发数据。

  • TCP 模块的处理(传输层)

在 TCP 模块中,首先会计算一下校验和,判断数据是否被破坏。然后检查是否在按照序号接收数据。检查端口号,确定具体的应用程序。数据被完整地接收以后,会传给由端口号识别的应用程序

  • 应用程序的处理(应用层)

接收端应用程序会直接接收发送端发送的数据。通过解析数据,展示相应的内容。

TCP 和 UDP

在上述表格中,网际协议 IP 是 TCP/IP 中非常重要的协议。负责对数据加上 IP 地址(有发送它的主机的地址(源地址)和接收它的主机的地址(目的地址)) 和其他的数据以确定传输的目标。而 TCP 和 UDP 都是传输层的协议,传输层主要为两台主机上的应用程序提供端到端的通信。 但是 TCP 和 UDP 最不同的地方是,TCP 提供了一种可靠的数据传输服务,TCP 是面向连接的,也就是说,利用 TCP 通信的两台主机首先要经历一个建立连接的 过程,等到连接建立后才开始传输数据,而且传输过程中采用“带重传的肯定确 认”技术来实现传输的可靠性。TCP 还采用一种称为“滑动窗口”的方式进行流量控 制,发送完成后还会关闭连接。所以 TCP 要比 UDP 可靠的多。 UDP(User Datagram Protocol 的简称, 中文名是用户数据报协议)是把数 据直接发出去,而不管对方是不是在接收,也不管对方是否能接收的了,也不需 要接收方确认,属于不可靠的传输,可能会出现丢包现象,实际应用中要求程序 员编程验证。

我们一些常见的网络应用基本上都是基于 TCP 和 UDP 的,这两个协议又会 使用网络层的 IP 协议。但是我们完全可以绕过传输层的 TCP 和 UDP,直接使用 IP,比如 Linux 中 LVS,甚至直接访问链路层,比如 tcpdump 程序就是直接和链 路层进行通信的。

image.png

MAC 地址和端口号

MAC 地址

我们常听说 MAC 地址和 IP 地址。 MAC 地址全称叫做媒体访问控制地址,也称为局域网地址(LAN Address), MAC 位址,以太网地址(Ethernet Address)或物理地址(Physical Address),由 网络设备制造商生产时写在硬件内部。MAC 地址与网络无关,也即无论将带有 这个地址的硬件(如网卡、集线器、路由器等)接入到网络的何处,都有相同的 MAC 地址,它由厂商写在网卡的 BIOS 里,从理论上讲,除非盗来硬件(网卡), 否则是没有办法冒名顶替的。

MAC 地址共 48 位(6 个字节)。前 24 位由 IEEE(电气和电子工程师协会) 决定如何分配,后 24 位由实际生产该网络设备的厂商自行制定。例如: FF:FF:FF:FF:FF:FF 或 FF-FF-FF-FF-FF-FF

ip地址

IP 地址(Internet Protocol Address)的全称叫作互联网协议地址,它的本义 是为互联网上的每一个网络和每一台主机配置一个唯一的逻辑地址,用来与物理 地址作区分。

所以 IP 地址用来识别 TCP/IP 网络中互连的主机和路由器。IP 地址基于逻 辑,比较灵活,不受硬件限制,也容易记忆。

IP 地址分为:IPv4 和 IPv6。我们这里着重讲的是 IPv4 地址,IP 地址是由 32 位的二进制数组成,它们通常被分为 4 个“8 位二进制数”,我们可以把它理解 为 4 个字节,格式表示为:(A.B.C.D)。其中,A,B,C,D 这四个英文字母表 示为 0-255 的十进制的整数。例:192.168.1.1

IP 地址和 MAC 地址之间的区别

对于网络中的一些设备,路由器或者是 PC 及而言,IP 地址的设计是出于 拓扑设计出来的,只要在不重复 IP 地址的情况下,它是可以随意更改的;而 MAC 地址是根据生产厂商烧录好的,它一般不能改动的,一般来说,当一台 PC 机的 网卡坏了之后,更换了网卡之后 MAC 地址就会变了。

在前面的介绍里面,它们最明显的区别就是长度不同,IP 地址的长度为 32 位,而 MAC 地址为 48 位。 它们的寻址协议层不同。IP 地址应用于 OSI 模型的网络层,而 MAC 地址 应用在 OSI 模型的数据链路层。 数据链路层协议可以使数据从一个节点传递到

相同链路的另一个节点上(通过 MAC 地址),而网络层协议使数据可以从一个 网络传递到另一个网络上(ARP 根据目的 IP 地址,找到中间节点的 MAC 地址, 通过中间节点传送,从而最终到达目的网络)。

分配依据不同。IP 地址的分配是基于我们自身定义的网络拓扑,MAC 地 址的分配是基于制造商。

端口号

在传输层也有这种类似于地址的概念,那就是端口号。端口号用来识别同一 台计算机中进行通信的不同应用程序。因此,它也被称为程序地址。 一台计算机上同时可以运行多个程序。传输层协议正是利用这些端口号识别 本机中正在进行通信的应用程序,并准确地将数据传输。

TCP 的三次握手四次分手

三次握手

image.png

  • 第一次握手:客户端将标志位 SYN 置为 1,随机产生一个值 seq=i,并将该数据包发送给服务器端,客户端进入 SYN_SENT 状态,等待服务器端确认。
  • 第二次握手:服务器端收到数据包后由标志位 SYN=1 知道客户端请求建立连接,服务器端将标志SYN 和 ACK 都置为 1,ack=i+1,随机产生一个值 seq=j,并将该数据包发送给客户端以确认连接请求,服务器端进入 SYN_RCVD 状态。
  • 第三次握手:客户端收到确认后,检查 ack 是否为 i+1,ACK 是否为 1,如果正确则将标志位 ACK置为 1,ack=i+1,并将该数据包发送给服务器端,服务器端检查 ack 是否为 j+1,ACK 是否为 1,如果正确则连接建立成功,客户端和服务器端进入 ESTABLISHED 状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。

相关信息可以可以通过Wireshark 来抓包,验证

为什么要三次握手

tcp 链接是全工位,可靠传输,就是既可以接受数据也可以发送数据,只有三次握手才能确定本次双方都是可以正常接受,并且确定对方身份,三次握手是保证数据可靠传输又能提高传输效率的最小次数。

四次分手

image.png

  • 一次分手:客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=j,此时,客户端进入FIN-WAIT-1(终止等待1)状态。

  • 二次分手:服务器收到连接释放报文,发出确认报文,ACK=1,ack=j+1,并且带上自己的序列号seq=K,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)

  • 三次分手: 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=j+1,seq=i,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=i,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

  • 四次分手:客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=i+1,而自己的序列号是seq=j+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

Java BIO 和NIO 实现网络数据交互处理

BIO

同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销;

image.png 工作机制:

  1. 服务器端启动一个 ServerSocket,注册端口,调用accpet方法监听客户端的Socket连接。
  2. 客户端启动 Socket对服务器进行通信,默认情况下服务器端需要对每个客户 建立一个线程与之通讯
  3. 每个Socket接收到,都会创建一个线程,线程的竞争、切换上下文影响性能
  4. 每个线程都会占用栈空间和CPU资源;‘
  5. 并不是每个socket都进行IO操作,无意义的线程处理;
  6. 客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。
  7. 对于简单单一程序bio 的效率要高于Nio

NIO

同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求就进行处理

image.png

工作机制:

image.png 每个 channel 都会对应一个 Buffer一个线程对应Selector , 一个Selector对应多个 channel(连接)程序切换到哪个 channel 是由事件决定的Selector 会根据不同的事件,在各个通道上切换Buffer 就是一个内存块 , 底层是一个数组数据的读取写入是通过 Buffer完成的 , BIO 中要么是输入流,或者是输出流, 不能双向,但是 NIO 的 Buffer 是可以读也可以写。

Java NIO系统的核心在于:通道(Channel)和缓冲区 (Buffer)。通道表示打开到 IO 设备(例如:文件、 套接字)的连接。若需要使用 NIO 系统,需要获取 用于连接 IO 设备的通道以及用于容纳数据的缓冲 区。然后操作缓冲区,对数据进行处理。简而言之,Channel 负责传输, Buffer 负责存取数据

Buffer 中的重要概念:

容量 (capacity) :作为一个内存块,Buffer具有一定的固定大小,也称为"容量",缓冲区容量不能为负,并且创建后不能更改。

限制 (limit):表示缓冲区中可以操作数据的大小(limit 后数据不能进行读写)。缓冲区的限制不能为负,并且不能大于其容量。 写入模式,限制等于buffer的容量。读取模式下,limit等于写入的数据量。

位置 (position):下一个要读取或写入的数据的索引。缓冲区的位置不能为 负,并且不能大于其限制

标记 (mark)与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法 指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这 个 position.

标记、位置、限制、容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity

image.png

通道(Channel)

image.png 通道(Channel):由 java.nio.channels 包定义 的。Channel 表示 IO 源与目标打开的连接。 Channel 类似于传统的“流”。只不过 Channel 本身不能直接访问数据,Channel 只能与 Buffer 进行交互。

[1] NIO 的通道类似于流,但有些区别如下:

通道可以同时进行读写,而流只能读或者只能写

通道可以实现异步读写数据

通道可以从缓冲读数据,也可以写数据到缓冲:

[2] BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)是双向的,可以读操作,也可以写操作。

[3] Channel 在 NIO 中是一个接口

选择器(Selector)

选择器(Selector) 是 SelectableChannle 对象的多路复用器,Selector 可以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector可使一个单独的线程管理多个 Channel。Selector 是非阻塞 IO 的核心

Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector(选择器)

Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个

Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。

只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程

避免了多线程之间的上下文切换导致的开销

linux 内核的网络通信

image.png 在Linux内核实现中,链路层协议靠网卡驱动来实现,内核协议栈来实现网络层和传输层。内核对更上层的应用层提供socket接口来供用户进程访问,数据的接受通过硬中断和软中断来处理网络数据的处理;

Linux下的IO复用(NIO)

  • select
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述副就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以 通过遍历fdset,来找到就绪的描述符。   select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select的缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低

  • poll
int poll (struct pollfd *fds, unsigned int nfds, int timeout);

select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述副就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以 通过遍历fdset,来找到就绪的描述符。   select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select的缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低

  • poll
int epoll_create(int size);

创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大,这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值,参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽

  • 总结 内核需要将消息传递到用户空间,都需要内核拷贝动作poll|同select|epoll epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。 内核需要将消息传递到用户空间,都需要内核拷贝动作poll|同select|epoll select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善。

从硬件角度看待数据传输

当网卡上收到数据以后,Linux中第一个工作的模块是网络驱动。 网络驱动会以DMA的方式把网卡上收到的帧写到内存里。再向CPU发起一个中断,以通知CPU有数据到达。第二,当CPU收到中断请求后,会去调用网络驱动注册的中断处理函数。 网卡的中断处理函数并不做过多工作,发出软中断请求,然后尽快释放CPU。ksoftirqd检测到有软中断请求到达,调用poll开始轮询收包,收到后交由各级协议栈处理。最后会被放到用户socket的接收队列中。   当网络上数据过来以后,CPU和操作系统通过中断机制得知此消息,计算机执行程序时,会有优先级的需求。比如,当计算机收到断电信号时(电容可以保存少许电量,供CPU运行很短的一小段时间),它应立即去保存数据,保存数据的程序具有较高的优先级。   一般而言,由硬件产生的信号需要cpu立马做出回应(不然数据可能就丢失),所以它的优先级很高。cpu理应中断掉正在执行的程序,去做出响应;当cpu完成对硬件的响应后,再重新执行用户程序

中断

image.png

中断是为了实现多道程序并发执行而引入的一种技术,中断的本质就是发生中断时需要操作系统介入开展管理工作,中断当前程序,去执行因外接数据的变化引起的cpu 处理机制,当数据处理完成之后回传到当前运行程序继续执行;