Python Socket通信(多线程通信、文件传输、Pyside2)

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的深入学习和多线程的深入学习才能解决,所以说学习之路漫漫长,每一步都可能是一个问题,但是每一次解决也有相应的进步,诸君共勉吧!

源码 包含ui文件

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

便宜点的龙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值