Python:使用socket包和threading包实现多线程服务器
第一次写博客,原创,谢绝转载,欢迎指正,谢谢!
Created on Sat Feb 1 12:22:07 2020
@author: 步棋晟
1. 说明
本文程序利用socket包和threading包实现简单的ftp和ssh功能。
功能演示如下:
(1). 启动服务端(一定要先启动服务端)
(2). 启动客户端
(3). 演示ssh功能,输入"dir"命令
(4). 演示ftp功能,输入"get C++.pdf"
(5). 打开第二个客户端
(6). 运行第二个客户端
2. 客户端
客户端程序如下:
可以打开多个终端运行客户端,来测试服务端多线程的有效性。
具体解释请看代码中的注释。
# -*- coding: utf-8 -*-
"""
Created on Fri Jan 24 15:26:47 2020
@author: 步棋晟
"""
import os
import socket
import hashlib
import datetime
# 客户端要用Ctrl+C断开,服务端不会出现异常,点×会出现异常。为什么?
def main():
# 声明一个socket实例作为客户端,并将客户端与服务器连接。
client = socket.socket()
client.connect(('localhost',6969)) # (("192.168.43.244", 6969)) # 用于测试
# 进入循环交互
while True:
# 客户端输入要发送给服务端的指令,不能输入空指令。
msg = input(">>:").strip()
if msg == '':
continue
# 将指令发送给服务端,并接收服务端将发送过来的数据的大小。
client.send(msg.encode("utf-8"))
data_size = int(client.recv(1024).decode())
# 发送一个"ready!"给服务端,防止粘包,初始化已接收到的数据的大小。
client.send("ready!".encode("utf-8"))
recv_data_size = 0
# "get"指令用于ftp服务,如发送的指令为"get book.pdf",
# 服务端将把book.pdf(如有)发送给客户端。
if msg.startswith("get"):
# 声明文件句柄, 为了测试将创建时间加在文件名前。
file_handle = open(datetime.datetime.now().strftime('%Y%m%e%H%M%S_')+msg.split()[1], "wb")
# 使用handlib.md5()来检查文件的完整性。
m = hashlib.md5()
# 开始接收数据,若已接收到的数据大小小于预先发送过来的数据大小,就循环接收。
# 1、接收数据, 2、将接收到的数据的大小累加到recv_data_size上,
# 3、显示就接收进度, 4、更新m, 5、将数据写入文件。
while data_size > recv_data_size:
data = client.recv(1024)
recv_data_size += len(data)
n = int(recv_data_size*30/data_size)
print('['+'#'*n+'-'*(30-n)+']({}/{})\r'.format(recv_data_size, data_size), end='')
m.update(data)
file_handle.write(data)
else:
# 数据接收完毕,为防止粘包,发送"receive finish!"给服务端。
# 接收服务端的hashlib.md5()生成的密文,
# 并与接收到的数据生成的密文进行比较,检查文件的完整性。
file_handle.close()
print('\n', end='')
client.send("receive finish!".encode("utf-8"))
m_recv = client.recv(1024).decode()
if m.hexdigest() == m_recv:
print("get done!")
# 以下用于ssh服务, 接收并显示指令的回文。
else:
recv_data = ''
while data_size > recv_data_size:
data = client.recv(1024)
recv_data_size += len(data)
recv_data += data.decode()
else:
print("recv:\n", recv_data)
client.close()
if __name__ == '__main__':
main()
os.system("pause")
3.服务端
服务端的代码如下:
服务端除非强行中断,否则不会停止。
具体解释请看代码注释。
# -*- coding: utf-8 -*-
"""
Created on Fri Jan 24 15:26:47 2020
@author: 步棋晟
"""
import os
import socket
import hashlib
import threading
#说明:自编多线程服务器,不使用socketserver包。
# 服务端类,用于生产socket实例,绑定端口,监听,处理多线程,
# 类似于socketserver.ThreadingTCPServer类。
class TCPserver(object):
def __init__(self, server_address, handler):
self.server = socket.socket()
self.server.bind(server_address) # ("192.168.43.244", 6969))
self.handler = handler
def serve_forever(self):
self.server.listen()
while True:
try:
request, address = self.server.accept()
thread = threading.Thread(target=self.handler,
args=(request, address, self.server))
thread.start() # 线程是否需要手动关闭?还是执行完了线程自动关闭?
except Exception as excep:
print(excep)
# 类似于socketserver.BaseRequestHandler类,用于处理与客户端的交互,
# 每接收到一个客户端,生成一个该类的实例。
class Handler(object):
# 可客户端连接句柄,客户端地址,服务端句柄传进来并保存。
# 启动setup(), handle(), finish()三个函数。
def __init__(self, request, client_address, server):
self.request = request
self.client_address = client_address
self.server = server
self.setup()
try:
self.handle()
finally:
self.finish()
# 显示客户端信息及地址。
def setup(self):
print("client request:")
print(self.request, self.client_address)
print("#"*40)
# 处理与客户端的交互。
def handle(self):
# 循环交互。
while True:
try:
# 接收客户端的指令,并判断指令是否为空,若为空,认为客户端断开,结束。
# 不知道这样结束是不是有什么不对?
data = self.request.recv(1024).decode()
if not data:
break
# "get"指令为ftp服务。
# 以下为将文件发送过去的过程,不解释了,配合客户端看吧!改天有心情在说。
if data.startswith("get"):
filename = data.split()[1]
m = hashlib.md5()
if os.path.isfile(filename):
with open(filename, "rb") as file_handle:
file_size = os.stat(filename).st_size
self.request.send(str(file_size).encode("utf-8"))
self.request.recv(1024)
send_size = 0
for line in file_handle:
send_size += len(line)
n = int(send_size*30/file_size)
print('['+'#'*n+'-'*(30-n)+']({}/{})\r'.format(send_size, file_size), end='')
m.update(line)
self.request.send(line)
print('\n', end='')
self.request.recv(1024)
self.request.send(m.hexdigest().encode("utf-8"))
# 以下为ssh服务过程。
# 也不解释了,配合客户端看吧!改天有心情在说。
else:
print("Execute:", data)
cmd_res = os.popen(data).read().encode("utf-8")
if cmd_res == "":
cmd_res = "invalid command! Or without return!"
self.request.send(str(len(cmd_res)).encode("utf-8"))
self.request.recv(1024)
self.request.send(cmd_res)
except Exception as excep:
print(excep.args)
break
# 客户端断开,显示该客户端断开,及其信息与地址。
def finish(self):
print("!"*80)
print("client disconnect::")
print(self.request, self.client_address)
print("="*80)
def main():
server = TCPserver(('localhost', 6969), Handler)
server.serve_forever()
os.system("pause")
if __name__ == '__main__':
main()