一、说明
直接的原因是昨晚《计算机网络(自顶向下方法)》到货了,以为能讲得有些不一样,但看完整本也就是老调地讲过来讲应用层、传输层、网络层、网络接口层。感觉比之谢希仁的《计算机网络》还有不如。
看了很多关于网络模型的文章,总感觉部是在讲原理原理也讲得挺明白,但个人觉得就是看完后不能让读者明白这个东西在编程上是什么、在编程上要怎么实现。
二、各网络模型定义
2.1 定义
ISO/OSI七层网络参考模型:
应用层 |
表示层 |
会话层 |
传输层 |
网络层 |
数据链路层 |
物理层 |
TCP/IP四层网络模型:
应用层 |
传输层 |
网络层 |
网络接口层 |
教学五层网络模型:
应用层 |
传输层 |
网络层 |
数据链路层 |
物理层 |
2.2 各网络模型之间的关系
1. TCP/IP就相当于把ISO/OSI的应用层、表示层、会话层压缩为应用层;把数据链路层、物理层合并为网络接口层(更简单点网络接口层可以认为就是网卡)。压缩倾向于说把表示层和会话层的头部给砍掉了,然后其功能也部份砍掉了,合并倾向于说物理层的头部砍掉了但要实现的功能还是实现的。
2. 教学五层网络模型相当于把TCP/IP的网络接口层,恢复成数据链路层和物理层两个层;注意教学五层网络模型在工业界是没有的(也就是没有过五层网络模型对应的产品),这个模型只是为了教育界方便教学弄出来的。注意关系,五层模型是从四层模型来的不是从七层模型来的,也不是七层模型从五层模型来的。
3. 为什么说教学五层网络模型方便教学,物理层真没什么感觉啊?我对物理层也没有准确的定义,我觉得高电平、低电平、上升沿、下降沿分别表示什么,波分复用、码分多址,电压电流大小,插口形状大小等应该都是物理层的工作。
4. 当前TCP/IP占领了市场,不是因为TCP/IP先被工业界应用,ISO/OSI才被学术界提出来的;而是OSI/ISO最先提出来,而且联合了各大厂商大家都基本同意以其为标准来进行生产但都没什么动静,然后TCP/IP越搞越火大家都去搞TCP/IP了。所以说,按我理解的话ISO/OSI确实是被TCP/IP正面击败的。
5. 当年上计算机网络课的时候,老师对ISO/OSI已被淘汰的说法嗤之以鼻并说网络最终一定会向ISO/OSI方向发展。当时感到不解,现在想来老师可能是指SSL的发展,但如果是指SSL的发展的话那其断言也就不准确。从功能上说ISO/OSI就相当于被砍了表示层和会话层,我总感觉各种对这两个层功能的定义似是而非,表示层有“解密”类似的关键字,但要说当时标准的制定者预期会话层和表示层的完成任务就是当前SSL层所做的工作,鉴于多处可见的“制定者没有预想要网络会在世界普及所以只使用了32位IP地址”的说法以及后来的http、ftp都没太考虑安全性,ISO/OSI制定者则有远超别人的预见性是不太能令人相信的。也就是说个人认为,SSL是方向,但SSL并不就是ISO/OSI的表示层、会话层。
6. 对于ISO/OSI为什么会被TCP/IP所击败,我并没有深入去了解当明的情况只是个人推测。首先说物理层学术界直觉上需要一个物理层头部工业界发现使用网卡后完全没必要去管物理层,所以工业界就将物理层头部给砍了。然后再是表示层和会话层,可能是学术界自己不生产不能给工业界清楚地描述这两个层在编程上是要怎么实现,而工业界自己又发现到传输层之后信息就传到进程中工业界自己想怎么玩怎么玩就不去管学术界复杂又说得不清不楚的那一套,正如有人和你说你到那后要左拐然后右拐然后再左拐但你自己到那后发现直直走进去就可以了,你就直接走进去了。总的而言就是学术界过于理想化、脱离实际生产(当然根本上来说工业界跨越性的进步还是仰仗学术的进步,当如果学术界自己已搞不了生产再没有理论的引领作用那就真尸位素餐了)。
7. SSL最经典的实现是https,但是本质上ssl就是在传输层和应用层之间插入的一个新层,SSL和https并没有必然的绑定关系,SSL层之上完全可以是其他任何应用层协议,比如ftp、telnet、smtp、pop、imap等等。
8. IOT宣称将网络简化成感知层、网络层和应用层,对IOT数据包我也只是一知半解,但我一直认为如果条件允许标准化才是趋势优势不明显的专用方案终会被淘汰。具体到IOT而言,如果硬件条件允许那么IOT的系统将Linux化,网络将TCP/IP化。至少当前从手机到安防到汽车这些代表性行业都在呈现这种趋势。
9. 因为我们实现一个服务监听使用的是socket,而socket就是tcp/ip,所以客户端访问也需要使用tcp/ip协议--电脑客户端访问服务时要使用tcp/ip协议手机客户端访问时亦需要tcp/ip协议。所以不管是3G、4G、5G不管他们多复杂,他们启的作用就是“物理层+数据链路层”的作用,亦即把数据汇总到一个类似路由器的网关,进而实现手机与因特网的通信。WiFi等也一个意思。
三、编程角度的理解
3.1 传统的网络解说
以太网头部是这样的:
ip头部是这样的:
tcp头部是这样的:
各层具有的协议是这样的:
整个网络传输过程是这样的:
3.2 网络协议理解
上一节的讲解如果是已经知道各头部在数据包中长什么样的人看,应该会感觉各要点都已涉及到了,讲的不错。但就个人而言,当时学完计算机网络后要进行网络编程时很是害怕,因为在编程上自己又完全不懂怎么实现。
讲封装的文章会给出下边这个图,这和我想表达的意思是一样的,但感这图还是不太够直接。
3.2.1 从存储角度对网络协议的理解
从存储角度,数据包可以认为就是一个字符串数组。比如一个帧长为200字节,我们称之为strBuffer[200],各层对应数组中的位置如下
头部 | 对应字符串数组位置 |
以太头 | strBuffer[0-13]、strBuffer[196-199] |
ip头 | strBuffer[14-33] |
tcp头 | strBuffer[34-53] |
应用层 | strBuffer[54-195] |
为了增强对数据包就只不过是个字符串数组这个观点,我们来看一个wireshark截获的http数据包,高亮处是ip头其前14个字节是以太头,后20个字节是tcp头,再后边就是http层数据
3.2.2 从编程角度实现网络协议
仍以上边str[200]来说明,当我们要从电脑向外发一个帧,其本质工作就是把以太头信息填到strBuffer[0-13]和strBuffer[196-199],把ip头信息填到strBuffer[14-33],把tcp头信息填到strBuffer[34-53],再把应用层内容填到strBuffer[54-195],最后把这个数组发出去。总结而言就是填充字符串数组,然后把字符串数组发出去。
逐字节填充是可以但那会相当费劲,通常而言我们会给各协议头部定义一个结构(struct),然后定义一个结构对应的变量,然后填充该变量,最后将变量使用strcpy等函数将头部复制到其对应在字符串数组中的位置即可。
//ip头部,共20字节
typedef struct iphdr{
UCHAR ver_pack; //4位版本号+4位ip头长度
UCHAR tos; //8位tos
USHORT total_len; //16位tcp头+应用层长度
USHORT ident; //16位
USHORT frag_and_flags; //16位
UCHAR ttl; //8位ttl
UCHAR proto; //8位上层协议号
USHORT checksum; //8位ip头部校验和
UINT sourceIP; //32位源ip地址
UINT desrIP; //32位目的ip地址
}IPHEADER,*PIPHEADER; //tcp头部,其20字节
typedef struct tcphdr //定义TCP首部
{
USHORT th_sport; //16位源端口
USHORT th_dport; //16位目的端口
unsigned int th_seq; //32位序列号
unsigned int th_ack; //32位确认号
unsigned char th_lenres; //4位首部长度+6位保留字
unsigned char th_flag; //6位标志位
USHORT th_win; //16位窗口大小
USHORT th_sum; //16位校验和
USHORT th_urp; //16位紧急数据偏移量
}TCPHEADER;
比如ip头部就是:定义一个ip头部变量:IPHEADER ip_header----填充ip_header各成员----strcpy(strBuffer+14,ip_header)。
其他以太头、tcp头等等都是类似操作的。
3.2.3 应用层和其他层在编码上的区别
经过前边的介绍,以太头、ip头、tcp头应该比较清楚了,但自己学习时感觉还有一个隔阂,就是应用层为什么和其他网络接口层、ip层、tcp层那么不一样。
为什么同是复制到同一个字符串数组内其他三层需要结构、需要填一堆二进制的东西,而应用层strBuffer += "hello client\r\n"就完事了。
造成这样的原因主要是因为,以太头、ip头、tcp头用数值编码,而应用层上使用的是ascii编码。比如1024这个数,如果在以太头、ip头、tcp头那么会被编码成0x1000,而如果是在应用层那就会编码成0x31303134。
至于为什么以太头、ip头、tcp头使用数值编码而不使用ascii编码,那应该是方便运算。这些头部从生成到传输过程其主要都是用于进行加减、比较、求反等数值运算,而数值运算上到机器中最终还是得用数值编码的,使用ascii编码既耗费了长度运算时还得转化一次那是对机器相当不友好的。另外一般的网络编程并不需要去修改这些头部,对人不友好一点是没很影响的。
至于为什么应用层不使用数值编码,首先应用层传输更多的是字符串而不是数值,即便要传数值比如传个1024你觉得要你自己把数转成十六进制写进去方便还是接收方int(str)方便?