PyQt5+socket+多线程,实现简单聊天室( 二 )


def getFlag(self,flag):flag = flag.split("@@@")if flag[1] == "connect": # 如果传来连接成功,则新开一个线程监听self.buildServer()elif flag[1] == "disconnect": # 如果连接出现问题self.serverDict[flag[0]].runflag = False
监听线程实例(子线程):
消息持续接受函数:循环尝试接受消息,接收到则调用函数发送消息给主线程(主线程负责在界面上显示消息) , 如果失败则发送失败原因给主线程(主线程接受并显示在状态栏上) , 发送连接失败文本 , 发送连接失败状态 , 然后主动关闭线程
def getMessage(self):while self.runflag:try:data = http://www.kingceram.com/post/self.clientsocket.recv(1024).decode('utf-8') # 接受到字符串并按照utf-8编译self.sendText(data)except Exception as reason:self.sendMessage(str(reason))self.sendText(str(self.addr)+" break connect...")self.sendFlag(1)# 发送断开连接标志breakself.clientsocket.close()print("线程关闭成功")
消息发送函数:由主线程依次调用不同子线程的发送函数发送 。
def sendToClient(self,info):print(self.clientsocket)try:self.clientsocket.send(info.encode("utf-8"))print("广播成功")except Exception as reason:print("广播失败原因",reason)self.sendMessage(self.addr+" break connect...")self.sendFlag(1)
客户端子线程实例:
启动子线程之后自动执行run函数,持续接受消息 , 作用与服务端监听线程实例的函数差不多
def run(self):while self.runflag:try:msg = self.serverSocket.recv(1024).decode("utf-8") # 接受服务端消息self.sendText(msg)except Exception as reason:self.sendMessage(reason)self.sendFlag(1)# 发送连接失败标志break
5.完整代码
这个代码主要使用在有图形化界面的系统上,可以即当服务端也可以当客户端 , 租来的服务器没有图形化界面可用不了...
from PyQt5.QtWidgets import *from PyQt5.QtCore import *from PyQt5.QtGui import *from threading import Threadimport socketimport sys# 页面实例class MainWin(QMainWindow):def __init__(self,parent = None):super(MainWin,self).__init__(parent)self.pc = None# 预设这个变量self.MainSet()self.createWidget()self.componentWidgets()# 设定Main窗口属性的函数def MainSet(self):self.setWindowTitle("SimpleChat!!")#self.setFixedSize(600,400)# 加载控件def createWidget(self):self.centerWidget = QWidget()# 中心窗口控件self.mainLayout = QGridLayout()self.rightTopLayout = QGridLayout()self.rightBottomLayout = QVBoxLayout()self.ConfigBox = QGroupBox("Configuartion")self.ContralBox = QGroupBox("Contral Panel")self.chatEdit = QTextEdit()# 聊天对话框self.inputLine = QLineEdit()# 输入框self.sendBtn = QPushButton("发送")# 发送按钮self.sendBtn.clicked.connect(self.sendInfo)# 向服务器发送信息(如果是服务器本身则广播)self.clearBtn = QPushButton("清空")# 清空按钮self.clearBtn.clicked.connect(lambda:self.inputLine.clear())self.ipEdit = QLineEdit()# IP输入栏self.ipEdit.setInputMask('000.000.000.000; ') # 设定为IP输入格式self.hostIPbtn = QPushButton("获得本机IP")# 点击直接获得本机IPself.hostIPbtn.clicked.connect(self.getHostIP)self.portEdit = QLineEdit()# 端口输入栏self.portEdit.setPlaceholderText("9999")# 默认端口为9999self.hostEdit = QLineEdit()# 主机名输入栏self.hostEdit.setPlaceholderText(socket.gethostname()) # 默认为本地主机self.serverRbtn = QRadioButton("Server")# 选择为服务器self.serverRbtn.setChecked(True)self.serverRbtn.toggled.connect(self.radiobtnChange)self.clientRbtn = QRadioButton("Client")# 选择为客户端self.connectBtn = QPushButton("连接服务器")# 连接服务器按钮self.connectBtn.clicked.connect(self.setClient)self.connectBtn.setEnabled(False)self.buildServerBtn = QPushButton("建立服务器") # 建立服务器按钮self.buildServerBtn.clicked.connect(self.setServer)self.quitBtn = QPushButton("退出")# 退出按钮self.quitBtn.clicked.connect(self.quit)# ---------------------------------------------------------self.statusBar = QStatusBar()# 状态栏# 组装控件def componentWidgets(self):self.setCentralWidget(self.centerWidget)self.setStatusBar(self.statusBar)# --------------------------------------------------------self.centerWidget.setLayout(self.mainLayout)self.mainLayout.addWidget(self.chatEdit,0,0,6,2)self.mainLayout.addWidget(self.inputLine,6,0,1,2)self.mainLayout.addWidget(self.sendBtn,7,0,1,1)self.mainLayout.addWidget(self.clearBtn,7,1,1,1)self.mainLayout.addWidget(self.ConfigBox,0,2,5,1)self.mainLayout.addWidget(self.ContralBox,5,2,3,1)self.ConfigBox.setLayout(self.rightTopLayout)self.rightTopLayout.addWidget(QLabel("Server IP"),0,0,1,4)self.rightTopLayout.addWidget(self.ipEdit,1,0,1,3)self.rightTopLayout.addWidget(self.hostIPbtn,1,3,1,1)self.rightTopLayout.addWidget(QLabel("Server Port"),2,0,1,1)self.rightTopLayout.addWidget(self.portEdit,2,1,1,3)self.rightTopLayout.addWidget(QLabel("Host Name"),3,0,1,1)self.rightTopLayout.addWidget(self.hostEdit,3,1,1,3)self.rightTopLayout.addWidget(self.serverRbtn,4,0,1,2)self.rightTopLayout.addWidget(self.clientRbtn,4,2,1,2)self.ContralBox.setLayout(self.rightBottomLayout)self.rightBottomLayout.addWidget(self.connectBtn)self.rightBottomLayout.addWidget(self.buildServerBtn)self.rightBottomLayout.addWidget(self.quitBtn)# 静置函数 - 用于写事件函数-------------------------# 单选按钮切换函数def radiobtnChange(self,status):if status:self.connectBtn.setEnabled(False)self.buildServerBtn.setEnabled(True)else:self.connectBtn.setEnabled(True)self.buildServerBtn.setEnabled(False)def getHostIP(self):hostip = socket.gethostbyname_ex(socket.gethostname())self.ipEdit.setText(hostip[-1][-1])# 状态栏情况发送函数def sendInfo(self):if self.pc == None:self.statusBar.showMessage("sned info field case out connected!!")else:info = self.inputLine.text()if info != "":info = self.pc.hostName+":\n"+infoself.pc.btnsend(info)else:self.statusBar.showMessage("input can't be none!")# 设定本主机为服务器def setServer(self):host = self.hostEdit.text()port = self.portEdit.text()ip = self.ipEdit.text()print(ip)if host == "":host = "服务管理员"# 服务主机if port == "":port = 9999# 默认端口if ip == "...":ip = "127.0.0.1"# 默认IPself.pc = Server(self,ip,host,int(port))# 设定本主机为客户端def setClient(self):host = self.hostEdit.text()port = self.portEdit.text()ip = self.ipEdit.text()if host == "":host = "匿名用户"# 匿名用户if port == "":port = 9999# 默认端口if ip == "...":ip = "127.0.0.1"# 默认IPself.pc = Client(self,ip,host,int(port))def quit(self):if self.pc != None:self.pc.closeThread()self.close()# 服务端class Server():def __init__(self,widget,ip,host,port):# 设定本主机的一些基本信息 ---------------------------------------self.widget = widgetself.ip = ip# 获得该主机ipself.hostName = host# 获得该主机名self.port = port# 设定默认端口号(服务器端口号和客户端接入端口号都是这个默认端口)self.serverDict = {}# 服务线程字典self.serverID = 0# 初始的服务线程idself.buildSocket()# 创建网络连接实例def buildSocket(self):self.socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)# socket.AF_INET(!!INET - IPv4INET6 - IPv6)# socket.SOCK_STREAM - 传输控制协议(TCP)self.initialServer()# 初始化服务(仅限服务器)def initialServer(self):# 首先绑定服务端口号self.socket.bind((self.ip,self.port))# 绑定端口与主机名self.socket.listen(5)# 设定最大连接数self.buildServer()# 初始化一个服务线程# 创建服务线程def buildServer(self):server = ServerThread(str(self.serverID),self.socket)self.serverDict[str(self.serverID)] = serverself.serverID+=1server._flag.connect(self.getFlag)server._signal.connect(self.getMessage)server._text.connect(self.getText)server.start()# 广播所有消息def bordCastInfo(self,info):print(len(self.serverDict))for client in self.serverDict:try:if self.serverDict[client].clientsocket != None:print("尝试将信息广播出去")self.serverDict[client].sendToClient(info)# 将消息传入指定的客户端print("广播成功")except Exception as reason:self.getFlag("@@@".join([client,"disconnect"]))# 运行函数,停止某个客户端的监听(相当于关闭)print("服务端",reason)def btnsend(self,text):self.widget.chatEdit.append(text)self.bordCastInfo(text)# 关闭所有的服务def closeThread(self):for server in self.serverDict:self.serverDict[server].runflag = Falsedef getFlag(self,flag):flag = flag.split("@@@")if flag[1] == "connect": # 如果传来连接成功,则新开一个线程监听self.buildServer()elif flag[1] == "disconnect": # 如果连接出现问题self.serverDict[flag[0]].runflag = Falsedef getMessage(self,signal):signal = signal.split("@@@")self.widget.statusBar.showMessage("serverID "+signal[0]+" status:"+signal[1])def getText(self,text):self.widget.chatEdit.append(text)self.bordCastInfo(text)# 广播出去# 客户端class Client(): # 主机默认为本地主机,def __init__(self,widget,ip,hostName,port):self.widget = widgetself.ip = ipself.hostName = hostName# 获得该主机名self.port = port# 设定默认端口号(服务器端口号和客户端接入端口号都是这个默认端口)self.buildSocket()def buildSocket(self):self.socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)self.buildClient()def buildClient(self):self.client = ClientThread(self.socket)# 获取连接self.client._flag.connect(self.getFlag)self.client._signal.connect(self.getMessage)self.client._text.connect(self.getText)if self.client.connectServer(self.ip,self.port):self.client.start()def sendToServer(self,text):# 向服务器发送消息try:self.socket.send(text.encode('utf-8'))except Exception as reason:self.getMessage(reason)self.getFlag("disconnect")# 发送连接失败标志def btnsend(self,text):self.sendToServer(text)def closeThread(self):self.runflag = Falsedef getFlag(self,flag):if flag == "connect":self.widget.statusBar.showMessage("connect success!!")elif flag == "disconnect":self.client.runflag = Falsedef getMessage(self,signal):self.widget.statusBar.showMessage(signal)def getText(self,text):self.widget.chatEdit.append(text)# 监听连接线程,负责构成会话(服务端线程)class ServerThread(QThread):_signal = pyqtSignal(str)# 设定信号,主要向主线程发送信号_text = pyqtSignal(str)# 设定信号,向主线程发送接收到的信息_flag = pyqtSignal(str)# 设定信号,向主线程发送连接状态标志def __init__(self,serverID,serverSocket):super(ServerThread, self).__init__()self.serverID = serverID# 获得主机实例self.serverSocket = serverSocketself.clientsocket = Noneself.addr = Noneself.runflag = Trueself.connectList = ["connect","disconnect"]# 连接成功与连接失败#自动进行该函数def run(self):self.sendMessage("Waiting for customer......")self.clientsocket,self.addr = self.serverSocket.accept() # 收到客户端的连接后返回 连接控件,地址(持续监听,直到接收到执行下一个操作)print(self.clientsocket)self.sendText("Customer IP: %s" % str(self.addr)+" is linking!")self.sendFlag(0) # 发送连接成功标志self.getMessage()# 持续接受消息def getMessage(self):while self.runflag:try:data = http://www.kingceram.com/post/self.clientsocket.recv(1024).decode('utf-8') # 接受到字符串并按照utf-8编译self.sendText(data)except Exception as reason:self.sendMessage(str(reason))self.sendText(str(self.addr)+" break connect...")self.sendFlag(1)# 发送断开连接标志breakself.clientsocket.close()print("线程关闭成功")def sendToClient(self,info):print(self.clientsocket)try:self.clientsocket.send(info.encode("utf-8"))print("广播成功")except Exception as reason:print("广播失败原因",reason)self.sendMessage(self.addr+" break connect...")self.sendFlag(1)# 发送状态信号函数def sendMessage(self,message):self._signal.emit("@@@".join([self.serverID,message]))# 发送接收到的消息信号def sendText(self,text):self._text.emit(text)# 发送连接状态标志def sendFlag(self,flagIndex):self._flag.emit("@@@".join([self.serverID,self.connectList[flagIndex]]))class ClientThread(QThread):_signal = pyqtSignal(str)_text = pyqtSignal(str)_flag = pyqtSignal(str)def __init__(self,serverSocket):super(ClientThread,self).__init__()self.serverSocket = serverSocketself.runflag = Trueself.connectList = ["connect","disconnect"]# 连接成功与连接失败def connectServer(self,ip,port):try:self.serverSocket.connect((ip,port))self.sendFlag(0)# 发送连接成功标志return Trueexcept Exception as reason:self.sendMessage(reason)self.sendFlag(1)# 发送链接失败标志return reasondef run(self):while self.runflag:try:msg = self.serverSocket.recv(1024).decode("utf-8") # 接受服务端消息self.sendText(msg)except Exception as reason:self.sendMessage(reason)self.sendFlag(1)# 发送连接失败标志breakdef sendMessage(self,message):self._signal.emit(str(message))def sendText(self,text):self._text.emit(str(text))def sendFlag(self,flagIndex):self._flag.emit(str(self.connectList[flagIndex]))if __name__ == "__main__":app = QApplication(sys.argv)win = MainWin()win.show()sys.exit(app.exec_())