python学习笔记 第十章 网络编程
Python学习笔记 第十章 网络编程
补充一点导入包的知识
导入一个包(文件夹)相当于执行了这个包下的__init__文件
并不相当于把这个包下的所有文件都导入进来
#想直接导入某个包下的文件
方式一:
import glance.api
方式二:
from glance import api
即from ... import xxx 后面的xxx不能带.
文件目录如下
-app
-cmd
__init__.py
versions.py
-db
-api
-__init__.py
在cmd下的__init__.py需要导入cmd下的versions.py
可以通过sys.path.append(r'D:\app')
或者在app目录下的__init__.py中添加from app import cmd 使用:cmd.
想要在
# . 表示当前目录 ..表示上级目录
当你需要写一个功能,这个功能不是直接运行的,而是被别人导入之后使用的,这种情况如果你的独立功能形成文件夹
文件夹内的所有文件都需要使用相对导入
如果我们自己开发一个项目,这个项目有一些文件需要直接运行的,这情况下不适合用相对导入,适合绝对导入
面向对象的补充:
class Person(object):
def __init__(self, name, age):
self.name = name
slef.age = age
def __eq__(self, other): #两个对象比较的时候会自动调用这个方法
if self.name == other.name and self.age == other.age:
return True
else:
return False
apple = Person('apple', 12)
banana = Person('banana', 18)
print(apple == banana) # == 符号刚好回调用apple对象对应的__eq__方法
#banana会当作参数传到__eq__方法当中
#apple == banana 得到的值就是__eq__方法的返回值
1.基础知识
不变的:mac地址 能够唯一的标识你这台机器
变化的:ip地址 能够更好的更方便的找到你的机器
局域网:连接在同一个交换机的 交换机:识别mac地址
知道一台机器的ip地址,要给这台机器发送信息,这时候交换机不认识ip地址,就先获取这台机器的mac地址,然后双方都知道对方的mac地址,用交换机就可以进行数据的传输
这个过程用到了交换机, 广播(发给所有的)、单播(单独给一台机器)、组播(给其中一部分发,另一部分不发)
arp协议:地址解析协议,通过一台机器的ip地址获取到它的mac地址,用到了交换机的广播和单播
局域网:网段 交换机 不能理解ip地址,只能理解mac地址
局域网和局域网之间的通信: 网关 路由器 可以理解ip地址
机器1 - 》 交换机1 - 〉 网关 -》路由器 -〉
ip地址:
ipv4:四位点分十进制 0.0.0.0 - 255.255.255.255
公网地址:需要我们自己申请购买的地址
内网地址:保留字段,192.168 是常见的内网地址,永远不会和公网地址冲突 还有
- 10.0.0.0 - 10.255.255.255 公司
- 172.16.0.0 - 172.31.255.255 学校
- 192.168.0.0 - 192.168.255.255 学校
特殊的ip地址:127.0.0.1 本地回环地址 测试的时候使用
查看自己的ip地址 ipconfig (win) ifconfig(mac/linux)
子网掩码:也是一个ip地址,用来判断两台机器在不在一个局域网内
192.168.12.1
11000000.10101000.00000110.00000001
255.255.255.0
11111111.11111111.11111111.00000000 #192.168.12.0
ipv6: 0:0:0:0:0:0 - FFFFFF:FFFFFF:FFFFFF:FFFFFF:FFFFFF:FFFFFF
ip和mac是确认机器的
端口:0-65535 确认机器上的具体应用程序
ip:8080
1.1概念的整理:
- 局域网的概念
- 交换机
- 在同一个局域网内的机器由交换机负责通信
- 交换机只认识mac地址
- 可以完成广播、组播、单播(mac地址,在网卡上)
- 局域网之间的通信
- 路由器
- 提供网关ip,同一个局域网的所有机器共享一个网关
- 我们不能访问除了本局域网之外的其他内网的ip地址
- 子网掩码
- 用来判断两台机器是不是在一个网段内
- 路由器
- ip地址:ipv4 ipv6
- mac地址:arp协议(通过ip找到mac)
- 端口port:0-65535 用来确认一台机器上的应用程序
- 交换机
client端 客户端:多个 用户自由的打开关闭 我们自己安装
server端 服务端:一个 7*24小时工作 不需要安装
1.2网络开发架构:
- C/S 架构 client客户端 server服务器 需要安装才能使用的
- B/S 架构 browser浏览器 server服务器 如百度、博客园、谷歌
- B/S和C/S有什么关系:B/S架构也是C/S架构的一种
C/S架构的好处
- 可以离线使用
- 功能更完善
- 安全性更高
B/S架构的好处
- 不安装可以使用
- 统一PC端用户入口
1.3 分层协议
应用层 表示层 会话层
传输层 网络层 数据链路层
物理层
osi五层协议:
应用层
传输层 port 与端口打交道
网络层 ipv4 ipv6 路由器 (三层交换机--具有路由器的功能)
数据链路层 mac arp协议 交换机 网卡
物理层
(层数以数楼层来,从下往上数)
1.4tcp和udp
tcp:语音聊天、视频聊天、线下缓存高清电影、远程控制、发邮件
-
需要先建立连接,然后才能通信
-
占用连接、可靠(消息不回丢失)、实时性高、慢
-
建立连接-三次握手、四次挥手
-
三次握手
客户端向服务器发送syn请求建立连接,服务端向客户端回复ack并发送syn请求,客户端接收到请求之后再回复ack表示建立连接
由客户端的connect+服务端的accept
-
四次握手
客户端向服务端发送FIN请求,服务端向客户端回复ack,等数据完成传输之后再想客户端发送FIN请求断开连接,客户端回复ack表示断开连接
客户端的close和服务端的close
server. <--------------------SYN 请求连接服务------------------client -------------------ACK-----------------> | -> SYN+ACK -------------------SYN-------------------> | <----------------------ACK---------------------------- 全双工通信;双方能相互通信 send --------------------------------------------------> recv <-------------------------------------------------- 挥手为什么要四次:因为还有遗留的数据没传送完,挥手的这两次必须要分开 ---------------------FIN---------------------------> <--------------------ACK---------------------------- <--------------------FIN---------------------------- ---------------------ACK---------------------------->
-
udp:发消息- 在线播放视频、qq微信
- 不需要建立连接、就可以通信的
- 不占用连接、不可靠、消息可能因为网络不稳定丢失、快
2.socket
socket套接字:
#先启动server.py
import socket
#server 服务端
sk = socket.socket()
sk.bind(('127.0.0.1', 9000))
sk.listen() #监听
conn, addr = sk.accept() #conn是连接
conn.send(b'hello')
msg = conn.recv(1024) #只接收1024
print(msg)
conn.close() #断开连接
sk.close() #关闭连接
#返回b'byebye'
#再启动client.py
import socket
#client 客户端
sk = socket.socket()
sk.connect(('127.0.0.1', 9000))
msg = sk.recv(1024)
print(msg)
sk.send(b'byebye')
sk.close()
#返回b'hello'
3.tcp协议和udp协议
3.1tcp协议
server.py
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8001)) #申请操作系统的资源
sk.listen()
while True: #能够和多个客户端进行握手
conn, addr = sk.accept() # conn里存储的是一个客户端和一个server端的连接信息,连接需要进行三次握手
while True: #为了能和一个客户端讲多句话
send_msg = input('>>>')
conn.send(send_msg.encode('utf-8')) #按照utf-8的格式转换为buyes类型
#str -> encode -> bytes b'12278' -> decoded('utf-8') -> 你
if send_msg.upper() == 'Q':break
msg = conn.recv(1024)
msg2 = msg.decode('utf-8')
if msg.upper() == 'Q': break
print(msg, msg2)
conn.close() #挥手断开连接
sk.close() #归还操作系统的资源
client.py
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8001))
whike True:
msg = sk.recv(1024) #接收到的bytes
msg2 = msg.decode('utf-8') #将bytes按utf-8的方式进行解码,得到字符
print(msg, msg2)
if msg2.upper() == 'Q':break
send_msg = input('>>>')
sk.send(send_msg.encode('utf-8'))
if send_msg.upper() == 'Q':break
sk.close()
操作系统:统一分配计算机的所有资源
端口号:0-65535
3.2udp协议
server.py
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
sk.bind(('127.0.0.1', 9001))
while True:
msg, addr = sk.recvfrom(1024) #不需要连接
print(msg.decode('utf-8'))
msg = input('>>>')
sk.sendto(msg.encode('utf-8'), addr) #收到谁的消息就给谁发
client.py
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
server = ('127.0.0.1', 9001)
while True:
msg = input('>>>')
if msg.upper() == 'Q': break
sk.sendto(msg.encode('utf-8'), server)
msg = sk.recv(1024).decode('utf-8')
if msg.upper() == 'Q': break
print(msg)
3.3tcp协议完成文件上传
server.py
import json
import struct
import socket
#接收
sk = socket.socket()
sk.bind(('127.0.0.1', 8001))
sk.listen()
conn, addr = sk.accept()
#解决粘包现象
mlen = conn.recv(4) #收到mlen
dic_len = struct.unpack('i', mlen)[0] #解包之后是一个元组
#接收文件名和文件大小
msg = conn.recv(dic_len).decode('utf-8')
msg = json.loads(msg)
#接收文件
with open(msg['file_name'], 'wb') as f:
file_data = conn.recv(msg['file_size'])
f.write(file_data)
#对于大文件传输
while msg['file_size'] > 0:
content = conn.recv(1024)
msg['file_size'] -= len(content) #tcp每次收到的不一定就是1024,通过网络tcp会自动拆包
f.write(content)
conn.close()
sk.close()
client.py
import os
import json
import struct
import socket
#发送
sk = socket.socket()
sk.connect(('127.0.0.1', 8001))
#文件名、文件大小、文件
abs_path = r'D:\app\tmp'
file_name = os.path.basename(abs_path)
file_size = os.path.getsize(abs_path)
#传输习惯上习惯传输json
dic = {'file_name':file_name, 'file_size':file_size}
str_dic = json.dumps(dic)
sk.send(str_dic.encode('utf-8')) #此处可能会发生粘包
#解决粘包问题
b_dic = str_dic.encode('utf-8')
mlen = struct.pack('i', len(b_dic))
sk.send(mlen) #四个字节表示字典的长度
sk.send(b_dic) #具体的字典
#传输文件
with open('a.txt', mode = 'rb') as f:
file_data = f.read()
sk.send(file_data)
#对于大文件
while file_size > 0:
content = f.read(1024)
file_size -= 1024
sk.send(content)
sk.close()
4.粘包
粘包现象:只出现在tcp协议当中,多条消息之间没有边界,并且还有一堆的优化算法
- 发送端:两条消息很短并且发送的间隔很短
- 接收端:接收消息不及时,多条消息在接收方的缓存短堆在一起导致的粘包
粘包的本质:tcp协议的传输是流式传输,数据和数据之间没有边界
怎么解决粘包:自定义协议
- 先发送四个字节的数据长度 #先接收四个字节 知道数据的长度
- 再按照长度发送数据 #再按照长度接收数据
网络的最大带宽限制为MTU=15000字节
udp协议中如果没有做优化,会有发送的字节的限制(如qq消息发送会限制一定的长度)
自定义协议:
server.py
import struct
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8001))
sk.listen()
conn, addr = sk.accept()
msg1 = input('>>>').encode()
msg2 = input('>>>').encode()
num = str(len(msg1)) #
ret = num.zfill(4) #补充到四个字节为止
print(ret)
conn.send(ret.encode('utf-8'))
#blen = struct.pack('i', len(msg1))
#conn.send(blen)
conn.send(msg1)
conn.send(msg2)
client.py
import struct
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8001))
length = int(sk.recv(4).decode('utf-8'))
#length = sk.recv(4)
#length = struct.unpack('i', length)[0]
msg1 = sk.recv(length)
msg2 = sk.recv(1024)
print(msg1.decode('utf-8'))
print(msg2.decode('utf-8'))
当数据超过0-9999四个字节的时候
import struct
nums1 = 12345152134
num2 = 123
num3 = 8
ret1 = struct.pack('i', num1)
#只要这个数字不超过2**32,那么就能使用struct表示成4个字节
#可以传两个G的数据
struct.unpack('i', ret1)
通过计算即将要发送的数据的长度,通过struct模块把长度转换成固定的四个字节,发送四个字节的长度
收四个字节,再使用struct.unpack把四个字节转换成数字,这个数字就是即将要接收的数据的长度,再根据长度进行接收数据
python 代码: 用户态
操作系统:内核态
硬件:
5.验证客户端的合法性
什么场景?一定是公司内部,无用户的情况下
外界想要对开放的服务器获取端口号和服务,可以通过扫端口来进行
服务器为了安全,可以设置一些不能正常访问到的端口(蜜罐),只要使用了扫端口,那么可以立马封禁这个ip
日志一般会定期发送到一个专门存储的系统,但是会被外界发现后,同时也发送一个病毒到这个系统,会导致信息泄漏或者服务器的损坏
会制定一个认证机制,约定好的一个字符串作为密钥,密钥一般不会经过网络
客户端:2.使用随机发送的内容 + 密钥经过一个算法 = 结果 3.将结果发送会服务端
服务端:1.随机发送一个内容给客户端 4.服务端接收结果,用发送的内容+密钥 经过同一个算法,和接收的结果进行比对 5.相同则认证成功,这个是一个合法的客户端
当然经过网络的过程中,会被拦截,这个涉及到更安全的
#生成一个随机字符串
import os
ret = os.urandom(32) #随机生成一个32位的字符串
print(ret)
import hashlib
sha = hashlib.sha1('密钥')
sha.update(ret) #传入随机的字符串
result = sha.hexdigest()
#hmac 可以替代hashlib模块
import hmac
h = hmac.new(b'apple', os.urandom(32))
ret = h.digest()
print(ret) # b'uu\...'
server.py
import os
import socket
import hashlib
sk = socket.socket()
sk.bind(('127.0.0.1', 8001))
sk.listen()
conn, addr = sk.accept()
#密钥
secret_key = b'apple'
#创建一个随机的字符串
ret = os.urandom(32)
sha = hashlib.sha1(secret_key)
sha.update(ret)
result = sha.hexdigest()
#发送字符串
conn.send(ret)
#接收字符串
msg = conn.recv(len(result))
if msg.decode('utf-8') == result:
print('验证成功')
else:
#不合法的客户端
conn.close()
sk.close()
client.py
import hashlib
import socket
sk = socket.socket()
sk = sk.connect(('127.0.0.1', 8001))
#密钥
secret_key = b'apple'
#接收32位字符串
msg = sk.recv(32)
#将接收的内容+密钥经过算法得到余个字符串
sha = hsahlib.sha1(secret_key)
sha.update(msg)
result = sha.hexdigest()
sk.send(result.encode('utf-8'))
sk.close()
使用hmac
server.py
import socket
import os
import hmac
sk = socket.socket()
sk.bind(('127.0.0.1', 9001))
sk.listen()
msg = os.urandom(32)
secret_res = hmac.new(b'apple', msg, digestmod='MD5')
res = secret_res.digest()
conn, addr = sk.accept()
conn.send(msg)
result = conn.recv(len(res))
if res == result:
print('登陆成功')
else:
conn.close()
sk.close()
client.py
import socket
import hmac
sk = socket.socket()
sk.connect(('127.0.0.1', 9001))
msg = sk.recv(32)
secret_res = hmac.new(b'apple', msg, digestmod='MD5')
res = secret_res.digest()
sk.send(res)
sk.close()
7.socketserver
socket是更底层的模块,封装度低,效率不固定
socketserver模块是基于socket完成的,封装度更高,效率比较固定
处理tcp协议的server端处理并发的客户端的请求
server.py
import socket
import time
sk = socket.socket()
sk.bind(('127.0.0.1', 8001))
sk.listen()
while True:
conn, addr = sk.accept()
while True:
try:
msg = conn.recv(1024)
conn.send(msg.upper().encode('utf-8'))
time.sleep(0.5)
except ConnectionResetError:
break
conn.close()
sk.close()
client.py
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8001))
sk.send(b'hello')
msg = sk.recv(1024)
print(msg.decode('utf-8'))
sk.close()
这样只能在一个客户端关闭以后才能连接下一个客户端
改进:server.py
import time
import socketserver
class Myserver(socketserver.BaseRequestHandler): #自己没有__init__,到父类中去找,父类中会有self.handle()执行函数
#此时的self是Myserver,当然父类中也有handle方法,Myserver不实现handle则
#可以理解为每一个客户端都会有自己的一个客户端
def handle(self):
#所有的客户端都会从第一句开始执行,只需要考虑一个客户端怎么做
conn = self.request
while True:
try:
msg = conn.recv(1024)
conn.send(msg.upper().encode('utf-8'))
time.sleep(0.5)
except ConnectionResetError:
break
conn.close()
server = socketserver.ThreadingTCPServer(('127.0.0.1', 8001), Myserver)
server.serve_forever()
"""
class BaseRequestHandler:
def __init__(self):
#初始化
self.handle()
def handle(self):
pass
class Myserver(BaseRequestHandler):
def handle(self):
pass
my = Myserver() #先找自己类的handle方法,再找父类中的handle
#由于自己没有__init__,会调用父类的__init__方法
"""
6.小结
tcp协议的多人通信
- 和一个人通信说多几句话
- 和一个人聊完再和其他人聊
- socket() tcp协议的
- bind绑定一个IP和端口(元组)
- listen监听,代表socket服务的开启
- accept等到有客户端来访问和客户端建立连接
- send直接通过连接发送信息,不需要写地址
- sendto需要写一个对方的地址
- recv只接收消息
- recvfrom接收消息和地址
- connect客户端/tcp协议的方法,和server端建立连接
- close关闭连接、服务
udp:
- socket(type = SOCK_DGRAM)
- senddto 需要写一个对方的地址
- recvfrom接收消息和地址
- close关闭服务、连接
每一句话什么意思?执行到哪里程序会阻塞,为什么阻塞,什么时候结束阻塞?
- input #等待,用户输入enter
- accept #阻塞,有客户端来和我建立完连接之后
- recv #阻塞,直到对方发过来消息之后
- recvfrom #阻塞,直到对方发送消息之后
- connect #阻塞,直到server端结束了一个对client的服务,开始和当前client建立连接的时候
注意:
-
tcp协议的自定义协议解决粘包问题
- recv(1024)不代表一定收到1024个字节,而是最多只能收到这么多
- 两条连续发送的数据一定要避免粘包问题
- 先发送数据的长度,再发送数据
- 发送的数据相关的内容组成json:先发json的长度,再发json,json中存了接下来要发的数据的长度,再发送数据
-
验证客户端合法性
- 什么场景用
- 什么逻辑 什么是密钥 为什么要发送随机的字符串 使用什么算法 算法有什么要求(客户端和服务端的算法相同)
- 代码只能实现
-
并发的tcp协议server端
-
会背
import socketserver class Myserver(socketserver.BaseRequestHandler): def handle(self): conn = self.request """ """ conn.close() server = socketserver.ThreadingTCPServer(('127.0.0.1', 8001), Myserver) server.serve_forever()
-
知道代码从哪里开始:有客户端来的时候,从handle方法开始
-
作业:
1.登陆+文件下载+文件上传(密文登陆,至少要在server端进行一次摘要)
2.通过socketserver来实现