И снова чат

Привет, Хабр!

После прочтения постов про по созданию чат приложений, я решил попробовать написать свой чат(ну как свой, вот исходники)и прикрутить к нему GUI. Может кому нибудь пригодится, и так начнем. Я использовал Python 3.7 + PyQt5.

Qt Designer

Это основное окно серверной части нашего чата. Здесь у нас 2 поля listWidget, одно для событий на сервере, другое для отображения списка пользователей. 2 pushbutton, одна для отправки сообщений всем пользователям, вторая для остановки сервера. lineEdit для ввода и редактирования сообщений и Label к listWidget.

Это окно с настройками сервера. Здесь 2 pushbutton, их названия говорят сами за себя и 3 textlabel.

Основное окно клиентской части. Textbrowser отображает сообщения от других пользователей, listWidget — список пользователей, lineEdit и pushbutton для ввода редактирования и отправки соответственно.

Если это можно назвать окном авторизации, то пусть будет так. Здесь пользователь вводит данные для входа на сервер.

Клиент и Сервер

Сервер

from socket import * import threading import random import pickle import queue from PyQt5.QtCore import QThread, QTimer  class Server(QThread):     def __init__(self, host, port, clientField, debugField):         super(Server, self).__init__(parent = None)         self.connections = []         self.messageQueue = queue.Queue()         self.host = host         self.port = port         self.clientField = clientField         self.debugField = debugField          # создаем socket         self.serversock = socket(AF_INET, SOCK_STREAM)         self.serversock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)          # создаем a qtimer для вызова метода sendMsgs каждые 0.5 секунд         self.timer = QTimer()         self.timer.timeout.connect(self.sendMsgs)         self.timer.start(500)          # запускаем сервер на указанном порте         try:             self.port = int(self.port)             self.serversock.bind((self.host, self.port))         except Exception as e:             print(e)             # если произошла ошибка, попробуйте другой номер порта             self.port = int(self.port) + random.randint(1, 1000)             self.serversock.bind((self.host, self.port))          self.serversock.listen(5)          debugMsg = "Server running on {} at port {}".format(self.host, self.port)          self.debugField.addItem(debugMsg)         print(debugMsg)      def run(self):         while True:             self.clientsock, self.addr = self.serversock.accept()              if self.clientsock:                 self.data = self.clientsock.recv(1024)                 self.nickname = pickle.loads(self.data)                 self.clientField.addItem(self.nickname)                 self.connections.append((self.nickname, self.clientsock, self.addr))                 threading.Thread(target=self.receiveMsg, args=(self.clientsock, self.addr), daemon=True).start()          def receiveMsg(self, sock, addr):         length = len(self.connections)         debugMsg = "{} is connected with {} on port {} ".format(self.connections[length-1][0], self.connections[length-1][2][0], self.connections[length-1][2][1])         self.debugField.addItem(debugMsg)          while True:             try:                 self.data = sock.recv(1024)             except:                 self.data = None              if self.data:                 self.data = pickle.loads(self.data)                                                   self.messageQueue.put(self.data)      def sendMsgs(self):         while(not self.messageQueue.empty()):             if not self.messageQueue.empty():                 self.message = self.messageQueue.get()                 self.message = pickle.dumps(self.message)                 if self.message:                     for i in self.connections:                         try:                             i[1].send(self.message)                         except:                             debugMsg = "[ERROR] Sending message to " + str(i[0])                             print(debugMsg)                             self.debugField.addItem(debugMsg)

Клиент

import socket import pickle import sys from PyQt5.QtCore import QThread  class Client(QThread):     def __init__(self, host, port, sendbtn, nickname, sendmsg, textbrowser):         super(Client, self).__init__(parent = None)         # устанавливаем локальные переменные для предоставленных аргументов         self.textbrowser = textbrowser         self.nickname = nickname        # для идентификации сервером и другими клиентами         self.port = port                # порт, используемый для подключения к серверу         self.host = host                # имя хоста, используемое для подключения к серверу         self.sendmsg = sendmsg      # экземпляр поля, в котором набирается текст для отправки, необходимо определить,                                     # возвращаются ли пользовательские нажатия для отправки сообщения          # инициализируем переменную в пустую строку         self.message = ""          # флаг, который указывает, работает ли клиент         self.running = True          # подключаем слот «self.send» к его сигналам         # при нажатии кнопки "SEND" вызывается метод отправки         sendbtn.clicked.connect(self.send)         # метод send вызывается при нажатии Enter в поле редактирования сообщения.         self.sendmsg.returnPressed.connect(self.send)          # создаем экземпляр socket         self.clientsoc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)          # подключаемся к серверу         # try except блок, потому что мы можем попытаться подключиться к серверу, который может не работать         try:             self.clientsoc.connect((self.host, self.port))             self.clientsoc.send(pickle.dumps(self.nickname))             self.textbrowser.append("Connected to server")         except:             print("Could not connect to server")        # print error             self.stop()         # вызываем метод для закрытия соединения с сервером             sys.exit()          # останавливаем скрипт               # Этот метод будет связан с QThread, когда вызывается унаследованный метод start     def run(self):         while self.running:             self.receive()      # вызываем метод receive      # метод получения сообщений, пересылаемых сервером     def receive(self):         while self.running:             # try except блок для обработки ситуаций, когда сервер внезапно перестает работать и клиент все еще работает             try:                 # получение сообщения в byte формате с сервера                 self.data = self.clientsoc.recv(1024)                 # конвертирование из формата byte в список python                 # первый индекс - юзернейм отправителя, второй индекс - сообщение                 self.data = pickle.loads(self.data)                 # если data not None                 if self.data:                             # print юзернейм и сообщение                     print(str(self.data[0] + ": " +self.data[1]))                     # не показывать сообщение, если вы отправитель                     if self.data[0] != self.nickname:                         #self.emit(QtCore.SIGNAL("MESSAGES", self.message))                         self.textbrowser.append(str(self.data[0] + ": " +self.data[1]))             except:                 #сделаем всплывающее окно, чтобы показать ошибку                 print("Error receiving")       # print error                 self.stop()                    # вызываем метод для закрытия соединения с сервером                 sys.exit()                     # останавливаем программу      # метод отправки сообщения, находящегося в данный момент в поле редактирования сообщения, на сервер     def send(self):         self.message = self.sendmsg.text()         # текст находящийся в настоящее время в поле редактирования сообщения         self.textbrowser.append("You: " + self.message)         # показать сообщение в истории сообщений         self.sendmsg.setText("")                    # установить текст в поле редактирования сообщений на пустую строку         self.data = (self.nickname, self.message)               # связываем имя отправителя и сообщение в список         self.data = pickle.dumps(self.data)                     # конвертируем список в byte формат          # try except блок потому что сервер может не работать         try:             self.clientsoc.send(self.data)          # отправить сообщение на сервер         except:             print("Error sending message")      # метод закрытия соединения с сервером     def stop(self):         self.running = False        # set flag to false         self.clientsoc.close()      # close the socket

Сборка

Конвертируем .ui в .py

pyuic5 имя файла.ui -o имя файла.py

Собираем сервер

сервер

# -*- coding: utf-8 -*-  import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QMessageBox import serverMain import serverSetup import server  class main(QMainWindow, serverMain.Ui_MainWindow):     def __init__(self, parent=None):         super(main, self).__init__(parent)         self.setupUi(self)          # создаем экземпляры форм         self.userInput = serverSetup.Ui_Form()         self.client = self.clientWidget         self.debug = self.whatsHap          # создаем виджеты         self.inputWidget = QWidget(self)  # виджет для отображения ввода номера порта           # добавляем формы в виджеты         self.userInput.setupUi(self.inputWidget)          # show inputWidget, остальное скрываем         self.inputWidget.setVisible(True)         self.client.setHidden(True)         self.debug.setHidden(True)         self.sendbtn.setHidden(True)         self.stopbtn.setHidden(True)         self.globalMsg.setHidden(True)         self.label.setHidden(True)          # подключаем метод start_server         self.userInput.pushButton.clicked.connect(self.server_start)  # при нажатии на кнопку          self.userInput.lineEdit.returnPressed.connect(self.server_start)  # при нажатии Enter         # подключаем метод server_stop         self.stopbtn.clicked.connect(self.server_stop)  # при нажатии на кнопку          self.userInput.pushButton_2.clicked.connect(self.close) # поключаем метод closeEvent          self.show()          # устанавливаем значения по умолчанию         self.hostname = "127.0.0.1"         self.port = 8080      def server_start(self):         # если номер порта введен, иначе используйте номер порта по умолчанию         if self.userInput.lineEdit.text() != "":             self.port = self.userInput.lineEdit.text()          if self.userInput.pushButton.text() == "CONNECT":             self.label.setText("CLIENTS")             self.client.setVisible(True)             self.debug.setVisible(True)             self.inputWidget.setHidden(True)             self.sendbtn.setVisible(True)             self.globalMsg.setVisible(True)             self.stopbtn.setVisible(True)             self.label.setVisible(True)              self.server = server.Server(self.hostname, self.port, self.client, self.debug)             self.server.start()         else:             self.inputWidget.setVisible(True)             self.debug.setHidden(True)             self.client.setHidden(True)             self.sendbtn.setHidden(True)             self.stopbtn.setHidden(True)             self.label.setHidden(True)             self.globalMsg.setHidden(True)      def server_stop(self):         self.inputWidget.setVisible(True)         self.debug.setHidden(True)         self.client.setHidden(True)         self.sendbtn.setHidden(True)         self.stopbtn.setHidden(True)         self.label.setHidden(True)         self.globalMsg.setHidden(True)      def closeEvent(self, event):         reply = QMessageBox.question(self, 'Message',                                      "Are you sure to quit?", QMessageBox.Yes |                                      QMessageBox.No)          if reply == QMessageBox.Yes:             event.accept()         else:             event.ignore()    if __name__ == "__main__":     app = QApplication(sys.argv)     mainWindow = main()     mainWindow.show()     sys.exit(app.exec())

вот что будет если setHidden(False)

Клиент

import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QMessageBox import clientMain import clientSetup import client import random  class main(QMainWindow, clientMain.Ui_MainWindow):     def __init__(self, parent=None):         super(main, self).__init__(parent)         self.setupUi(self)         self.setupWidget = QWidget(self)         self.config = clientSetup.Ui_Form()         self.config.setupUi(self.setupWidget)         self.show()          # скрываем centralwidget         self.centralwidget.setHidden(True)          # устанавливаем значения по умолчанию         self.hostname = "127.0.0.1"         self.port = 8080         self.nickname = "User " + str(random.randint(1, 1000))      # генерируем рандомный юзернейм          # подключаем метод "self.start" этими сигналами         self.config.serverf.returnPressed.connect(self.start)    # вызываем метод start при нажатии Enter в поле server         self.config.portf.returnPressed.connect(self.start)      # вызываем метод start при нажатии Enter в поле port         self.config.usernamef.returnPressed.connect(self.start)  # вызываем метод start при нажатии Enter в поле username         self.config.connbtn.clicked.connect(self.start)          # вызываем метод start при нажатии кнопки "CONNECT"         # подключаем метод closeEvent         self.config.exitbtn.clicked.connect(self.close)           # Вызов close() отправляет событие close,                                                                   # которое впоследствии будет доставлено в обработчик                                                                   # closeEvent окна.      def start(self):         # проверяем пользовательский ввод, чтобы определить, использовать ли значения по умолчанию или нет         if self.config.serverf.text() != "":             # если пользователь вводит имя хоста, устанавливаем для имени хоста то, что пользователь ввел             self.hostname = self.config.serverf.text()         if self.config.portf.text() != "":             # если пользователь вводит номер порта, устанавливаем номер порта на тот, что пользователь ввел             self.port = self.config.portf.text()         if self.config.usernamef.text() != "":             # если пользователь вводит юзернейм, устанавливаем введенный пользователем юзернейм             self.nickname = self.config.usernamef.text()          #self.setWindowTitle(self.nickname + "'s chat")      # тут можно изменить WindowTitle(сотрите '#')         self.textBrowser.append("Welcome " + self.nickname)       # при входе показывает юзернейм          print("Nickname ----:", self.nickname)      # print nickname         print("Hostname: ---:", self.hostname)      # print hostname         print("Port number -:", self.port)          # print port number          # создаем экземпляр клиента         self.client = client.Client(self.hostname, self.port, self.sendbtn, self.nickname, self.sendmsg, self.textBrowser)         self.client.start()     # вызов метода, унаследованного от QThread, чтобы запустить метод в клиенте          # скрываем setupWidget после того как пользователь нажал на Enter или кнопку "CONNECT"         self.setupWidget.setHidden(True)         # show centralwidget         self.centralwidget.setVisible(True)      def closeEvent(self, event):         reply = QMessageBox.question(self, 'Message',                                      "Are you sure to quit?", QMessageBox.Yes |                                      QMessageBox.No)          if reply == QMessageBox.Yes:             event.accept()         else:             event.ignore()  if __name__=="__main__":     app = QApplication(sys.argv)     mainWindow = main()         # создаем экземпляр main     sys.exit(app.exec())        # останавливаем скрипт при выходе из графического интерфейса

Приложение готово к работе!

Небольшое отступление. Я только учусь, и начал изучать python и программирование в целом, примерно 2 месяца назад. В основном ищу уже готовые решения и стараюсь их анализировать, и уже после пытаюсь добавить в них что-то свое. В данном приложении не работает: список пользователей клиентской части, поле для ввода и редактирования серверной части для рассылки глобальных сообщений и кнопка. Но я обязательно доделаю этот функционал.

Буду рад конструктивной критике. Благодарю за потраченное на прочтение время.

FavoriteLoadingДобавить в избранное
Posted in Без рубрики

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *