Qt使用UDP搜索同一个网段上一个组播的多个服务器数据和QUdpSocket的使用经验

今天在工作工作中遇到的问题时网络连接的问题,普通的客户端网络连接用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秒左右这个信号就能发出!非常不错的效果!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值