网络编程
七层网络协议
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()