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