python3+PyQt5 创建多线程网络应用-TCP客户端和TCP服务器实例

本文在上文的基础上重新实现支持多线程的服务器。

以下为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)
       quitButton = QPushButton("&Quit")
       if not MAC:
         self.bookButton.setFocusPolicy(Qt.NoFocus)
         self.unBookButton.setFocusPolicy(Qt.NoFocus)
   
       buttonLayout = QHBoxLayout()
       buttonLayout.addWidget(self.bookButton)
       buttonLayout.addWidget(self.unBookButton)
       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, 1, 1, 4)
       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)
       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)
   
   
     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 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)#overwrite seek(0)
       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()
         #stream >> action >> room
         action=stream.readQString()
         room=stream.readQString()
         if action != "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))
         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, QReadWriteLock, QThread,QIODevice, Qt)
   from PyQt5.QtWidgets import (QApplication, QMessageBox, QPushButton)
   from PyQt5.QtNetwork import (QAbstractSocket,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 Thread(QThread):
   
     lock = QReadWriteLock()
   
     def __init__(self, socketId, parent):
       super(Thread, self).__init__(parent)
       self.socketId = socketId
   
   
     def run(self):
       socket = QTcpSocket()
       if not socket.setSocketDescriptor(self.socketId):
         #self.emit(SIGNAL("error(int)"), socket.error())
         self.error.connect(socket.error)
         return
       while socket.state() == QAbstractSocket.ConnectedState:
         nextBlockSize = 0
         stream = QDataStream(socket)
         stream.setVersion(QDataStream.Qt_5_7)
         if (socket.waitForReadyRead() and
           socket.bytesAvailable() >= SIZEOF_UINT16):
           nextBlockSize = stream.readUInt16()
         else:
           self.sendError(socket, "Cannot read client request")
           return
         if socket.bytesAvailable() < nextBlockSize:
           if (not socket.waitForReadyRead(60000) or
             socket.bytesAvailable() < nextBlockSize):
             self.sendError(socket, "Cannot read client data")
             return
         action = ""
         room = ""
         date = QDate()
         action=stream.readQString()
         if action in ("BOOK", "UNBOOK"):
           room=stream.readQString()
           stream >> date
           try:
             Thread.lock.lockForRead()
             bookings = Bookings.get(date.toPyDate())
           finally:
             Thread.lock.unlock()
           uroom = str(room)
         if action == "BOOK":
           newlist = False
           try:
             Thread.lock.lockForRead()
             if bookings is None:
               newlist = True
           finally:
             Thread.lock.unlock()
           if newlist:
             try:
               Thread.lock.lockForWrite()
               bookings = Bookings[date.toPyDate()]
             finally:
               Thread.lock.unlock()
           error = None
           insert = False
           try:
             Thread.lock.lockForRead()
             if len(bookings) < MAX_BOOKINGS_PER_DAY:
               if uroom in bookings:
                 error = "Cannot accept duplicate booking"
               else:
                 insert = True
             else:
               error = "{0} is fully booked".format(date.toString(Qt.ISODate))
           finally:
             Thread.lock.unlock()
           if insert:
             try:
               Thread.lock.lockForWrite()
               bisect.insort(bookings, uroom)
             finally:
               Thread.lock.unlock()
             self.sendReply(socket, action, room, date)
           else:
             self.sendError(socket, error)
         elif action == "UNBOOK":
           error = None
           remove = False
           try:
             Thread.lock.lockForRead()
             if bookings is None or uroom not in bookings:
               error = "Cannot unbook nonexistent booking"
             else:
               remove = True
           finally:
             Thread.lock.unlock()
           if remove:
             try:
               Thread.lock.lockForWrite()
               bookings.remove(uroom)
             finally:
               Thread.lock.unlock()
             self.sendReply(socket, action, room, date)
           else:
             self.sendError(socket, error)
         else:
           self.sendError(socket, "Unrecognized request")
         socket.waitForDisconnected()
         try:
           Thread.lock.lockForRead()
           printBookings()
         finally:
           Thread.lock.unlock()
   
   
     def sendError(self, socket, 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)
       socket.write(reply)
   
     def sendReply(self, socket, 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)
       socket.write(reply)
   
   class TcpServer(QTcpServer):
   
     def __init__(self, parent=None):
       super(TcpServer, self).__init__(parent)
   
   
     def incomingConnection(self, socketId):
       thread = Thread(socketId, self)
       #self.connect(thread, SIGNAL("finished()"),
       #       thread, SLOT("deleteLater()"))
       thread.finished.connect(thread.deleteLater)
       thread.start()
   
   
   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_()

 

posted on 2021-06-16 22:45  BabyGo000  阅读(342)  评论(0)    收藏  举报