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