同时处理多个 Qtcp 连接以显示在大理石小部件上
Handle multiple Qtcp connections concurrently to show on marble widget
我有一个带有 qt creator 的应用程序,它获取地理坐标数据并将其显示在 map.it 上,只需要一个连接就可以正常工作,但是当涉及到不止一次连接时,这就成了一个谜。
我想从多个具有不同地址和端口的 qtcp 连接循环获取数据,然后将数据放在单独的列表中,在读取每个连接地理数据的列表后,在不同线程的地图上添加单独的图层,每 3 秒更新一次,而所有这些从网络数据包中并发获取数据。假设我有许多不同的 gps 接收器来收集它们的位置数据,我想将其集成到地图上。
这是我的代码示例:
1.define qtcp 客户端应连接的服务器列表:
globals.cpp
#define PACKET 50
struct Connects{
static QMap<QString,int> list()
{
QMap<QString,int> m;
m["sender1.com"] = 4456;
m["sender2.com"] = 4457;
m["sender3.com"] = 4458;
return m;
}
static const QMap<QString,int> myMap;
};
QMap<QString, int> const Connects::myMap = list();
2.main window 启动地图:
main.cpp
void add_layer(MarbleWidget *mapWidget,MainWindow *window,Client *client,QTimer *timer ){
//get data and append to a list every 3 sec
timer = new QTimer;
QObject::connect(timer,SIGNAL(timeout()),window,SLOT(next_client()));
QObject::connect(client,SIGNAL(Client_Connected()),window,SLOT(online_slot()));
timer->start(3000);
// Add New Layer based on positions in the list
MPaintLayer* layer = new MPaintLayer(mapWidget);
for(int j = 0; j < client->data.count(); ++j){
layer->setPosition(client->data.at(j).Lat,client->data.at(j).Lon);
}
mapWidget->addLayer(layer);
}
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
// Load Marble
MarbleWidget *mapWidget = new MarbleWidget;
mapWidget->show();
//iterate over connections address and port list
QMapIterator<QString,int> i(Connects::myMap);
while (i.hasNext()) {
i.next();
client = new Client;
client->ConnectToServer(i.key(),i.value());
//make thread pool and create new thread for adding each layer
QThreadPool pool;
QFuture<void> tr;
tr = QtConcurrent::run(&pool,add_layer,mapWidget,this,client,timer);
}
}
void MainWindow::online_slot()
{
//fetch data from the buffer and separate each entity based on size
while (client->GetLanBufferSize() >= PACKET) {
client->ReadData((char*)buffer,PACKET);
double lat,lon;
memcpy(&lat,&buffer[8],sizeof(double));
memcpy(&lon,&buffer[16],sizeof(double));
//put buffer data to target list which is defined in client
Target trg;
client->data.append(trg);
}
//remove has-readed data
for (int var = 0; var < client->data.count(); ++var) {
client->data.removeAt(var);
var--;
}
}
3.declare class 用于根据收到的地图数据添加或更新新图层:
paintlayer.cpp
MPaintLayer::MPaintLayer(MarbleWidget* widget)
{
S_N = QPixmap(":/mode-s.png");
}
bool MPaintLayer::render( GeoPainter *painter,ViewportParams *viewport,
const QString& renderPos, GeoSceneLayer * layer )
{
//throw a tiny png file on map based on retrieved locations
for (int var = 0; var < pos.count(); ++var) {
painter->drawPixmap(pos[var],S_N));
}
return true;
}
void MPaintLayer::setPosition(double lat,double lon)
{
pos.append(GeoDataCoordinates(lat,lon,0,GeoDataCoordinates::Degree));
}
5.define一个class接收数据的格式:
client.h
class Target
{
public:
Target()
{
Lat = Lon = 0;
}
double Lat,Lon;
GeoDataCoordinates Position;
void update(double lat,double lon)
{
Lat = lat;
Lon = lon;
Position = GeoDataCoordinates(Lon,Lat,0,GeoDataCoordinates::Degree);
}
};
6.declare class 用于建立新的 qtcp 连接和接收数据:
client.cpp
Client::Client(QObject *parent) : QObject(parent)
{
socket = new QTcpSocket(this);
connect(socket, SIGNAL(connected()),this, SLOT(on_connected()));
}
void Client::on_connected()
{
connect(socket, SIGNAL(disconnected()),this, SLOT(on_Disconnected()));
socket->setReadBufferSize(12e6);
emit Client_Connected();
}
bool Client::ReadData(char *buffer, int Leanth)
{
socket->read(buffer , Leanth);
return true;
}
int Client::GetLanBufferSize()
{
return socket->bytesAvailable();
}
void Client::on_Disconnected()
{
emit Client_DisConnected();
}
void Client::ConnectToServer(QString Address , int Port)
{
socket->connectToHost(Address, Port);
server = Address;
server_Port = Port;
}
从发送方接收到的数据格式是移动物体的位置,如下所示:
35.51243 51.22478
35.51260 51.22667
35.69270 51.03961
最佳实践解决方案是什么?因为它在地图上什么也没有显示。当我使用硬代码时,它显示了与每个连接等效的层,但读取数据流具有挑战性。
任何线索将不胜感激。
您没有以正确的方式使用 QTcpSocket。
在您的 Client
class 中,您可以处理所有阅读内容,甚至不需要那些 while 循环。在槽内使用 QIODevice::readyRead() signal and QIODevice::read(char *data, qint64 maxSize) 方法,它应该读取数据并解析它。
这里的问题是不能保证这些数据会在发送方发生对齐时收到。例如,如果您从发送方发送 >>>SOME_LENGTHY_DATA<<<
,QTcpSocket 可能会发出两次 readyRead()
信号,您可以在两次不同的读取槽调用中读取 >>>SOME_LENG
和 THY_DATA<<<
。
因此,当数据逐字节传入时,您应该注意解析数据。
您可以在 class 中的某个地方自由缓冲传入数据,或者只使用 QTcpSocket 内部缓冲区。您真的应该注意分离传入的消息块。
这是 Client
的一个实现。我使用 QPointF
来存储点而不是你的 Target
class 因为我不知道它是什么。您可以自由创建自己的 class 并根据已解析的 lat/lon 将其附加到 Client::data
。 我没有测试代码,但它应该可以工作。
我不确定您是否需要与此实现并发,但恕我直言,您不需要。
//Client.h
class Client : public QTcpSocket
{
Q_OBJECT
public:
explicit Client(QObject *parent = nullptr);
private:
QString serverAddress;
int serverPort;
QList<QPointF> data; //you can use your own type, just for test
QByteArray lastLine;
void parseData();
signals:
void Client_Connected();
public slots:
void connectToServer(const QString& address, quint16 port);
void processData();
};
//Client.cpp
#include "client.h"
Client::Client(QObject *parent) : QTcpSocket (parent)
{
// we don't really want Client_Connected signal!
// you can use QTcpSocket::connected signal
connect(this, SIGNAL(connected()),this, SIGNAL(Client_Connected()));
// readyRead will be emitted when there is data in buffer to be read
connect(this, SIGNAL(readyRead()),this, SLOT(processData()));
}
void Client::parseData()
{
QString str(lastLine);
str = str.trimmed();
QStringList parts = str.split(" ",QString::SkipEmptyParts);
if(parts.count()==2){
bool success = true;
double latitude = success ? parts[0].toDouble(&success) : 0;
double longitude = success ? parts[1].toDouble(&success) : 0;
if(success)
data.append(QPointF(longitude,latitude));
}
}
void Client::connectToServer(const QString& address, quint16 port)
{
data.clear();
lastLine.clear();
serverPort = port;
serverAddress = address;
this->connectToHost(address,port);
}
void Client::processData()
{
QByteArray d = this->readAll();
for(int i = 0 ; i < d.count() ; ++i){
if(d.at(i)=='\r')
{
parseData();
lastLine.clear();
}
else
lastLine.append(d.at(i));
}
}
我有一个带有 qt creator 的应用程序,它获取地理坐标数据并将其显示在 map.it 上,只需要一个连接就可以正常工作,但是当涉及到不止一次连接时,这就成了一个谜。 我想从多个具有不同地址和端口的 qtcp 连接循环获取数据,然后将数据放在单独的列表中,在读取每个连接地理数据的列表后,在不同线程的地图上添加单独的图层,每 3 秒更新一次,而所有这些从网络数据包中并发获取数据。假设我有许多不同的 gps 接收器来收集它们的位置数据,我想将其集成到地图上。 这是我的代码示例:
1.define qtcp 客户端应连接的服务器列表:
globals.cpp
#define PACKET 50
struct Connects{
static QMap<QString,int> list()
{
QMap<QString,int> m;
m["sender1.com"] = 4456;
m["sender2.com"] = 4457;
m["sender3.com"] = 4458;
return m;
}
static const QMap<QString,int> myMap;
};
QMap<QString, int> const Connects::myMap = list();
2.main window 启动地图:
main.cpp
void add_layer(MarbleWidget *mapWidget,MainWindow *window,Client *client,QTimer *timer ){
//get data and append to a list every 3 sec
timer = new QTimer;
QObject::connect(timer,SIGNAL(timeout()),window,SLOT(next_client()));
QObject::connect(client,SIGNAL(Client_Connected()),window,SLOT(online_slot()));
timer->start(3000);
// Add New Layer based on positions in the list
MPaintLayer* layer = new MPaintLayer(mapWidget);
for(int j = 0; j < client->data.count(); ++j){
layer->setPosition(client->data.at(j).Lat,client->data.at(j).Lon);
}
mapWidget->addLayer(layer);
}
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
// Load Marble
MarbleWidget *mapWidget = new MarbleWidget;
mapWidget->show();
//iterate over connections address and port list
QMapIterator<QString,int> i(Connects::myMap);
while (i.hasNext()) {
i.next();
client = new Client;
client->ConnectToServer(i.key(),i.value());
//make thread pool and create new thread for adding each layer
QThreadPool pool;
QFuture<void> tr;
tr = QtConcurrent::run(&pool,add_layer,mapWidget,this,client,timer);
}
}
void MainWindow::online_slot()
{
//fetch data from the buffer and separate each entity based on size
while (client->GetLanBufferSize() >= PACKET) {
client->ReadData((char*)buffer,PACKET);
double lat,lon;
memcpy(&lat,&buffer[8],sizeof(double));
memcpy(&lon,&buffer[16],sizeof(double));
//put buffer data to target list which is defined in client
Target trg;
client->data.append(trg);
}
//remove has-readed data
for (int var = 0; var < client->data.count(); ++var) {
client->data.removeAt(var);
var--;
}
}
3.declare class 用于根据收到的地图数据添加或更新新图层:
paintlayer.cpp
MPaintLayer::MPaintLayer(MarbleWidget* widget)
{
S_N = QPixmap(":/mode-s.png");
}
bool MPaintLayer::render( GeoPainter *painter,ViewportParams *viewport,
const QString& renderPos, GeoSceneLayer * layer )
{
//throw a tiny png file on map based on retrieved locations
for (int var = 0; var < pos.count(); ++var) {
painter->drawPixmap(pos[var],S_N));
}
return true;
}
void MPaintLayer::setPosition(double lat,double lon)
{
pos.append(GeoDataCoordinates(lat,lon,0,GeoDataCoordinates::Degree));
}
5.define一个class接收数据的格式:
client.h
class Target
{
public:
Target()
{
Lat = Lon = 0;
}
double Lat,Lon;
GeoDataCoordinates Position;
void update(double lat,double lon)
{
Lat = lat;
Lon = lon;
Position = GeoDataCoordinates(Lon,Lat,0,GeoDataCoordinates::Degree);
}
};
6.declare class 用于建立新的 qtcp 连接和接收数据:
client.cpp
Client::Client(QObject *parent) : QObject(parent)
{
socket = new QTcpSocket(this);
connect(socket, SIGNAL(connected()),this, SLOT(on_connected()));
}
void Client::on_connected()
{
connect(socket, SIGNAL(disconnected()),this, SLOT(on_Disconnected()));
socket->setReadBufferSize(12e6);
emit Client_Connected();
}
bool Client::ReadData(char *buffer, int Leanth)
{
socket->read(buffer , Leanth);
return true;
}
int Client::GetLanBufferSize()
{
return socket->bytesAvailable();
}
void Client::on_Disconnected()
{
emit Client_DisConnected();
}
void Client::ConnectToServer(QString Address , int Port)
{
socket->connectToHost(Address, Port);
server = Address;
server_Port = Port;
}
从发送方接收到的数据格式是移动物体的位置,如下所示:
35.51243 51.22478
35.51260 51.22667
35.69270 51.03961
最佳实践解决方案是什么?因为它在地图上什么也没有显示。当我使用硬代码时,它显示了与每个连接等效的层,但读取数据流具有挑战性。 任何线索将不胜感激。
您没有以正确的方式使用 QTcpSocket。
在您的 Client
class 中,您可以处理所有阅读内容,甚至不需要那些 while 循环。在槽内使用 QIODevice::readyRead() signal and QIODevice::read(char *data, qint64 maxSize) 方法,它应该读取数据并解析它。
这里的问题是不能保证这些数据会在发送方发生对齐时收到。例如,如果您从发送方发送 >>>SOME_LENGTHY_DATA<<<
,QTcpSocket 可能会发出两次 readyRead()
信号,您可以在两次不同的读取槽调用中读取 >>>SOME_LENG
和 THY_DATA<<<
。
因此,当数据逐字节传入时,您应该注意解析数据。 您可以在 class 中的某个地方自由缓冲传入数据,或者只使用 QTcpSocket 内部缓冲区。您真的应该注意分离传入的消息块。
这是 Client
的一个实现。我使用 QPointF
来存储点而不是你的 Target
class 因为我不知道它是什么。您可以自由创建自己的 class 并根据已解析的 lat/lon 将其附加到 Client::data
。 我没有测试代码,但它应该可以工作。
我不确定您是否需要与此实现并发,但恕我直言,您不需要。
//Client.h
class Client : public QTcpSocket
{
Q_OBJECT
public:
explicit Client(QObject *parent = nullptr);
private:
QString serverAddress;
int serverPort;
QList<QPointF> data; //you can use your own type, just for test
QByteArray lastLine;
void parseData();
signals:
void Client_Connected();
public slots:
void connectToServer(const QString& address, quint16 port);
void processData();
};
//Client.cpp
#include "client.h"
Client::Client(QObject *parent) : QTcpSocket (parent)
{
// we don't really want Client_Connected signal!
// you can use QTcpSocket::connected signal
connect(this, SIGNAL(connected()),this, SIGNAL(Client_Connected()));
// readyRead will be emitted when there is data in buffer to be read
connect(this, SIGNAL(readyRead()),this, SLOT(processData()));
}
void Client::parseData()
{
QString str(lastLine);
str = str.trimmed();
QStringList parts = str.split(" ",QString::SkipEmptyParts);
if(parts.count()==2){
bool success = true;
double latitude = success ? parts[0].toDouble(&success) : 0;
double longitude = success ? parts[1].toDouble(&success) : 0;
if(success)
data.append(QPointF(longitude,latitude));
}
}
void Client::connectToServer(const QString& address, quint16 port)
{
data.clear();
lastLine.clear();
serverPort = port;
serverAddress = address;
this->connectToHost(address,port);
}
void Client::processData()
{
QByteArray d = this->readAll();
for(int i = 0 ; i < d.count() ; ++i){
if(d.at(i)=='\r')
{
parseData();
lastLine.clear();
}
else
lastLine.append(d.at(i));
}
}