一. 了解2个名词及语义: "OSI 7层参考模型"和"TCP/IP协议"
OSI 7层参考模型之所以叫它为参考模型 , 是因为它并没有被实现出来 , 它是软件工程学的东西 , 分层的目的就是为了解耦 , 让每层只做自己的事情 , 别的层该做的事情交给别的层去做 , 最终实现网络通讯的目的(参考图:OSI7层参考模型)
TCP/IP协议是基于OSI 7层参考模型已经实现的一种协议 , TCP/IP协议在最终实现时 , 是把OSI 7层参考模型分成了5层 , 这里面主要是把"应用层","表示层","会话层"缩到了"应用层"一起 , 顾名思义 , "应用层"由应用程序来提供 , "传输控制层" , "网络层" , "链路层" , "物理层" 这4层放到了操作系统的内核里面 , 以内核和应用程序划分了2个鸿沟 , 内核的东西由操作系统提供 , 剩下"应用层"就需要应用程序来提供了
到这里 , OSI 7层参考模型和TCP/IP协议这2个名词算是解释清楚了 , OSI 7层参考模型中理论上的东西就不聊了, 比如每层该干些什么啊之类的 , 我们进入重点 , 接下来聊聊已经实现了的TCP/IP协议.
TCP/IP协议总的来说就是"应用层","传输控制层","网络层","链路层","物理层"这么些层次的分法 , 比如说"应用层"的常见协议有HTTP协议,HTTPS协议,FTP协议,SSH协议等等这些应用程序当中使用到的协议 , "传输控制层"有TCP协议,UDP协议 , "网络层"有IP协议或者是关于IP的一些协议 , "链路层"有ARP协议 , "物理层"就是计算机编程里面硬件上面制定的一些协议了,比如编码解码器之类的 , 每个层有每层自己的相关协议来规定和约束一些工作的事情 , 层与层之间没有重叠 , 互相调用就可以了 , 这就实现就TCP/IP协议.
到这里 , TCP/IP协议这个名词的语义也解释清楚了
二. TCP/IP协议 之 应用层
我们先不去深挖TCP/IP协议的事 , 先来说说"应用层" , 应用程序使用的那些协议 , 比如说WEB工程师最常见的"HTTP协议 , SSH协议" , 我们先来看HTTP协议 , 那么什么是HTTP协议啊? 比如说我们的服务器和网址www.huangcuigang.cn的服务器请求页面的过程就要走一个HTTP协议 , 咱们用一个简单的方式来演示 , 使用LINUX操作系统 , 这里有一些琐碎的知识点和概念 , 一定要细心的观察演示的过程 , 后面的东西才能听懂 , 比如说先演示一个大家看不懂的一个事情:
[root@iZbp19r2vmlr853x5azt4rZ ~]# exec 8<> /dev/tcp/www.huangcuigang.cn/80
这里解读一下我们写下的这段字符串,
第一部分的字符串是"exec" , 它是一个命令(exec命令) , "exec"是"execute"单词的简写 , 顾名思义 , 就是执行的意思 , 这个命令简单了解一下就行 , 不是我们要演示的重点 , 简单知道是干什么的就行 ,
第二部分的字符串是一个数字8(这个数字可以随便写,不是必须为8)紧邻着<> , "<>"是Shell 输入/输出重定向 , 我们这里是把输入输出重定向到了一个数字8上了 , 也就是说这里的数字8可以输入也可以输出 ,
第三部分的字符串是一个路径 , 它是一个文件目录 ,
我们思考一下这段字符串的语义 , 当我们按下回车键时 , 在linux系统中 , 一切都是文件 , 比如说我们的打印机可以是一个文件 , 我们把一个东西对着这个文件写 , 那么打印机会把东西打印出来 , 我们的摄像头可以是一个文件 , 我们读取这个文件 , 就会把摄像头拍到的东西读出来 ,
如果我们与网址www.huangcuigang.cn建立TCP连接 , 它的连接也是一个文件 , 那么对着这个文件读写的话 , 就造成了对网址www.huangcuigang.cn的TCP连接的一种输入输出 , linux中一切皆文件 , 那么这段命令的语义就是 , 对着网址www.huangcuigang.cn的80端口建立一个TCP连接 , 并用一个数字8来代表这个TCP连接 , 且这个数字8代表的TCP连接可以输入也可以输出 ,
按下回车 , 终端上什么提示输出都没有 , 没提示就是最好的提示 , 说明成功了 , 到这里我们就创建好了一个数字8的TCP连接对象 , 通过这个数字8的TCP连接对象我们可以拿到输入流 , 也可以拿到输出流 , 这个理论一定要能明白
我们要演示的是HTTP协议 , 我们都知道HTTP协议是用来发送HTTP请求的 , 那么什么叫做HTTP协议?简单说HTTP协议就是一个基于TCP/IP通信协议来传递数据所制定的规范 , 只有发送数据方和接收数据方都遵守HTTP协议来发送数据 , 双方才能根据HTTP协议的约定去解析出来想要的数据
我们现在是在服务器上去演示 , 我们要把我们自己的双手来充当浏览器的角色 , 浏览器在请求一个网址时 , 必须要创造出一个基于HTTP协议请求的文本内容 , 我们来用echo命令打印一下这个文本内容 ( HTTP协议的所有约定细节这里就不细谈了 , 主要目的是演示讲解 )
小知识点 : echo 的 -e选项是激活转义字符 , 也就是解析\n为换行 , 而不是把\n作为字符串打印出来
[root@iZbp19r2vmlr853x5azt4rZ ~]# echo -e "GET / HTTP/1.0\n"
GET / HTTP/1.0
[root@iZbp19r2vmlr853x5azt4rZ ~]#
我们现在是用echo把字符串打印到我们的终端了 , 其实我们应该是要把这个字符串发送到网址www.huangcuigang.cn的TCP连接去 , 我们再来重新改一下我们的echo输出 , 使用">"输出重定向 , 把echo的输出重定向到数字8 , 数字8代表了我们的一个TCP连接 , 但是这里的数字8其实是一个文件描述符 , ">"输出重定向的对象要求是一个文件 , 而输出重定向到文件描述符的话要用">&", 然后数字8又代表了网址www.huangcuigang.cn的TCP连接 , 那么这样 , 这个echo输出的内容就写入TCP连接对象了
按下回车 , 我们就已经向www.huangcuigang.cn的TCP连接对象按照HTTP协议发送了请求了 ,
[root@iZbp19r2vmlr853x5azt4rZ ~]# echo -e "GET / HTTP/1.0\n" >& 8
按照HTTP协议 , 收到消息了就必须要返回消息 , 我们发送给www.huangcuigang.cn的TCP连接的消息是写入到文件描述符8了 , 那么www.huangcuigang.cn的TCP连接的返回数据应该也是写入到文件描述符8了 , 我们读取文件描述符8的内容就能得到返回的内容了 , 这里我们使用cat命令去读取 , cat加上<&输出重定向 从文件描述符读取数据 , 敲下回车 .
注意图片红色圈出来的部分命令 , 使用cat <& 8去读取文件描述符的内容时 , 是没有任何数据的 , 我们要注意一下这个事情 , 这是因为我们再写这篇文章时 , 花的时间比较长 , 造成TCP连接被断开了 , 这里的知识点 , 我们如果想要给对方发送数据 , 我们就必须先与对方建立TCP连接 , TCP连接有了之后 , 我们才能够给对方发送数据 , 那么重点来了 , 我们与对方建立了连接 , 但是我们什么时候会给对方发送消息 , 这是一个未知数 , 我们有可能建立连接后很快就发送了数据给对方 , 也有可能一直不发消息给对方 , 所以站在www.huangcuigang.cn服务器的一方去想 , 如果别人与我们建立了连接之后 , 就相当于我们也创建了TCP连接的资源 , 也是一个文件描述符 , 我从这个连接去读数据 , 就是在做I/O操作 , 对方一直没有发送数据 , 一是会造成我们的资源浪费 , 二是一直在对文件做I/O操作也是性能浪费 , 再者就是这有可能会造成一种阻塞状态 , 所以才会出现BIO , NIO , 多路复用等等技术的出现 , 所以这完全是没有意义的事情 , 所以才会断开连接 , 有兴趣的朋友 , 可以想想 , 如果没有超时自动断开连接这个机制会怎么样? 比如我们写一个程序 , 使用不同的虚拟IP , 大量的与你服务器创建TCP连接但是不发送数据 , 其实每台服务器可以创建的连接是有限的 , 你的服务器会怎么样? 再好的硬件也撑不住吧.
既然TCP连接断开了 , 那我们再重新创建一个TCP连接不就OK了吗 , 到这里 , 请求回了www.huangcuigang.cn域名返回的页面数据了
这里有一张截图 , 上部分是www.huangcuigang.cn所在的服务器 , IP为47.111.242.7 , 下半部分是发起请求的一方 , IP为121.196.47.141 , 首先IP47.111.242.7使用tcpdump命令监听了网卡上与IP121.196.47.141的数据包 , 然后IP121.196.47.141使用exec命令创建了一个TCP连接 , 每一条数据前面都有时间 , 根据时间可以看到 , 20分28秒的时候 , IP47.111.242.7与IP121.196.47.141完成了3次握手 , 然后,我们什么都没有动 , 建立了TCP连接都也不发送数据了 , 在29分28秒的时候 , www.huangcuigang.cn所在的服务器IP47.111.242.7向发起请求方IP121.196.47.141发送了数据包完成了4次分手 , 也就断开了连接了
TCP/IP协议 之 应用层 的 总结 :
我使用HTTP协议请求服务器获得页面的演示 , 核心就是HTTP协议 , 如果我们的"GET / HTTP/1.1\n"不按照HTTP协议的这种格式去写 , 那么我们发送给服务器 , 服务器当然也能够接收到数据 ,但是服务器根本就不知道我们是想要干什么 , 就更不会正确的返回给我们想要的数据了 , 再比如我们自己手写了一个xx程序 , xx程序运行在服务器上 , 我们xx程序监听系统的1314端口 , 我们xx程序自己制定了一套xx协议 , xx协议规定 , 发送字符串"操作:连接xx|账号:xxx|密码:xxx"格式到与我们xx程序的TCP连接 , 就代表了连接与登录操作 , 再有就是增删改查等等的操作我们xx协议都有规定 , 那么你要连接登录我们xx程序发过来字符串为:"我要登录 , 我的账号是xx , 密码你等等 , 我再想想" , 这样的字符串我们xx程序按照xx协议去解析 , 并不能知道你想要干什么 , 当然也不能给你完成连接与登录了 , 这就是应用层的协议完成是事情.
三. TCP/IP协议 之 传输控制层
接着上面 , 我们已经知道了应用层只是完成了各种应用程序之间通讯的一些协议 , 创建好了TCP连接并拿到了输入输出流 , 就搞定了 , 至于这些数据是怎么发出去的 , 怎么走的 , 从那一块网卡进出的 , 我们根本就没有去管 , 因为这些事情都统一的交给了系统内核的TCP/IP协议层了 , "传输控制层" 就是控制数据怎么传输 , 以什么方式来传输的 , 那么 "传输控制层" 的协议都有哪些呢? 常见的就有TCP协议 , UDP协议 , 我们这里只分析TCP协议.
什么是TCP协议?
简单来说,TCP协议就是网络通讯协议中的一种传输控制协议,TCP的工作原理是将消息或文件分解成更小的片段(称为数据包),在通过Internet发送。然后,这些数据包由另一个TCP层接收,然后将该数据重组为完整的文件或消息。TCP还负责对数据流进行错误检查,以确保数据的传递; 如果发现错误,则TCP重新传输数据包。这样来确保不同节点之间的端到端数据传输。Internet协议提供用于传输数据的指令,同时TCP创建连接并确保将数据传递到正确的目标。这两种协议通常是协同工作的,称为TCP / IP协议.所以 , TCP协议是面向连接的 , 可靠的传输控制协议
这有引发一个新的问题 , 既然TCP协议是面向连接的 , 那么什么是连接?
这里的连接 , 他肯定不是通过物理的连接吧 , 所以是虚拟的连接 , 既然是虚拟的连接 , 那就不要纠结"连接"这个词了 , 我们真实的问题是 , TCP连接 , 是通过什么形式来代表的连接? 这里就引出了 "3次握手和4次分手"的过程了 , 比如客户端和服务端想要建立连接 , 客户端是要向服务端发送一个sync数据包 , 表示想建立连接 , 那么服务端收到了客户端发送的建立连接的请求 , 也回一个sync+akc数据包给客户端 , sync也代表服务器想与客户端建立连接 , akc表示确认收到了客户端建立连接的请求 , 这个时候 , 才完成了2次握手 , 服务端并不确定自己发给客户端的sync+akc有没有被客户端正确的收到 , 所以 , 这时客户端又给服务端回复了一个ack,表示确认收到了 , 这时 , 客户端和服务端就完成了3次握手了 , 双方都创建了为对方服务的资源 , 这就是某种意义上的连接 , 这里的为对方服务的资源就是我们上面演示HTTP协议时创建TCP连接见到的输入输出流文件描述符 , 服务端的消息通过这个文件可以给到客户端 , 那么客户端的消息也可以通过这个文件给到服务端 , 这不就是真正意义上的连接了吗.
简单回答 , 就是经过3次握手为对方开辟的资源 , 就是连接.
socket
还有一个比较重要的东西 , 一说到传输控制层 , 除了上面聊的传输控制协议 TCP协议 , UDP协议 , 还有一个概念叫socket , 个人理解 , 本质上socket还是TCP协议 , 但是建立连接的方式不一样 , 我们来通过一个命令 netstat -natp 来找找socket的感觉 ,
这里就能看明白很多东西了, 有Proto列标识使用的协议 , 有Recv-Q,Send-Q列标识发送接收的序列 , 重点来了 , local Address和Foreign Address代表了本地地址和对端的地址,都是IP:Prot , 这就是socket的概率 , 后面还State标识了socket状态 , 简单了解一下这个状态 , LISTEN 表示套接字正在监听连接[调用listen后] , ESTABLISHED 表示已经3次握手 , 连接已建立 , CLOSE_WAIT 远程套接字已经关闭:正在等待关闭这个套接字[被动关闭的一方收到FIN] , TIME_WAIT 这个套接字已经关闭,正在等待远程套接字的关闭传送[FIN、ACK、FIN、ACK都完毕,这是主动方的最后一个状态,在过了2MSL时间后变为CLOSED状态] , 还有一些状态 , 不在这里展开聊 , PID/Program name标识进程ID和进程名称在使用当前socket连接
这里我们来看第3行PID为873的sshd进程 , 它是我们现在远程登录服务器所使用的一个协议在linux系统上的一个服务进程 , 它现在是LISTEN状态 , 表示它是在监听状态 , 然后监听的是22端口 , 这个不是我们与服务器创建TCP连接后产生的 , 它本身就一直在 , 下面还有2个sshd的socket连接进程 , PID:8138和PID:7688 , 我们注意看着两个的状态 , 都是表示已经3次握手 , 连接已建立 , 那是因为我们现在确实是起了2个连接到服务器 , 再看 local Address和Foreign Address的值 , 服务器本地地址一样 , 但是客户端的端口不一样了 , 说明客户端是2个进程来连接了我们的服务器的22端口 , 这个可以换例子去理解 , 我们在使用浏览器去访问网址www.huangcuigang.cn时 , 可以打开多个标签都访问同一个网址 , 但是这些浏览器的标签其实都是不同的进程 , 不同的端口去与服务器建立的连接进行通讯的 , 这样各自是各自的连接 , 他们的数据才能发送到正确的端与端 .
聊到这里 , 新的问题来了 , 上面我们能看到 , 服务端的22端口 , 同时与我们的客户端创建了2个TCP连接 , 那么我们会不会有疑问 , 同一个端口号可以创建的TCP连接会没有上限 , 如果有的话 , 上限时多少个连接?
同一个端口号可以创建的TCP连接数有限制吗?
答案是有 , 连接数是有上限的 , 我们可以使用ulimit -a 命令去查看一下 , ulimit -a是用来显示目前资源限制的设定 , 里面有很多 , 我们没必要展开都解释 , 我们看open files这个参数 , 值是65535 , 代表1个进程可以创建可以创建的文件描述符的数量 , 换言之 , 一个PHP程序可以创建变量的数量或者创建对象的数量 , 一切皆文件 , socket也是文件 , 用文件描述符 , 打开真正的文件 , 也用文件描述符 , 打开一个设备 , 设备被抽象成文件了也是一个文件描述符 , 所以1个进程可以创建多少个连接 , 受这个内核参数的约束的影响 , 如果我们修改nginx的配置文件 , 让nginx一个进程可以创建10万个连接 , 那就不好意思了 , 没用 , 因为受系统内核的限制 , 一个nginx进程只能向系统申请到65535个文件描述符 , 或者说65535个TCP连接所使用的文件描述符 . 当然我们的应用程序虽然受限制与系统内核 , 但是系统内核本身也只是一段程序 , 所以内核的限制值是可以改的 , 不过65535本身已经是最大值了
我们打印当期进程的fd目录 , 可以看到 , 一开始只有0 ,1 ,2 ,255 , 一个进程必然会有0 ,1 ,2这个3个文件描述符 , 0 是标准输入文件描述符 , 1 是标准输出 , 2 是报错输出 , 但是我孟创建了一个TCP连接 , 使用8代表文件描述符后就多了一个为8的文件描述符了 , 也就是说 , 对于我们当前这个进程而言 , 这fd目录下最多可以有65535个文件描述符 , 并且新打开一个SSH连接进程 , 去查看自己进程的fd目录 , 是没有文件描述符8的
所以 , 传输控制层还有一个重要的东西 , 那就是断开连接 , 释放资源 , 本身系统的可用连接数是有限的 , 断开连接就是 4 次分手的过程
TCP/IP协议 之 传输控制层 的 总结:
我们这里分析了TCP协议 , 对3次握手4次分手有了一个宏观的概念 , 传输控制层只创建了传输控制包 , 重点在控制 , 传输控制层本身也完成不了传输数据包给别人 , 所以下面才有其他的层
四. TCP/IP协议 之 网络层
接着上面传输控制层 , 传输控制层本身是传输不了数据包的 , 数据包的传输还得交给网络层 , 聊到网络层 , IP协议就出现了 , 那么先提一个问题 ,
IP是干什么的 , IP是为了解决什么问题的 ?
IP是用来寻址的 , 解决端到端的问题的 , 互联网上那么多计算机 , 没有IP谁能知道数据要怎么传输什么地址去
IP地址,掩码,网关分别的作用?
IP地址里面包含两个东西 , 一个叫做"网络号" , 一个叫做"主机号","网络号"用于定位到区域,也就是我们常说的局域网" , "主机号"用于定位到局域网的主机 , 在一段IP地址中怎么去区分哪些是代表"网络"的 , 哪些是代表"主机"的 ? 这就跟我们的掩码有关系了 , 一说算法就知道了 , IP地址是点分字节 , 点分字节就是两点之间放的是一个字节可以表示的10进制数 , 1个字节由8个2进制位组成 , 这8个2进制位从全0到全1的演变过程 , 它能表示的10进制的数的范围是0到255 , 那么我们来看掩码的特征"255.255.255.0" , "255"这个字节为全1,"0"这个字节为全0 , 我们的IP地址"192.168.0.100"的每个点分字节也是代表了某种0和1的二进制组合 , 那么掩码和ip做的事情就是 , 用IP地址和掩码做二进制的"按位与运算"后会得到一个数 , 切割后产生网络号和主机号 , 也就是我们的IP地址.
那么网关是起到什么作用呢? 互联网中有很多的计算机 , 那么每个计算机访问到另一个计算机就需要有网关作为一个桥梁来连接了 , 这里面有一个机制叫做"下一跳"机制 , 英文原文叫"Nxet dump" , 我们访问本机电脑访问网址www.huangcuigang.cn , 在IP寻址时 , 通过网关发现网址的IP地址不是我们同一个网关内的设备 , "下一跳"到网关的上级网关 , 比如是路由器 , 路由器的网关寻址发现也不是同一个路由器下的设备 , 又"下一跳"到路由器服务商的网关去找 , 就这样直到找到为止 , 所以叫"下一跳"机制
我们来看一个有意思的东西 , 使用route -n命令查看机器的如有表 , 这里面有des描述 , 有Gateway网关 , 还有Genmask掩码 , 这里有3条配置 , 当我们ping www.huangcuigang.cn时 , 网络会从DNS去解析回IP地址是47.111.242.7,这个IP使用路由表的那一条数据的掩码能匹配上? 我们不会用二进制按位与的算法就看des描述字段就行 , 能与谁匹配上 ? 很明显169.254.0.0和172.16.48.0都匹配不上 , 所有IP地址使用掩码0做二进制按位与运算后都为0 , 所以能匹配上第1条路由配置 , 那么就得到了下一跳的网关地址172.16.63.253 , ping www.huangcuigang.cn这个网络请求的数据包就给到下一跳的网关地址172.16.63.253去处理了
TCP/IP协议 之 网络层 的 总结:
网络层的主要工作就是用来完成下一跳机制的 , 访问本机局域网的IP地址时 , 根据子网掩码可以匹配出路由规则 , 网关地址为0.0.0.0 , 则代表不需要下一跳网关 , 就在本机局域网内寻找主机即可 , 如果访问的IP地址为外网地址, 那么根据路由配置只能使用子网掩码0.0.0.0做二进制按位与运算后匹配上 , 并且得到下一跳网关地址 . 数据包交给下一跳网关 , 进入链路层的工作来完成数据包在各个网关中跳来跳去并正确的到达正确的主机
五. TCP/IP协议 之 链路层
链路层的链字就比较有意思了 , 因为它是一个一个的节点链起来的 , 所以才能这个点不行就跳到下一个点去 , 每层都有协议 , 链路层有个ARP协议 , 简单来说 , 就是我们通过ARP协议 , 把要访问的IP地址广播出去 , 局域网内相同IP地址的主机收到广播后回复消息 , 然后就找到了对应的主机了 , 举个例子 , 我们当前主机去访问www.huangcuigang.cn , 请求的数据包下一跳到了网关100.xxx.xxx.100 , 网关100.xxx.xxx.100 中还是根据IP地址子网掩码去匹配 , 匹配不上继续把数据包下一跳给下一个网关101.xxx.xxx.100 , 根据IP地址和子网掩码能匹配上 , 说明IP地址已经找到了对应的局域网了 , 我们根据ARP协议再广播一个www.huangcuigang.cn的IP地址101.xxx.xxx.200 , 局域网内101.xxx.xxx.200收到消息后立即回复一个消息 , 就正确的找到www.huangcuigang.cn的所在主机了
到这里了 , 我们总的来理一下逻辑 , 想要把一个请求头发出去 , 要做的那些事情 , 应用层根据HTTP协议去封装请求的数据 , 传输控制层要建立TCP连接 , 建立TCP连接的时候就要先发送第1次握手的包 , 发送第1次握手的包就要走网络层找路由条目的下一跳网关 , 找到路由条目的下一跳地址后还得去看链路层有没有下一跳网关地址的网卡硬件地址 , 没有的话还要触发ARP协议去请求回来 , 最终到"物理层"那些电啊光啊的硬件的东西了 , 这样第1次握手的包才能到达目标地址 , 反反复复的这样才能建立3次握手建立TCP连接