Python使用socket包和threading包实现多线程服务器

Python:使用socket包和threading包实现多线程服务器

第一次写博客,原创,谢绝转载,欢迎指正,谢谢!
Created on Sat Feb 1 12:22:07 2020
@author: 步棋晟

1. 说明

本文程序利用socket包和threading包实现简单的ftp和ssh功能。
功能演示如下:
(1). 启动服务端(一定要先启动服务端)
setup_server
(2). 启动客户端
setup_client
(3). 演示ssh功能,输入"dir"命令
ssh
(4). 演示ftp功能,输入"get C++.pdf"
ftp
(5). 打开第二个客户端
setup_second_client
(6). 运行第二个客户端
ftp_second_client

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()

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值