基于QT的TCP通信服務(wù)的實(shí)現(xiàn)
一、結(jié)構(gòu)
1.1 套接字
應(yīng)用層通過(guò)傳輸層進(jìn)行數(shù)據(jù)通信時(shí),TCP和UDP會(huì)遇到同時(shí)為多個(gè)應(yīng)用程序進(jìn)程提供并發(fā)服務(wù)的問(wèn)題。多個(gè)TCP連接或多個(gè)應(yīng)用程序進(jìn)程可能需要 通過(guò)同一個(gè)TCP協(xié)議端口傳輸數(shù)據(jù)。為了區(qū)別不同的應(yīng)用程序進(jìn)程和連接,許多計(jì)算機(jī)操作系統(tǒng)為應(yīng)用程序與TCP/IP協(xié)議交互提供了稱(chēng)為套接字 (Socket)的接口,區(qū)分不同應(yīng)用程序進(jìn)程間的網(wǎng)絡(luò)通信和連接。
實(shí)際上套接字做的事情就是為我們通信的兩端做一個(gè)連接
1.2 socket通信流程
對(duì)于TCP而言,socket通信的流程大概如下:
1.3 QTcpsocket
對(duì)于客戶(hù)端我們就使用的這個(gè)QTcpsocket
類(lèi)去請(qǐng)求服務(wù)器端,我們先看官方給的文檔可以知道:
使用該類(lèi)需要#include <QTcpSocket>
頭文件,并且該類(lèi)是繼承QAbstractSocket
類(lèi)的,而且我們發(fā)現(xiàn)對(duì)于這個(gè)類(lèi)沒(méi)有新增很多的函數(shù),那么我們就應(yīng)該去看它的父類(lèi),果不其然,父類(lèi)中有很多的函數(shù),我們后面進(jìn)行TCP
通信其實(shí)也主要是用到父類(lèi)的一些函數(shù),所以看一下文檔還是有必要的,對(duì)于每一個(gè)函數(shù),你都能點(diǎn)進(jìn)去看參數(shù)、以及描述
雖然QAbstractSocket
有這么多的函數(shù),但是我們實(shí)際上使用的函數(shù)就那么幾個(gè),我們后面一一介紹,我們現(xiàn)在先來(lái)說(shuō)說(shuō)QT
客戶(hù)端創(chuàng)建網(wǎng)絡(luò)連接的流程:
1.我們需要new
一個(gè)QTcpSocket
的對(duì)象,當(dāng)然初始化只需要將當(dāng)前的obj
傳入即可,也就是this
,然后給這個(gè)對(duì)象的readyRead
創(chuàng)建一個(gè)凹槽做一些收到信號(hào)后的處理(比如將收到的數(shù)據(jù)顯示在某個(gè)地方)。
2.通過(guò)connectToHost
函數(shù)去連接服務(wù)器,函數(shù)中傳入ip
和port
(ip
要強(qiáng)轉(zhuǎn)為QHostAddress
類(lèi))
3.通過(guò)調(diào)用waitForConnected
函數(shù),來(lái)判斷服務(wù)器是否連接超時(shí),一般設(shè)置1000
,表示的是1s
未連接就超時(shí),通過(guò)官方的文檔我們能知道如果返回的是true
表示的是建立了連接,否則表示建立失敗或未建立連接
注意的是這里,只有使用waitForConnected()
后,QTcpSocket
才真正嘗試連接服務(wù)器,并返回是否連接的結(jié)果。
4.當(dāng)我們的客戶(hù)端接收到readyRead
的信號(hào),我們就可以通過(guò)readAll()
函數(shù)讀取服務(wù)器返回的信息,同樣的我們也可以通過(guò)write()
函數(shù)向服務(wù)器發(fā)送信息
注意的是這里服務(wù)端讀到的數(shù)據(jù)是一個(gè)QByteArray
類(lèi)型的,我們寫(xiě)入的數(shù)據(jù)可以是Qstring
類(lèi)型的,當(dāng)然也可以是QByteArray
那么這就是客戶(hù)端的通信流程了
1.4 QTcpServer
對(duì)于服務(wù)器端我們需要用到QTcpServer
類(lèi),同樣在官網(wǎng)的文檔我們能得到這個(gè)類(lèi)的一些基本信息:
服務(wù)端的流程:
1.首先創(chuàng)建一個(gè)QTcpServer
類(lèi),并初始化,然后給這個(gè)對(duì)象的 QTcpServer::newConnection()
建立一個(gè)凹槽,用于處理與客戶(hù)端建立連接后要做的一些事情,例如繼續(xù)為QTcpSocket::readyRead
創(chuàng)建一個(gè)凹槽進(jìn)行數(shù)據(jù)讀取操作、為QTcpSocket::disconnected
創(chuàng)建凹槽用于對(duì)服務(wù)端失聯(lián)后的操作……
2.通過(guò)listen(QHostAddress::Any,port)
函數(shù)監(jiān)聽(tīng)所有的ip
請(qǐng)求
3.當(dāng)有新的客戶(hù)端連接服務(wù)器的時(shí)候,會(huì)自動(dòng)觸發(fā)newConnection()
信號(hào)函數(shù),然后我們可以通過(guò)通過(guò)QTcpSocket * nextPendingConnection()
成員函數(shù)來(lái)獲取當(dāng)前連接上的新的客戶(hù)端類(lèi).然后再對(duì)QTcpSocket
來(lái)進(jìn)行信號(hào)槽綁定(這里可以寫(xiě)一個(gè)客戶(hù)端的池但是我這里為了方便就只寫(xiě)了一個(gè)客戶(hù)端連接的情況)
4.對(duì)于數(shù)據(jù)的讀取的話(huà),由于我們這里只寫(xiě)了一個(gè)客戶(hù)端的情況,那么就可以直接給這個(gè)QTcpSocket
對(duì)象綁定和客戶(hù)端相同的事件就好
二、設(shè)計(jì)UI
我們直接使用QT Creator
自帶的繪制工具,簡(jiǎn)單繪制一下就好,界面不重要,重要是控件的objectName
經(jīng)量設(shè)置合理一點(diǎn),下面是我的設(shè)置:
2.1 客戶(hù)端UI
2.2 服務(wù)器端UI
三、核心代碼
對(duì)于客戶(hù)端來(lái)說(shuō):mainwindow.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QTcpSocket> #include <QLabel> #include <QHostAddress> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { setWindowTitle(QString("客戶(hù)端")); ui->setupUi(this); ui->port_2->setText("8899"); ui->ip->setText("127.0.0.1"); //剛開(kāi)始 客戶(hù)端的 [斷開(kāi)服務(wù)] 按鈕不可用 ui->disconnect->setDisabled(true); //創(chuàng)建一個(gè)監(jiān)聽(tīng)器服務(wù)對(duì)象 //Tcpserver m_tcp=new QTcpSocket(this); //客戶(hù)端 被動(dòng)的接收服務(wù)器信號(hào) connect(m_tcp,&QTcpSocket::readyRead,this,[=](){ QByteArray array=m_tcp->readAll(); ui->record->append("服務(wù)端說(shuō):"+array); }); //客戶(hù)端 斷開(kāi) connect(m_tcp,&QTcpSocket::disconnected,this,[=](){ m_status->setPixmap(QPixmap(":/a/tmp/disconnect.png").scaled(20,20)); ui->record->append("斷開(kāi)鏈接服務(wù)器"); ui->connect->setDisabled(false); ui->disconnect->setDisabled(true); }); //操作狀態(tài)欄圖標(biāo) connect(m_tcp,&QTcpSocket::connected,this,[=](){ m_status->setPixmap(QPixmap(":/a/tmp/connect.png").scaled(20,20)); ui->record->append("已經(jīng)鏈接成功服務(wù)器"); //操作按鈕互斥,鏈接成功了,自然鏈接按鈕不能用只有斷開(kāi)按鈕可以用 ui->connect->setDisabled(true); ui->disconnect->setDisabled(false); }); //增加一點(diǎn)動(dòng)畫(huà)效果 狀態(tài)欄的顏色變化 m_status =new QLabel; //狀態(tài)欄的圖片添加 ui->statusbar->addWidget(new QLabel("鏈接狀態(tài):")); ui->statusbar->addWidget(m_status); //裝到菜單狀態(tài)欄 } MainWindow::~MainWindow() { delete ui; } //點(diǎn)擊監(jiān)聽(tīng)服務(wù),自然而然去啟動(dòng)監(jiān)聽(tīng)服務(wù) void MainWindow::on_send_clicked() { //把發(fā)出信息框的數(shù)據(jù)拿到,通過(guò)socket套接字發(fā)送出去 QString string=ui->sendmsg->toPlainText(); m_tcp->write(string.toUtf8()); ui->record->append("客戶(hù)端說(shuō):"+string); ui->sendmsg->clear(); } void MainWindow::on_connect_clicked() { //獲取 IP 端口 才能鏈接 QString ip=ui->ip->text(); unsigned short port=ui->port_2->text().toUShort(); qDebug("click_on_connect state = %d\n",m_tcp->state()); m_tcp->connectToHost(QHostAddress(ip),port); if(m_tcp->waitForConnected(1000)) { qDebug("connected !\n"); ui->record->clear(); } else qDebug("connect out time limit !\n"); } void MainWindow::on_disconnect_clicked() { qDebug("loc1 state = %d\n",m_tcp->state()); m_tcp->disconnectFromHost(); qDebug("loc2 state = %d\n",m_tcp->state()); if(m_tcp->state() == QAbstractSocket::UnconnectedState || m_tcp->waitForDisconnected(1000)) qDebug("Disconnected!\n"); else qDebug("Disconnect fail!\n"); m_tcp->close(); ui->connect->setDisabled(false); ui->disconnect->setDisabled(false); }
對(duì)于服務(wù)器來(lái)說(shuō):mainwindow.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QTcpServer> #include <QTcpSocket> #include <QLabel> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); ui->port_2->setText("8899"); //創(chuàng)建一個(gè)監(jiān)聽(tīng)器服務(wù)對(duì)象 //Tcpserver m_s=new QTcpServer(this); m_tcp = new QTcpSocket; //啟動(dòng)監(jiān)聽(tīng) 通過(guò)點(diǎn)擊監(jiān)聽(tīng)按鈕實(shí)現(xiàn),并且在按鈕轉(zhuǎn)到的槽函數(shù)實(shí)現(xiàn)監(jiān)聽(tīng) //上述完成監(jiān)聽(tīng),就等待用戶(hù)/客戶(hù)端的鏈接 connect(m_s,&QTcpServer::newConnection,this,[=](){ //狀態(tài)欄變色 m_status->setPixmap(QPixmap(":/a/tmp/connect.png").scaled(20,20)); //程序到此步驟證明有用戶(hù)鏈接,啟用socket通信傳輸并解析數(shù)據(jù) //實(shí)例此次通信對(duì)象 nextPendingConnection得到一個(gè)可供通信的套接字對(duì)象 m_tcp=m_s->nextPendingConnection(); QString client_ip = m_tcp->peerAddress().toString().split("::ffff:")[1]; quint16 client_port = m_tcp->peerPort(); ui->record->append(tr("%1:%2 connected!\n").arg(client_ip).arg(client_port)); //進(jìn)行對(duì)象處理,檢測(cè)傳輸?shù)臄?shù)據(jù),利用connect對(duì)tcp套接字操作 connect(m_tcp,&QTcpSocket::readyRead,this,[=](){ QByteArray array = m_tcp->readAll(); ui->record->append("客戶(hù)端說(shuō):"+array); }); //客戶(hù)端斷開(kāi)操作 connect(m_tcp,&QTcpSocket::disconnected,this,[=](){ QString client_ip = m_tcp->peerAddress().toString().split("::ffff:")[1]; quint16 client_port = m_tcp->peerPort(); ui->record->append(tr("%1:%2 Disconnected!\n").arg(client_ip).arg(client_port)); m_tcp->disconnectFromHost(); if(m_tcp->state() == QAbstractSocket::UnconnectedState || m_tcp->waitForDisconnected(1000)) qDebug("Disconnected!\n"); else qDebug("Disconnect fail!\n"); m_status->setPixmap(QPixmap(":/a/tmp/disconnect.png").scaled(20,20)); }); }); //增加一點(diǎn)動(dòng)畫(huà)效果 狀態(tài)欄的顏色變化 m_status =new QLabel; //狀態(tài)欄的圖片添加 //m_status->setPixmap(QPixmap(":/a/tmp/connect.png").scaled(20,20)); ui->statusbar->addWidget(new QLabel("鏈接狀態(tài):")); ui->statusbar->addWidget(m_status); //裝到菜單狀態(tài)欄 } MainWindow::~MainWindow() { delete ui; } //點(diǎn)擊監(jiān)聽(tīng)服務(wù),自然而然去啟動(dòng)監(jiān)聽(tīng)服務(wù) void MainWindow::on_setlisten_clicked() { setWindowTitle("服務(wù)器"); //得到窗口 lineedit窗口的端口號(hào) unsigned short port=ui->port_2->text().toShort(); //進(jìn)行監(jiān)聽(tīng) ip 端口 m_s->listen(QHostAddress::Any,port); ui->setlisten->setDisabled(true); } void MainWindow::on_send_clicked() { //把發(fā)出信息框的數(shù)據(jù)拿到,通過(guò)socket套接字發(fā)送出去 QString string=ui->sendmsg->toPlainText(); m_tcp->write(string.toUtf8()); ui->record->append("服務(wù)端說(shuō):"+string); ui->sendmsg->clear(); }
四、效果圖
到此這篇關(guān)于基于QT的TCP通信服務(wù)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)QT TCP通信內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Qt中TCP協(xié)議通信詳解
- QT實(shí)現(xiàn)簡(jiǎn)單TCP通信
- Qt實(shí)現(xiàn)簡(jiǎn)單的TCP通信
- QT編寫(xiě)tcp通信工具(Client篇)
- QT網(wǎng)絡(luò)通信TCP客戶(hù)端實(shí)現(xiàn)詳解
- Qt?TCP網(wǎng)絡(luò)通信學(xué)習(xí)
- Qt網(wǎng)絡(luò)編程實(shí)現(xiàn)TCP通信
- QT5實(shí)現(xiàn)簡(jiǎn)單的TCP通信的實(shí)現(xiàn)
- QT網(wǎng)絡(luò)編程Tcp下C/S架構(gòu)的即時(shí)通信實(shí)例
- Qt TCP實(shí)現(xiàn)簡(jiǎn)單通信功能
相關(guān)文章
關(guān)于數(shù)據(jù)結(jié)構(gòu)單向鏈表的各種操作
這篇文章主要介紹了關(guān)于數(shù)據(jù)結(jié)構(gòu)單向鏈表的各種操作,關(guān)于數(shù)據(jù)結(jié)構(gòu)鏈表的操作一般涉及的就是增刪改查,下面將關(guān)于無(wú)空頭鏈表展開(kāi)介紹,需要的朋友可以參考下2023-04-04C++將音頻PCM數(shù)據(jù)封裝成wav文件的方法
這篇文章主要為大家詳細(xì)介紹了C++將音頻PCM數(shù)據(jù)封裝成wav文件的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01C++簡(jiǎn)單集合類(lèi)的實(shí)現(xiàn)方法
如何使用C++實(shí)現(xiàn)一個(gè)簡(jiǎn)單的集合類(lèi),這篇文章主要介紹了C++簡(jiǎn)單集合類(lèi)的實(shí)現(xiàn)方法,感興趣的小伙伴們可以參考一下2016-07-07visual?studio?2022?編譯出來(lái)的文件被刪除并監(jiān)視目錄中的文件變更(示例詳解)
這篇文章主要介紹了visual?studio?2022?編譯出來(lái)的文件被刪除?并監(jiān)視目錄中的文件變更,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08C語(yǔ)言實(shí)現(xiàn)單元測(cè)試的示例詳解
單元測(cè)試(unit testing),是指對(duì)軟件中的最小可測(cè)試單元進(jìn)行檢查和驗(yàn)證。這篇文章主要為大家介紹了C語(yǔ)言實(shí)現(xiàn)單元測(cè)試的方法,需要的可以參考一下2022-09-09C++類(lèi)中的常數(shù)據(jù)成員與靜態(tài)數(shù)據(jù)成員之間的區(qū)別
常數(shù)據(jù)成員是指在類(lèi)中定義的不能修改其值的一些數(shù)據(jù)成員,類(lèi)似于我們以前學(xué)過(guò)的常變量,雖然是變量,也有自己的地址,但是一經(jīng)賦初值,便不能再被修改2013-10-10C++下如何將TensorFlow模型封裝成DLL供C#調(diào)用
這篇文章主要介紹了C++下如何將TensorFlow模型封裝成DLL供C#調(diào)用問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11