今天我們一起來學習伺服器端的代碼。
8個主要函數。
為節約篇幅一些簡單的代碼就不解釋了。
def initServer(self): self.tcpPort = 7788 self.tcpServer = QTcpServer(self) self.clientConnection = QTcpSocket(self) self.tcpServer.newConnection.connect(self.sendMessage) self.serverStatuslabel.setText("請選擇要傳送的文件") self.progressBar.reset() self.serverOpenBtn.setEnabled(True) self.serverSendBtn.setEnabled(False) self.tcpServer.close()
新增的兩個類:QTcpServer、QTcpSocket我們先來瞭解一下。
此類可以接受傳入的TCP連接。您可以指定埠或讓QTcpServer自動選擇一個。您可以收聽特定地址或所有機器地址。
調用listen()讓伺服器偵聽傳入的連接。每次客戶端連接到伺服器時,都會發出newConnection()信號。
調用nextPendingConnection()以接受掛起的連接作為已連接的QTcpSocket。該函數返回一個指向QAbstractSocket.ConnectedState中QTcpSocket的指針,您可以使用該指針與客戶端進行通信。
如果發生錯誤,serverError()將返回錯誤類型,並且可以調用errorString()以獲取對所發生情況的可讀描述。
監聽連接時,伺服器正在偵聽的地址和埠可用作serverAddress()和serverPort()。
調用close()會使QTcpServer停止偵聽傳入連接。
儘管QTcpServer主要設計用於事件循環,但可以在沒有事件循環的情況下使用它。在這種情況下,您必須使用waitForNewConnection(),它會阻塞,直到連接可用或超時到期。
詳見:
TCP(傳輸控制協議)是一種可靠的,面向流的,面向連接的傳輸協議。 它特別適用於連續傳輸數據。
QTcpSocket是QAbstractSocket的子類,允許您建立TCP連接並傳輸數據流。
self.tcpPort = 7788 self.tcpServer = QTcpServer(self) self.clientConnection = QTcpSocket(self) self.tcpServer.newConnection.connect(self.sendMessage)QTcpSocket Classself.tcpPort = 7788 self.tcpServer = QTcpServer(self) self.clientConnection = QTcpSocket(self) self.tcpServer.newConnection.connect(self.sendMessage)
這裡我指定了TCP埠為7788,你可以隨意改,只要不衝突就行了。
self.tcpServer = QTcpServer(self) self.clientConnection = QTcpSocket(self) self.tcpServer.newConnection.connect(self.sendMessage)
我們創建一個Tcp伺服器和一個Tcp套接字。當有新的連接來的時候發出newConnection信號,我們連接到sendMessage()函數。
self.serverStatuslabel.setText("請選擇要傳送的文件") self.progressBar.reset() self.serverOpenBtn.setEnabled(True) self.serverSendBtn.setEnabled(False) self.tcpServer.close()
顯示我們開始創建的對話框,打開按鈕是可用的,發送按鈕是不可用的,進度條複位,先關閉伺服器。
def sendMessage(self): self.serverSendBtn.setEnabled(False) self.clientConnection = self.tcpServer.nextPendingConnection() self.clientConnection.bytesWritten.connect(self.updateClientProgress) self.serverStatuslabel.setText("開始傳送文件 {} !".format(self.theFileName))
self.localFile = QFile(self.fileName) if not(self.localFile.open(QFile.ReadOnly)): errorMsg = "無法讀取文件 {}: {}".format(self.fileName, self.localFile.errorString()) QMessageBox.warning(self, "應用程序", errorMsg) return
self.serverCloseBtn.setText("取消")
self.totalBytes = self.localFile.size() sendOut = QDataStream(self.outBlock, QIODevice.WriteOnly) sendOut.setVersion(QDataStream.Qt_5_4) self.time.start() currentFile = self.fileName.split("/")[-1] sendOut.writeInt64(0) sendOut.writeInt64(0) sendOut.writeQString(currentFile) self.totalBytes += self.outBlock.size() sendOut.device().seek(0) sendOut.writeInt64(self.totalBytes) sendOut.writeInt64(self.outBlock.size()-2) self.bytesToWrite = self.totalBytes - self.clientConnection.write(self.outBlock) self.outBlock.resize(0)
開始傳送文件。
self.serverSendBtn.setEnabled(False)
發送按鈕不可用。
self.clientConnection = self.tcpServer.nextPendingConnection() self.clientConnection.bytesWritten.connect(self.updateClientProgress)
self.clientConnection作為連接的QTcpSocket對象返回下一個掛起的連接。
同時當連接中每次將數據有效載荷寫入設備的當前寫通道時,都會發出此信號。在此有效負載中寫入的數據量為位元組數。
嘗試打開文件,要是存在問題就報錯。
準備開始傳輸了。
self.totalBytes = self.localFile.size()
記錄一下需要傳輸的文件大小。
sendOut = QDataStream(self.outBlock, QIODevice.WriteOnly) sendOut.setVersion(QDataStream.Qt_5_4)
這裡的self.outBlock是QByteArray()的對象,即位元組數組。
sendOut是我們新建的一個編碼信息二進位流,100%獨立於主計算機的操作系統,CPU或位元組順序。 例如,運行Solaris的Sun SPARC可以讀取由Windows下的PC寫入的數據流。
您還可以使用數據流來讀取/寫入原始未編碼的二進位數據。 數據流與QIODevice密切合作。 QIODevice表示可以從中讀取數據和向其寫入數據的輸入/輸出介質。
這裡我們指定QIODevice的模式為WriteOnly。
設定QDataStream的版本為Qt_5_4,這麼設定是為了避免不兼容的情況,因為不同版本Qt還是有差異性的。
self.time.start()
開始計時,這裡的self.time是QTime的對象。
currentFile = self.fileName.split("/")[-1]
傳輸的文件名。
sendOut.writeInt64(0) sendOut.writeInt64(0) sendOut.writeQString(currentFile) self.totalBytes += self.outBlock.size() sendOut.device().seek(0) sendOut.writeInt64(self.totalBytes) sendOut.writeInt64(self.outBlock.size()-2) self.bytesToWrite = self.totalBytes - self.clientConnection.write(self.outBlock) self.outBlock.resize(0)
PyQt5中QDataStream寫得方法與Qt上不同,這就造成了很多代碼不可能直接拿來改改就用。
這裡我們在sendOut中寫入文件名以及文件名和文件的大小,大小都是以位元組為單位的。
sendOut.device().seek(0) sendOut.writeInt64(self.totalBytes) sendOut.writeInt64(self.outBlock.size()-2)
QIODevice讀寫位置移動到0。然後分別寫入總的大小和文件名大小。
self.bytesToWrite = self.totalBytes - self.clientConnection.write(self.outBlock)
待傳輸文件的大小。
self.outBlock.resize(0)
outBlock清零。
def updateClientProgress(self, numBytes): qApp.processEvents() self.bytesWritten += numBytes if self.bytesWritten > 0: self.block = self.localFile.read(min(self.bytesToWrite, self.payloadSize)) self.bytesToWrite -= self.clientConnection.write(self.block) else: self.localFile.close()
byteSent = self.bytesWritten / (1024*1024) useTime = self.time.elapsed() / 1000 speed = self.bytesWritten / useTime / (1024*1024) total = self.totalBytes / (1024*1024) left = (total - byteSent) / speed if byteSent < 0.01: byteSent = self.bytesWritten / 1024 speed = self.bytesWritten / useTime / 1024 total = self.totalBytes / 1024 if left > 0: sendInfo = "已發送 {0:.2f}KB({1:.2f}KB/s) 共{2:.2f}KB 已用時:{3:.1f}秒 估計剩餘時間:{4:.1f}秒".format(byteSent, speed, total, useTime, left) else: sendInfo = "已發送 {0:.2f}KB({1:.2f}KB/s) 共{2:.2f}KB 用時:{3:.1f}秒 ".format(byteSent, speed, total, useTime) else: if left > 0: sendInfo = "已發送 {0:.2f}MB({1:.2f}MB/s) 共{2:.2f}MB 已用時:{3:.1f}秒 估計剩餘時間:{4:.1f}秒".format(byteSent, speed, total, useTime, left) else: sendInfo = "已發送 {0:.2f}MB({1:.2f}MB/s) 共{2:.2f}MB 用時:{3:.1f}秒 ".format(byteSent, speed, total, useTime)
self.progressBar.setMaximum(total) self.progressBar.setValue(byteSent)
if self.bytesWritten == self.totalBytes: self.serverCloseBtn.setText("關閉")
self.serverStatuslabel.setText(sendInfo)
這個函數是tcpServer開始寫得時候調用的。
qApp.processEvents()
這個是我們進行像複製這種長時間操作的時候可以寫上這句,以免窗口假死。
if self.bytesWritten > 0: self.block = self.localFile.read(min(self.bytesToWrite, self.payloadSize)) self.bytesToWrite -= self.clientConnection.write(self.block) else: self.localFile.close()
當我們待寫入的位元組數大於0時,我們每次讀取的數據都是小於等於self.payloadSize的,這個self.payloadSize我們定義是64KB,即64*1024個位元組。
self.bytesToWrite每次減少連接寫的數據量大小。
要是待寫入的位元組數小於等於0,則關閉文件。
byteSent = self.bytesWritten / (1024*1024) useTime = self.time.elapsed() / 1000speed = self.bytesWritten / useTime / (1024*1024) total = self.totalBytes / (1024*1024) left = (total - byteSent) / speed if byteSent < 0.01: byteSent = self.bytesWritten / 1024 speed = self.bytesWritten / useTime / 1024 total = self.totalBytes / 1024 if left > 0: sendInfo = "已發送 {0:.2f}KB({1:.2f}KB/s) 共{2:.2f}KB 已用時:{3:.1f}秒 估計剩餘時間:{4:.1f}秒".format(byteSent, speed, total, useTime, left) else: sendInfo = "已發送 {0:.2f}KB({1:.2f}KB/s) 共{2:.2f}KB 用時:{3:.1f}秒 ".format(byteSent, speed, total, useTime) else: if left > 0: sendInfo = "已發送 {0:.2f}MB({1:.2f}MB/s) 共{2:.2f}MB 已用時:{3:.1f}秒 估計剩餘時間:{4:.1f}秒".format(byteSent, speed, total, useTime, left) else: sendInfo = "已發送 {0:.2f}MB({1:.2f}MB/s) 共{2:.2f}MB 用時:{3:.1f}秒 ".format(byteSent, speed, total, useTime)
上面這段代碼看似複雜,其實比較好理解,就是文件傳輸進度的描述。
這裡useTime就是傳輸用了多長時間,left就是表示剩餘時間,speed表示傳輸速度。
{1:.2f}MB這種就表示保留2位小數的浮點數。
self.progressBar.setMaximum(total) self.progressBar.setValue(byteSent) if self.bytesWritten == self.totalBytes: self.serverCloseBtn.setText("關閉")
進度條顯示的方式,以及當傳輸的位元組數等於總的位元組數的時候,按鈕就顯示關閉。
@pyqtSlot() def on_serverOpenBtn_clicked(self): self.fileName = QFileDialog.getOpenFileName(self, 打開文件, ./)[0] if self.fileName: self.theFileName = self.fileName.split("/")[-1] self.serverStatuslabel.setText("要傳送的文件為:{}".format(self.theFileName)) self.serverSendBtn.setEnabled(True) self.serverOpenBtn.setEnabled(False)
打開文件按鈕準備發送。
@pyqtSlot() def on_serverSendBtn_clicked(self): if not(self.tcpServer.listen(QHostAddress.Any, self.tcpPort)): errorMsg = self.tcpServer.errorString() QMessageBox.warning(self, "錯誤", "發送失敗: {}".format(errorMsg)) self.TcpServer.close() return
self.serverStatuslabel.setText("等待對方接收... ...") self.serverSendBtn.setEnabled(False) self.sendFileName.emit(self.theFileName)
點擊發送按鈕,等待接收。
@pyqtSlot() def on_serverCloseBtn_clicked(self): if self.tcpServer.isListening(): self.tcpServer.close() if self.localFile.isOpen(): self.localFile.close() self.clientConnection.abort() if self.serverCloseBtn.text() == "取消": self.serverCloseBtn.setText("關閉") else: self.close() self.serverOpenBtn.setEnabled(True) self.serverSendBtn.setEnabled(False) self.progressBar.reset() self.totalBytes = 0 self.bytesWritten = 0 self.bytesToWrite = 0 self.serverStatuslabel.setText("請選擇要傳送的文件")
關閉傳輸對話框,相關數據、連接進行複位,為下次傳輸進行準備。
def refused(self): self.tcpServer.close() self.serverStatuslabel.setText("對方拒絕接收")
對方拒絕時,主程序會調用伺服器的refused()函數,關閉伺服器。
def closeEvent(self, event): self.on_serverCloseBtn_clicked()
產生關閉事件,直接調用關閉窗口按鈕函數。
好的,今天介紹就到這裡了,下期我們再見!如果你喜歡本篇文章,請給我點贊
讚賞(推薦)
分享給你的好友們吧!
歡迎關注微信公眾號:學點編程吧!加油!
(? ??_??)? (*????)
實操中有問題?來討論吧!