柚子快報邀請碼778899分享:Qt——TCP UDP網(wǎng)絡編程
柚子快報邀請碼778899分享:Qt——TCP UDP網(wǎng)絡編程
目錄
前言正文一、TCP二、UDP1、基本流程2、必備知識
三、代碼層級1、UDP服務端
END、總結的知識與問題1、如何獲取QByteArray中某一字節(jié)的數(shù)據(jù),并將其轉為十進制?2、如何以本年本月本日為基礎,獲取時間戳,而不以1970為基礎?3、如何將一個四個字節(jié)組成的數(shù)拆分成1個字節(jié)一個字節(jié)的?4、如何對前面的所有字節(jié)進行異或校驗?5、如何將QByteArray中的某個字節(jié)轉為十六進制?
參考
前言
恰好,有個項目需要用到UDP,之前使用比較多的是TCP,UDP的還是第一次搞,但是感覺流程也是差不多,甚至比TCP要更簡單就行,這里就稍微做一下總結,寫一下自己需要注意的點,以及從網(wǎng)上查找到的,比較有用的一些資料。本篇文章,主要偏向的還是UDP,TCP的話,大家可以稍微看下,當做過下眼。
正文
一、TCP
TCP(Transmission Control Protocol,傳輸控制協(xié)議)是一種面向連接的、可靠的傳輸協(xié)議,它是OSI(Open System Interconnection,開放式系統(tǒng)互聯(lián))模型中的第四層協(xié)議,通常使用于網(wǎng)絡中的應用層和傳輸層之間。
? TCP建立連接主要就是三次握手、斷開連接主要就是四次揮手。
三次握手的基本流程如下: 四次揮手的基本流程如下:
關于TCP Socket 的實現(xiàn)邏輯如下:
本篇文章關于TCP就點到為止了。下面就講下UDP了。
二、UDP
1、基本流程
UDP(User Data Protocol),用戶數(shù)據(jù)報協(xié)議,是一種簡單輕量級、不可靠、面向數(shù)據(jù)報、無連接的傳輸層協(xié)議,可以應用在可靠性不是十分重要的場合,如短消息、廣播信息等。
適用于以下幾種情況:
A、網(wǎng)絡數(shù)據(jù)大多為短消息。
B、擁有大量客戶端
C、對數(shù)據(jù)安全性無特殊要求
D、網(wǎng)絡負擔非常重,但對響應速度要求高。
這部分內容,應該有一部分難點在于UDP的拆包上面,但由于目前解除的項目不需要拆包,就暫不考慮這個問題了。
UDP編程的流程:
2、必備知識
1、數(shù)據(jù)報的長度一般不少于512字節(jié),每個數(shù)據(jù)報包含發(fā)送者和接收者的IP地址和端口等信息。 2、單播、廣播、組播的知識
單播(unicast)模式:一個UDP客戶端發(fā)出的數(shù)據(jù)報只發(fā)送到另一個指定地址和端口的UDP客戶端,是一對一的數(shù)據(jù)傳輸。 廣播(broadcast)模式:一個UDP客戶端發(fā)出的數(shù)據(jù)報,在同一網(wǎng)絡范圍內其他所有的UDP客戶端都可以收到。QUdpSocket支持IPv4廣播。廣播經(jīng)常用于實現(xiàn)網(wǎng)絡發(fā)現(xiàn)的協(xié)議。要獲取廣播數(shù)據(jù)只需在數(shù)據(jù)報中指定接收端地址為QHostAddress::Broadcast,一般的廣播地址為255.255.255.255。 組播(multicast)模式:也稱為多播。UDP客戶端加入到另一組播IP地址指定的多播組,成員向多組播地址發(fā)送的數(shù)據(jù)報組內成員都可以接收到,類似于QQ群的功能。
單播(Unicast):單播是一種點對點的通信方式,其中一個發(fā)送方(源)向一個接收方(目標)發(fā)送數(shù)據(jù)。在單播通信中,數(shù)據(jù)從發(fā)送方經(jīng)過網(wǎng)絡傳輸?shù)街付ǖ慕邮辗?,其他設備不會接收到該數(shù)據(jù)。單播適用于需要將數(shù)據(jù)傳輸?shù)教囟ㄔO備或主機的場景,例如客戶端-服務器通信。 廣播(Broadcast):廣播是一種一對多的通信方式,其中一個發(fā)送方向局域網(wǎng)中的所有設備發(fā)送數(shù)據(jù)。在廣播通信中,數(shù)據(jù)從發(fā)送方通過網(wǎng)絡傳輸?shù)酵痪钟蚓W(wǎng)中的所有設備。所有接收方都會接收到廣播數(shù)據(jù)。廣播適用于需要將數(shù)據(jù)傳輸?shù)骄钟蚓W(wǎng)中的所有設備的場景,例如局域網(wǎng)上的服務發(fā)現(xiàn)、網(wǎng)絡廣告等。 廣播UDP與單播UDP的區(qū)別就是IP地址不同,廣播使用廣播地址255.255.255.255,將消息發(fā)送到在同一廣播網(wǎng)絡上的每個主機。值得強調的是:本地廣播信息是不會被路由器轉發(fā)。當然這是十分容易理解的,因為如果路由器轉發(fā)了廣播信息,那么勢必會引起網(wǎng)絡癱瘓。 其實廣播顧名思義,就是想局域網(wǎng)內所有的人說話,但是廣播還是要指明接收者的端口號的,因為不可能接受者的所有端口都來收聽廣播。 3、報文大小的限制與各系統(tǒng)的協(xié)議實現(xiàn)有關,但不得超過其下層 IP 協(xié)議規(guī)定的64KB 4、所謂客戶端進行廣播,是指客戶端向一個廣播地址255.255.255.255 + 一個指定的服務端監(jiān)聽的端口號 進行發(fā)送數(shù)據(jù)。只要端口號是對的,那么服務端就會接收到數(shù)據(jù)。 5、如何監(jiān)聽某個端口:netstat -ano | findstr 1024 6、
三、代碼層級
1、UDP服務端
該代碼是我實現(xiàn)的UDP服務端,只負責接收信息。 UDPServer.h
#ifndef UDPSERVER_H
#define UDPSERVER_H
#include
#include
class CUdpServer : public QObject
{
Q_OBJECT
public:
explicit CUdpServer(QObject *parent = nullptr);
~CUdpServer();
bool OpenUdpServer(const int &_iPort);
protected:
bool _WriteDatagram(const QString &_sIp, const int &_iPort, char *_pData, int _iLength);
bool _ResponseFrame(const QString &_sIp, const int &_iPort, const QString &_sCmd, const QByteArray &_oArray);
bool _HandleLoginFrame(const QString &_sIp, const int &_iPort, const QByteArray &_oArray);
private:
void _Init();
uint8_t _XorCheck(uint8_t *pbuf, uint32_t length);
qint64 _GetNowDateTimeStampMs();
public slots:
void on_readyRead();
private:
QUdpSocket* m_pUdpServer = nullptr;
bool m_bOpen = false;
bool m_bDoing = false;
int m_iElectricQuantity = 0;
};
#endif // UDPSERVER_H
UDPServer.cpp
#include "UdpServer.h"
#include
#include
#include
#include
CUdpServer::CUdpServer(QObject *parent):
QObject(parent)
{
_Init();
connect(m_pUdpServer, &QUdpSocket::readyRead, this, &CUdpServer::on_readyRead);
}
CUdpServer::~CUdpServer()
{
}
bool CUdpServer::OpenUdpServer(const int &_iPort)
{
bool bRet = false;
if (false == m_bOpen)
{
bRet = m_pUdpServer->bind(QHostAddress::Any, _iPort);
if (bRet)
{
m_bOpen = true;
}
}
return bRet;
}
bool CUdpServer::_WriteDatagram(const QString &_sIp, const int &_iPort, char *_pData, int _iLength)
{
bool bRet = false;
QByteArray oOutputArray = QByteArray::fromRawData((char*)_pData, _iLength);
qint64 iLen = m_pUdpServer->writeDatagram(oOutputArray, QHostAddress(_sIp), _iPort);
return bRet;
}
bool CUdpServer::_ResponseFrame(const QString &_sIp, const int &_iPort, const QString &_sCmd, const QByteArray &_oArray)
{
bool bRet = false;
int iElectricQuantity = (int)_oArray.at(3);
uchar cArray[16]= {0};
cArray[0] = 0xAF;//幀頭
cArray[1] = 0x00;//設備類型
cArray[2] = 0x00;//設備編號
cArray[3] = 0x64;//電池電量
qint64 iTimeStamp = _GetNowDateTimeStampMs();
quint8 byte1 = (iTimeStamp >> 24) & 0xFF;
quint8 byte2 = (iTimeStamp >> 16) & 0xFF;
quint8 byte3 = (iTimeStamp >> 8) & 0xFF;
quint8 byte4 = iTimeStamp & 0xFF;
LOG_INFO << "--->z CUdpServer::_HandleHeartFrame byte1:"<< iTimeStamp<<"||"< cArray[4] = (int)byte1;//時間戳 cArray[5] = (int)byte2;//時間戳 cArray[6] = (int)byte3;//時間戳 cArray[7] = (int)byte4;//時間戳 cArray[8] = _oArray.at(8);//唯一ID cArray[9] = _oArray.at(9);//唯一ID cArray[10] = _oArray.at(10);//唯一ID cArray[11] = _oArray.at(11);//唯一ID bool bOK = false; cArray[12] = _sCmd.toInt(&bOK, 16);//命令類型 cArray[13] = 0x00;//包序列 cArray[14] = 0x00;//數(shù)據(jù)長度 cArray[15] = _XorCheck(cArray, sizeof(cArray) - 1);//校驗字節(jié) bRet = _WriteDatagram(_sIp, _iPort, (char*)cArray, sizeof(cArray)); return bRet; } bool CUdpServer::_HandleLoginFrame(const QString &_sIp, const int &_iPort, const QByteArray &_oArray) { bool bRet = false; if (_oArray.length() < 16) { return bRet; } if (m_bDoing) { return false; } m_bDoing = true; bRet = _ResponseFrame(_sIp, _iPort, "01", _oArray); m_bDoing = false; return bRet; } void CUdpServer::_Init() { m_pUdpServer = new QUdpSocket(this); } uint8_t CUdpServer::_XorCheck(uint8_t *pbuf, uint32_t length) { uint8_t temp = 0; uint32_t i; pbuf++; pbuf++; for (i = 0; i < length - 2; i++) { temp ^= *pbuf++; } return temp; } qint64 CUdpServer::_GetNowDateTimeStampMs() { // 獲取當前日期和時間 QDateTime currentDateTime = QDateTime::currentDateTime(); // 獲取當前年份 int iCurrentYear = currentDateTime.date().year(); int iCurrentMonth = currentDateTime.date().month(); int iCurrentDay = currentDateTime.date().day(); // 創(chuàng)建基準日期(以當前年份為基準) QDateTime baseDateTime(QDate(iCurrentYear, iCurrentMonth, iCurrentDay), QTime(0, 0, 0)); // 計算當前時間相對于基準日期的毫秒數(shù) qint64 timestamp = currentDateTime.toMSecsSinceEpoch() - baseDateTime.toMSecsSinceEpoch(); return timestamp; } void CUdpServer::on_readyRead() { //幀頭(af) 設備類型 設備編號 電池電量 時間戳 唯一ID 命令類型 包序列 數(shù)據(jù)長度 校驗字節(jié) 數(shù)據(jù)校驗 數(shù)據(jù) // 1 1 1 1 4 4 1 1 1 1 1 N while (m_pUdpServer->hasPendingDatagrams()) // 判斷是否有可讀數(shù)據(jù) { QNetworkDatagram datagram = m_pUdpServer->receiveDatagram(); // 讀取數(shù)據(jù) QByteArray replyData = datagram.data(); if(replyData.count()) { QString sIP = datagram.senderAddress().toString().remove("::ffff:"); int iPort = datagram.senderPort(); QString sCmd = QString("%1").arg((char)replyData.at(12), 2, 16, QChar('0')); for (int i = 0; i< replyData.size(); i++) { char byte = replyData.at(i); QString sByte = QString::number((int)byte, 16); QString hexString = QString("%1").arg((int)byte, 2, 16, QChar('0')); } if (sCmd == "01") { _HandleLoginFrame(sIP, iPort, replyData); } } } } 調用的時候,就直接new一個這個UDPServer的對象就可以了。 如果大家需要直接可以運行起來的程序,大家可以參考這位兄弟的,這位兄弟的就寫的比較完整了,客戶端,服務端都有。 https://github.com/mahuifa/QMDemo/tree/master/QMNetwork END、總結的知識與問題 1、如何獲取QByteArray中某一字節(jié)的數(shù)據(jù),并將其轉為十進制? int iElectricQuantity = (int)_oArray.at(3); 2、如何以本年本月本日為基礎,獲取時間戳,而不以1970為基礎? qint64 CUdpServer::_GetNowDateTimeStampMs() { // 獲取當前日期和時間 QDateTime currentDateTime = QDateTime::currentDateTime(); // 獲取當前年份 int iCurrentYear = currentDateTime.date().year(); int iCurrentMonth = currentDateTime.date().month(); int iCurrentDay = currentDateTime.date().day(); // 創(chuàng)建基準日期(以當前年份為基準) QDateTime baseDateTime(QDate(iCurrentYear, iCurrentMonth, iCurrentDay), QTime(0, 0, 0)); // 計算當前時間相對于基準日期的毫秒數(shù) qint64 timestamp = currentDateTime.toMSecsSinceEpoch() - baseDateTime.toMSecsSinceEpoch(); return timestamp; } 3、如何將一個四個字節(jié)組成的數(shù)拆分成1個字節(jié)一個字節(jié)的? qint64 iTimeStamp = _GetNowDateTimeStampMs();//這個實際獲取到的是4個字節(jié),但也沒關系,我們根據(jù)下面的方式,取到的一定是最前面的四個字節(jié) quint8 byte1 = (iTimeStamp >> 24) & 0xFF; quint8 byte2 = (iTimeStamp >> 16) & 0xFF; quint8 byte3 = (iTimeStamp >> 8) & 0xFF; quint8 byte4 = iTimeStamp & 0xFF; cArray[4] = (int)byte1;//時間戳 cArray[5] = (int)byte2;//時間戳 cArray[6] = (int)byte3;//時間戳 cArray[7] = (int)byte4;//時間戳 4、如何對前面的所有字節(jié)進行異或校驗? uint8_t CUdpServer::_XorCheck(uint8_t *pbuf, uint32_t length) { uint8_t temp = 0; uint32_t i; pbuf++; pbuf++; for (i = 0; i < length - 2; i++) { temp ^= *pbuf++; } return temp; } 5、如何將QByteArray中的某個字節(jié)轉為十六進制? char byte = replyData.at(i);//replayData就是一個QByteArray QString hexString = QString("%1").arg((int)byte, 2, 16, QChar('0')); 參考 參考: 1、UDP廣播與多播 2、Qt 網(wǎng)絡編程-UDP 3、Qt中實現(xiàn)UDP的分包和組包——參考“草上爬”的博客 柚子快報邀請碼778899分享:Qt——TCP UDP網(wǎng)絡編程 好文推薦
本文內容根據(jù)網(wǎng)絡資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉載請注明,如有侵權,聯(lián)系刪除。