七层网络协议
1、物理层:
数据与高低电信号相互转换的功能;
发送到将数据转为电信号,接收端将电信号转为数据;
2、数据链路层:
单纯的0/1电信号没有意义,必须按组进行划分,定义电信号的分组方式;
以太网协议:统一的网络协议,一组电信号构成一个数据包;一个数据包称为帧,帧有报头head和数据data两部分;
报头head:固定18个字节;
》发送者/源地址:6个字节
》接收者/尾地址:6个字节
数据类型:6个字节
数据data:最短46个字节,最长1500字节
》数据包的具体内容
报头head+数据data=最短64字节,最长1518字节,超过最大长度则分片发送
Mac地址:
接入Internet的设备必须具备网卡,发送端和接收端的地址是指网卡地址,即世界唯一的Mac地址。
Mac地址由12位16进制数表示;前6:厂商编号、后6:流水编号。
广播:
有了Mac地址,同一网络内的两台主机就可以通信了(一台主机通过ARP协议获取另一台主机的Mac地址);
Ethernet采用最原始的方式,广播的方式进行通信。
3、网络层:
世界范围的互联网是有一个个局域网构成的,如果所有的通信都采用以太网的广播方式,那么一台机器发送的包全世界都会收到,这是一种灾难。
必须通过一种方法来区分广播域,同一广播域采用广播;否则采用路由方式(向不同广播域/子网分发数据包),Mac地址是无法区分的。
网络层功能:引入一套新的地址用来区分不同的广播域/子网,即网络地址。
IP协议:
网络地址协议称为IP协议,它定义的地址称为ip地址,广泛采用v4版本即ipv4,由32位2进制表示;
范围0.0.0.0~255.255.255.255;也可用4为10进制数表示,172.168.12.10
ip地址:
网络部分:标识子网
主机部分:标识主机
注:单纯从两个ip(172.16.10.1/172.16.10.2)看,不能确定是否为同一网段,需要通过子网掩码来计算是否在同一网段;
子网掩码:
子网掩码是计算ip所在网络的参数,它在形式上等同于IP地址,它的网络部分全部为1,主机部分全部为0。
如子网掩码255.255.255.0(11111111.11111111.11111111.00000000),网络部分为是24个1,主机部分是8个0,子网可容纳256台主机
如子网掩码255.255.0.0(11111111.11111111.00000000.00000000),网络部分为是16个1,主机部分是16个0,子网可容纳65536台主机
如:172.168.10.1和255.255.255.0进行And按位与运算,获得172.168.10.0,若其他ip计算也是这个值,则两个ip属于同网段。
ip数据包:
ip数据包分为head和data两部分,ip数据包存储在以太网的data部分。
head:长度为20~60字节
data:最长为65515字节
以太网数据包的数据部分,最长只有1500字节。如果IP数据包超过了1500字节,它就需要分割成几个以太网数据包,分开发送了。
以太网头+以太网数据包(ip报头+ip数据+其他数据)
ARP协议:通信是基于Mac地址的广播方式实现的,计算机通信获取本机Mac简单,获取尾端Mac地址需要通过ARP协议。
例如:主机172.16.10.10/24访问172.16.10.11/24
首先通过ip地址和子网掩码区分出自己所处的子网,子网地址172.16.10.0,属于同一网络
场景数 据包地址
同一子网 目标主机mac,目标主机ip
不同子网 网关mac,目标主机ip
如果不是同一网络,那么下表中目标ip为172.16.10.1,通过arp获取的是网关的mac
源mac 目标mac 源ip 目标ip 数据部分
发送端主机 发送端mac FF:FF:FF:FF:FF:FF 172.16.10.10/24 172.16.10.11/24 数据
如果目标Mac是FF:FF:FF:FF:FF:FF则表示广播发送数据,所有接收端判断如果目标ip是自己的ip则应答自己的Mac地址。
RARP协议:
网络通信
=============== 1、创建和销毁socket ===============
socket 模块中的socket(family,type[,proto])函数创建一个新的socket对象。
family取值:
AF_INET,用于服务器间IPV4连接,默认值
AF_INET6,用于服务器间IPV6连接
AF_UNIX,用于Unix进程间通信
type取值:
SOCK_STREAM(TCP连接),默认值
SOCK_DGRAM(UDP链接)
=============== 2、服务器端socket ===============
import socket
sk = socket.socket()
#创建socket对象
address = ('127.0.0.1',8000)
#本地链接ip和端口
sk.bind(address)
#socket对象绑定address
sk.listen(3)#配置最大连接数
while True:
conn,addr = sk.accept()#开启监听连接
while True:
data =conn.recv(1024)#服务器接收客户端1024字节数据
print(str(data,'utf8'))#将接收的数据从byte类型转为utf8
if not data :
conn.close()#关闭客户端连接
break
conn.send(bytes('serve:约啊','utf8'))#只能发送byte数据类型,需要将数据从utf8转byte
sk.close()#关闭服务器连接池
=============== 3、客户端socket ===============
import socket
sk = socket.socket()
#创建socket对象
address = ('127.0.0.1',8000)
#服务器端的链接ip和端口
sk.connect(address)
#向服务器端发起连接
while True:
data = input('>>>')# 客户端接收服务器发送的1024字节数据
print(data.decode('utf8'))# 将数据从byte类型转为utf8
if data=='exit':
break
sk.send(bytes(data, 'utf8'))# 客户端向服务器端发送数据,从utf8转为byte类型
data = sk.recv(1024)
print(str(data, 'utf8')) # 将接收的数据从byte类型转为utf8
sk.close()#关闭服务器连接池
网络远程
=============== 1、服务器端执行命令 ===============
import socket
sk = socket.socket()
#创建socket对象
address = ('127.0.0.1',8000)
#本地链接ip和端口
sk.bind(address)
#socket对象绑定address
sk.listen(3)#配置最大连接数
while True:
conn,addr = sk.accept()#开启监听连接
while True:
data =conn.recv(1024)#服务器接收客户端1024字节数据
print(str(data,'utf8'))#将接收的数据从byte类型转为utf8
if not data :
conn.close()#关闭客户端连接
break
obj = subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE)
conn.send(byte(str(len(obj.stdout.read())),'utf8'))#先发送结果长度,方便客户的接收
conn.recv(1024)#此处接收任意数据,防止两次发送出现粘包现象,影响数据接收方处理
conn.send(obj.stdout.read())#只能发送byte数据类型,需要将数据从utf8转byte
sk.close()#关闭服务器连接池
=============== 2、客户端发送命令和读取结果 ===============
import socket
sk = socket.socket()
#创建socket对象
address = ('127.0.0.1',8000)
#服务器端的链接ip和端口
sk.connect(address)
#向服务器端发起连接
while True:
data = input('>>>')# 客户端接收服务器发送的1024字节数据
print(data)# 将数据从byte类型转为utf8
if data=='exit':
break
sk.send(bytes(data, 'utf8'))# 客户端向服务器端发送数据,从utf8转为byte类型
ret_len = int(str(sk.recv(1024)
,'utf8'))
sk.send('ok')#此处发送任意数据,通知对方继续发送数据,防止出现粘包现象
data = bytes()
while len(data) != ret_len:
recv = sk.recv(1024)
data += recv
print(str(data, 'gbk')) # 将接收的数据从byte类型转为utf8
sk.close()#关闭服务器连接池
socketserver并发聊天
1、服务端
import socketserver
class MyServer(socketserver.BaseRequestHandler):#必须继承该父类
def handle(self):#在此处理客户端逻辑
print('服务端启动:...')
while True:
conn = self.request#获取客户端连接
print(self.client_address)
while True:
client_data = conn.recv(1024)
print(str(client_data,'utf8'))
print('waiting....')
server_data = input('>>>:')
conn.sendall(bytes(server_data,'utf8'))
conn.close()
server = socketserver.ThreadingTCPServer(('127.0.0.1',8908), MyServer)
#创建server对象,MyServer类是处理业务逻辑
print('start')
server.serve_forever()
#启动服务端
2、客户端
import socket
ip_port = ('127.0.0.1', 8893)
sk = socket.socket()
sk.connect(ip_port)
print('启动客户端:')
while True:
inp = input('>>>')
sk.sendall(bytes(inp,'utf8'))
if inp=='exit':
break
ret = sk.recv(1024)
print(str(ret,'utf8'))
sk.close()
事件驱动模型
********************************************* 事件驱动模型 *********************************************
https://www.cnblogs.com/yuanchenqi/articles/5722574.html
目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:
1、有一个事件(消息)队列;
2、鼠标按下时,往这个队列中增加一个点击事件(消息);
3、有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;
4、事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;
=============== 1、非阻塞IO ===============
import time
import socket
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.bind(('127.0.0.1',6667))
sk.listen(5)
sk.setblocking(False)
#非阻塞设置
while True:
try:
#非阻塞状态下若监听不到客户端连接会报异常,所以可以循环处理异常
print ('waiting client connection .......')
connection,address = sk.accept() # 进程主动轮询
监听
print("+++",address)
client_messge = connection.recv(1024)
print(str(client_messge,'utf8'))
connection.close()
except Exception as e:
print (e)
time.sleep(4)
# select 模拟一个socket server,注意socket必须在非阻塞情况下才能实现IO多路复用。
# 接下来通过例子了解select 是如何通过单进程实现同时处理多个非阻塞的socket连接的。
#server端
import select
import socket
import queue
server = socket.socket()
server.bind(('localhost',9000))
server.listen(1000)
server.setblocking(False) # 设置成非阻塞模式,accept和recv都非阻塞
# 这里如果直接 server.accept() ,如果没有连接会报错,所以有数据才调他们
# BlockIOError:[WinError 10035] 无法立即完成一个非阻塞性套接字操作。
msg_dic = {}
inputs = [server,] # 交给内核、select检测的列表。
# 必须有一个值,让select检测,否则报错提供无效参数。
# 没有其他连接之前,自己就是个socket,自己就是个连接,检测自己。活动了说明有链接
outputs = [] # 你往里面放什么,下一次就出来了
while True:
readable, writeable, exceptional = select.select(inputs, outputs, inputs) # 定义检测
#新来连接 检测列表 异常(断开)
# 异常的也是inputs是: 检测那些连接的存在异常
print(readable,writeable,exceptional)
for r in readable:
if r is server: # 有数据,代表来了一个新连接
conn, addr = server.accept()
print("来了个新连接",addr)
inputs.append(conn) # 把连接加到检测列表里,如果这个连接活动了,就说明数据来了
# inputs = [server.conn] # 【conn】只返回活动的连接,但怎么确定是谁活动了
# 如果server活动,则来了新连接,conn活动则来数据
msg_dic[conn] = queue.Queue() # 初始化一个队列,后面存要返回给这个客户端的数据
else:
try :
data = r.recv(1024) # 注意这里是r,而不是conn,多个连接的情况
print("收到数据",data)
# r.send(data) # 不能直接发,如果客户端不收,数据就没了
msg_dic[r].put(data) # 往里面放数据
outputs.append(r) # 放入返回的连接队列里
except ConnectionResetError as e:
print("客户端断开了",r)
if r in outputs:
outputs.remove(r) #清理已断开的连接
inputs.remove(r) #清理已断开的连接
del msg_dic[r] ##清理已断开的连接
for w in writeable: # 要返回给客户端的连接列表
data_to_client = msg_dic[w].get() # 在字典里取数据
w.send(data_to_client) # 返回给客户端
outputs.remove(w) # 删除这个数据,确保下次循环的时候不返回这个已经处理完的连接了。
for e in exceptional: # 如果连接断开,删除连接相关数据
if e in outputs:
outputs.remove(e)
inputs.remove(e)
del msg_dic[e]
#*************************client
import socket
client = socket.socket()
client.connect(('localhost', 9000))
while True:
cmd = input('>>> ').strip()
if len(cmd) == 0 : continue
client.send(cmd.encode('utf-8'))
data = client.recv(1024)
print(data.decode())
client.close()