最近有個需求,需要將Qt/C++客戶端的一些數(shù)據(jù)傳輸至Node.js服務(wù)端,有些需要一次性發(fā)送锨天,另外一些由于更新頻率高预厌,需要建立Socket長鏈接。Google了一下,發(fā)現(xiàn)這方面的資料少得可憐,而自己在摸索的過程中也走了不少彎路,于是把這幾天學(xué)到的東西整理了一下站蝠,以Demo的形式把HTTP POST和TCP Socket通信過程加以實現(xiàn)。
技術(shù)選型
客戶端向服務(wù)端一次性發(fā)送一些數(shù)據(jù)卓鹿,最直接的就是HTTP請求的POST方法了菱魔,而如果需要建立Socket長鏈接,則有很多選擇吟孙。我首先嘗試的是WebSocket
澜倦,Node.js有一個很給力的ws
模塊,而Qt雖然有WebSockets
這個C++類杰妓,但實際應(yīng)用的話是要先斟酌一下的藻治。這一塊本來是個第三方的模塊,后來被Qt給采納了巷挥,所以有版本兼容性和穩(wěn)定性的問題桩卵。還有Socket.IO
,是一個面向?qū)崟rweb應(yīng)用的庫倍宾,主要使用WebSocket協(xié)議雏节,最大的特點就是兼容性好,不支持WebSocket的時候自動回退到其他方法凿宾,但應(yīng)用的話服務(wù)端和客戶端都需要使用該框架矾屯,而且它對C++的支持并不是“原生”的兼蕊。最后一個就是TCP Socket
了初厚,Node.js有用于底層網(wǎng)絡(luò)通信的內(nèi)置模塊Net
提供原生支持,Qt也通過QTcpSocket
對其實現(xiàn)了很好的支持孙技。綜上产禾,采用TCP Socket
通信成了首選。
Demo
Qt/C++客戶端
在Qt Creator中新建一個項目牵啦,在頭文件中引用需要的系統(tǒng)頭文件:
#include <QBuffer>
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
#include <QUrl>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QTcpSocket>
添加成員變量:
private:
QNetworkAccessManager* networkManager;
QTcpSocket *textTcpSocket;
QTcpSocket *imageTcpSocket;
QByteArray imageData;
添加槽函數(shù):
private slots:
void receiveImageTcpSocketMessage();
在源文件的構(gòu)造函數(shù)中亚情,添加如下代碼:
// 初始化
networkManager = new QNetworkAccessManager();
textTcpSocket = new QTcpSocket();
imageTcpSocket = new QTcpSocket();
connect(imageTcpSocket, SIGNAL(readyRead()), this, SLOT(receiveImageTcpSocketMessage()));
// 通過HTTP請求的POST方法發(fā)送一個對象
QJsonObject jsonObjPost;
jsonObjPost.insert("method", "post");
jsonObjPost.insert("message", "biebu.xin");
QByteArray postData = QJsonDocument(jsonObjPost).toJson(QJsonDocument::Compact);
QUrl postUrl("http://127.0.0.1:8080");
QNetworkRequest postRequest(postUrl);
postRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json; charset=utf-8");
networkManager->post(postRequest, postData);
// 通過TCP Socket連接服務(wù)端并發(fā)送一個對象
textTcpSocket->abort();
textTcpSocket->connectToHost("127.0.0.1", 8081);
QJsonObject jsonObjSocket;
jsonObjSocket.insert("method", "socket");
jsonObjSocket.insert("message", "biebu.xin");
QByteArray textData = QJsonDocument(jsonObjSocket).toJson(QJsonDocument::Compact);
textTcpSocket->write(textData);
// 通過TCP Socket連接服務(wù)端并上傳一張圖片
imageTcpSocket->abort();
imageTcpSocket->connectToHost("127.0.0.1", 8082);
QImage image;
image.load("demo.jpg", "JPG");
QBuffer buffer(&imageData);
image.save(&buffer, "JPG");
imageTcpSocket->write(QByteArray::number(imageData.size()));
定義receiveImageTcpSocketMessage()
函數(shù):
QByteArray message = imageTcpSocket->readAll();
if (message.toInt()) {
imageTcpSocket->write(imageData);
imageData.resize(0);
}
發(fā)送圖片的時候,如果直接把圖片數(shù)據(jù)發(fā)過去哈雏,有可能會出現(xiàn)需要分多次傳輸導(dǎo)致服務(wù)端單次接收數(shù)據(jù)不完整的情況楞件。所以衫生,一種穩(wěn)妥的做法是,先把圖片大小發(fā)過去土浸,然后再發(fā)送圖片數(shù)據(jù)罪针。
Node.js服務(wù)端
新建一個js文件,加載內(nèi)置模塊:
const http = require('http');
const net = require('net');
const fs = require('fs');
創(chuàng)建HTTP服務(wù)器:
http.createServer((req, res) => {
let source = "";
req.on('data', function(data) {
source += data;
});
req.on('end', function() {
console.log(`Post data: ${source.toString()}`);
});
}).listen(8080, '127.0.0.1', () => {
console.log('Http server listening on port 8080 at host 127.0.0.1');
});
創(chuàng)建用于接收文本的TCP Socket服務(wù)器:
net.createServer((socket) => {
console.log('textClient connected');
socket.on('data', (data) => {
console.log(`Socket textData: ${data.toString()}`);
});
socket.on('end', () => {
console.log('textClient disconnected');
});
}).listen(8081, () => {
console.log('Socket server for text started on port 8081');
});
創(chuàng)建用于接收圖片的TCP Socket服務(wù)器:
net.createServer((socket) => {
console.log('imageClient connected');
let imageSize = 0;
let imageBufferArray = [];
socket.on('data', (data) => {
if (imageSize) {
// 接收和拼接數(shù)據(jù)黄伊,當數(shù)據(jù)長度不夠時泪酱,下一次繼續(xù)
imageBufferArray.push(data);
let imageData = Buffer.concat(imageBufferArray);
if (imageData.byteLength >= imageSize) {
// 數(shù)據(jù)完整,寫入文件
fs.writeFile(`demo.jpg`, imageData, (err) => {
console.log('Save socket image success');
imageSize = 0;
imageBufferArray = [];
});
}
} else {
imageSize = parseInt(data.toString());
socket.write('1');
}
});
socket.on('end', () => {
console.log('imageClient disconnected');
});
}).listen(8082, () => {
console.log('Socket server for image started on port 8082');
});
效果
服務(wù)端先啟動还最,客戶端運行后墓阀,服務(wù)端的終端中會打印運行結(jié)果:
Socket server for text started on port 8081
Socket server for image started on port 8082
Http server listening on port 8080 at host 127.0.0.1
textClient connected
imageClient connected
Socket textData: {"message":"biebu.xin","method":"socket"}
Post data: {"message":"biebu.xin","method":"post"}
Save socket image success
可以看出,Qt客戶端發(fā)送的數(shù)據(jù)這里都是采用QByteArray類型的拓轻,而Node.js接收的時候斯撮,不管是http
模塊,還是net
模塊扶叉,其data事件的參數(shù)都是一個Buffer
吮成,fs
寫入文件的圖片數(shù)據(jù),也是Buffer
對象辜梳。注意到這點粱甫,就可以根據(jù)實際需求靈活編程了。
需要強調(diào)的是作瞄,這是個最小Demo茶宵,錯誤處理的部分都省略了,而在實際編程的過程中一定要考慮周全哦~
個人技術(shù)博客 biebu.xin宗挥,原文鏈接——Node.js服務(wù)端和Qt C++客戶端之HTTP POST和TCP Socket通信