python(七)回顾
socket练习
SocketServer
开发一个支持多用户在线的FTP程序
静态方法
与类无关,不能访问类里的任何属性和方法,不用self
类方法
只能访问类变量
属性方法@property
把一个方法变成一个静态属性
flight.status
@status.setter
flight.status = 3
@status.delter
反射
hasattr(obj,str)
setattr(obj,str,val)
getattr(obj,str)
delattr(obj,str)
动态导入模块
__import__(‘import_lib.metaclass’)
importlib.import_module(‘import_lib.metaclass’) #官方建议用这个
__new__,早于__init__
类名()():执行__call__方法
__metaclass__ 用了定义类以怎样的形式被创建
异常处理:
try :
……
except (ValueError,KeyError) as e:
print(e)
except Exception as e:
……
else:
……
finally:
……
raise:
断言:(断定)
assert type(“fgf”) is str # 断定是str类型,不是就报断言错误
print(“hehe”)
所有的网络协议本质上都是对数据的收发
socket 对以下封装:
tcp/ip send,recv
udp
family address # 地址簇
AF.INET ipv4
AF.INET6
AF.UNIX # 本地 local
socket protocol type
sock.SOCK_STREAM # tcp/ip
sock.SOCK_DGRAM # 数据报式socket,for UDP
socket服务端
server = socket.socket(AF.INET,sock.SOCK_STREAM)
server.bind((ip,port))
server.listen()
while True: # 不断接收新连接
conn,addr = server.accept() # 阻塞
while True: # 接收连接,多次通信
print("new conn",addr)
date = conn.recv(1024) #官方建议最大8192
conn.send(data.upper())
# recv 默认是阻塞的
if not data :
break # 客户端一断开,conn.recv 收的的就都是空数据,就进入死循环了
# 只能同时服务一个连接
server.close()
socket客户端
client = socket.socket()
client.connect((serverip,port))
client.send(data)
[... # 发多个]
client.recv(data)
client.close()
python3.x 发送只能是bytes类型
io缓冲区 buffer
缓冲区满了肯定会发
send(“”),强制触发
在上节(python(七)下:初识socket网络编程)中的问题:
如果执行命令结果超过1024,导致接收结果不全,执行新指令会显示上次的结果。如何解决这个问题?
则客户端需要重复接收多次结果,问题需要接收多少次?这就需要服务端在发送结果前就先把结果的大小发送给客户端。
SSH服务端
#/usr/bin/env python
# -*- coding: UTF-8 -*-
import socket, os
server = socket.socket()
server.bind(('localhost',9999))
server.listen()
while True:
conn, addr = server.accept()
print("new conn", addr)
while True:
print("等待新指令")
data = conn.recv(1024)
if not data:
print("客户端已断开")
break
print("执行指令:",data)
cmd_res = os.popen(data.decode()).read()
print("before send",len(cmd_res))
if len(cmd_res) == 0:
cmd_res = "cmd has no output..."
# 发送结果大小给客户端(整数不能encode,先转为str)
# 注意:这里是len结果的encode形式,和客户端len的方式要一致
conn.send( str(len(cmd_res.encode())).encode('utf-8') )
# 发送执行结果
conn.send(cmd_res.encode('utf-8'))
print("send done")
server.close()
# 防止客户端不断开的时候,服务端重启提示端口占用,可以如下实例化解决
# server = socket.socket() #获得socket实例
# server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
SSH客户端
#/usr/bin/env python
# -*- coding: UTF-8 -*-
import socket
client = socket.socket()
client.connect(('localhost', 9999))
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'' # 声明空bytes
# 循环接收命令结果
while received_size != int(cmd_res_size.decode()):
data = client.recv(1024)
received_size += len(data) # 每次收到的有可能小于1024,所以必须用len判断
print("当前接收大小:",received_size)
received_data += data # 接收命令结果
else: # 接收完命令结果之后,打印
print("cmd res receive done ...",received_size)
print(received_data.decode())
client.close()
上面的代码在windows中运行没有问题,但是在linux中运行客户端会有如下报错:
while received_size != int(cmd_res_size.decode()):
ValueError: invalid literal for int() with base 10: '26anaconda-ks.cfg\nc.py\ns.py\n'
明明接收大小是整数,怎么后面还连有其他数据?这就出现了粘包问题
看服务端代码:
conn.send( str(len(cmd_res.encode())).encode('utf-8') )
conn.send(cmd_res.encode('utf-8'))
虽然send可以强制发送数据,但是由于两次发送的间隔非常小,所以系统会当成一次send发送数据,从而数据粘在一起了。
由于是python3.x版本,如果是python2.x版本,在windows上也会出现粘包问题。3.x上偶尔也会出现在windows上
那么怎么解决粘包问题
方法1:sleep
两次发送send中,sleep0.5秒
import time
……
conn.send( str(len(cmd_res.encode())).encode('utf-8') )
time.sleep(0.5)
conn.send(cmd_res.encode('utf-8'))
方法二:接收客户端确实信息
两次send中间,不立刻执行第二条send。
服务端
conn.send( str(len(cmd_res.encode())).encode('utf-8') )
client_ack = conn.recv(1024) # wait client to confirm
conn.send(cmd_res.encode('utf-8'))
客户端
cmd_res_size = client.recv(1024) # 接收命令结果的长度
client.send("准备好接收命令结果了".encode())
FTP server 代码基本步骤:
1. 读取文件名
2. 检测文件是否存在
3. 打开文件
4. 检测文件大小
5. 发送大小给客户端
6. 等客户端确认
7. 开始边读边发数据
8. 发送MD5值
ftp服务端
#/usr/bin/env python
# -*- coding: UTF-8 -*-
import socket, os, hashlib
server = socket.socket()
server.bind(('localhost',9999))
server.listen()
while True:
conn, addr = server.accept()
print("new conn",addr)
while True:
print("等待新指令")
data = conn.recv(1024)
if not data :
print("客户端已经断开")
break
cmd, filename = data.decode().split()
print(filename)
if os.path.isfile(filename): # 判断是否是一个文件,而不是判断是否存在
f = open(filename, 'rb')
m = hashlib.md5()
file_size = os.stat(filename).st_size # 读取出文件大小
conn.send( str(file_size).encode() ) # 发送文件大小
conn.recv(1024) # 接收客户端确认,ack
for line in f: # hashlib 是不能直接对文件做MD5的
m.update(line) # update 连续的,hash只能bytes类型
conn.send(line) # 循环发数据
print("file md5", m.hexdigest())
f.close()
conn.send(m.hexdigest().encode()) # 发送MD5
print("send done")
server.close()
ftp客户端
#/usr/bin/env python
# -*- coding: UTF-8 -*-
import socket, hashlib
client = socket.socket()
client.connect(('localhost', 9999))
while True:
cmd = input(">>>").strip()
if len(cmd) == 0: continue
if cmd.startswith("get"): # 代表下载文件
client.send(cmd.encode()) # 发送命令
server_response = client.recv(1024) # 接收文件大小
print("server response:", server_response)
client.send(b"ready to recv file") # 发送确认信息
file_total_size = int(server_response.decode())
received_size = 0
filename = cmd.split()[1] # 获取文件名
f = open(filename+'.new', 'wb') # 打开写文件
m = hashlib.md5()
while received_size < file_total_size :
if file_total_size - received_size > 1024:
size = 1024 # 如果超过1024,只接收1024大小
else : # 最后一次,剩多少收多少
size = file_total_size - received_size
# 如果小于1024,只接收那一小部分,通过这种方式就避免了粘包现象
print("last receive:", size)
data = client.recv(size)
received_size += len(data)
m.update(data)
f.write(data)
else:
new_file_md5 = m.hexdigest()
print("file recv done", received_size, file_total_size)
f.close()
server_file_md5 = client.recv(1024)
print("server file md5:", server_file_md5)
print("client file md5:", new_file_md5)
client.close()
socket不支持多并发,socketserver最主要的作用:就是实现一个并发处理,前面只是铺垫。
socketserver就是对socket的再封装。简化网络服务器版的开发。
class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)
class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)
class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)
class socketserver.UnixDatagramServer(server_address, RequestHandlerClass,bind_and_activate=True)
类型之间的关系:
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
- 首先,您必须创建一个请求处理类,继承BaseRequestHandlerclass类并且重写父类的handle()方法,该方法将处理传入的请求。
- 其次,你必须实例化一个上面类型中的一个类(如TCPServer)传递服务器的地址和你上面创建的请求处理类 给这个TCPServer。
- 然后,调用handle_request()或者serve_forever()方法来处理一个或多个请求。
ser.handle_request() # 只处理一个请求,处理完就退出了
ser.serve_forever() # 处理多个请求,永远执行。
- 最后,调用server_close()关闭socket。
socketserver 服务端
#/usr/bin/env python
# -*- coding: UTF-8 -*-
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler): # 自己写的请求处理类
# 重写handle,跟客户端所有的交互都是在handle里完成的,请求处理过程都是handle规定的
def handle(self):
while True:
try:
self.data = self.request.recv(1024).strip() # 等同socket的server.recv
print("{} wrote:".format(self.client_address[0]))
print(self.data)
self.request.send(self.data.upper())
# 不支持像socket那样,通过 if not self.data: break 方式断开
except ConnectionResetError as e:
print("客户端已断开",e)
break
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
# server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) # 和socket一样,并没加入多并发功能
server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler) # 多线程
# server = socketserver.ForkingTCPServer((HOST, PORT), MyTCPHandler) # 多进程,在windows上不可以,不支持os.fork
server.serve_forever()
# 具体的调用关系可以看ThreadingTCPServer和ForkingTCPServer的源码
客户端
import socket
client = socket.socket() #声明socket类型,同时生成socket连接对象
client.connect(('localhost',9999))
while True:
msg = input(">>:").strip()
if len(msg) == 0:continue
client.send(msg.encode("utf-8"))
data = client.recv(10240)
print("recv:",data.decode())
client.close()
fileno() # 返回文件描述符
handle_request() # 处理单个请求
server_forever(poll_interval=0.5) # 每0.5秒检测一下是否发了让我shutdown的信号,做一些清理子进程或线程工作
server_close() # 告诉server_forever(),让它停掉
server_close() # 关闭
address_family # 地址簇
RequestHandlerClass # 请求处理类
server_address # IP地址
allow_reuse_address # 允许重用地址,用于服务器断开,客户端未断开,端口需要等待几十秒才能用
# 使用如下:
server = socket.socket() #获得socket实例
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
finish_request() # 和handle关系如下,看源码(BaseRequestHandler类构造函数):
def __init__(self, request, client_address, server):
......
self.setup()
try:
self.handle()
finally:
self.finish()
要求:
用户加密认证
允许同时多用户登录
每个用户有自己的家目录 ,且只能访问自己的家目录
对用户进行磁盘配额,每个用户的可用空间不同
允许用户在ftp server上随意切换目录
允许用户查看当前目录下文件
允许上传和下载文件,保证文件一致性
文件传输过程中显示进度条
附加功能:支持文件的断点续传
转载请务必保留此出处:http://blog.csdn.net/fgf00/article/details/52752746