多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。
一开始用socket来写客户端并不能实现多进程
from socket import *
server = socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5) #半进程池大小
while True:
print("等待客户端接入")
conn,client_addr = server.accept()
print(conn,client_addr)
while True:
try:
msg_from = conn.recv(1024).decode('utf-8')
print(f"收到[{client_addr}]发来信息:[{msg_from}]")
if len(msg_from) == 0:
break
conn.send(msg_from.upper().encode('utf-8'))
print(f"返回信息[{msg_from.upper()}]")
except Exception:
break
conn.close()
后来选择socketserver来实现多线程,服务端的代码比较固定,真正需要编辑的应该是数据收发部分。
先写下client的代码,由于是多线程,可以重复几个:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# author: Frank time:2023/1/12
from socket import *
import struct
import json
client = socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8081))
while True:
cmd = input(">>>").strip()
if len(cmd) == 0:continue
if cmd == 'quit':break
client.send(cmd.encode('utf-8'))
# (*****)为了防止粘包:
# 按照约定,前4位是头信息,那就先recv(4),然后反解出来完整信息,最终拿到body的size
header_struct = client.recv(4)
header_size = struct.unpack('i',header_struct)[0] #解包返回的是一个元组,第一位是元数据的真实size
header_json_byte = client.recv(header_size)
header_json = header_json_byte.decode('gbk')
header = json.loads(header_json)
body_size = header['body_size']
res = b''
res_size = 0
while res_size < body_size:
res_from_server = client.recv(1024)
res_size += len(res_from_server)
res += res_from_server
print(res.decode('gbk'))
client.close()
server代码:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# author: Frank time:2023/1/12
import subprocess
import socketserver
import json
import struct
class MyRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
print(self.request)
print(self.client_address)
while True:
try: #防止客户端报错,服务端是需要一直online的
cmd_from_client = self.request.recv(1024)
if len(cmd_from_client) == 0 : break #防止客户端突然中断,服务端一直接收空的情况
cmd = cmd_from_client.decode('gbk')
print("收到的命令是: %s"%cmd)
res_obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
# 打印正确输出的内容
res_out = res_obj.stdout.read() # 这边返回的是二进制
# 打印错误返回的内容
res_err = res_obj.stderr.read()
res = res_out + res_err
print("返回的结果是: %s"%res.decode('gbk'))
# (*****)为了避免粘包:
# 粘包问题: 服务端返回的数据超过了一次client.recv(数据大小) 中能收到的值,TCP是稳定的流式协议,没有recv到的数据
# 也并不会丢掉,会存到内存中,会保存在客户端内存中,当再次执行recv的时候会收到之前没有收到的值
# 解决办法: 核心是规定边界。我先发一个头文件过去,系统会把我的头文件和我后面要发的正文粘在一起(TCP内部逻辑),我
# 采用一种方式,固定我头文件的字节数,跟客户端约定好,先读固定字节数,拿到我头文件的信息,文件中包含
# 我正文的大小(len),这样客户端就好读取数据了
header_dict = {
'filename' : 'a.txt',
'body_size': len(res),
'md5' : '234sdlfjklkwj423hj4jk3453'
}
header_json = json.dumps(header_dict) #json用于信息传输格式 json字符串格式
header_json_byte = header_json.encode('utf-8')
# 用struct打包头,实际上是把头的长度信息包起来,用‘i’的方式,是固定长度为4的一个二进制字符串,
# return出来是一个长度为4的二进制字符,这个二进制字符串代表的就是这个头的大小
header_struct = struct.pack('i',len(header_json_byte))
self.request.send(header_struct) #先把头的大小发过去
self.request.send(header_json_byte) #再把头文件发过去,(里面是包含正文大小的信息的,除此之外也许还有别的待添加的信息)
self.request.send(res)
except Exception:
break
self.request.close()
# 服务端应该做两件事
# 第一件事:服务器应该循环从半连接池取连接,建立双向连接,并拿到对象conn
s = socketserver.ThreadingTCPServer(('127.0.0.1',8081),MyRequestHandler)
s.serve_forever()
#就等同于:
# while True:
# conn,client_addr = server.accept()
# 第二件事:服务器应该循环去处理拿到的连接对象,与其进行通信循环 ===> 这边的原理是拿到一个连接之后,MyRequestHandler内部会去做处理
# 调用handle这个方法,所以这边通信循环的操作应该是写在handle里面