1.基本概念
本文借鉴即时通讯网文章
(1) 网络通信
先上结论:网络传输的内容,如下图
<1> 五层架构
-
tcp的标头,放在ip的数据包中
-
ip的标头,放在以太网的数据包中
链接层
-
一组电信号(0和1组成的一组数组)构成一个数据包,叫做"帧"(Frame)。每一帧分成两个部分:标头(Head)和数据(Data)。
-
连入网络的所有设备,都必须具有"网卡"接口。数据包必须是从一块网卡,传送到另一块网卡。网卡的地址,就是数据包的发送地址和接收地址,这叫做MAC地址。
-
相同子网络内的mac地址采用的是广播方式发送数据,接收方会读取标头去判断和自己的mac地址是否一致,如果一致,则读取;否则.丢弃
网络层
-
不同子网络的,需要通过"网络地址"去区分两台机器是否属于同一个子网络(实现路由), 因此,规定网络地址的协议--IP协议产生,目前,广泛采用的是IP协议第四版,简称IPv4。IPv4这个版本规定,网络地址由32个二进制位组成; 互联网上的每一台计算机,都会分配到一个IP地址。这个地址分成两个部分,前一部分代表网络,后一部分代表主机。但是我们不知道每个机器的网络地址中,哪几位表示网络,哪几位表示主机,因此,为了解决这个问题---子网掩码产生,它在形式上等同于IP地址,也是一个32位二进制数字,它的网络部分全部为1,主机部分全部为0。比如,IP地址172.16.254.1,如果已知网络部分是前24位,主机部分是后8位,那么子网络掩码就是11111111.11111111.11111111.00000000,写成十进制就是255.255.255.0。具体怎么用呢?用两台机器的ip与子网掩码分别进行&操作,得到结果相同,则在同一个子网络;否则,不在同一个子网络
-
到这,我们知道网络传输最起码要两个地址,一个是mac地址,一个是网络地址(也就是ip地址);可是,ip地址我们能知道,但是mac地址是和网卡相关的,我们并不知道,这怎么办呢?--ARP协议产生,它能够从IP地址得到MAC地址。
|场景|数据包中接收方的地址| |-|-| |同一个子网络|对方的MAC地址,对方的IP地址| |非同一个子网络|网关的MAC地址,对方的IP地址|
-
第一种情况: 如果两台主机不在同一个子网络,那么事实上没有办法得到对方的MAC地址,只能把数据包传送到两个子网络连接处的 "网关"(gateway),让网关去处理;
-
第二种情况: 如果两台主机在同一个子网络,那么我们可以用ARP协议,得到对方的MAC地址。ARP协议也是发出一个数据包(包含在以太网数据包中),其中包含它所要查询主机的IP地址,以及mac地址,但在对方的MAC地址这一栏,填的是FF:FF:FF:FF:FF:FF,表示这是一个"广播"地址。它所在子网络的每一台主机,都会收到这个数据包,从中取出IP地址,与自身的IP地址进行比较。如果两者相同,都做出回复,向对方报告自己的MAC地址,否则就丢弃这个包。
传输层
-
到这,我们获取到了mac和ip地址,就可以进行通信了,但是又有一个问题,比如你开了两个程序,一个是看视频;另外一个是聊天,当一个数据包从互联网上发来的时候,你怎么知道,它是表示网页的内容,还是表示在线聊天的内容?--端口port产生,表示这个数据包到底供哪个程序(进程)使用。 端口是0到65535之间的一个整数,正好16位,0到1023的端口被系统占用,用户只能选用大于1023的端口。Unix系统管主机+端口,叫做"套接字"(socket);ip地址+端口,也是需要规范协议的--TCP|UDP协议产生
-
UDP协议的优点是比较简单,容易实现,但是缺点是可靠性较差,一旦数据包发出,无法知道对方是否收到。
-
TCP协议就诞生了。这个协议非常复杂,但可以近似认为,它就是有确认机制的UDP协议,每发出一个数据包都要求确认。如果有一个数据包遗失,就收不到确认,发出方就知道有必要重发这个数据包了。
应用层
- 应用程序收到"传输层"的数据,接下来就要进行解读。由于互联网是开放架构,数据来源五花八门,必须事先规定好格式,否则根本无法解读。"应用层"的作用,就是规定应用程序的数据格式。 举例来说,TCP协议可以为各种各样的程序传递数据,比如Email、WWW、FTP等等。那么,必须有不同协议规定电子邮件、网页、FTP数据的格式,这些应用程序协议就构成了"应用层"。这是最高的一层,直接面对用户。它的数据就放在TCP数据包的"数据"部分。
<2> 静态和动态IP
通常你必须做一些设置。有时,管理员(或者ISP)会告诉你下面四个参数,你把它们填入操作系统,计算机就能连上网了:注意:无论静态还是动态,都需要把下面的四个地址确定
- 本机的IP地址;
- 子网掩码;
- 网关的IP地址;
- DNS的IP地址。
-
静态IP : 由于它们是给定的,计算机每次开机,都会分到同样的IP地址,所以这种情况被称作"静态IP地址上网"。但是,这样的设置很专业,普通用户望而生畏,而且如果一台电脑的IP地址保持不变,其他电脑就不能使用这个地址,不够灵活。
-
动态IP : 计算机开机后,会自动分配到一个IP地址,不用人为设定。它使用的协议叫做DHCP协议; DHCP协议规定 : 每一个子网络中,有一台计算机负责管理本网络的所有IP地址,它叫做"DHCP服务器"。新的计算机加入网络,必须向"DHCP服务器"发送一个"DHCP请求"数据包,申请IP地址和相关的网络参数。(DHCP是应用层协议)
-
1)最前面的"以太网标头": 设置发出方(本机)的MAC地址和接收方(DHCP服务器)的MAC地址。前者就是本机网卡的MAC地址,后者这时不知道,就填入一个广播地址:FF-FF-FF-FF-FF-FF。
-
2)中间的"IP标头": 设置发出方的IP地址和接收方的IP地址。这时,对于这两者,本机都不知道。于是,发出方的IP地址就设为0.0.0.0,接收方的IP地址设为255.255.255.255。
-
3)后面的"UDP标头": 设置发出方的端口和接收方的端口。这一部分是DHCP协议规定好的,发出方是68端口,接收方是67端口。
这样,DHCP服务器读取到这个包,发现发出方IP是0.0.0.0,且接收方IP是255.255.255.255,就知道是给DHCP服务器的,因此,他会读取这个包,然后把分配给发出方的网络参数都返回给发出方,这样,新机器就知道了自己的IP地址,子网掩码,网关地址,DNS服务器等参数
<3> DNS协议 -- 附加案例
-
本机的IP地址:192.168.1.100;
-
子网掩码:255.255.255.0;
-
网关的IP地址:192.168.1.1;
-
DNS的IP地址:8.8.8.8。
比如,我们现在打开浏览器去访问google地址,这意味着浏览器要向google发送一个网页请求的数据包
-
我们知道发送数据包,必须要知道对方的IP地址。现在,我们只知道网址www.google.com,但是不知道它的IP地址。DNS协议可以帮助我们,将这个网址转换成IP地址。已知DNS服务器为8.8.8.8,于是我们向这个地址发送一个DNS数据包(53端口)。
-
然后,DNS服务器做出响应,告诉我们Google的IP地址是172.194.72.105。于是,我们知道了对方的IP地址。
-
接下来,我们要根据子网掩码去判断本机IP和google的IP是否在同一个子网络,计算发现,不在同一个子网络下面,因此需要通过网关去进行转发数据,那么就需要本机发送数据包给网关192.168.1.1,然后经过网关去转发到google的服务器上,最后得到响应,再用TCP协议发回来
浏览器用的是HTTP协议,那么他的整个数据包结构如下:
GET / HTTP/1.1
Host: [url=http://www.google.com]www.google.com[/url]
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 6.1) ......
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8
Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3
Cookie: ... ...
→ 我们假定这个部分的长度为4960字节,它会被嵌在TCP数据包之中。
→ TCP数据包需要设置端口,接收方(Google)的HTTP端口默认是80,发送方(本机)的端口是一个随机生成的1024-65535之间的整数,假定为51775。TCP数据包的标头长度为20字节,加上嵌入HTTP的数据包,总长度变为4980字节。
→ IP数据包嵌入以太网数据包。以太网数据包需要设置双方的MAC地址,发送方为本机的网卡MAC地址,接收方为网关192.168.1.1的MAC地址(通过ARP协议得到)。以太网数据包的数据部分,最大长度为1500字节,而现在的IP数据包长度为5000字节。
→ 因此,IP数据包必须分割成四个包。因为每个包都有自己的IP标头(20字节),所以四个包的IP数据包的长度分别为1500、1500、1500、560。(最终是5060)
Google服务器必须全部收到这四个数据包,会根据IP标头的序号进行排序,然后读取里面的http请求才可以返回响应
<4> 快速理解TCP协议
先明确一个事情,路由器就是基于IP协议,路由器内部有一张路由表,规定了 A 段 IP 地址的数据包走出口一,B 段地址走出口二,……通过这套"指路牌",实现了数据包的转发。IP 协议只是一个地址协议,并不保证数据包的完整。如果路由器丢包(比如缓存满了,新进来的数据包就会丢失),就需要发现丢了哪一个包,以及如何重新发送这个包。这就要依靠 TCP 协议。 简单说,TCP 协议的作用是,保证数据通信的完整性和可靠性,防止丢包。
1° TCP数据包的大小
以太网数据包(packet)的大小是固定的,最初是1518字节,后来增加到1522字节。其中, 1500 字节是负载(payload),22字节是头信息(head)。IP 数据包在以太网数据包的负载里面,它也有自己的头信息,最少需要20字节,所以 IP 数据包的负载最多为1480字节。
TCP 数据包在 IP 数据包的负载里面。它的头信息最少也需要20字节,因此 TCP 数据包的最大负载是 1480 - 20 = 1460 字节。由于 IP 和 TCP 协议往往有额外的头信息,所以 TCP 负载实际为1400字节左右。因此,一条1500字节的信息需要两个 TCP 数据包。HTTP/2 协议的一大改进, 就是压缩 HTTP 协议的头信息,使得一个 HTTP 请求可以放在一个 TCP 数据包里面,而不是分成多个,这样就提高了速度。
2° TCP数据包的编号
一个包1400字节,那么一次性发送大量数据,就必须分成多个包。比如,一个 10MB 的文件,需要发送7100多个包。发送的时候,TCP 协议为每个包编号(sequence number,简称 SEQ),以便接收的一方按照顺序还原。万一发生丢包,也可以知道丢失的是哪一个包。 第一个包的编号是一个随机数。为了便于理解,这里就把它称为1号包。假定这个包的负载长度是100字节,那么可以推算出下一个包的编号应该是101。这就是说,每个数据包都可以得到两个编号:自身的编号,以及下一个包的编号。接收方由此知道,应该按照什么顺序将它们还原成原始文件。
当前包的编号是45943,下一个数据包的编号是46183,由此可知,这个包的负载是240字节
3° TCP数据包的组装
-
收到 TCP 数据包以后,组装还原是操作系统完成的。应用程序不会直接处理 TCP 数据包。对于应用程序来说,不用关心数据通信的细节。除非线路异常,收到的总是完整的数据。应用程序需要的数据放在 TCP 数据包里面,有自己的格式(比如 HTTP 协议)。
-
TCP 并没有提供任何机制,表示原始文件的大小,这由应用层的协议来规定。比如,HTTP 协议就有一个头信息Content-Length,表示信息体的大小。对于操作系统来说,就是持续地接收 TCP 数据包,将它们按照顺序组装好,一个包都不少。
-
操作系统不会去处理 TCP 数据包里面的数据。一旦组装好 TCP 数据包,就把它们转交给应用程序。TCP 数据包里面有一个端口(port)参数,就是用来指定转交给监听该端口的应用程序。
-
系统根据 TCP 数据包里面的端口,将组装好的数据转交给相应的应用程序。上图中,21端口是 FTP 服务器,25端口是 SMTP 服务,80端口是 Web 服务器。
-
应用程序收到组装好的原始数据,以浏览器为例,就会根据 HTTP 协议的 Content-Length 字段正确读出一段段的数据。这也意味着,一次 TCP 通信可以包括多个 HTTP 通信。
4° TCP如何保证数据的完整性呢
前面说过,每一个数据包都带有下一个数据包的编号。如果下一个数据包没有收到,那么 ACK 的编号就不会发生变化。
举例来说,现在收到了4号包,但是没有收到5号包。ACK 就会记录,期待收到5号包。过了一段时间,5号包收到了,那么下一轮 ACK 会更新编号。如果5号包还是没收到,但是收到了6号包或7号包,那么 ACK 里面的编号不会变化,总是显示5号包。这会导致大量重复内容的 ACK。 如果发送方发现收到三个连续的重复 ACK,或者超时了还没有收到任何 ACK,就会确认丢包,即5号包遗失了,从而再次发送这个包。通过这种机制,TCP 保证了不会有数据包丢失。
Host B 没有收到100号数据包,会连续发出相同的 ACK,触发 Host A 重发100号数据包
(2) 三种通讯模型
BIO、NIO和AIO这三个概念分别对应三种通讯模型: 阻塞、非阻塞、非阻塞异步,具体这里就不详细写了。网上好多博客说Netty对应NIO,准确来说,应该是既可以是NIO,也可以是AIO,就看你怎么实现。
这三个概念的区别如下:
- 1)BIO:一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理,线程开销大。
- 2)NIO:一个请求一个线程,客户端发送的连接请求会注册到多路复用器上,多路复用器轮询到该连接有I/O请求时才启动一个线程进行处理;
- 3)AIO:一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
通俗地概括一下就是:
- 1)BIO是面向流的,NIO是面向缓冲区的;
- 2)BIO的各种流是阻塞的,而NIO是非阻塞的;
- 3)BIO的Stream是单向的,而NIO的channel是双向的。