TCP聊天 传输文件服务器服务器套接字v2.0
所有版本记录:
v1.0
: TCP聊天服务器套接字|PyQt5 socket(TCP端口映射 端口放行) logging Thread(含日志,html) anaconda打包32位exe(3.4万字)|python高阶v1.1
: python TCP套接字服务器v1.1-修改新服务端命令功能bug(socket PyQt5)v1.2
: python TCP服务器v1.2 - 服务端新用户登录注册(json, md5加密)v1.3
: python TCP服务器v1.3 - 服务器抗压试验及关闭套接字处理v1.4
: python TCP服务器v1.4 - 处理客户端连接服务器异常(异常情况分类)v1.5
: PyQt下拉框可编辑(comboBox):editable - python TCP服务器v1.5 - 增加客户端连接界面的自定义参数(设置加时, 可选连接地址)v1.6
: Python TCP服务器v1.6 - multiprocessing多进程及Ctrl-c(SIGINT)退出v1.7
: Python TCP服务器v1.7 - PyQt5 server服务端来临v1.8
: python TCP服务器v1.8 - PyQt5登录界面美化 淡入淡出v1.9
: socketTCP协程文件 信息传递 - TCP聊天文件服务器v1.9 - 划时代版本更新(4.6万字)
文章目录
- 服务端data.py 致命bug
- PyQt5 信号化
- 服务端Client 改进
- 服务端 最大接受单位
- 将字节转换为合适的单位(`Kb`, `Mb`, `Gb` ...)
- 服务端和客户端 `message_handle`类文件传输 不同点问题
-
-
- 服务端的message_handle类:
- 客户端的message_handle类:
-
- 客户端文件传输可视化
-
- listWidget添加布局
- listWidget添加自定义部件显示不完整, 不想在Item但是,如何在点击信号中确认哪一个? 解决措施
-
-
- 添加自定义部件
- 显示不全 `Item.setSizeHint(QtCore.QSize(width, height))`
- 不想在Item但是,如何在点击信号中确认哪一个? 解决措施
-
- QProgressBar 未响应等待(即繁忙提示)
-
-
-
-
- (怎么调用? `emit`呗)
-
-
-
- 客户端图片(同目录下)
- 全部代码
-
- 客户端
-
-
-
- user.pyw
- ProgressBar.py
-
-
- 服务端
-
-
-
- server.pyw
- data.py
-
-
__version__ = 2.0
服务端data.py 致命bug
我测试的时候, 我说为什么只能注册不能登录, 粗心写错了.
def __login__(self, username, password) -> bool: return self.data[username][0] == encode(>> username <<) <- 这里!
def __login__(self, username, password) -> bool: return self.data[username][0] == encode(password)
源码直接给你放这里
from json import load, dump
from os import path, mkdir
from hashlib import md5
from time import time
def encode(data: str):
m = md5()
m.update(data.encode('utf8'))
return m.hexdigest()
file = '.\clients\data.json'
folder = '.\clients'
if not path.exists(folder):
mkdir(folder)
class user():
def __init__(self):
if path.exists(file):
with open(file, 'r') as f:
self.data = load(f)
else:
self.data = {
}
def __get__(self, username, default=None) -> tuple:
return self.data.get(username, default)
def __in__(self, username) -> bool:
return username in self.data.keys()
def __write__(self) -> None:
with open(file, 'w') as f:
dump(self.data, f, indent=4)
def __register__(self, username, password, time: (int, float)) -> None:
self.data[username] = (encode(password), int(time))
self.__write__()
def __login__(self, username, password) -> bool:
return self.data[username][0] == encode(password)
def handler(self, username, password) -> bool:
if self.__in__(username):
return self.__login__(username, password)
else:
self.__register__(username, password, time())
return True
def get_time(self, username):
return self.data[username][1]
PyQt5 信号化
无非就是QtCore.pyqtSignal(*args, **kwargs)
. 触发就是<pyqtSignal>.emit(*args, **kwargs)
.
文章里头改动挺多的 , 就不给你一一列出了, 也没有什么意义, PyQt5 不能在其他线程中修改界面线程的ui, 所以运行的时候可能闪退, 无响应 现在可以了, 99.9%不会死机(除内存分配不足外).
服务端Client 改进
def input(self, text):
self._send(text)
return self.recv()
因此受变动的还有login()
@ignore
def login(self):
self.username = self.recv()[:15]
if self.server.user_record.__in__(self.username):
if self.server.user_record.handler(self.username, self.input("<font color='red'>请输入您的密码: (右下[send]键发送)</font>")):
self._send(f'<font color="green">欢迎回来, {
self.username}.</font>')
else:
self._send('<font color="red">密码错误,请重试.</font>')
self.__del__() # = del self
else:
def normal(string):
return (4 <= len(string) <= 10) and not ('\n' in string)
while True:
p1 = self.input("<font color='blue'>[i]提示: 密码需在4 ~ 10位之间, 且不能换行.</font>\n<font color='red'>请输入您的密码: (右下[send]键发送)</font>")
if normal(p1):
break
while True:
p2 = self.input("<font color='red'>再次输入您的密码: (右下[send]键发送)</font>")
if p1 == p2:
break
else:
self._send("<font color='red'>密码与前次不符!</font>")
self.server.user_record.handler(self.username, p1)
self._send(f'初来乍到, {
self.username}')
self._login = True
self.server.login(self.username, self.addr)
login
; forever_receive
函数, 从计入日志@to_logging
转变为了忽略错误@ignore
. Client类给你放这了
class Client(object):
class QuitError(Exception):
def __init__(self, *args):
super().__init__(*args)
def __init__(self, socket, addr, server: Server):
self.socket = socket
self.addr = addr
if not isinstance(server, Server):
raise ValueError
self.server = server
self.encode = self.server.encode
self.max_count = self.server.max_count
self.com = Command_Handler(self)
@self.error
def _recv(self) -> bytes:
return self.socket.recv(self.max_count).decode(encoding=self.encode).strip()
self._recv = lambda: _recv(self)
@self.error
def _send(self, message=None) -> None:
if message:
if isinstance(message,str):
message = repr((normal_text, message)).encode(self.encode)
message += b'\n' #防止粘包
self.socket.sendall(message)
self._send = lambda m: _send(self, m)
def __del__(self):
self.socket.close()
def isLogin(self) -> bool:
return hasattr(self, "_login") and self._login
def isOpen(self) -> bool:
return not getattr(self.socket, "_closed", True)
def __filter__(self) -> bool:
"""返回是否在线并已可接受消息"""
return self.isLogin() and self.isOpen()
def recv(self) -> str:
if not hasattr(self, "queue"): self.queue = [] #列队机制
if self.queue:
return self.queue.pop(0)
while True:
result = list(self.server.user_handle.handle(self._recv(), self))
if result:
self.queue.extend(result)
return self.queue.pop(0)
def input(self, text):
self._send(text)
return self.recv()
@ignore
def login(self):
self.username = self.recv()[:15]
if self.server.user_record.__in__(self.username):
if self.server.user_record.handler(self.username, self.input("<font color='red'>请输入您的密码: (右下[send]键发送)</font>")):
self._send(f'<font color="green">欢迎回来, {
self.username}.</font>')
else:
self._send('<font color="red">密码错误,请重试.</font>')
self.__del__() # = del self
else:
def normal(string):
return (4 <= len(string) <= 10) and not ('\n' in string)
while True:
p1 = self.input("<font color='blue'>[i]提示: 密码需在4 ~ 10位之间, 且不能换行.</font>\n<font color='red'>请输入您的密码: (右下[send]键发送)</font>")
if normal(p1):
break
while True:
p2 = self.input("<font color='red'>再次输入您的密码: (右下[send]键发送)</font>")
if p1 == p2:
break
else:
self._send("<font color='red'>密码与前次不符!</font>")
self.server.user_record.handler(self.username, p1)
self._send(f'初来乍到, {
self.username}')
self._login = True
self.server.login(self.username, self.addr)
def quit(self) -> None:
if hasattr(self, 'Quitted'):
return
self.Quitted = True
if self.isOpen() is True:
self.socket.close()
self.server.quit(self.username, self.addr)
@ignore
def forever_receive(self):
self.login()
while self.__filter__():
string = self.recv()
if string is None:
continue
elif string == Client.QuitError:
return
elif self.com.iscommand(string):
self._send(self.com.handler(string))
else:
self.server.UserMessage(self.addr, self.username, string)
def error(self, func):
def function(*args, **kwargs):
try:
res = func(*args, **kwargs)
return res
except (ConnectionAbortedError,ConnectionRefusedError,ConnectionResetError, OSError) as e:
self.quit()
except Exception:
logger.exception("error")
return Client.QuitError
return function
def run(self):
self.thread = threading(True,name=f"客户端{
self.addr}",target=self.forever_receive)
服务端 最大接受量单位
为什么老是把最大接受量调到≥1MB还是不行? 原来我自己都忘了自己写的单位忘换算了…
def retranslateUi(self):
self.label_2.setText(_translate("MainWindow", "Maximum load(kb):"))
# ...
@to_logging
def handle(self):
self.max_recv = int(self.lineEdit.text()) * 1024 # 单位是kb, 换算为字节.
# ...
Iterface界面代码:
class Interface(QtWidgets.QMainWindow): Database_signal = QtCore.pyqtSignal(str) Usernum_signal = QtCore.pyqtSignal(int) def __init__(self): super(Interface, self).__init__() self.setupUi() self.show() def setupUi(self): self.setObjectName("MainWindow") self.resize(1088, 685) font = QtGui.QFont() font.setFamily("Consolas") font.setPointSize(11) self.setFont(font) self.setStyleSheet("") self.centralwidget = QtWidgets.QWidget(self) self.centralwidget.setObjectName("centralwidget") self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) self.gridLayout.setObjectName("gridLayout") self.label_6 = QtWidgets.QLabel(self.centralwidget) self.label_6.setObjectName("label_6") self.gridLayout.addWidget(self.label_6, 4, 0, 1, 1) self.textEdit_2 = QtWidgets.QTextEdit(self.centralwidget) self.textEdit_2.setObjectName("textEdit_2") self.gridLayout.addWidget(self.textEdit_2, 5, 0, 1, 1) self.groupBox = QtWidgets.QGroupBox(self.centralwidget) self.groupBox.setObjectName("groupBox") self.formLayout_2 = QtWidgets.QFormLayout(self.groupBox) self.formLayout_2.setObjectName("formLayout_2") self.label_2 = QtWidgets.QLabel(self.groupBox) self.label_2.setObjectName("label_2") self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_2) self.lineEdit = QtWidgets.QLineEdit(self.groupBox) self.lineEdit.setObjectName("lineEdit") self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.lineEdit) self.label_8 = QtWidgets.QLabel(self.groupBox) self.label_8.setObjectName("label_8") self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_8) self.lineEdit_3 = QtWidgets.QLineEdit(self.groupBox) self.lineEdit_3.setObjectName("lineEdit_3") self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.lineEdit_3) self.label_7 = QtWidgets.QLabel(self.groupBox) self.label_7.setObjectName("label_7") self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_7) self.lineEdit_2 = QtWidgets.