今天我們一起來學習伺服器端的代碼。

程序結構

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我們先來瞭解一下。

  • QTcpServer類提供基於TCP的伺服器。

此類可以接受傳入的TCP連接。您可以指定埠或讓QTcpServer自動選擇一個。您可以收聽特定地址或所有機器地址。

調用listen()讓伺服器偵聽傳入的連接。每次客戶端連接到伺服器時,都會發出newConnection()信號。

調用nextPendingConnection()以接受掛起的連接作為已連接的QTcpSocket。該函數返回一個指向QAbstractSocket.ConnectedState中QTcpSocket的指針,您可以使用該指針與客戶端進行通信。

如果發生錯誤,serverError()將返回錯誤類型,並且可以調用errorString()以獲取對所發生情況的可讀描述。

監聽連接時,伺服器正在偵聽的地址和埠可用作serverAddress()和serverPort()。

調用close()會使QTcpServer停止偵聽傳入連接。

儘管QTcpServer主要設計用於事件循環,但可以在沒有事件循環的情況下使用它。在這種情況下,您必須使用waitForNewConnection(),它會阻塞,直到連接可用或超時到期。

詳見:

QTcpServer Class?

doc.qt.io

  • QTcpSocket類提供TCP套接字。

TCP(傳輸控制協議)是一種可靠的,面向流的,面向連接的傳輸協議。 它特別適用於連續傳輸數據。

QTcpSocket是QAbstractSocket的子類,允許您建立TCP連接並傳輸數據流。

詳見:

QTcpSocket Class?

doc.qt.io

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.localFile = QFile(self.fileName)
if not(self.localFile.open(QFile.ReadOnly)):
errorMsg = "無法讀取文件 {}:
{}".format(self.fileName, self.localFile.errorString())
QMessageBox.warning(self, "應用程序", errorMsg)
return

嘗試打開文件,要是存在問題就報錯。

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.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()

產生關閉事件,直接調用關閉窗口按鈕函數。

最後

好的,今天介紹就到這裡了,下期我們再見!如果你喜歡本篇文章,請給我點贊

讚賞(推薦)

分享給你的好友們吧!

歡迎關注微信公眾號:學點編程吧!加油!

(? ??_??)? (*????)

實操中有問題?來討論吧!

學點編程吧-百度貼吧--計算機程序學習的園地!--學點編程吧,讓我們的生活更簡單,更高效!能用計算機解決的事情,盡量不要讓人解決。如果你在學習當中有任何疑問、學習心得、職業發展等內容歡迎在貼吧中分享,讓我?

tieba.baidu.com


推薦閱讀:
相關文章