今天在工作工作中遇到的问题时网络连接的问题,普通的客户端网络连接用Qt实现很简单,就是新建一个QTcoSocket/QUdpSocket
,然后输入端口号和ip地址,建立三个信号和槽(连接、断开、读取)实现对应的槽函数即可。
但是今天遇到的问题要求是在同一个网络里面有好的服务器,而且每一个服务器发送的UDP的IP地址和端口号相同,唯一识别它们不同的地方在于连接上UDP后发送的数据里面有TCP的地址,每一个服务器发送的tcp地址的不同是区别它们的唯一方式。我需要做的就是在这个网段上搜索到这些服务器发送的不同tcp地址,然后根据地址列出表格,然后让用户自己选择连接的对应控制器。
我经过仔细思考,感觉只能通过不断发送和断开UDP连接,然后将所有搜到的信息列出来,最后让客户选择想要连接的那个服务器。最后我写了一个测试代码,效果不错。关键代码如下:
#ifndef GUI_SEARCHING_H
#define GUI_SEARCHING_H
#include <QDialog>
#include "robot_info.h"
class QTcpSocket;
class QUdpSocket;
namespace Ui {
class gui_searching;
}
class gui_searching : public QDialog
{
Q_OBJECT
public:
explicit gui_searching(QWidget *parent = 0);
~gui_searching();
private slots:
void net_deal_init_search();
void processPendingDatagrams_search();
void udp_robot_state_get(QJsonDocument *doc_p, ROBOT_CONTROL_INFO *info_p);
void on_pushButton_searching_clicked();
void updateListWidget();
void on_listWidget_result_doubleClicked(const QModelIndex &index);
private:
Ui::gui_searching *ui;
//! 定义网络链接
QTcpSocket *tcpSocket = NULL;
QUdpSocket *udpSocket = NULL;
//! 机器人控制器信息
QList <ROBOT_CONTROL_INFO> list_robot_control_info;
bool testIsContainIP(const QString &);
};
#endif // GUI_SEARCHING_H
#include <QTcpSocket>
#include <QUdpSocket>
#include <QMessageBox>
#include <QDebug>
gui_searching::gui_searching(QWidget *parent) :
QDialog(parent),
ui(new Ui::gui_searching)
{
ui->setupUi(this);
}
gui_searching::~gui_searching()
{
delete ui;
}
void gui_searching::net_deal_init_search()
{
if(udpSocket && udpSocket->isOpen())
{
qDebug()<<"断开了UDP连接";
udpSocket->leaveMulticastGroup(QHostAddress(UDP_IP));
udpSocket->close();
udpSocket->deleteLater();
udpSocket = NULL;
}
udpSocket = new QUdpSocket(this);
QHostAddress mcast_addr(UDP_IP);
udpSocket->bind(QHostAddress::AnyIPv4, UDP_PORT, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
//加入组播地址
udpSocket->joinMulticastGroup(mcast_addr);
connect(udpSocket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams_search()));
// udp_frame_tmp.resize(0);
}
//读取UDP反馈数据
void gui_searching::processPendingDatagrams_search()
{
int robot_control_num = -1; //已连接上位机的控制器编号
if (udpSocket->hasPendingDatagrams())
{ //至少一条数据已经准备被读取
QByteArray datagram; //存储收到的数据
datagram.resize(udpSocket->pendingDatagramSize());
QHostAddress sender; //存储发送者的UDP的IP地址
quint16 senderPort; //存储发送者的端口号
udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
QJsonParseError *error = new QJsonParseError;
QJsonDocument doc = QJsonDocument::fromJson(datagram, error);
if (error->error == QJsonParseError::NoError)
{
//解析UDP信息
ROBOT_CONTROL_INFO info;
info.socket = tcpSocket;
info.ip = sender.toString();
info.link = RETURN_UNKNOWN;
udp_robot_state_get(&doc, &info);
if(!testIsContainIP(info.udp_state.ip))
{
list_robot_control_info.append(info);
qDebug()<<"id:" << info.udp_state.id << "ip : " << info.udp_state.ip;
updateListWidget();
}
// udpSocket->leaveMulticastGroup(QHostAddress(UDP_IP));
// udpSocket->close();
// udpSocket->deleteLater();
// udpSocket = NULL;
// net_deal_init_search();
}
else
qDebug() << "<" + sender.toString() << tr("> UDP数据解析错误: ") << error->errorString();
delete error;
}
}
//获取UDP上报信息
void gui_searching::udp_robot_state_get(QJsonDocument *doc_p, ROBOT_CONTROL_INFO *info_p)
{
QJsonObject obj = doc_p->object();
info_p->udp_state.id = obj["id"].toString();
info_p->udp_state.ip = obj["IP"].toString();
info_p->udp_state.robot_state = obj["robot_state"].toInt();
info_p->udp_state.op_state = obj["op_state"].toInt();
info_p->udp_state.en_state = obj["en_state"].toInt();
info_p->udp_state.servo_state = obj["servo_state"].toInt();
info_p->udp_state.motion_state = obj["motion_state"].toInt();
info_p->udp_state.comm_state = obj["comm_state"].toInt();
QJsonArray list = obj["position"].toArray();
for (int i = 0; i < list.count(); i++)
info_p->udp_state.list_position.append(list.at(i).toInt());
list = obj["axisPosition"].toArray();
for (int i = 0; i < list.count(); i++)
info_p->udp_state.list_axisPosition.append(list.at(i).toInt());
info_p->udp_state.speed = obj["speed"].toDouble();
QJsonObject obj_io = obj["IO"].toObject();
list = obj_io["DI"].toArray();
for (int i = 0; i < list.count(); i++)
info_p->udp_state.list_io_di.append(list.at(i).toInt());
list = obj_io["DO"].toArray();
for (int i = 0; i < list.count(); i++)
info_p->udp_state.list_io_do.append(list.at(i).toInt());
QJsonObject obj_stack = obj["stack"].toObject();
info_p->udp_state.stack_project = obj_stack["Project"].toString();
info_p->udp_state.stack_file = obj_stack["File"].toString();
info_p->udp_state.stack_cn = obj_stack["CN"].toString();
info_p->udp_state.stack_func_start_line = obj_stack["FuncStartLine"].toInt();
info_p->udp_state.stack_pc = obj_stack["PC"].toInt();
}
void gui_searching::on_pushButton_searching_clicked()
{
//检查rivo-stdio是否和控制器有链接
for (int i = 0; i < list_robot_control_info.count(); i++)
{
if (list_robot_control_info.at(i).socket &&
list_robot_control_info.at(i).socket->isOpen())
{ //如果当前连接有控制器,提醒客户是否断开连接,因为搜索时必须先断开连接!
QString IP = list_robot_control_info.at(i).ip;
if(QMessageBox::warning(this,tr("警告"),tr("当前正连接着ip是:1%的控制器,搜索前必须断开连接,"
"请是否确定断开连接?").arg(IP),QMessageBox::Yes | QMessageBox::No)
== QMessageBox::Yes)
{
if(udpSocket && udpSocket->isOpen())
{
qDebug()<<"断开了UDP连接";
udpSocket->leaveMulticastGroup(QHostAddress(UDP_IP));
udpSocket->close();
udpSocket->deleteLater();
udpSocket = NULL;
}
list_robot_control_info.at(i).socket->close();
list_robot_control_info.at(i).socket->deleteLater();
list_robot_control_info[i].socket = nullptr;
list_robot_control_info[i].link = RETURN_UNKNOWN;
break;
}
else
return;
}
}
ui->listWidget_result->clear();
list_robot_control_info.clear();
net_deal_init_search();
}
void gui_searching::updateListWidget()
{
int count = ui->listWidget_result->count();
qDebug()<<"当前listWidget里面的数目:"<<count;
if(count+1 == list_robot_control_info.count())
ui->listWidget_result->addItem(list_robot_control_info.at(count).udp_state.ip);
}
bool gui_searching::testIsContainIP(const QString & s)
{
for(int i = 0 ; i != list_robot_control_info.count() ; ++i)
if(list_robot_control_info.at(i).udp_state.ip == s)
return true;
return false;
}
void gui_searching::on_listWidget_result_doubleClicked(const QModelIndex &index)
{
qDebug()<<"进入了双击的槽函数,现在选择的ip是:"
<<index.data().toString();
//TCP初始化
tcpSocket = new QTcpSocket(this);
connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(tcp_readClient()));
connect(tcpSocket, SIGNAL(connected()), this, SLOT(tcp_OnConnected()));
connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(tcp_OnDisconnected()));
//将socket加入到list里面
for(int i = 0 ; i != list_robot_control_info.count() ; ++i)
if(list_robot_control_info.at(i).udp_state.ip == index.data().toString())
{
list_robot_control_info[i].socket = tcpSocket;
break;
}
tcpSocket->connectToHost(QHostAddress(index.data().toString()), TCP_PORT);
this->accept();
}
里面通过一个全局的链表存储每次读取数据的中UDP传输过来的TCPIP并存储,然后当里面没有相同的TCPIP地址时加入进去,然后断开UDP,然后再次连接,这样一直重复,理论上能被所有服务器的ip都提取出来。
附录·:
第二天继续研究UDP连接,今天请教一位高手,他说UDP连接只要加入组播,就不需要断开,同一个网段上相同UDPIP和端口发送的数据都能够收到!我晕,昨天废了老大得劲编写一个不断断线重连的功能完全没用。今天改了一下。
同时我还发现QUdpSocket连接后的读取数据时可以直接读到其TCP的IP地址……我真是服了,原来可以这样用!!
后来我发现QUdpSocket这个类可以在连接的时候断开信号和槽函数,但是断开后就不能再次和这个槽函数连接了!这样每当我不需要更新Udp读取的数据的时候可以断开,但是当我再次需要读取UDP数据的时候,只能再次新建一个QUdpSocket,然后再和重新建立信号和槽才能够触发这个槽函数!这很奇怪,但是我也不知道为啥,以后有机会要向高人请教!!同时今天还解决了一个问题,就是如何在和服务器断开连接后能够及时的响应到,说明已经和网络断开了,一般晚上使用的方法是“心跳连接”,就是隔一段时间发送一条命令,如果服务器没有发出回应的话就说明断开了连接。我使用Qt自带的检查网络是否连接的类QNetworkConfigurationManager,这个类在网络状态发生改变的时候发送一个信号void QNetworkConfigurationManager::onlineStateChanged(bool isOnline),这样就能够及时的对网络断开发生响应了,我检测了一下,大概断开后5秒左右这个信号就能发出!非常不错的效果!