python3+PyQt5 创建网络应用-TCP客户端和TCP服务器

版权声明:本文为CSDN博主「basisworker」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xiaoyangyang20/article/details/70905452

本文通过python3+PyQt5实现《python Qt GUI快速编程》第18章的例子Building Services application。本例分别创建一个TCP客户端和TCP服务器,采用PyQt的QtNetwork模块,而不是Python标准库或Twisted网络引擎。 
以下为TCP客户端的程序代码:

#!/usr/bin/env python3

import sys
from PyQt5.QtCore import (QByteArray, QDataStream, QDate, QIODevice,
                          QRegExp, Qt)
from PyQt5.QtWidgets import (QApplication, QDateEdit, QFrame, QGridLayout,
                             QHBoxLayout, QLabel, QLineEdit, QPushButton,
                             QWidget)
from PyQt5.QtGui import QRegExpValidator
from PyQt5.QtNetwork import (QTcpSocket,)

MAC = True
try:
    from PyQt5.QtGui import qt_mac_set_native_menubar
except ImportError:
    MAC = False

PORT = 9407
SIZEOF_UINT16 = 2


class BuildingServicesClient(QWidget):

    def __init__(self, parent=None):
        super(BuildingServicesClient, self).__init__(parent)

        self.socket = QTcpSocket()
        self.nextBlockSize = 0
        self.request = None

        roomLabel = QLabel("&Room")
        self.roomEdit = QLineEdit()
        roomLabel.setBuddy(self.roomEdit)
        regex = QRegExp(r"[0-9](?:0[1-9]|[12][0-9]|3[0-4])")
        self.roomEdit.setValidator(QRegExpValidator(regex, self))
        self.roomEdit.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        dateLabel = QLabel("&Date")
        self.dateEdit = QDateEdit()
        dateLabel.setBuddy(self.dateEdit)
        self.dateEdit.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.dateEdit.setDate(QDate.currentDate().addDays(1))
        self.dateEdit.setDisplayFormat("yyyy-MM-dd")
        responseLabel = QLabel("Response")
        self.responseLabel = QLabel()
        self.responseLabel.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)

        self.bookButton = QPushButton("&Book")
        self.bookButton.setEnabled(False)
        self.unBookButton = QPushButton("&Unbook")
        self.unBookButton.setEnabled(False)
        self.bookingsOnDateButton = QPushButton("Bookings &on Date?")
        self.bookingsForRoomButton = QPushButton("Bookings &for Room?")
        self.bookingsForRoomButton.setEnabled(False)
        quitButton = QPushButton("&Quit")
        if not MAC:
            self.bookButton.setFocusPolicy(Qt.NoFocus)
            self.unBookButton.setFocusPolicy(Qt.NoFocus)
            self.bookingsOnDateButton.setFocusPolicy(Qt.NoFocus)
            self.bookingsForRoomButton.setFocusPolicy(Qt.NoFocus)

        buttonLayout = QHBoxLayout()
        buttonLayout.addWidget(self.bookButton)
        buttonLayout.addWidget(self.unBookButton)
        buttonLayout.addWidget(self.bookingsOnDateButton)
        buttonLayout.addWidget(self.bookingsForRoomButton)
        buttonLayout.addStretch()
        buttonLayout.addWidget(quitButton)
        layout = QGridLayout()
        layout.addWidget(roomLabel, 0, 0)
        layout.addWidget(self.roomEdit, 0, 1)
        layout.addWidget(dateLabel, 0, 2)
        layout.addWidget(self.dateEdit, 0, 3)
        layout.addWidget(responseLabel, 1, 0)
        layout.addWidget(self.responseLabel, 1, 1, 1, 3)
        layout.addLayout(buttonLayout, 2, 0, 1, 5)
        self.setLayout(layout)

        self.socket.connected.connect(self.sendRequest)
        self.socket.readyRead.connect(self.readResponse)
        self.socket.disconnected.connect(self.serverHasStopped)
        #self.connect(self.socket,
        #             SIGNAL("error(QAbstractSocket::SocketError)"),
            #            self.serverHasError)
        self.socket.error.connect(self.serverHasError)

        self.roomEdit.textEdited.connect(self.updateUi)
        self.dateEdit.dateChanged.connect(self.updateUi)

        self.bookButton.clicked.connect(self.book)
        self.unBookButton.clicked.connect(self.unBook)
        self.bookingsOnDateButton.clicked.connect(self.bookingsOnDate)
        self.bookingsForRoomButton.clicked.connect(self.bookingsForRoom)
        quitButton.clicked.connect(self.close)

        self.setWindowTitle("Building Services")


    def updateUi(self):
        enabled = False
        if (self.roomEdit.text() and
            self.dateEdit.date() > QDate.currentDate()):
            enabled = True
        if self.request is not None:
            enabled = False
        self.bookButton.setEnabled(enabled)
        self.unBookButton.setEnabled(enabled)
        self.bookingsForRoomButton.setEnabled(enabled)


    def closeEvent(self, event):
        self.socket.close()
        event.accept()


    def book(self):
        self.issueRequest("BOOK", self.roomEdit.text(),
                          self.dateEdit.date())


    def unBook(self):
        self.issueRequest("UNBOOK", self.roomEdit.text(),
                          self.dateEdit.date())


    def bookingsOnDate(self):
        self.issueRequest("BOOKINGSONDATE",
                          self.roomEdit.text(), self.dateEdit.date())

    def bookingsForRoom(self):
        self.issueRequest("BOOKINGSFORROOM",
                          self.roomEdit.text(), self.dateEdit.date())


    def issueRequest(self, action, room, date):
        self.request = QByteArray()
        stream = QDataStream(self.request, QIODevice.WriteOnly)
        stream.setVersion(QDataStream.Qt_5_7)
        stream.writeUInt16(0)
        stream.writeQString(action)
        stream.writeQString(room)
        stream << date        
        stream.device().seek(0)
        stream.writeUInt16(self.request.size() - SIZEOF_UINT16)
        self.updateUi()
        if self.socket.isOpen():
            self.socket.close()
        self.responseLabel.setText("Connecting to server...")
        self.socket.connectToHost("localhost", PORT)


    def sendRequest(self):
        self.responseLabel.setText("Sending request...")
        self.nextBlockSize = 0
        self.socket.write(self.request)
        self.request = None


    def readResponse(self):
        stream = QDataStream(self.socket)
        stream.setVersion(QDataStream.Qt_5_7)

        while True:
            if self.nextBlockSize == 0:
                if self.socket.bytesAvailable() < SIZEOF_UINT16:
                    break
                self.nextBlockSize = stream.readUInt16()
            if self.socket.bytesAvailable() < self.nextBlockSize:
                break
            action = ""
            room = ""
            date = QDate()
            action=stream.readQString()
            room=stream.readQString()             
            if action == "BOOKINGSFORROOM":
                dates = []
                for x in range(stream.readInt32()):
                    stream >> date
                    dates.append(str(date.toString(Qt.ISODate)))
                dates = ", ".join(dates)
            if action not in ("BOOKINGSFORROOM", "ERROR"):
                stream >> date
            if action == "ERROR":
                msg = "Error: {0}".format(room)
            elif action == "BOOK":
                msg = "Booked room {0} for {1}".format(room,date.toString(Qt.ISODate))
            elif action == "UNBOOK":
                msg = "Unbooked room {0} for {1}".format(room,date.toString(Qt.ISODate))
            elif action == "BOOKINGSONDATE":
                msg = "Rooms booked on {0}: {1}".format(date.toString(Qt.ISODate),room)
            elif action == "BOOKINGSFORROOM":
                msg = "Room {0} is booked on: {1}".format(room,dates)
            self.responseLabel.setText(msg)
            self.updateUi()
            self.nextBlockSize = 0


    def serverHasStopped(self):
        self.responseLabel.setText("Error: Connection closed by server")
        self.socket.close()


    def serverHasError(self, error):
        self.responseLabel.setText("Error: {0}".format(self.socket.errorString()))
        self.socket.close()


app = QApplication(sys.argv)
form = BuildingServicesClient()
form.show()
app.exec_()

以下为TCP服务器端的程序代码:

#!/usr/bin/env python3

import bisect
import collections
import sys
from PyQt5.QtCore import (QByteArray, QDataStream, QDate, QIODevice, Qt)
from PyQt5.QtWidgets import (QApplication, QMessageBox, QPushButton)
from PyQt5.QtNetwork import (QHostAddress, QTcpServer, QTcpSocket)

PORT = 9407
SIZEOF_UINT16 = 2
MAX_BOOKINGS_PER_DAY = 5

# Key = date, value = list of room IDs
Bookings = collections.defaultdict(list)

def printBookings():
    for key in sorted(Bookings):
        print(key, Bookings[key])
    print()


class Socket(QTcpSocket):

    def __init__(self, parent=None):
        super(Socket, self).__init__(parent)
        self.readyRead.connect(self.readRequest)
        self.disconnected.connect(self.deleteLater)
        self.nextBlockSize = 0


    def readRequest(self):
        stream = QDataStream(self)
        stream.setVersion(QDataStream.Qt_5_7)
        #print(self.bytesAvailable())
        if self.nextBlockSize == 0:
            if self.bytesAvailable() < SIZEOF_UINT16:
                return
            self.nextBlockSize = stream.readUInt16()
            #print(self.nextBlockSize)
        if self.bytesAvailable() < self.nextBlockSize:
            return

        action = ""
        room = ""
        date = QDate()
        action=stream.readQString()
        if action in ("BOOK", "UNBOOK", "BOOKINGSONDATE",
                      "BOOKINGSFORROOM"):
            room=stream.readQString()
            stream >> date            
            bookings = Bookings.get(date.toPyDate())
            uroom = str(room)
        if action == "BOOK":
            if bookings is None:
                bookings = Bookings[date.toPyDate()]
            if len(bookings) < MAX_BOOKINGS_PER_DAY:
                if uroom in bookings:
                    self.sendError("Cannot accept duplicate booking")
                else:
                    bisect.insort(bookings, uroom)
                    self.sendReply(action, room, date)
            else:
                self.sendError("{0} is fully booked".format(date.toString(Qt.ISODate)))
        elif action == "UNBOOK":
            if bookings is None or uroom not in bookings:
                self.sendError("Cannot unbook nonexistent booking")
            else:
                bookings.remove(uroom)
                self.sendReply(action, room, date)
        elif action == "BOOKINGSONDATE":
            bookings = Bookings.get(date.toPyDate())
            if bookings is not None:
                self.sendReply(action, ", ".join(bookings), date)
            else:
                self.sendError("there are no rooms booked on {0}".format(date.toString(Qt.ISODate)))
        elif action == "BOOKINGSFORROOM":
            dates = []
            for date, bookings in Bookings.items():
                if room in bookings:
                    dates.append(date)
            if dates:
                dates.sort()
                reply = QByteArray()
                stream = QDataStream(reply, QIODevice.WriteOnly)
                stream.setVersion(QDataStream.Qt_5_7)
                stream.writeUInt16(0)
                stream.writeQString(action)
                stream.writeQString(room)
                stream.writeInt32(len(dates))
                for date in dates:
                    stream << QDate(date)
                stream.device().seek(0)
                stream.writeUInt16(reply.size() - SIZEOF_UINT16)
                self.write(reply)
            else:
                self.sendError("room %1 is not booked".format(room))
        else:
            self.sendError("Unrecognized request")
        printBookings()


    def sendError(self, msg):
        reply = QByteArray()
        stream = QDataStream(reply, QIODevice.WriteOnly)
        stream.setVersion(QDataStream.Qt_5_7)
        stream.writeUInt16(0)      
        stream.writeQString("ERROR")
        stream.writeQString(msg)        
        stream.device().seek(0)
        stream.writeUInt16(reply.size() - SIZEOF_UINT16)
        self.write(reply)


    def sendReply(self, action, room, date):
        reply = QByteArray()
        stream = QDataStream(reply, QIODevice.WriteOnly)
        stream.setVersion(QDataStream.Qt_5_7)
        stream.writeUInt16(0)
        stream.writeQString(action)
        stream.writeQString(room)
        stream<<date        
        stream.device().seek(0)
        stream.writeUInt16(reply.size() - SIZEOF_UINT16)
        self.write(reply)


class TcpServer(QTcpServer):

    def __init__(self, parent=None):
        super(TcpServer, self).__init__(parent)


    def incomingConnection(self, socketId):
        socket = Socket(self)
        socket.setSocketDescriptor(socketId)


class BuildingServicesDlg(QPushButton):

    def __init__(self, parent=None):
        super(BuildingServicesDlg, self).__init__(
                "&Close Server", parent)
        self.setWindowFlags(Qt.WindowStaysOnTopHint)

        self.loadBookings()
        self.tcpServer = TcpServer(self)
        if not self.tcpServer.listen(QHostAddress("0.0.0.0"), PORT):
            QMessageBox.critical(self, "Building Services Server","Failed to start server: {0}".format(self.tcpServer.errorString()))
            self.close()
            return

        self.clicked.connect(self.close)
        font = self.font()
        font.setPointSize(24)
        self.setFont(font)
        self.setWindowTitle("Building Services Server")


    def loadBookings(self):
        # Generate fake data
        import random

        today = QDate.currentDate()
        for i in range(10):
            date = today.addDays(random.randint(7, 60))
            for j in range(random.randint(1, MAX_BOOKINGS_PER_DAY)):
                # Rooms are 001..534 excl. 100, 200, ..., 500
                floor = random.randint(0, 5)
                room = random.randint(1, 34)
                bookings = Bookings[date.toPyDate()]
                if len(bookings) >= MAX_BOOKINGS_PER_DAY:
                    continue
                bisect.insort(bookings, "{0:1d}{1:02d}".format(
                              floor, room))
        printBookings()


app = QApplication(sys.argv)
form = BuildingServicesDlg()
form.show()
form.move(0, 0)
app.exec_()

运行结果: 
服务端: 


客户端 

————————————————

发布了4 篇原创文章 · 获赞 4 · 访问量 2288

猜你喜欢

转载自blog.csdn.net/bluewhu/article/details/101846318