Day08的课程要点记录
详细教程地址:Day8 - �Socket编程进阶
一、上节内容回顾
1. 类的高级方法
静态方法:@staticmethod
与类无关,不能访问类里的任何属性和方法
类方法:@classmethod
只能访问类变量,不能访问实例变量
属性方法:@property
把一个方法变成一个静态属性
__new__
:先于init执行
__call__
:Foo()() 执行call方法
__metaclass__
:用来定义这个类以怎样的形式被创建。
2. 反射:
getattr(obj, str)
setattr(obj, str, val)
hasattr(obj, str)
delattr(obj, str)
动态导入模块
通过字符串来导入所需模块
# 解释器内部使用
lib = __import__('lib.aa')
print(lib.aa.C().name)
# 官方建议用这个
import importlib
aa = importlib.import_module("lib.aa")
print(aa.C().name)
3. 异常处理
try:
...
except (xxError, KeyError) as e:
...
except Exception as e: # 放在异常处理最后,抓取所有异常
...
else:
... # 没异常,就执行
finally:
... # 无论如何都执行
raise # 触发异常
4. 断言
当后续代码较为重要,执行前进行检查。(类似安检)
assert type(obj.name) is str # 断言其类型,正确则继续,错误则停止。
print("That is right.")
二、socket
2.1 socket的组成
2.1.1 family address 地址簇
AF_INET
AF_INET6
AF_UNIX
2.1.2 socket protocal type socket协议类型
SOCK_STEAM
SOCK_DGRAM
2.1.3 示例
# 服务端
server = socket.socket(sock.AF_INET, sock.SOCK_STEARM)
server.bind(localhost, 9999)
server.listen()
while True:
conn, addr = server.accept() # 等待
while True:
data = conn.recv(1024) # 官方不建议超过8192
print(data)
if not data:
break # 客户端断开,conn.recv()会重复接收空数据
conn.send(data.upper())
# 客户端
client = socket.socket()
client.connect((server_ip, server_port))
client.send(data)
client.recv(data)
2.2 通过socket实现ssh
# 服务端
import os
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 6666))
server.listen()
while True:
conn, addr = server.accept()
print("new conn:", addr)
while True:
data = conn.recv(1024)
if not data:
print("客户端已断开。")
break
print("执行指令:", data)
cmd_res = os.popen(data.decode()).read() # 接收字符串,执行结果也是字符串
print("Before send.")
if len(cmd_res) == 0:
cmd_res = "cmd_res does not exist."
conn.send(str(len(cmd_res.encode('utf-8'))).encode('utf-8'))
conn.send(cmd_res.encode('utf-8'))
print("After send.")
server.close()
# 客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('localhost', 6666))
while True:
cmd = input(">>: ").strip()
if len(cmd) == 0:
continue
client.send(cmd.encode('utf-8'))
cmd_res_size = client.recv(1024) # 接收命令结果的长度
print("收到的命令大小:", cmd_res_size)
received_size = 0
received_data = b''
while received_size < int(cmd_res_size.decode()):
data = client.recv(1024)
received_size += len(data) # 收到的数据可能小于1024
print(received_size)
received_data += data
# print(data.decode())
else:
print("cmd has received done...", received_size)
print(received_data.decode())
client.close()
2.3 socket 解决粘包问题
# 服务器
import os
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 6666))
server.listen()
while True:
conn, addr = server.accept()
print("new conn:", addr)
while True:
data = conn.recv(1024)
if not data:
print("客户端已断开。")
break
print("执行指令:", data)
cmd_res = os.popen(data.decode()).read() # 接收字符串,执行结果也是字符串
print("Before send.")
if len(cmd_res) == 0:
cmd_res = "cmd_res does not exist."
conn.send(str(len(cmd_res.encode('utf-8'))).encode('utf-8'))
# 连续send 2条会粘包
client_ack = conn.recv(1024) # 接收客户端响应
print("client ack: ", client_ack)
conn.send(cmd_res.encode('utf-8'))
print("After send.")
server.close()
# 客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('localhost', 6666))
while True:
cmd = input(">>: ").strip()
if len(cmd) == 0:
continue
client.send(cmd.encode('utf-8'))
cmd_res_size = client.recv(1024) # 接收命令结果的长度
print("收到的命令大小:", cmd_res_size)
client.send("client received data size.".encode('utf-8')) # 发送服务器信息,响应已收到大小
received_size = 0
received_data = b''
while received_size < int(cmd_res_size.decode()):
data = client.recv(1024)
received_size += len(data) # 收到的数据可能小于1024
print(received_size)
received_data += data
# print(data.decode())
else:
print("cmd has received done...", received_size)
print(received_data.decode())
client.close()
2.4 socket 实现文件发送
2.4.1 要点:
ftp server
- 读取文件名
- 检测文件是否存在
- 打开文件
- 检测文件大小
- 发送文件大小给客户端
- 等待客户端确认
- 开始边读边发数据
- 发送文件MD5值给客户端
- 客户端验证文件
- 关闭文件
ftp client
- 接收服务器发送的文件大小信息
- 向服务器发送收到信息的回应
-
wb
模式打开新文件 - 对比收到文件大小和总文件大小,若是不足1024bytes,接收剩余大小
- 边收文件边写MD5值
- 写入文件
- 关闭文件
- 接收服务器发来的文件MD5值,并与接收完毕的文件MD5值做比对。
三、SocketServer
3.1 socketserver服务类型
socketserver.TCPServer
socketserver.UDPServer
3.2 socketserver使用步骤:
- 自己创建一个请求处理类,该类要继承BaseRequestHandler,并且重构方法
- 实例化一个服务类,并且传递server ip 和 前一步创建的请求处理类 给 该服务类
- server.handle_request() # 只处理一个请求, server.serve_forever() # 永久执行
根据socketserver.BaseRequestHandler
源码
请求进来前self.setup()
请求处理时self.handle() # 一般这个就足够了
请求结束后self.finish()
示例:
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
while True:
try:
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# if not self.data:
# print("Client is disconnected.")
# break
self.request.sendall(self.data.upper())
except ConnectionResetError as e:
print("ConnectResetError:", e)
break
if __name__ == "__main__":
HOST, PORT = "localhost", 6666
# server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) # 单线程
server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler) # 多线程
# server = socketserver.ForkingTCPServer((HOST, PORT), MyTCPHandler) # 多进程,windows不可用
server.serve_forever()
四、作业:高级FTP
高级FTP服务器开发:
- 用户加密认证
- 多用户同时登陆
- 每个用户有自己的家目录且只能访问自己的家目录
- 对用户进行磁盘配额、不同用户配额可不同
- 用户可以登陆server后,可切换目录
- 查看当前目录下文件
- 上传下载文件,保证文件一致性
- 传输过程中现实进度条
- 支持断点续传