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

 这几天为了解决这些问题一直在发愁,今天晚上终于成功了,前后算起来花了将近4天的时间,现在做一个总结。

想要在QT中制作一个上位机实现三个主要功能:

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

在此过程中遇到两个主要的问题:

问题一: 成员变量是类对象,且该类对象的构造函数是含有参数的

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

#include "widget.h"
#include "ui_widget.h"
#include 

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 
 #include 
 #include "ros.h"
 #include 
 #include 
 
 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的错误

ROS与windows进行TCP传送消息时的问题解决_第1张图片

 

通过网上搜索,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一下!

你可能感兴趣的:(ROS,QT,计算机网络)