socket多线程通信
#设置好IP和端口
IP = ''
PORT = 10000
_ _ init _ _函数中:
# 初始化服务端Server_socket
self.Server_socket = socket(AF_INET, SOCK_STREAM)
# Server_socket绑定接收的IP和端口号
self.Server_socket.bind((IP, PORT))
启动服务端Server_socket sRun函数中:
# S_socket监听l个信号
self.Server_socket.listen(self.l)
# 准备l个子线程 线程入口函数clientthread
thread_list = []
for i in range(self.l):
t = Thread(target=self.clientthread)
thread_list.append(t)
#依次开启l个子线程
for t in thread_list:
t.start()
线程入口函数clientthread:
def clientthread(self):
#Csocket,address = Ssocket.accept接收客户端信息
Client_socket, address = self.Server_socket.accept()
self.Cs.text_print.emit(f'接受一个客户端连接: {address}')
while True:
#data = Csocket.recv(1024)表示每次最多接收1024个字节
self.resv_data = Client_socket.recv(1024)
#没有data break
if not self.resv_data:
break
# 收到客户端消息
if self.resv_data.decode('utf-8') == '1':
data = Client_socket.recv(1024)
#收到的信息必须decode()解码
info = data.decode()
#通过发送信号在界面上显示,下面有详解
self.Cs.text_print.emit(f'收到{address}信息: {info}')
# 收到客户端文件,下面再解析文件传输内容
if self.resv_data.decode('utf-8') == '2':
self.fsocket()
#关闭Csocket
Client_socket.close()
在多线程中为什么我没有选择while True等待监听来创建线程,因为界面的维持是一个无限循环,无限循环套无限循环是不可取的,还有其他的解决方案但是我还没研究明白
服务端的信息接收和下载文件进度条的更新需要通过Signal(信号)发送到Slot(槽)中处理
(Slot 槽 说白了就是处理Signal信号的函数)
# 信号定义为 该类的 一个静态属性,值为Signal实例对象
# Signal实例对象的初始化参数指定的类型,是发出信号对象时,传递的参数数据类型
# 通过connect方法处理Signal信号
# 通过信号对象的emit方法传递参数(实例化对象初始化参数指定的类型)
class CSignal(QObject):
text_print = Signal(str)#输出的文本
p = Signal(int)#进度条的值
Sui类中实例化对象connect连接处理的Slot
_ _ init _ _函数中:
self.Cs = CSignal()
self.Cs.text_print.connect(self.printToSui) # Signal的实例对象用来实现信息的实时输出
self.Cs.p.connect(self.fprogress)
两个处理的slot:
# 信息接收的slot(槽)
def printToSui(self, text):
self.ui.TextEdit.setPlainText(
self.ui.TextEdit.toPlainText() + '\n' + str(text)
)
# 进度条更新的slot
def fprogress(self, value):
self.ui.fProgress.setValue(value)
当服务端接收到信息:
ata = Client_socket.recv(1024)
对这个信号用emit方法发送到槽中:
self.Cs.text_print.emit(f'收到{address}信息: {info}')
socket文件传输
客户端文件处理:
1.通过Qt中QFileDialog打开文件获取文件内容:
filename, _ = QFileDialog.getOpenFileName(
self.ui, # 父窗口对象
"选择你要上传的图片", # 标题
r"d:\\data", # 起始目录
"图片类型 (*.png *.jpg *.bmp *.pdf)" # 选择类型过滤项,过滤内容在括号中
)
2.先发送filename和filesize给服务端:
#分割符
SEPARATOR = "<SEPARATOR>"
# 设置文件缓冲区
BUFFER_SIZE = 4096
#文件大小获得
file_size = os.path.getsize(filename)
# 发送文件名字和名字大小,必须进行编码处理
f_Client_socket.send(f"{filename}{SEPARATOR}{file_size}".encode())
3.进行文件传输:
with open(filename, "rb") as f:
while True:
# 读取文件
bytes_read = f.read(BUFFER_SIZE)
if not bytes_read:
break
#发送接收的所有字节
f_Client_socket.sendall(bytes_read)
f_Client_socket.close()
服务端文件处理:
1.处理客户端发来的filename和filesize:
#先对发来数据进行解码处理
received = f_Client_socket.recv(BUFFER_SIZE).decode()
#将分割符split掉得到发来的filename和filesize
filename, file_size = received.split(SEPARATOR)
# 通过os.path的basename方法只获取文件名字 不要路径
filename = os.path.basename(filename)
#将filesize变成整数型数据
file_size = int(file_size)
2.Qt中进度条的处理:
#通过得到的filesize设置(setRange)进度条的范围
self.ui.fProgress.setRange(0, file_size)
#reset初始化进度条
self.ui.fProgress.reset()
#后续写文件会通过emit方法发送写了多少字节的整型数据
#然后通过上面对进度条进行处理的槽来更新(setValue)进度条
# 进度条更新的slot
def fprogress(self, value):
self.ui.fProgress.setValue(value)
3.服务端对文件的写入:
BUFFER_SIZE = 4096 # 设置文件缓冲区
#在__init__中设置
self.plen = 0 # 设置进度条的数据
# 文件处理
with open(filename, "wb") as f:
while True:
# 从客户端读取数据
bytes_read = f_Client_socket.recv(BUFFER_SIZE)
if not bytes_read:
break
# 写文件并更新进度条
f.write(bytes_read)
#计算写入了多少的字节
self.plen = self.plen + len(bytes_read)
#发送字节数来通过slot更新进度条
self.Cs.p.emit(int(self.plen))
#初始化设置进度条的数据
self.plen = 0
#必须关闭开启的socket
f_Client_socket.close()
f_Server_socket.close()
客户端按Enter键发送信息
1.需要设置类的继承:
class Cui(QWidget):
2.在_ _ init _ _函数中设置:
super(Cui, self).__init__()
self.ui.cTextEdit.installEventFilter(self)
3.定义事件过滤器:
def eventFilter(self, widget, event): # 定义事件过滤器
if event.type() == QEvent.KeyPress: # 先判断事件类型是否是键盘事件
if event.key() == Qt.Key_Return: # 再判断是否是回车
self.Csend()
# 这里要return,要不然会报错
return QWidget.eventFilter(self, widget, event)
读取qtdesigner中的ui文件
先认识一下ui文件:
类似于html,可以直接拿记事本打开,文件后缀是.ui
在类内的读入:
需要import QtUiTools中的QUiLoader
from PySide2.QtUiTools import QUiLoader
然后在_ _ init _ _中:
self.ui = QUiLoader().load('CUI.ui')
之后记得你的所有qt内容都在self.ui中就可以,像:
self.ui.cButton.clicked.connect(self.Csend)
self.ui.cTextEdit.installEventFilter(self)
self.ui.fButton.clicked.connect(self.Fsend)
总结
总结一下问题吧:
1.服务端保持通信需要一个无限循环来维持,而图形化界面的维持也需要一个无限循环来维持,一个无限循环套一个无限循环显然是不可能的,所以我使用到了QT中的Signal(信号)和slot(槽)的概念。
2.图形化界面就是简单qt designer,也没有很复杂的设计,但是学习了qt中读取文件和进度条的知识点,但是只是很简单的实现没有深入挖掘,所以这两个部分有很多的bug,之后有时间就改一下吧。
3.文件传输和信息通信很难放在一个socket中,我的感觉是因为文件传输后要关闭socket才能顺利接收文件,所以我在传文件是会单独再开一对独立的客户端和服务端的socket进行通信,通过客户端发送的类别来进行消息或文件的传输。
4.socket的多线程通信只是进行了简单的实现,而且之前写的时候会发生文件下载抢线程的事情,这确实让我一度头疼,之后改了很多次逻辑才实现了最起码的多线程通信,至于多线程的文件下载,我的想法是可以在文件传输部分再加多线程就好,但是因为实验的文件很小,我就没有尝试做,不过感觉大概率还是有问题,可以之后再深入的学习。
5.对于老师的要求,实现了Enter键发送消息和客户端发送空白信息显示重新发送,浅尝辄止的学习了qt的事件过滤器,了解到大键盘的Enter键应该是Key_Return,至于阻止客户端发送空白信息这件事,我感觉应该是要用阻止socket传送信息的方式来实现,但是我没有找到相关的解决方案,就对服务端进行了不显示空白信息,再反馈给客户端这样的处理,如果再深入的学习socket的话可能会进一步改进。
6.socket直接关闭的代码也是我现在悬之未解决的问题,因为外部调用socket会显示调用非套嵌字,而且多线程的环境下,我需要关闭的竟然是很多个socket才行,这些问题也应该需要socket的深入学习和多线程的深入学习才能解决,所以说学习之路漫漫长,每一步都可能是一个问题,但是每一次解决也有相应的进步,诸君共勉吧!