OSI模型(七层)
TCP/IP 4层模型
一、链路层
其中,目标MAC(Media Access Control)地址、源MAC地址和类型组成14(6+6+2)字节的帧头,后面为数据部分,最后为CRC(Cyclic Redundancy Check)部分。
目标MAC地址(6B) | 源MAC地址 (6B) | 类型 (2B) | 数据 (46~1500B) | CRC (4B) |
---|
MAC地址为主机网络接口卡地址;类型为来自网络层的数据类型,IPv4为0x0800,ARP为0x0806,PPPoE为0x8864, 802.1Q tag为0x8100,IPv6为0x86DD, MPLSLabel为0x8847;数据部分为来自网络层的数据,最少为46字节,最大为1500字节;CRC为循环冗余校验码,主要校验所收到的数据是否有错。链路层数据帧最小为64(6+6+2+46+4)字节,最大为1518(6+6+2+1500+4)字节,主要利用网络接口卡的物理地址即MAC地址进行通信。
主机作为数据发送方时,链路层负责将来自本机网络层的数据报文封装为数据帧进行发送,数据接收方在收到数据帧后会给数据发送方发送反馈信息,如果数据传输有误,发送方需要重新发送出错的帧数据;主机作为数据接收方时,链路层负责对接收到的数据帧进行CRC校验,并给数据发送方发送反馈信息,要求重新发送出错的帧数据,并将接收到的正确帧数据的目标MAC地址、源MAC地址和CRC部分去掉后,递交给网络层处理。链路层通信用MAC地址识别主机,主机间交换数据帧。
"""
@author:oxff
@github:https://github.com/oxff644
"""
#获取本机网卡的MAC地址
import uuid node =uuid.uuid1()#通过调用uuid的uuid1()函数,得到由MAC地址、当前时间戳和随机数字生成,以对象node表示的UUID值
print('type(node) =',type(node))
print('node= ',node)
hex =node.hex#通过对象node的hex属性得到包含主机MAC地址的字符串print('hex=',hex) mac_addr =hex[-12:]#取字符串的后12位,即为主机的MAC地址 print('mac_addr=',mac_addr)
Python的第三方模块psutil可以获取主机的大量信息,其中包括主机全部网卡的设备名称和MAC地址。举个例子:
"""
@author:oxff
@github:https://github.com/oxff644
"""
#利用psutil获取主机网卡设备名称和MAC地址程序
import psutil
info =psutil.net_if_addrs()#调用psutil的net_if_addrs()函数获取主机全部的网卡信息
print('info= ',info)
print('type(info)=',type(info))
net1 =info['eth0']
print('net1= ',net1)
packet =net1[2]
print('packet=',packet)
print('type(packet)=',type(packet))
print('mac_addr =',packet.address)#取得网卡的MAC地址
二、网络层
* 数据报文格式
首部(20-60B) | 数据(0-65516B) |
---|
VER: IP协议的版本号。值为二进制0100时为IPv4,值为二进制0110时为IPv6。
IHL:数据报文首部长度。数据报文首部长度等于IHL值乘以4,例如,IHL为二进制0101时,数据报文首部长度为20(5×4)B;IHL为二进制1111时,数据报文首部长度为60(15×4)B。
DS:区分服务(Differentiated Services)。DS的前3位表示优先级,优先级低的数据报文在网络拥塞时会被丢弃;接下来4位分别表示最小时延、最大吞吐量、最高可靠性和最小代价,表示数据报文传送过程中的期望,该4位中最多有1位为1,共有5种组合;DS的最后1位未使用。
标识:唯一标识数据报文的16位二进制数值。当数据报文总长度大于1500B时,对数据报文分片时,该值被复制到分好片的数据报文中;同理,来自链路层,具有相同标识的多个数据报文会被重新组装为一个大的数据报文。
标志:表示数据报文是否可分片或者是否为最后一个分片。最高位是预留位,其值为0。当中间位为0时,表示该数据报文可以分片;当中间位为1时,表示该数据报文不可以分片。当最后的位为1时,表示该数据报文为分片数据报,且后面还有分片;当最后的位为0时,表示该数据报文已是最后一个分片。
分片偏移:表示该数据报文在原数据报文中的相对位置。相对位置必须为8字节的整数倍,也就是分片偏移值乘以8。
生存时间:以数据报文在网络中的剩余跳数表示数据报文在网络中的寿命。数据报文到达某个路由器时,在路由器转发该数据报文之前,先将生存时间减1,如果生存时间变为0,则丢弃该数据报文;否则,进行转发。数据报文的生存时间最大值为255,表示数据报文在网络中最多被转发255次,如果数据报文的生存时间初始值为1,则该数据报文只能在本地局域网内部传输。
协议:指出该数据报文内容所属的协议。值为1时,来自ICMP(Internet Control MessageProtocol);值为2时,来自IGMP(Internet Group Management Protocol);值为6时,来自TCP(Transmission Control Protocol);值为17时,来自UDP(User DatagramProtocol);值为89时,来自OSPF(Open Shortest Path First)协议。
首部校验和:该数据报文的首部校验和,未包括数据部分。因为数据报文每经过一个路由器,首部的一些内容会发生变化,如生存时间,这就需要路由器重新计算首部校验和,如果包括了数据部分,则会加大计算量。
源IP地址:发送数据的主机的IPv4地址。
目标IP地址:接收数据的主机的IPv4地址。
选项:可选数据。
3.IPV6:
1. 冒分十六进制表示法
2. 0位压缩表示法
3. 内嵌IPv4地址表示法
ARP(地址解析协议) | 用于将网络层的IP地址对应到链路层的网卡MAC地址 |
---|---|
RARP(反向地址解析协议) | 用于局域网中的主机向网关的ARP表或者缓存请求IP地址 |
ICMP(Internet控制报文协议) | 用于主机与路由器之间传递控制消息,控制消息包括网络是否通畅、数据是否到达目标主机、路由是否可用等内容 |
IGMP(Internet组管理协议) | 用于管理组播组成员的加入和离开,主机通过IGMP通知组播路由器希望接收或离开某个特定组播组的信息,组播路由器通过IGMP周期性地查询组播组成员是否处于活动状态,实现所连网段组成员关系的收集与维护。 |
Python的第三方模块psutil可以获取计算机IP地址及其他网络配置信息,举个例子:
"""
@author:oxff
@github:https://github.com/oxff644
"""
#获取计算机IP地址
import psutil
info =psutil.net_if_addrs()
print('info =',info)
net1 =info['eth0']
net2 =info['lo']
print('net1=',net1,end=' ')
print('net2=',net2,end=' ')
print('net1[0]=',net1[0])
print('net1[1]=',net1[1])
print('IPv4_addr=',net1[0].address)
print('IPv6_addr=',net1[1].address)
#coding=utf-8
"""
@author:oxff
@github:https://github.com/oxff644
"""
#获取局域网网关地址
import netifaces
info =netifaces.gateways()
print('info =',info)
print('type(info)=',type(info))
gateway_addr =info['default'][2][0]
print('gateway_addr=',gateway_addr)
#利用python 实现获取主机收发数据的统计信息
#coding=utf-8
"""
@author:oxff
@github:https://github.com/oxff644
"""
#获取局域网网关地址
import psutil
info =psutil.net_io_counters()#调用net_io_counters()函数获取主机收发数据的统计信息,统计信息存入snetio对象info中
'''
调用net_io_counters()函数时,如果加上参数pernic=True,
即info=psutil.net_io_counters(pernic=True)
将逐个网卡取得数据收发信息并存入字典变量info中。
'''
print('info=',info)
print('type(info)=',type(info))
print('bytes_sent=',info.bytes_sent)#收发数据字节数
print('bytes_recv=',info.bytes_recv)
print('packets_sent=',info.packets_sent)#收发数据报文数
print('packets_recv=',info.packets_recv)
print('errin =',info.errin)#提取出错的收发报文数
print('errout =',info.errout)
print('dropin=',info.dropin)#提取丢弃的收发数据报文shu
print('dropout=',info.dropout)
三、传输层
功能:主要为网络中的两台主机提供端到端的数据传输服务,作为数据发送方,传输层将来自应用层的数据进行分割并加上传输层报文头部后组装成传输层数据报文,递交给网络层处理;作为数据接收方,传输层将来自网络层的数据去掉传输层报文头部并对数据进行组装,然后将数据递交给应用层。
网络中一台主机向另外一台主机发送数据的过程
传输层协议
协议名称 | 特点 | 功能 | 应用场景 |
---|---|---|---|
TCP | 面向连接,可靠 | 提供面向连接、可靠的数据传输服务 | |
UDP | 无连接,不对传输的数据进行可靠性保证,资源消耗小、处理速度快,实现简单 | 提供的服务没有连接、只能提供尽力而为的数据传输服务, | 适合于一次传输少量数据和传输中偶尔出现错误对结果影响不大的服务(例如,音频、视频等服务),但对于需要传输较多数据,且数据传输出现错误需要重传的服务并不适合,使用UDP协议包括:TFTP(简单文件传输协议)、SNMP(简单网络管理协议)、DNS(域名解析协议)、NFS、BOOTP |
TCP协议的三次握手和四次挥手:
注:seq:"sequance"序列号;ack:"acknowledge"确认号;SYN:"synchronize"请求同步标志;;ACK:“acknowledge"确认标志”;FIN:"Finally"结束标志。
TCP连接建立过程:首先Client端发送连接请求报文,Server段接受连接后回复ACK报文,并为这次连接分配资源。Client端接收到ACK报文后也向Server段发生ACK报文,并分配资源,这样TCP连接就建立了。
TCP连接断开过程:假设Client端发起中断连接请求,也就是发送FIN报文。Server端接到FIN报文后,意思是说"我Client端没有数据要发给你了",但是如果你还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据。所以你先发送ACK,“告诉Client端,你的请求我收到了,但是我还没准备好,请继续你等我的消息”。这个时候Client端就进入FIN_WAIT状态,继续等待Server端的FIN报文。当Server端确定数据已发送完成,则向Client端发送FIN报文,“告诉Client端,好了,我这边数据发完了,准备好关闭连接了”。Client端收到FIN报文后,"就知道可以关闭连接了,但是他还是不相信网络,怕Server端不知道要关闭,所以发送ACK后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。“,Server端收到ACK后,“就知道可以断开连接了”。Client端等待了2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,我Client端也可以关闭连接了。Ok,TCP连接就这样关闭了!
为什么要三次挥手?
在只有两次“握手”的情形下,假设Client想跟Server建立连接,但是却因为中途连接请求的数据报丢失了,故Client端不得不重新发送一遍;这个时候Server端仅收到一个连接请求,因此可以正常的建立连接。但是,有时候Client端重新发送请求不是因为数据报丢失了,而是有可能数据传输过程因为网络并发量很大在某结点被阻塞了,这种情形下Server端将先后收到2次请求,并持续等待两个Client请求向他发送数据…问题就在这里,Cient端实际上只有一次请求,而Server端却有2个响应,极端的情况可能由于Client端多次重新发送请求数据而导致Server端最后建立了N多个响应在等待,因而造成极大的资源浪费!所以,“三次握手”很有必要!
为什么要四次挥手?
试想一下,假如现在你是客户端你想断开跟Server的所有连接该怎么做?第一步,你自己先停止向Server端发送数据,并等待Server的回复。但事情还没有完,虽然你自身不往Server发送数据了,但是因为你们之前已经建立好平等的连接了,所以此时他也有主动权向你发送数据;故Server端还得终止主动向你发送数据,并等待你的确认。其实,说白了就是保证双方的一个合约的完整执行!
使用TCP的协议:FTP(文件传输协议)、Telnet(远程登录协议)、SMTP(简单邮件传输协议)、POP3(和SMTP相对,用于接收邮件)、HTTP协议等。
1.多进程TCP实例
服务端代码:
#!/usr/bin/env python3
#coding=utf-8
"""
@author:oxff
@github:https://github.com/oxff644
"""
'''
多进程TCP实例(服务端)
要实现socketserver的多进程TCP编程
需要调用socketserver的ForkingTCPServer()函数
'''
import socketserver
def factorial(n):
s=1
for x in range(2,n+1):
s =s*x
return s
class Factorial_server(socketserver.StreamRequestHandler):
def handle(self):
conn =self.request
try:
data_b =conn.recv(1024)
data_s =data_b.decode('utf-8')
data =int(data_s)
if data >1:
fact =factorial(data)
else:
fact =1
fact_s =str(fact)
fact_b =fact_s.encode('utf-8')
conn.send(fact_b)
print('factorial(%d) =%s,from %s'% (data,fact_s,self.client_address[0]))
except Exception as e:
print('Error is ',e)
ip ='192.168.0.103'
server =socketserver.ForkingTCPServer((ip,8899),Factorial_server)
print('wait for TCO connecting...')
2.多进程UDP实例
服务端程序
#!/usr/bin/env python3
#coding=utf-8
"""
@author:oxff
@github:https://github.com/oxff644
"""
'''
UDP服务器程序
要实现socketserver的多进程UDP编程,
需要调用socketserver的Forking-UDPServer()函数
'''
import socketserver
def factorial(n):
s=1
for x in range(2,n+1):
s=s*x
return s
class Factorial_server(socketserver.DatagramRequestHandler):
def handle(self):
try:
(data_b,s) =self.request
data_s =data_b.decode('utf-8')
data =int(data_s)
if data>1:
fact =factorial(data)
else:
fact =1
fact_s =str(fact)
fact_b =fact_s.encode('utf-8')
s.sendto(fact_b,self.client_address)
print('factorial(%d)= %s,from %s' % (data,fact_s,self.client_address[0]))
except Exception as e:
print('Error is ',e)
ip ='192.168.0.103'
server =socketserver.ForkingUDPServer((ip,8988),Factorial_server)
print('Bins UDP on 8988....')
server.serve_forever()
客户端程序
#!/usr/bin/env python3
#coding=utf-8
"""
@author:oxff
@github:https://github.com/oxff644
"""
import socket
ip ='192.168.0.103'
s =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
xx_s =input('enter a number:')
xx_b =xx_s.encode('utf-8')
s.sendto(xx_b,(ip,8988))
while True:
(result_b,s_addr) =s.recvfrom(1024)
if s_addr[0] ==ip:
result_s =result_b.decode('utf-8')
print('Factorial(%s)= %s' %(xx_s,result_s))
break
s.close()
四、应用层
利用Python3的urllib所包含的模块request可以设置HTTP请求报文首部,获取指定服务器端的HTTP响应报文,代码如下:
#coding=utf-8
"""
@author:oxff
@github:https://github.com/oxff644
"""
#获取指定服务器端的HTTP响应报文
from urllib import request
url ='http://www.baidu.com'
header ={
'Accept':'text/html',
'Connection':'keep-alive'
}
req =request.Request(url,headers=header)
response =request.urlopen(req)
print('Status code=',response.getcode())
print('url=',response.geturl())
print('info=',response.info())
2.HTTPS
HTTPS使用SSL(Secure Sockets Layer)或者TLS(Transport Layer Security)对HTTP数据报文进行加密后递交给传输层的TCP进行传输,使用端口默认为443
主机间使用HTTPS通信时,会同时使用到非对称加密算法即公/私钥加密算法、对称加密算法、HASH算法等,其中,非对称加密算法用于加密密码,对称加密算法用于加密要发送的消息和HASH值,HASH算法用于计算要发送消息的HASH值
3.FTP
FTP(File Transfer Protocol,文件传输协议)用于Internet上主机间文件的共享与传输。
FTP服务实现包括服务器端和客户端。服务器端是提供文件存储空间的计算机,经常被称为FTP服务器;客户端是通过Internet以FTP访问服务器的主机。通过Internet,从FTP服务器端复制文件至客户端,称为“下载(download)”;将客户端的文件复制到FTP服务器上,则称为“上传(upload)”。
FTP服务器端和客户端的连接需要使用两个独立的TCP连接:一个是命令链路,用来传送命令和状态数据;另一个是数据链路,用来传输数据。
FTP协议有两种工作方式:PORT和PASV,即主动式和被动式工作方式。
1.PORT(主动)工作方式的过程是:客户端向服务器的FTP端口(默认是21)发送连接请求,服务器接受连接,建立一条命令链路。当需要传送数据时,客户端在命令链路上用PORT命令告诉服务器,“我打开了XX端口,你来连接我”,于是服务器利用20端口向客户端的XX端口发送连接请求,建立一条数据链路来传送数据。
2.PASV(被动)工作方式的过程是:客户端向服务器的FTP端口(默认是21)发送连接请求,服务器接受连接,建立一条命令链路。当需要传送数据时,服务器在命令链路上用PASV命令告诉客户端,“我打开了XX端口,你来连接我”,于是客户端向服务器的XX端口发送连接请求,建立一条数据链路来传送数据。(实际应用中经常使用PASV工作方式)
4.DNS
这里举例说明域名解析过程。
例如,某用户单击链接www.linux.com,则其本地DNS服务器开始搜索自己的DNS数据库信息,如果没有搜索到,就转到上级DNS服务器,若上级DNS服务器也没有该域名的记录,则继续转上级DNS服务器,直到根DNS服务器。根DNS服务器在其DNS数据库里查找COM顶级域,然后它用NS记录回复该用户DNS服务器,指示可以从linux.com的DNS服务器ns.linux.com处查询到www.linux.com的信息。于是,经过DNS服务器ns.linux.com的转换,该用户得到了www.linux.com的对应IP地址,并且其DNS服务器缓存了该NS记录。下次如果有用户再需要解析该域名时,相关信息在本地即可获得。
5.SMTP
SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,用于将邮件从源地址传送到目的地址。
SMTP利用传输层的TCP协议传输信件数据,使用端口一般为25。
利用SMTP通信的双方通过命令和应答的方式交换文本信息,文本信息以CRLF作为结束标志,类似于FTP的命令链路,因此,没有定义特定的报文格式
6.POP3
POP3(Post Office Protocol 3)即邮局协议版本3,用于收信方从自己的邮件服务器上接收其他用户发给自己的邮件,是常用的邮件接收协议
POP3利用传输层的TCP协议传输数据,使用端口一般为110
POP3属于离线式协议,即收信方登录到邮件服务器后,会一次性将所有邮件下载到本地计算机上,服务器上的邮件同时被删除。
与SMTP类似,使用POP3通信的双方通过命令和应答方式交换文本信息,文本信息以CRLF作为结束标志,因此,没有定义特定的报文格式
IMAP(Internet Mail Access Protocol)即互联网邮件操作协议,属于在线式协议,允许用户在线操作邮件,方便用户随时随地利用不同终端设备处理邮件,弥补了POP3的不足,是目前最常用的邮件接收协议
IMAP与POP3主要功能比较
7. DHCP
DHCP(Dynamic Host Configuration Protocol)即动态主机配置协议,是一个简化主机IP地址分配管理的协议。用户可以利用DHCP服务器管理动态的IP地址分配及其他相关的环境配置工作(如DNS、WINS、Gateway的设置)
8.NAT协议
NAT网络地址转换(Network Address Translation)属接入广域网(WAN)技术,是一种将私有(保留)地址转化为合法IP地址的转换技术,它被广泛应用于各种类型Internet接入方式和各种类型的网络中。原因很简单,NAT不仅完美地解决了lP地址不足的问题,而且还能够有效地避免来自网络外部的攻击,隐藏并保护网络内部的计算机。
五、举例
** **在浏览器中输入 www.baidu.com 后执行的全部过程
现在假设如果我们在客户端(客户端)浏览器中输入http://www.baidu.com,而baidu.com为要访问的服务器(服务器),下面详细分析客户端为了访问服务器而执行的一系列关于协议的操作:
1)客户端浏览器通过DNS解析到www.baidu.com的IP地址220.181.27.48,通过这个IP地址找到客户端到服务器的路径。客户端浏览器发起一个HTTP会话到220.161.27.48,然后通过TCP进行封装数据包,输入到网络层。
2)在客户端的传输层,把HTTP会话请求分成报文段,添加源和目的端口,如服务器使用80端口监听客户端的请求,客户端由系统随机选择一个端口如5000,与服务器进行交换,服务器把相应的请求返回给客户端的5000端口。然后使用IP层的IP地址查找目的端。
3)客户端的网络层不用关系应用层或者传输层的东西,主要做的是通过查找路由表确定如何到达服务器,期间可能经过多个路由器,这些都是由路由器来完成的工作,不作过多的描述,无非就是通过查找路由表决定通过那个路径到达服务器。
4)客户端的链路层,包通过链路层发送到路由器,通过邻居协议查找给定IP地址的MAC地址,然后发送ARP请求查找目的地址,如果得到回应后就可以使用ARP的请求应答交换的IP数据包现在就可以传输了,然后发送IP数据包到达服务器的地址。
参考:
1.《python网络编程(linux)》赵宏,包广斌,马栋林编著.—北京:清华大学出版社,2018(大数据与人工智能技术丛书)
2.https://www.cnblogs.com/maybe2030/p/4781555.html