Qt基于文本协议的网络应用开发
文章目錄
- 1 文本協(xié)議的設計與實現(xiàn)
- 1.1 文本協(xié)議設計介紹
- 1.2 文本協(xié)議設計示例
- 2 從字節(jié)流裝配文本協(xié)議對象
- 3 文本協(xié)議中的中文處理
- 4 文本協(xié)議的網(wǎng)絡應用
1 文本協(xié)議的設計與實現(xiàn)
1.1 文本協(xié)議設計介紹
我們首先來看一下TCP應用編程中的問題:
- 數(shù)據(jù)接收端無法知道數(shù)據(jù)的發(fā)送方式!
網(wǎng)絡程序設計中的期望:
- 每次發(fā)送一條完整的消息,每次接收一條完整的消息。
- 即使接收緩沖區(qū)中有多條消息,也不會消息粘連。
- 消息中涵蓋了數(shù)據(jù)類型和數(shù)據(jù)長度等信息。
應用層協(xié)議設計:
- 什么是協(xié)議?
- 協(xié)議是通信雙方為數(shù)據(jù)交換而建立的規(guī)則、標準和約定的集合。
- 協(xié)議對數(shù)據(jù)傳輸?shù)淖饔?#xff1a;
- 通信雙方根據(jù)協(xié)議能夠正確收發(fā)數(shù)據(jù)。
- 通信雙方根據(jù)協(xié)議能夠解釋數(shù)據(jù)的意義。
1.2 文本協(xié)議設計示例
協(xié)議設計示例:
- 目標:基于TCP設計可用于文本傳輸?shù)膮f(xié)議。
- 完整消息包含:
- 數(shù)據(jù)頭:數(shù)據(jù)類型(即:數(shù)據(jù)區(qū)用途,固定長度)。
- 數(shù)據(jù)長度:數(shù)據(jù)區(qū)長度(固定長度)。
- 數(shù)據(jù)區(qū):字符數(shù)據(jù)(變長區(qū)域)。
實現(xiàn)代碼如下:
TextMessage.h:
#ifndef TEXTMESSAGE_H #define TEXTMESSAGE_H#include <QObject>class TextMessage : public QObject {QString m_type;QString m_data;public:TextMessage(QObject* parent = NULL);TextMessage(QString type, QString data, QObject* parent = NULL);QString type();int length();QString data();QString serialize();bool unserialize(QString s); };#endif // TEXTMESSAGE_HTextMessage.cpp:
#include "TextMessage.h"TextMessage::TextMessage(QObject* parent) : QObject(parent) {m_type = "";m_data = ""; }TextMessage::TextMessage(QString type, QString data, QObject* parent) : QObject(parent) {m_type = type.trimmed();m_type.resize(4, ' ');m_data = data.mid(0, 0xFFFF); }QString TextMessage::type() {return m_type.trimmed(); }int TextMessage::length() {return m_data.length(); }QString TextMessage::data() {return m_data; }QString TextMessage::serialize() {QString len = QString::asprintf("%X", m_data.length());len.resize(4, ' ');return m_type + len + m_data; }bool TextMessage::unserialize(QString s) {bool ret = (s.length() >= 8);if( ret ){QString type = s.mid(0, 4);QString len = s.mid(4, 4).trimmed();int l = len.toInt(&ret, 16);ret = ret && (l == (s.length() - 8));if( ret ){m_type = type;m_data = s.mid(8, l);}}return ret; }main.cpp:
#include <QCoreApplication> #include <QDebug> #include "TextMessage.h"int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);TextMessage tm("AB", "1234567890");QString s = tm.serialize();qDebug() << s;TextMessage tmt;tmt.unserialize(s);qDebug() << tmt.type();qDebug() << tmt.length();qDebug() << tmt.data();return a.exec(); }2 從字節(jié)流裝配文本協(xié)議對象
問題:
- 如何將緩沖區(qū)中的數(shù)據(jù)裝配成為協(xié)議對象?
深度思考:數(shù)據(jù)是否能夠裝配成協(xié)議對象?
- 數(shù)據(jù)量足夠:
- 如果數(shù)據(jù)量足夠,是否能夠裝配不止一個對象?
- 如何處理剩余數(shù)據(jù)(屬于下一個協(xié)議對象)?
- 數(shù)據(jù)量不足:
- 是否達到協(xié)議最小長度(8字節(jié))?
- 如何處理數(shù)據(jù)量超過最小長度,但不足以產(chǎn)生一個對象的情況?
初步的解決方案:
- 定義一個類用于接收字節(jié)流并裝配協(xié)議對象。
- 類中提供容器(隊列)暫存字節(jié)流。
- 當容器中至少存在8個字節(jié)時開始狀態(tài):
- 首先裝配協(xié)議中的類型(type)和數(shù)據(jù)區(qū)長度(length)。
- 根據(jù)數(shù)據(jù)區(qū)長度從容易中取數(shù)據(jù)裝配協(xié)議數(shù)據(jù)(data)。
- 當協(xié)議數(shù)據(jù)裝配完成時,創(chuàng)建協(xié)議對象并返回,否則,返回NULL。
協(xié)議對象裝配類的初步設計:
assemble()函數(shù)的實現(xiàn)流程:
assemble()函數(shù)的注意事項:
- 以m_type作為標志決定是否解析類型和長度。
- m_length是接收后續(xù)數(shù)據(jù)的基礎。
- 當m_data的長度與m_length相同時創(chuàng)建協(xié)議對象。
- 否則,返回NULL。
makeTypeAndLength()實現(xiàn)要點:
makeMessage()實現(xiàn)要點:
代碼實現(xiàn)如下:
TxtMsgAssembler.h:
TxtMsgAssembler.cpp:
#include "TxtMsgAssembler.h"TxtMsgAssembler::TxtMsgAssembler(QObject* parent) : QObject(parent) {}void TxtMsgAssembler::clear() {m_type = "";m_length = 0;m_data = ""; }QString TxtMsgAssembler::fetch(int n) {QString ret = "";for(int i=0; i<n; i++){ret += m_queue.dequeue();}return ret; }void TxtMsgAssembler::prepare(const char* data, int len) {if( data != NULL ){for(int i=0; i<len; i++){m_queue.enqueue(data[i]);}} }QSharedPointer<TextMessage> TxtMsgAssembler::assemble() {TextMessage* ret = NULL;bool tryMakeMsg = false;if( m_type == "" ){tryMakeMsg = makeTypeAndLength();}else{tryMakeMsg = true;}if( tryMakeMsg ){ret = makeMessage();}if( ret != NULL ){clear();}return QSharedPointer<TextMessage>(ret); }QSharedPointer<TextMessage> TxtMsgAssembler::assemble(const char* data, int len) {prepare(data, len);return assemble(); }bool TxtMsgAssembler::makeTypeAndLength() {bool ret = (m_queue.length() >= 8);if( ret ){QString len = "";m_type = fetch(4);len = fetch(4);m_length = len.trimmed().toInt(&ret, 16);if( !ret ){clear();}}return ret; }TextMessage* TxtMsgAssembler::makeMessage() {TextMessage* ret = NULL;if( m_type != "" ){int needed = m_length - m_data.length();int n = (needed <= m_queue.length()) ? needed : m_queue.length();m_data += fetch(n);if( m_length == m_data.length() ){ret = new TextMessage(m_type, m_data);}}return ret; }void TxtMsgAssembler::reset() {clear();m_queue.clear(); }main.cpp:
#include <QCoreApplication> #include <QDebug> #include "TextMessage.h" #include "TxtMsgAssembler.h"int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);TextMessage tm("AB", "1234567890");QString s = tm.serialize();qDebug() << s;TxtMsgAssembler as;QSharedPointer<TextMessage> pt;pt = as.assemble(s.toStdString().c_str(), s.length());if( pt != NULL ){qDebug() << "assemble successfully";qDebug() << pt->type();qDebug() << pt->length();qDebug() << pt->data();}return a.exec(); }總結一下:
- 從連續(xù)字節(jié)流裝配協(xié)議對象是應用自定義協(xié)議的基礎。
- 裝配類(TxtMsgAssembler)用于解析自定義協(xié)議。
- 裝配類的實現(xiàn)的關鍵是如何處理字節(jié)數(shù)據(jù)不夠的情況。
- 自定義協(xié)議類和裝配類能夠有效解決數(shù)據(jù)粘連問題。
3 文本協(xié)議中的中文處理
問題:
- 文本協(xié)議的設計與實現(xiàn)能夠支持中文嗎?
回顧協(xié)議設計:
我們看一下下面的代碼會有相同的輸出嗎?
第一種方式正常輸出,第二種方式則不行。
深度分析:
- 文本協(xié)議的實現(xiàn)只考慮了ASCII碼的情況,對于中文類型的寬字符編碼情況并未考慮(寬字符>=2字符)。
協(xié)議設計的微小改動:
- Type:4個ASCII字符。
- Length:4個ASCII字符(存儲數(shù)據(jù)區(qū)字節(jié)數(shù))。
- 數(shù)據(jù)區(qū):使用UTF-8方式進行編碼。
編碼小知識:
- ASCII:
- 最早的統(tǒng)一編碼標準,規(guī)定了128個字符編碼(0-127)。
- Unicode
- 一個很大的字符集,規(guī)定了字符的二進制代碼(編碼標準)。
- UTF-8
- 使用最廣泛的一種Unicode編碼標準的實現(xiàn)。
UTF-8編碼的特點:
UTF-8編碼示例:
協(xié)議實現(xiàn)的改動:
首先看一下相對上面的代碼都做了什么修改:
4 文本協(xié)議的網(wǎng)絡應用
將TextMessage對象作為網(wǎng)絡傳輸?shù)幕締挝?#xff1a;
架構設計:
實現(xiàn)概要:
- 客戶端提供發(fā)送TextMessage對象的成員函數(shù)。
- 客戶端和服務端均內(nèi)置TxtMsgAssembler對象:
- 用于從網(wǎng)絡字節(jié)流裝配TextMessage對象。
- 當成功收到TextMessage對象:
- 使用TxtMsgHandler接口進行異步通知。
為了使得服務端可以正確接收多個客戶端的TextMessage消息,我們需要為每一個與客戶端通信的TcpSocket對象分配一個專用的裝配對象:
簡單看一下Qt中的QMap容器:
- QMap是一種基于鍵值對(Key-Value)的字典數(shù)據(jù)結構。
- QMap是通過模板定義的,鍵和值都可以是自定義數(shù)據(jù)類型。
- QMap使用示例:
解決方案如下:
- 客戶端連接時,動態(tài)創(chuàng)建裝配對象,插入字典中。
- 網(wǎng)絡字節(jié)流達到時,在字典中查找裝配對象。
- 通過查找到的裝配對象處理字節(jié)流。
- 客戶端斷開時,銷毀對應的裝配對象。
代碼組織結構如下:
TxtMsgHandler.h:
TextMessage.h:
#ifndef TEXTMESSAGE_H #define TEXTMESSAGE_H#include <QObject> #include <QByteArray>class TextMessage : public QObject {QString m_type;QString m_data;public:TextMessage(QObject* parent = NULL);TextMessage(QString type, QString data, QObject* parent = NULL);QString type();int length();QString data();QByteArray serialize();bool unserialize(QByteArray ba); };#endif // TEXTMESSAGE_HTextMessage.cpp:
#include "TextMessage.h"TextMessage::TextMessage(QObject* parent) : QObject(parent) {m_type = "";m_data = ""; }TextMessage::TextMessage(QString type, QString data, QObject* parent) : QObject(parent) {m_type = type.trimmed();m_type.resize(4, ' ');m_data = data.mid(0, 15000); }QString TextMessage::type() {return m_type.trimmed(); }int TextMessage::length() {return m_data.length(); }QString TextMessage::data() {return m_data; }QByteArray TextMessage::serialize() {QByteArray ret;QByteArray dba = m_data.toUtf8();QString len = QString::asprintf("%X", dba.length());len.resize(4, ' ');ret.append(m_type.toStdString().c_str(), 4);ret.append(len.toStdString().c_str(), 4);ret.append(dba);return ret; }bool TextMessage::unserialize(QByteArray ba) {bool ret = (ba.length() >= 8);if( ret ){QString type = QString(ba.mid(0, 4));QString len = QString(ba.mid(4, 4)).trimmed();int l = len.toInt(&ret, 16);ret = ret && (l == (ba.length() - 8));if( ret ){m_type = type;m_data = QString(ba.mid(8));}}return ret; }TxtMsgAssembler.h:
#ifndef TXTMSGASSEMBLER_H #define TXTMSGASSEMBLER_H#include <QObject> #include <QQueue> #include <QSharedPointer> #include "TextMessage.h"class TxtMsgAssembler : public QObject {QQueue<char> m_queue;QString m_type;int m_length;QByteArray m_data;void clear();QByteArray fetch(int n);bool makeTypeAndLength();TextMessage* makeMessage(); public:TxtMsgAssembler(QObject* parent = NULL);void prepare(const char* data, int len);QSharedPointer<TextMessage> assemble(const char* data, int len);QSharedPointer<TextMessage> assemble();void reset(); };#endif // TXTMSGASSEMBLER_HTxtMsgAssembler.cpp:
#include "TxtMsgAssembler.h"TxtMsgAssembler::TxtMsgAssembler(QObject* parent) : QObject(parent) {}void TxtMsgAssembler::clear() {m_type = "";m_length = 0;m_data.clear(); }QByteArray TxtMsgAssembler::fetch(int n) {QByteArray ret;for(int i=0; i<n; i++){ret.append(m_queue.dequeue());}return ret; }void TxtMsgAssembler::prepare(const char* data, int len) {if( data != NULL ){for(int i=0; i<len; i++){m_queue.enqueue(data[i]);}} }QSharedPointer<TextMessage> TxtMsgAssembler::assemble() {TextMessage* ret = NULL;bool tryMakeMsg = false;if( m_type == "" ){tryMakeMsg = makeTypeAndLength();}else{tryMakeMsg = true;}if( tryMakeMsg ){ret = makeMessage();}if( ret != NULL ){clear();}return QSharedPointer<TextMessage>(ret); }QSharedPointer<TextMessage> TxtMsgAssembler::assemble(const char* data, int len) {prepare(data, len);return assemble(); }bool TxtMsgAssembler::makeTypeAndLength() {bool ret = (m_queue.length() >= 8);if( ret ){QString len = "";m_type = QString(fetch(4));len = QString(fetch(4));m_length = len.trimmed().toInt(&ret, 16);if( !ret ){clear();}}return ret; }TextMessage* TxtMsgAssembler::makeMessage() {TextMessage* ret = NULL;if( m_type != "" ){int needed = m_length - m_data.length();int n = (needed <= m_queue.length()) ? needed : m_queue.length();m_data.append(fetch(n));if( m_length == m_data.length() ){ret = new TextMessage(m_type, QString(m_data));}}return ret; }void TxtMsgAssembler::reset() {clear();m_queue.clear(); }ClientDemo.h:
#ifndef CLIENTDEMO_H #define CLIENTDEMO_H#include <QObject> #include <QTcpSocket> #include "TextMessage.h" #include "TxtMsgAssembler.h" #include "TxtMsgHandler.h"class ClientDemo : public QObject {Q_OBJECTQTcpSocket m_client;TxtMsgAssembler m_assembler;TxtMsgHandler* m_handler; protected slots:void onConnected();void onDisconnected();void onDataReady();void onBytesWritten(qint64 bytes);public:ClientDemo(QObject* parent = NULL);bool connectTo(QString ip, int port);qint64 send(TextMessage& message);qint64 available();void setHandler(TxtMsgHandler* handler);void close(); };#endif // CLIENTDEMO_HClientDemo.cpp:
#include "ClientDemo.h" #include <QHostAddress> #include <QDebug>ClientDemo::ClientDemo(QObject* parent) : QObject(parent), m_handler(NULL) {connect(&m_client, SIGNAL(connected()), this, SLOT(onConnected()));connect(&m_client, SIGNAL(disconnected()), this, SLOT(onDisconnected()));connect(&m_client, SIGNAL(readyRead()), this, SLOT(onDataReady()));connect(&m_client, SIGNAL(bytesWritten(qint64)), this, SLOT(onBytesWritten(qint64))); }void ClientDemo::onConnected() {}void ClientDemo::onDisconnected() {m_assembler.reset(); }void ClientDemo::onDataReady() {char buf[256] = {0};int len = 0;while( (len = m_client.read(buf, sizeof(buf))) > 0 ){QSharedPointer<TextMessage> ptm = m_assembler.assemble(buf, len);if( (ptm != NULL) && (m_handler != NULL) ){m_handler->handle(m_client, *ptm);}} }void ClientDemo::onBytesWritten(qint64 bytes) {(void)bytes; }bool ClientDemo::connectTo(QString ip, int port) {m_client.connectToHost(ip, port);return m_client.waitForConnected(); }qint64 ClientDemo::send(TextMessage& message) {QByteArray ba = message.serialize();return m_client.write(ba.data(), ba.length()); }qint64 ClientDemo::available() {return m_client.bytesAvailable(); }void ClientDemo::close() {m_client.close(); }void ClientDemo::setHandler(TxtMsgHandler* handler) {m_handler = handler; }ServerDemo.h:
#ifndef SERVERDEMO_H #define SERVERDEMO_H#include <QObject> #include <QTcpServer> #include <QMap> #include "TextMessage.h" #include "TxtMsgAssembler.h" #include "TxtMsgHandler.h"class ServerDemo : public QObject {Q_OBJECTQTcpServer m_server;QMap<QTcpSocket*, TxtMsgAssembler*> m_map;TxtMsgHandler* m_handler; public:ServerDemo(QObject* parent = NULL);bool start(int port);void stop();void setHandler(TxtMsgHandler* handler);~ServerDemo();protected slots:void onNewConnection();void onConnected();void onDisconnected();void onDataReady();void onBytesWritten(qint64 bytes); };#endif // SERVERDEMO_HServerDemo.cpp:
#include "ServerDemo.h" #include <QHostAddress> #include <QTcpSocket> #include <QObjectList> #include <QDebug>ServerDemo::ServerDemo(QObject* parent) : QObject(parent), m_handler(NULL) {connect(&m_server, SIGNAL(newConnection()), this, SLOT(onNewConnection())); }void ServerDemo::onNewConnection() {QTcpSocket* tcp = m_server.nextPendingConnection();TxtMsgAssembler* assembler = new TxtMsgAssembler();m_map.insert(tcp, assembler);connect(tcp, SIGNAL(connected()), this, SLOT(onConnected()));connect(tcp, SIGNAL(disconnected()), this, SLOT(onDisconnected()));connect(tcp, SIGNAL(readyRead()), this, SLOT(onDataReady()));connect(tcp, SIGNAL(bytesWritten(qint64)), this, SLOT(onBytesWritten(qint64))); }void ServerDemo::onConnected() {}void ServerDemo::onDisconnected() {QTcpSocket* tcp = dynamic_cast<QTcpSocket*>(sender());if( tcp != NULL ){delete m_map.take(tcp);} }void ServerDemo::onDataReady() {QTcpSocket* tcp = dynamic_cast<QTcpSocket*>(sender());char buf[256] = {0};int len = 0;if( tcp != NULL ){TxtMsgAssembler* assembler = m_map.value(tcp);while( (len = tcp->read(buf, sizeof(buf))) > 0 ){QSharedPointer<TextMessage> ptm = (assembler != NULL) ? assembler->assemble(buf, len) : NULL;if( (ptm != NULL) && (m_handler != NULL) ){m_handler->handle(*tcp, *ptm);}}} }void ServerDemo::onBytesWritten(qint64 bytes) {(void)bytes; }bool ServerDemo::start(int port) {bool ret = true;if( !m_server.isListening() ){ret = m_server.listen(QHostAddress("127.0.0.1"), port);}return ret; }void ServerDemo::stop() {if( m_server.isListening() ){m_server.close();} }ServerDemo::~ServerDemo() {const QObjectList& list = m_server.children();for(int i=0; i<list.length(); i++){QTcpSocket* tcp = dynamic_cast<QTcpSocket*>(list[i]);if( tcp != NULL ){tcp->close();}}const QList<TxtMsgAssembler*>& al = m_map.values();for(int i=0; i<al.length(); i++){delete al.at(i);} }void ServerDemo::setHandler(TxtMsgHandler* handler) {m_handler = handler; }main.cpp:
#include <QCoreApplication> #include <QDebug> #include "TextMessage.h" #include "TxtMsgAssembler.h" #include "TxtMsgHandler.h" #include "ClientDemo.h" #include "ServerDemo.h"class Handler : public TxtMsgHandler { public:void handle(QTcpSocket& socket, TextMessage& message){qDebug() << &socket;qDebug() << message.type();qDebug() << message.length();qDebug() << message.data();} };int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);TextMessage message("Demo", "Delphi Tang 狄泰軟件學院!");Handler handler;ServerDemo server;ClientDemo client;server.setHandler(&handler);server.start(8890);client.setHandler(&handler);client.connectTo("127.0.0.1", 8890);client.send(message);return a.exec(); }參考資料:
總結
以上是生活随笔為你收集整理的Qt基于文本协议的网络应用开发的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华谊兄弟被强制执行3亿 到底发生了什么
- 下一篇: 实现磁盘操作接口