Qt UDP的初步使用
為了使用Qt自帶的Socket進行網絡編程,先必須熟悉Socket編程的原理,另外還需對Qt一些基本類的操作比較熟悉。由于剛接觸不久,所以還是以看人家的代碼來學習。這次主要是學Qt下UDP的編程,且熟悉一些Qt下代碼的編寫流程,所以本文參照的是《Qt及Qt Quick開發實戰精解》一書中的第5個例子:局域網聊天工具中的UDP聊天部分。
? ? ?另外http://www.yafeilinux.com/?上有其源碼和相關教程下載。
? ? ?該程序實現的功能是:局域網內,每個用戶登錄到聊天軟件,則軟件界面的右端可以顯示在線用戶列表,分別顯示的是用戶名,主機名,ip地址。軟件左邊那大塊是聊天內容顯示界面,這里局域網相當于qq中的qq群,即群聊。每個人可以在聊天輸入界面中輸入文字并發送。其聊天界面如下:
? ?
? ? ?該程序實現的是每個用戶登錄既是客戶端又是服務器端,這就需要看你站在哪個角度看問題了。簡單的說,當用戶發送信息給別人時就是客戶端,當接收別人的信息是就可以看做是服務器端。
下面分服務器端和客戶端2部分來介紹。
服務器端:建立一個UDP Socket并綁定在固定端口后,用信號與槽的方式進行監聽是否有數據來臨。如果用,接收其數據并分析數據的消息類型,如果消息是新用戶登錄則更新用戶列表并在聊天顯示窗口中添加新用戶上線通知;同理,如果是用戶下線,則在用戶列表中刪除該用戶且在聊天顯示窗口中顯示下線通知;如果是聊天消息,則接收該消息并且在窗口中顯示。其流程圖如下:
? ?
客戶端:首先當客戶端登錄時,獲取本機的用戶名,計算機名和ip地址,并廣播給局域網的服務器更新用戶列表。然后當客戶端需要發送信息時,則在聊天輸入欄中輸入信息并按發送鍵發送聊天內容,當然于此同時也廣播本地系統的各種信息。其流程圖如下:
?
程序主要代碼和注釋如下:
widget.h:
#ifndef WIDGET_H #define WIDGET_H#include <QWidget> class QUdpSocket;namespace Ui { class Widget; }// 枚舉變量標志信息的類型,分別為消息,新用戶加入,用戶退出,文件名,拒絕接受文件 enum MessageType{Message, NewParticipant, ParticipantLeft, FileName, Refuse};class Widget : public QWidget {Q_OBJECTpublic:explicit Widget(QWidget *parent = 0);~Widget();protected:void newParticipant(QString userName,QString localHostName, QString ipAddress);void participantLeft(QString userName,QString localHostName, QString time);void sendMessage(MessageType type, QString serverAddress="");QString getIP();QString getUserName();QString getMessage();private:Ui::Widget *ui;QUdpSocket *udpSocket;qint16 port;private slots:void processPendingDatagrams();void on_sendButton_clicked(); };#endif // WIDGET_Hwidget.cpp:
#include "widget.h" #include "ui_widget.h" #include <QUdpSocket> #include <QHostInfo> #include <QMessageBox> #include <QScrollBar> #include <QDateTime> #include <QNetworkInterface> #include <QProcess>Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget) {ui->setupUi(this);udpSocket = new QUdpSocket(this);//創建一個QUdpSocket類對象,該類提供了Udp的許多相關操作port = 45454;//此處的bind是個重載函數,連接本機的port端口,采用ShareAddress模式(即允許其它的服務連接到相同的地址和端口,特別是//用在多客戶端監聽同一個服務器端口等時特別有效),和ReuseAddressHint模式(重新連接服務器)udpSocket->bind(port, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);//readyRead()信號是每當有新的數據來臨時就被觸發connect(udpSocket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams()));sendMessage(NewParticipant);//打開軟件時就向外發射本地信息,讓其他在線用戶得到通知 }Widget::~Widget() {delete ui; }// 使用UDP廣播發送信息,MessageType是指頭文件中的枚舉數據類型 //sendMessage即把本機的主機名,用戶名+(消息內容后ip地址)廣播出去 void Widget::sendMessage(MessageType type, QString serverAddress) {QByteArray data; //字節數組//QDataStream類是將序列化的二進制數據送到io設備,因為其屬性為只寫QDataStream out(&data, QIODevice::WriteOnly);QString localHostName = QHostInfo::localHostName();//返回主機名,QHostInfo包含了一些關于主機的靜態函數QString address = getIP(); //調用自己類中的getIP()函數//將type,getUserName(),localHostName按照先后順序送到out數據流中,消息類型type在最前面out << type << getUserName() << localHostName;switch(type){case Message :if (ui->messageTextEdit->toPlainText() == "") { //將輸入框里的文字轉化成純文本發送//當發送的文本為空時創建一個警告信息窗口,tr函數為譯本函數,即譯碼后面的text內容QMessageBox::warning(0,tr("警告"),tr("發送內容不能為空"),QMessageBox::Ok); return;}out << address << getMessage();//將ip地址和得到的消息內容輸入out數據流ui->messageBrowser->verticalScrollBar() //返回垂直條->setValue(ui->messageBrowser->verticalScrollBar()->maximum());//設置垂直滑動條的最大值break;case NewParticipant :out << address; //為什么此時只是輸出地址這一項呢?因為此時不需要傳遞聊天內容break;case ParticipantLeft :break;case FileName :break;case Refuse :break;}//一個udpSocket已經于一個端口bind在一起了,這里的data是out流中的data,最多可以傳送8192個字節,但是建議不要超過//512個字節,因為這樣雖然可以傳送成功,但是這些數據需要在ip層分組,QHostAddress::Broadcast是指發送數據的目的地址//這里為本機所在地址的廣播組內所有機器,即局域網廣播發送udpSocket->writeDatagram(data,data.length(),QHostAddress::Broadcast, port);//將data中的數據發送 }// 接收UDP信息 void Widget::processPendingDatagrams() {//hasPendingDatagrams返回true時表示至少有一個數據報在等待被讀取while(udpSocket->hasPendingDatagrams()){QByteArray datagram;//pendingDatagramSize為返回第一個在等待讀取報文的size,resize函數是把datagram的size歸一化到參數size的大小一樣datagram.resize(udpSocket->pendingDatagramSize());//將讀取到的不大于datagram.size()大小數據輸入到datagram.data()中,datagram.data()返回的是一個字節數組中存儲//數據位置的指針udpSocket->readDatagram(datagram.data(), datagram.size());QDataStream in(&datagram, QIODevice::ReadOnly);//因為其屬性為只讀,所以是輸入int messageType; //此處的int為qint32,在Qt中,qint8為char,qint16為uintin >> messageType; //讀取1個32位長度的整型數據到messageTyep中 QString userName,localHostName,ipAddress,message;QString time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");//將當前的時間轉化到括號中的形式switch(messageType){case Message://in>>后面如果為Qstring,則表示讀取一個直到出現'\0'的字符串in >> userName >> localHostName >> ipAddress >> message;ui->messageBrowser->setTextColor(Qt::blue);//設置文本顏色ui->messageBrowser->setCurrentFont(QFont("Times New Roman",12));//設置字體大小// ui->messageBrowser->append("[ " +userName+" ] "+ time);//輸出的格式為用戶名加時間顯示//輸出的格式為主機名加時間顯示,但輸出完后為什么會自動換行呢?ui->messageBrowser->append("[ " +localHostName+" ] "+ time);ui->messageBrowser->append(message);//消息輸出break;case NewParticipant:in >>userName >>localHostName >>ipAddress;newParticipant(userName,localHostName,ipAddress);break;case ParticipantLeft:in >>userName >>localHostName;participantLeft(userName,localHostName,time);break;case FileName:break;case Refuse:break;}} }// 處理新用戶加入 void Widget::newParticipant(QString userName, QString localHostName, QString ipAddress) {//此處的findItems表示找到與內容localHostName匹配的item,其匹配是基于變體的匹配模式bool isEmpty = ui->userTableWidget->findItems(localHostName, Qt::MatchExactly).isEmpty();if (isEmpty) { //沒有找到相應的主機名//新建3個小的item,分別為user,host,ipQTableWidgetItem *user = new QTableWidgetItem(userName);QTableWidgetItem *host = new QTableWidgetItem(localHostName);QTableWidgetItem *ip = new QTableWidgetItem(ipAddress);ui->userTableWidget->insertRow(0);//先設置的是第0行,即新來的用戶放在最上面ui->userTableWidget->setItem(0,0,user);//第0行的第1列...ui->userTableWidget->setItem(0,1,host);ui->userTableWidget->setItem(0,2,ip);ui->messageBrowser->setTextColor(Qt::gray);ui->messageBrowser->setCurrentFont(QFont("Times New Roman",10));//arg為返回后面文本的一個副本,%1表示輸出的內容按照第1個.arg后面的輸出?ui->messageBrowser->append(tr("%1 在線!").arg(userName));ui->userNumLabel->setText(tr("在線人數:%1").arg(ui->userTableWidget->rowCount()));//在線人數為條目的行數 sendMessage(NewParticipant);//該句的功能是讓新來的用戶也能收到其它在線用戶的信息,可擁于更新自己的好友列表 } }// 處理用戶離開 void Widget::participantLeft(QString userName, QString localHostName, QString time) {//找到第一個對應的主機名int rowNum = ui->userTableWidget->findItems(localHostName, Qt::MatchExactly).first()->row();ui->userTableWidget->removeRow(rowNum); //此句執行完后,rowCount()內容會自動減1ui->messageBrowser->setTextColor(Qt::gray);//設置文本顏色為灰色ui->messageBrowser->setCurrentFont(QFont("Times New Roman", 10));ui->messageBrowser->append(tr("%1 于 %2 離開!").arg(userName).arg(time));ui->userNumLabel->setText(tr("在線人數:%1").arg(ui->userTableWidget->rowCount())); }// 獲取ip地址,獲取本機ip地址(其協議為ipv4的ip地址) QString Widget::getIP() {//QList是Qt中一個容器模板類,是一個數組指針?QList<QHostAddress> list = QNetworkInterface::allAddresses();//此處的所有地址是指ipv4和ipv6的地址//foreach (variable, container),此處為按照容器list中條目的順序進行迭代foreach (QHostAddress address, list) { if(address.protocol() == QAbstractSocket::IPv4Protocol)return address.toString();}return 0; }// 獲取用戶名 QString Widget::getUserName() {QStringList envVariables;//將后面5個string存到envVariables環境變量中envVariables << "USERNAME.*" << "USER.*" << "USERDOMAIN.*"<< "HOSTNAME.*" << "DOMAINNAME.*";//系統中關于環境變量的信息存在environment中QStringList environment = QProcess::systemEnvironment();foreach (QString string, envVariables) {//indexOf為返回第一個匹配list的索引,QRegExp類是用規則表達式進行模式匹配的類int index = environment.indexOf(QRegExp(string));if (index != -1) {//stringList中存的是environment.at(index)中出現'='號前的字符串QStringList stringList = environment.at(index).split('=');if (stringList.size() == 2) {return stringList.at(1);//at(0)為文字"USERNAME.",at(1)為用戶名break;}}}return "unknown"; }// 獲得要發送的消息 QString Widget::getMessage() {QString msg = ui->messageTextEdit->toHtml();//轉化成html語言進行發送 ui->messageTextEdit->clear();//發送完后清空輸入框ui->messageTextEdit->setFocus();//重新設置光標輸入焦點,即焦點保持不變return msg; }// 發送消息 void Widget::on_sendButton_clicked() {sendMessage(Message); }main:
#include <QtGui/QApplication> #include "widget.h" #include <QTextCodec> //處理不同語言編碼的類int main(int argc, char *argv[]) {QApplication a(argc, argv);QTextCodec::setCodecForTr(QTextCodec::codecForLocale());//對不同的文字選擇不同的編碼 Widget w;w.show();return a.exec(); }總結
以上是生活随笔為你收集整理的Qt UDP的初步使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 历史首次!中国首部8k超高清太空短片发布
- 下一篇: Linux下boost库的安装