使用Qt - udp通信方式,構建局域網聊天通信軟件實例
1.簡介效果
在之前的文章中,介紹了TCP協議在Qt中的應用實現方式傳送門,TCP通信方式是面向對象,可靠的連接服務。而與之對應的是不可靠,無連接的UDP通信協議,兩者是TCP/IP協議簇中較爲常用的兩者通信方式。
之前的博文中,實現了利用Qt調用TCP協議的局域網通信聊天,而這篇博文將介紹UDP協議在Qt中的簡單運用,其想要達到的效果如下。
使用UDP就和TCP不同的是,UDP協議只管發,實用於及時通信領域,它只管發送,不管對方是否收到,但因爲其傳輸效率高效,消耗資源小,所以嚐嚐結合於TCP協議一同使用在通信連接中。我們最常用的就是QQ,QQ採用TCP連接來保持在線狀態,使其連接至上層的騰訊服務器,而聊天發送消息功能大都採用UDP方式,當UDP協議不能正常轉發的時候,就會採用TCP協議進行發送,所以,TCP和UDP協議常常一同使用。
2.項目設計
1)流程圖
UDP協議流程圖,如下圖所示,經此流程圖後,我們可以清晰的知道,UDP是如何通信工作,並進行調用。
UDP協議沒有明確的客戶端和服務端可言,只要綁定了對應的IP地址和端口號,就可以進行通信,如很多的組播軟件,可以使綁定同一個IP。使得多個端口號的客戶在此IP下進行通信和數據傳輸。
2)項目構建
新建爲widget項目,構建如下所示項目樹
3)界面構建
兩個界面設計構建如下
- 界面一 - secondwdiget
- 界面二 - widget
使用行編輯框和文本編輯框構建簡單的界面如上所示。
4)代碼設計
和Qt使用TCP一樣,想要在Qt中使用網絡編程的包。需要在.pro工程文件裏面添加如下代碼
QT += network
a.widget.h
首先還是需要添加Qt中封裝好的UDP的頭文件
#include <QUdpSocket>
之後對其進行初始化聲明,並定義一個處理消息隊列的槽函數,用以在UDP通信套接字連接後發送消息,之後便是重寫事件過濾器用以響應鍵盤Enter鍵的輸入,具體代碼如下
public:
void dealMsg(); //槽函數,處理對方發過來的數據
protected:
bool eventFilter(QObject *target, QEvent *event);//事件過濾器
private:
QUdpSocket *udpSocket;
b.widget.cpp
在執行文件中,對構建函數中的udpSocket分配動態空間,當連接好之後,自動並觸發連接的槽函數。
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
this->resize(450,300);
this->setWindowTitle("服務器端口:8888");
udpSocket = new QUdpSocket(this);
//綁定端口號
udpSocket->bind(8888);
//udpSocket->bind(QHostAddress::AnyIPv4,8888); //必須是主機的ipv4類通信方式,以及本機的ip地址
//當對方發送數據過來。自動觸發readyRead()
connect(udpSocket,&QUdpSocket::readyRead,this,&Widget::dealMsg);
//鍵盤Enter鍵設置
ui->ButtonSend->setFocus();
ui->ButtonSend->setDefault(true);
ui->textEditShow->installEventFilter(this);//設置完後自動調用其eventFilter函數
}
之後便是對自定義處理消息隊列的槽函數進行函數實現
void Widget::dealMsg()
{
//讀取對方發送的內容
char buf[1024] = {0}; //內容
QHostAddress cliAddr; //對方地址
quint16 port; //對方端口
qint64 len = udpSocket->readDatagram(buf,sizeof(buf),&cliAddr,&port);
//添加時間
QTime cur_time = QTime::currentTime();
QString time_info = cur_time.toString("hh:mm:ss");
if(len >0)
{
//獲取到文本格式化 如 [192.168.1.1 : 8888]aaaa 的格式
QString str = QString("[%1:%2] %3 [%4]")
.arg(cliAddr.toString())
.arg(port)
.arg(buf)
.arg(time_info);
//ui->textEditShow->setText(str);
ui->textEditShow->append(str);
}
}
然後便是對事件過濾器的函數實現
bool Widget::eventFilter(QObject *target, QEvent *event)
{
if(target == ui->textEditShow) //對象爲需要發送的焦點
{
if(event->type() == QEvent::KeyPress)//回車鍵
{
QKeyEvent *k = static_cast<QKeyEvent *>(event);
if(k->key() == Qt::Key_Return)
{
on_ButtonSend_clicked();
return true;
}
}
}
return QWidget::eventFilter(target,event);
}
最後是點擊發送按鈕,獲取編輯框中的內容,使用通信套接字,將其發送給綁定特定端口號的一方
void Widget::on_ButtonSend_clicked()
{
//獲取對方的ip和端口
QString ip = ui->lineEditIp->text();
qint16 port = ui->lineEditPort->text().toInt();
//獲取編輯區內容
QString str = ui->textEditShow->toPlainText();
//給指定的ip發送數據
udpSocket->writeDatagram(str.toUtf8(),QHostAddress(ip),port);
ui->textEditShow->clear();
}
c.secondwidget.h
添加socket頭文件方法和聲明,以及消息隊列處理函數,和鍵盤響應函數同上
public:
void dealMsg2(); //槽函數,處理對方發過來的數據
protected:
bool eventFilter(QObject *target, QEvent *event);//事件過濾器
private:
QUdpSocket *udpSocket;
d.secondwidget.cpp
構造函數和第一個界面有一點點不同,如下
SecondWidget::SecondWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::SecondWidget)
{
ui->setupUi(this);
this->resize(450,300);
this->setWindowTitle("服務器端口2:9999");
udpSocket2 = new QUdpSocket(this);
//綁定
udpSocket2->bind(9999);
connect(udpSocket2,&QUdpSocket::readyRead,this,&SecondWidget::dealMsg2);
ui->ButtonSend2->setFocus();
ui->ButtonSend2->setDefault(true);
//ui->ButtonSend->setShortcut(Qt::Key_Enter|Qt::Key_Return);
ui->textEditShow2->installEventFilter(this);//設置完後自動調用其eventFilter函數
}
其他的函數實現方式和第一個界面相同,這類不再贅述。待正確編譯之後,即可實現這樣的效果
- 注意:Qt中UDP通信只需要在同一個ip下(需要綁定自己電腦上的ip,使用ipconfig命令查看該電腦的ipv4的地址),綁定對應的端口號。即可進行通信連接
3.源代碼
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QUdpSocket>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
void dealMsg(); //槽函數,處理對方發過來的數據
protected:
bool eventFilter(QObject *target, QEvent *event);//事件過濾器
private slots:
void on_ButtonSend_clicked();
void on_ButtonClose_clicked();
private:
Ui::Widget *ui;
private:
QUdpSocket *udpSocket;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QHostAddress>
#include <QDebug>
#include <QTime>
#include <QKeyEvent>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
this->resize(450,300);
this->setWindowTitle("服務器端口:8888");
udpSocket = new QUdpSocket(this);
//綁定端口號
udpSocket->bind(8888);
//udpSocket->bind(QHostAddress::AnyIPv4,8888); //必須是主機的ipv4類通信方式,以及本機的ip地址
//當對方發送數據過來。自動觸發readyRead()
connect(udpSocket,&QUdpSocket::readyRead,this,&Widget::dealMsg);
ui->ButtonSend->setFocus();
ui->ButtonSend->setDefault(true);
ui->textEditShow->installEventFilter(this);//設置完後自動調用其eventFilter函數
}
Widget::~Widget()
{
delete ui;
}
void Widget::dealMsg()
{
//讀取對方發送的內容
char buf[1024] = {0}; //內容
QHostAddress cliAddr; //對方地址
quint16 port; //對方端口
qint64 len = udpSocket->readDatagram(buf,sizeof(buf),&cliAddr,&port);
QTime cur_time = QTime::currentTime();
QString time_info = cur_time.toString("hh:mm:ss");
if(len >0)
{
//獲取到文本格式化 [192.168.1.1 : 8888]aaaa
QString str = QString("[%1:%2] %3 [%4]")
.arg(cliAddr.toString())
.arg(port)
.arg(buf)
.arg(time_info);
//ui->textEditShow->setText(str);
ui->textEditShow->append(str);
}
}
bool Widget::eventFilter(QObject *target, QEvent *event)
{
if(target == ui->textEditShow)
{
if(event->type() == QEvent::KeyPress)//回車鍵
{
QKeyEvent *k = static_cast<QKeyEvent *>(event);
if(k->key() == Qt::Key_Return)
{
on_ButtonSend_clicked();
return true;
}
}
}
return QWidget::eventFilter(target,event);
}
void Widget::on_ButtonSend_clicked()
{
//獲取對方的ip和端口
QString ip = ui->lineEditIp->text();
qint16 port = ui->lineEditPort->text().toInt();
//獲取編輯區內容
QString str = ui->textEditShow->toPlainText();
//給指定的ip發送數據
udpSocket->writeDatagram(str.toUtf8(),QHostAddress(ip),port);
ui->textEditShow->clear();
}
void Widget::on_ButtonClose_clicked()
{
udpSocket->disconnectFromHost();
udpSocket->close();
qDebug() << "主服務器連接斷開!";
this->close();
}
secondwidget.h
#ifndef SECONDWIDGET_H
#define SECONDWIDGET_H
#include <QWidget>
#include <QUdpSocket>
namespace Ui {
class SecondWidget;
}
class SecondWidget : public QWidget
{
Q_OBJECT
public:
explicit SecondWidget(QWidget *parent = nullptr);
~SecondWidget();
void dealMsg2(); //消息隊列處理
protected:
bool eventFilter(QObject *target, QEvent *event);//事件過濾器
private slots:
void on_ButtonSend2_clicked();
void on_ButtonClose2_clicked();
private:
Ui::SecondWidget *ui;
private:
QUdpSocket *udpSocket2;
};
#endif // SECONDWIDGET_H
secondwidget.cpp
#include "secondwidget.h"
#include "ui_secondwidget.h"
#include <QHostAddress>
#include <QDebug>
#include <QTime>
#include <QKeyEvent>
SecondWidget::SecondWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::SecondWidget)
{
ui->setupUi(this);
this->resize(450,300);
this->setWindowTitle("服務器端口2:9999");
udpSocket2 = new QUdpSocket(this);
//綁定
udpSocket2->bind(9999);
connect(udpSocket2,&QUdpSocket::readyRead,this,&SecondWidget::dealMsg2);
ui->ButtonSend2->setFocus();
ui->ButtonSend2->setDefault(true);
//ui->ButtonSend->setShortcut(Qt::Key_Enter|Qt::Key_Return);
ui->textEditShow2->installEventFilter(this);//設置完後自動調用其eventFilter函數
}
SecondWidget::~SecondWidget()
{
delete ui;
}
void SecondWidget::dealMsg2()
{
//讀取對方發送的內容
char buf[1024] = {0}; //內容
QHostAddress cliAddr; //對方地址
quint16 port; //對方端口
qint64 len = udpSocket2->readDatagram(buf,sizeof(buf),&cliAddr,&port);
QTime cur_time = QTime::currentTime();
QString time_info = cur_time.toString("hh:mm:ss");
if(len >0)
{
//獲取到文本格式化 [192.68.1.144 : 8888]aaaa
QString str = QString("[%1:%2] %3 [%4]")
.arg(cliAddr.toString())
.arg(port)
.arg(buf)
.arg(time_info);
//ui->textEditShow2->setText(str);
ui->textEditShow2->append(str);
}
}
bool SecondWidget::eventFilter(QObject *target, QEvent *event)
{
if(target == ui->textEditShow2)
{
if(event->type() == QEvent::KeyPress)//回車鍵
{
QKeyEvent *k = static_cast<QKeyEvent *>(event);
if(k->key() == Qt::Key_Return)
{
on_ButtonSend2_clicked();
return true;
}
}
}
return QWidget::eventFilter(target,event);
}
void SecondWidget::on_ButtonSend2_clicked()
{
//獲取對方的ip和端口
QString ip = ui->lineEditIp2->text();
qint16 port = ui->lineEditPort2->text().toInt();
//獲取編輯區內容
QString str = ui->textEditShow2->toPlainText();
//給指定的ip發送數據
udpSocket2->writeDatagram(str.toUtf8(),QHostAddress(ip),port);
ui->textEditShow2->clear();
}
void SecondWidget::on_ButtonClose2_clicked()
{
udpSocket2->disconnectFromHost();
udpSocket2->close();
qDebug() << "斷開連接!";
this->close();
}
如果感興趣想要源文件的,請移至傳送門源代碼文件下載