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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM