• ROS与windows进行TCP传送消息时的问题解决


    这几天为了解决这些问题一直在发愁,今天晚上终于成功了,前后算起来花了将近4天的时间,现在做一个总结。
    想要在QT中制作一个上位机实现三个主要功能:

        使用UDP协议接收外部发过来的数据
        对接受到的UDP数据进行处理,得到想要的数据
        将处理好的数据按照ROS的格式和要求使用TCP协议转发出去

    在此过程中遇到两个主要的问题:
    问题一: 成员变量是类对象,且该类对象的构造函数是含有参数的

    UDP部分的功能已经实现,主要的实现程序如下:

        #include "widget.h"
        #include "ui_widget.h"
        #include <QHostAddress>
         
        Widget::Widget(QWidget *parent) :
            QWidget(parent),
            ui(new Ui::Widget)
        {
            ui->setupUi(this);
         
         
            //分配空间,指定父对象
            udpSocket = new QUdpSocket(this);
         
            //绑定
            udpSocket->bind(10022);
         
            setWindowTitle("本端口为:10022");
         
         
            //当对方成功发送数据过来
            //自动触发 readyRead()
            connect(udpSocket,SIGNAL(readyRead()),this,SLOT(dealMsg()));
         
        }
         
        void Widget::dealMsg()
        {
         
            //读取对方发送的内容
            char buf[1024] = {0};
            QHostAddress cliAddr;  //对方IP
            quint16 port;           //对方端口
            qint64 len = udpSocket->readDatagram(buf,sizeof(buf),&cliAddr,&port);
            if(len > 0)
            {
                //格式化
                QString str = QString ("%1").arg(buf);   //获取的数据
                //给编辑区设置内容
                ui->textEdit->append(str);
            }
         
        }
         
        Widget::~Widget()
        {
            delete ui;
        }

    而ros与windows通信的例程可以参考我之前写的一篇博客:《基于rosserial_windows实现ROS与windows通信(亲测可行)》

    其中给的代码例程如下:

        /* rosserial_hello_world.cpp : Example of sending command velocities from Windows using rosserial
         */
         #include "stdafx.h"
         #include <string>
         #include <stdio.h>
         #include "ros.h"
         #include <geometry_msgs/Twist.h>
         #include <windows.h>
         
         using std::string;
         
         int _tmain (int argc, _TCHAR * argv[])
         {
           //创建节点句柄
           ros::NodeHandle nh;
           //ROS所在主机IP
           char *ros_master = "1.2.3.4";
         
           printf ("Connecting to server at %s\n", ros_master);
           nh.initNode (ros_master);
         
           printf ("Advertising cmd_vel message\n");//发布cmd_vel信息
            //初始化消息类型
           geometry_msgs::Twist twist_msg;
            //创建一个发布者发布话题
           ros::Publisher cmd_vel_pub ("cmd_vel", &twist_msg);
           nh.advertise (cmd_vel_pub);
           
           printf ("Go robot go!\n");
            //发布的内容
           while (1)
           {
             twist_msg.linear.x = 5.1;
             twist_msg.linear.y = 0;
             twist_msg.linear.z = 0;
             twist_msg.angular.x = 0;
             twist_msg.angular.y = 0;
             twist_msg.angular.z = -1.8;
             cmd_vel_pub.publish (&twist_msg);
         
             //循环等待回调函数
             nh.spinOnce ();
             //休眠100ms
             Sleep (100);
        }
         
        printf ("All done!\n");
        return 0;
        }

    首先需要在QT中包含ros生成的头文件库,所需要包含的东西全部写到.pro文件当中:

        //因为用到网络编程,所以添加network
        QT       += core gui network
         
        //将几个头文件和.cpp文件添加到工程当中
        SOURCES += main.cpp\
                widget.cpp \
            ros_lib/duration.cpp \
            ros_lib/time.cpp \
            ros_lib/WindowsSocket.cpp
         
        HEADERS  += widget.h \
            ros_lib/ros.h \
            ros_lib/WindowsSocket.h
         
        //这一步是添加ros_lib的路径,其中是头文件库
        INCLUDEPATH +=F:\3Code\QtCode\UDP\ros_lib
         
        //添加依赖,相当于VS当中的#pragma comment(lib, "ws2_32.lib")
        LIBS += -lpthread libwsock32 libws2_32
        LIBS += -lWs2_32
         
        //支持C++11
        CONFIG += c++11

    如何将UDP和ROS中的TCP客户端两段代码写到一起呢?

    难点在于在构造函数中的这一部分,如果我们这样写的话:

        {
            ui->setupUi(this);
         
            //给ros发送数据的初始化
            ros::NodeHandle nh;
            char *ros_master = "192.168.1.3";
            nh.initNode(ros_master);
            geometry_msgs::Twist twist_msg;
            ros::Publisher cmd_vel_pub("cmd_vel", &twist_msg);
            nh.advertise(cmd_vel_pub);
            //赋一个值
            twist_msg.linear.x = 5.1;
            twist_msg.linear.y = 0;
            twist_msg.linear.z = 0;
            twist_msg.angular.x = 0;
            twist_msg.angular.y = 0;
            twist_msg.angular.z = -1.9;
         
            //分配空间,指定父对象
            udpSocket = new QUdpSocket(this);
            //绑定
            udpSocket->bind(10022);
            setWindowTitle("本端口为:10022");
            //当对方成功发送数据过来
            //自动触发 readyRead()
            connect(udpSocket,SIGNAL(readyRead()),this,SLOT(dealMsg()));
         
        }
         
        //槽函数,QT中的槽函数在这里没有办法传递参数
        void Widget::dealMsg()
        {
         
            //读取对方发送的内容
            char buf[1024] = {0};
            QHostAddress cliAddr;  //对方IP
            quint16 port;           //对方端口
            qint64 len = udpSocket->readDatagram(buf,sizeof(buf),&cliAddr,&port);
            if(len > 0)
            {
                //格式化
                QString str = QString ("%1").arg(buf);   //获取的数据
                //给编辑区设置内容
                ui->textEdit->append(str);
         
                //发送部分
                cmd_vel_pub.publish(&twist_msg);
                nh.spinOnce();
            }
        }

    如果QT的槽函数传递参数了的话,需要信号函数也有参数,且槽函数的参数类型需要和信号函数的参数类型一致,并且槽函数的参数个数不能大于信号函数的参数个数。但是readyRead()是一个没有参数的信号函数,那么槽函数中的这两句代码就没有办法使用:

                //发送部分
                cmd_vel_pub.publish(&twist_msg);
                nh.spinOnce();

    必须把cmd_vel_pub,twist_msg,nh传递进来,他们分别是三个类的实例对象。如果没有办法让槽函数传递参数进来,那么想到的办法就是将以上的三个对象变为Widget类的成员变量,只不过是三个类成员变量而已,那么槽函数就直接可以使用这三个对象啦,哈哈!

    但是困难接踵而来,可以在private中这样写:

        private:
            ros::NodeHandle nh;
            geometry_msgs::Twist twist_msg;
            ros::Publisher cmd_vel_pub("cmd_vel", &twist_msg);

    但是ros::Publisher cmd_vel_pub("cmd_vel", &twist_msg);这句死活不行啊,查看Publisher类发现,它的构造函数只有一个有参的构造函数,如下:

        Publisher(const char * topic_name, Msg * msg, int endpoint = rosserial_msgs::TopicInfo::ID_PUBLISHER) :
            topic_(topic_name),
            msg_(msg),
            endpoint_(endpoint) {};

    这种数据成员是对象,并且这个对象只有含参数的构造函数,没有无参数的构造函数,是如何声明的呢?
    如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,而没有默认构造函数,这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,使用初始化裂变对齐进行初始化,如果没有初始化列表,那么他将无法完成第一步,就会报错。
     

    具体可以参考这两篇博客:c++中在一个类中定义另一个带参数构造函数的类的对象、C++必须使用【初始化列表】初始化数据成员的三种情况

    那么在我的程序中,就应该这么写:

        // widget.h
         
        private:
            ros::NodeHandle nh;
            geometry_msgs::Twist twist_msg;
            ros::Publisher cmd_vel_pub("cmd_vel", &twist_msg);

        //   widget.cpp
         
        Widget::Widget(QWidget *parent) :
            QWidget(parent),
            cmd_vel_pub("cmd_vel", &twist_msg),
            ui(new Ui::Widget)

    这样一来,三个对象就都可以在槽函数中自由的使用啦!
    问题二:发送给ROS时出现10053的错误

     

    通过网上搜索,10053的错误可能引起的原因如下:

    10053错误:1、可能软件的其它地方关闭了socket;
                         2、可能对端已关闭了连接。

    那么具体是是么原因引起的呢?使用抓包软件进行抓包处理,分析一波:

    这个是正常进行TCP发送数据时抓到的包:

    可以看出正常的情况下,经过TCP的三次握手之后,就可以进行数据的传输。

    那么出现10053的情况下进行抓包的结果如下:

    我们可以看出,虽然握手成功,但是1s之后,ROS这边TCP服务器就关闭了套接字,将FIN置为1。

    看来问题是出现在了ROS这边,如果ROS和windows奖励起TCP连接后,如果1s内收不到数据,那么服务器就会将套接字关闭。接下来通过查看ROS中rosserial_sever包中socket_node源码,找到了修改的办法。没有源码的可以将之前安装的rosserial_sever包卸载掉,再下载源码进行编译。

    我们找到源码中session.h这个头文件,将 attempt_interval_ = boost::posix_time::milliseconds(1000);进行修改,可以将1000改的大一点,例如10000,这样只要间隔10s内可以收到数据,服务器就不会关闭套接字。

    修改过之后记得要catkin_make一下!
    ————————————————
    版权声明:本文为CSDN博主「编程芝士」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/zzu_seu/article/details/95379087

  • 相关阅读:
    uniapp开发微信小程序
    requests自动登录禅道并提交bug 测试
    [转载]使用CPU时间戳进行高精度计时
    lu面
    音量控制面板项目说明
    【转载】粤语翻译工具
    专业操盘手的买卖法则
    自动刷新查询火车票脚本
    股东权益和权益比
    异形魔方SQ1的暴力解法
  • 原文地址:https://www.cnblogs.com/feifanrensheng/p/15719931.html
Copyright © 2020-2023  润新知