Привет, Хабр!
После прочтения постов про по созданию чат приложений, я решил попробовать написать свой чат (ну как свой, вот исходники) и прикрутить к нему GUI. Может кому нибудь пригодится, и так начнем. Я использовал Python 3.7 + PyQt5.
Это основное окно серверной части нашего чата. Здесь у нас 2 поля listWidget, одно для событий на сервере, другое для отображения списка пользователей. 2 pushbutton, одна для отправки сообщений всем пользователям, вторая для остановки сервера. lineEdit для ввода и редактирования сообщений и Label к listWidget.
Это окно с настройками сервера. Здесь 2 pushbutton, их названия говорят сами за себя и 3 textlabel.
Основное окно клиентской части. Textbrowser отображает сообщения от других пользователей, listWidget — список пользователей, lineEdit и pushbutton для ввода редактирования и отправки соответственно.
Если это можно назвать окном авторизации, то пусть будет так. Здесь пользователь вводит данные для входа на сервер.
Конвертируем .ui в .py
Собираем сервер
вот что будет если setHidden(False)
Приложение готово к работе!
Небольшое отступление. Я только учусь, и начал изучать python и программирование в целом, примерно 2 месяца назад. В основном ищу уже готовые решения и стараюсь их анализировать, и уже после пытаюсь добавить в них что-то свое. В данном приложении не работает: список пользователей клиентской части, поле для ввода и редактирования серверной части для рассылки глобальных сообщений и кнопка. Но я обязательно доделаю этот функционал.
Буду рад конструктивной критике. Благодарю за потраченное на прочтение время.
После прочтения постов про по созданию чат приложений, я решил попробовать написать свой чат (ну как свой, вот исходники) и прикрутить к нему 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 месяца назад. В основном ищу уже готовые решения и стараюсь их анализировать, и уже после пытаюсь добавить в них что-то свое. В данном приложении не работает: список пользователей клиентской части, поле для ввода и редактирования серверной части для рассылки глобальных сообщений и кнопка. Но я обязательно доделаю этот функционал.
Буду рад конструктивной критике. Благодарю за потраченное на прочтение время.
Комментарии (3)
Snow_Bars
15.03.2019 07:15+1Ммм… А смысл выкладывать статью про чат, в котором не работает примерно 60% функционала?
Это ни туториал, ни готовый продукт, ни хау ту, никакой полезной информации в себе не несет…
kibizoidus
Так здесь критиковать нечего, это домашнее задание универа, а не топик для Хабра…
Coriolis
Старшие классы школы.