上一篇文章socket网络程序设计实验二简单演示了socket编程的大致框架,这次我们加强一下功能,实现客户端与服务器端一对一聊天
本次实验的一个难点是多线程和PyQt界面的实时刷新并显示接收到的信息
老规矩,先上图!
和前两篇文章一样,先用Py Designer分别设计服务器和客户端的界面ui,并分别保存为server_4.ui和client_4.ui。
可以参考我的这样布局摆放:
找到刚才保存的两个ui文件,右键,-> Extarl tools -> Py UIC,就会帮我们自动生成.py文件了,在两个.py文件代码的最后加上:
if __name__ == '__main__':
app = QApplication(sys.argv)
MainWindow = QMainWindow()
ui = Ui_Dialog()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
前面加上:
import sys
import time
import socket
import threading # 多线程
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog
from PyQt5.QtCore import QThread, pyqtSignal, QObject
到此为止,可以试运行了,会出现界面,但是现在是没有任何功能的
这里因为客户端和服务器端差别较大,我们分开实现客户端和服务器端的功能
PyQt界面的显示就是一个主线程,因此要实现socket的并发运行,肯定要创建新进程。
先给类加个初始化方法吧,定义一下类内成员变量:
def __init__(self):
self.s = socket.socket()
self.c = None
self.msg_send = ''
self.msg_rec = ''
当点击监听按钮时,启动新线程来监听socket;
def listen_button(self): # 监听按钮
add1 = self.lineEdit.text()
add2 = self.lineEdit_2.text()
self.s.bind((add1, int(add2)))
self.s.listen()
print('正在监听。。。')
self.textBrowser.setText('开始监听。。。')
t1 = threading.Thread(target=self.accept_socket)
t1.start()
当接收到客户端请求连接时,启动新线程来循环接收消息
def accept_socket(self): # 接收socket连接
while 1:
print('等待连接中。。。')
self.c, addr = self.s.accept()
print('已连接')
break
t2 = threading.Thread(target=self.rec_msg)
t2.start()
def rec_msg(self): # 接受信息
while 1:
print('等待接受消息中。。。')
msg = self.c.recv(1024).decode('GB2312')
print('收到消息', msg)
self.msg_rec += msg
print(self.msg_rec)
# time.sleep(2)
定义断开按钮:
def break_button(self): # 断开按钮
self.s.close()
定义发送按钮:
def send_button(self): # 发送按钮
MSG = '[' + time.ctime() + ']:' +self.lineEdit_3.text() + '\n'
self.c.send(MSG.encode('GB2312'))
print('已发送:', MSG)
self.msg_send += MSG
self.textBrowser.setText(self.msg_send) # 更新显示的消息
self.lineEdit_3.setText('') # 发送后清空内容
再将按钮的对应事件关联起来,在retranslateUi方法最后加上:
self.lineEdit.setText("127.0.0.1")
self.lineEdit_2.setText("21567")
self.pushButton_3.clicked.connect(self.send_button)
self.pushButton_4.clicked.connect(self.listen_button)
self.pushButton_5.clicked.connect(self.break_button)
self.pushButton_3.setShortcut(QtCore.Qt.Key_Return) # 设置回车键快捷发送
Ok,目前位置就已经完成了基本的功能,但是有一点,就是接收到的消息的显示问题,
把self.textBrowser_2.setText(self.msg_rec)放在rec_msg方法中,程序会在接受消息的时候卡死,因为PyQt常规情况下,只有点击按钮才会做出相应发生改变,我们总不能为了接收消息,设置一个刷新键一直点吧。。
百度了一下需要用到PyQt5的QObject和QThread,直接看代码,
新建一个类,继承QObject:
class BackendThread(QObject): # 用来实时更新显示收到的消息
update_date = pyqtSignal(str) # 定义信号类型
# 定义方法
def run(self):
while True:
self.update_date.emit('')
time.sleep(1) # 设置间隔时间
然后在retranslateUi方法最下面加上:
# 调用刚才的自定义类
self.backend = BackendThread()
self.backend.update_date.connect(self.handleDisplay) # 连接信号事件
self.thread = QThread() # 创建进程
self.backend.moveToThread(self.thread)
self.thread.started.connect(self.backend.run)
self.thread.start()
定义handleDisplay方法,显示收到的消息:
def handleDisplay(self): # 显示收到的信息
self.textBrowser_2.setText(self.msg_rec)
完成!
服务器端就可以了,接下来再实现客户端
有了服务器端的经验,客户端就照着做就好了
初始化方法:
def __init__(self):
self.s = socket.socket()
self.msg_send = ''
self.msg_rec = ''
定义连接按钮的方法:
def connect_button(self): # 连接按钮
add1 = self.lineEdit.text()
add2 = self.lineEdit_2.text()
self.s.connect((add1, int(add2)))
print('已连接到服务器')
self.textBrowser.setText('连接成功~')
t = threading.Thread(target=self.rec_msg)
t.start()
定义断开按钮方法:
def break_button(self): # 断开按钮
self.s.close()
print('已关闭服务器')
定义发送按钮方法:
def send_button(self): # 发送按钮
MSG = '[' + time.ctime() + ']:' +self.lineEdit_3.text() + '\n'
self.s.send(MSG.encode('GB2312'))
print('已发送:', MSG)
self.msg_send += MSG
self.textBrowser.setText(self.msg_send)
self.lineEdit_3.setText('')
定义接收消息方法:
def rec_msg(self): # 接受消息
while 1:
print('等待接受消息中。。。')
msg = self.s.recv(1024).decode('GB2312')
print('收到消息', msg)
self.msg_rec += msg
print(self.msg_rec)
定义实时显示接受的消息方法:
def handleDisplay(self): # 显示收到的消息
self.textBrowser_2.setText(self.msg_rec)
然后导入服务器端代码中我们自定义的类:
from test4.server_4 import BackendThread # 导入自定义类
最后在retranslateUi方法最后加上:
self.lineEdit.setText("127.0.0.1")
self.lineEdit_2.setText("21567")
self.pushButton_3.clicked.connect(self.send_button)
self.pushButton_4.clicked.connect(self.connect_button)
self.pushButton_5.clicked.connect(self.break_button)
self.pushButton_3.setShortcut(QtCore.Qt.Key_Return) # 回车键发送
# 调用自定义类,同server_4
self.backend = BackendThread()
self.backend.update_date.connect(self.handleDisplay)
self.thread = QThread() # 创建进程
self.backend.moveToThread(self.thread)
self.thread.started.connect(self.backend.run)
self.thread.start()
至此,客户端也完成了
服务器端和客户端一起运行,先服务器监听,然后客户端连接,就可以互发信息了
完整代码已上传在我的github上:https://github.com/LiePy/socket.git