通信

1. Socket套接字实现TCP/UDP通信

  1. TCP服务端
//TCP-server的实现

#include <stdio.h>  //预处理命令,使用标准函数库中的输入输出函数
#include <ctype.h>  //字符处理
#include <sys/socket.h>  //socket套接字函数,accept(),bind(),等
#include <arpa/inet.h>  //网络字节序转化函数,IP地址转换
#include <stdlib.h>  //定义杂项函数及内存分配函数,如exit()
#include <string.h>  //字符串处理
#include <unistd.h>  //linux系统调用函数,如read(),write()函数等
#include <errno.h>  //定义了通过错误码来回复错误信息的宏
#include <pthread.h>  //编写linux下的多线程文件,需要使用该头文件
  
#define SERV_PORT 9527  

void sys_err(const char *str)  
{  
    perror(str);  // perror()用来将上一个函数发生错误的原因输出到标准设备

    exit(1);  //exit的功能为,退出当前运行的程序,并将参数value返回给主调进//程。在main中return v;的效果 与exit(v);相同。exit(0)表示程//序正常退出,非0表示非正常退出
}  
  

int main(int argc, char *argv[])  // argc是命令行总的参数个数,argv[]为保存命令行参数的字符串指针
{  
    int lfd = 0, cfd = 0; //文件描述符 
    int ret, i;  
    char buf[BUFSIZ], client_IP[1024];  //字符数组

    struct sockaddr_in serv_addr, clit_addr;  // 定义服务器地址结构 和 客户端地址结构  
    socklen_t clit_addr_len;                  // 客户端地址结构大小  

    serv_addr.sin_family = AF_INET;             // IPv4  
    serv_addr.sin_port = htons(SERV_PORT);      // 转为网络字节序的 端口号  
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 获取本机任意有效IP  

    lfd = socket(AF_INET, SOCK_STREAM, 0);      //创建一个 socket  
    if (lfd == -1) {  
        sys_err("socket error");  
    }  

    bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));//给服务器socket绑定地址结构(IP+port)  

    listen(lfd, 128);                   //  设置监听上限  

    clit_addr_len = sizeof(clit_addr);  //  获取客户端地址结构大小  
    cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);   // 阻塞等待客户端连接请求  
    if (cfd == -1)  
        sys_err("accept error");  
    printf("client ip:%s port:%d\n",   
            inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, client_IP, sizeof(client_IP)),   
            ntohs(clit_addr.sin_port));         // 根据accept传出参数,获取客户端 ip 和 port  

    while (1) {  //死循环,会一直执行
        ret = read(cfd, buf, sizeof(buf));      // 读客户端数据  
        write(STDOUT_FILENO, buf, ret);         // 写到屏幕查看  
        for (i = 0; i < ret; i++)                // 小写 -- 大写  
            buf[i] = toupper(buf[i]);  
        write(cfd, buf, ret);                   // 将大写,写回给客户端。  
    }  

    close(lfd);  
    close(cfd);  

    return 0;  
}  
 
  1. TCP客户端
#include<sys/socket.h>
#include<arpa/inet.h>
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>


#define SERV_PORT 9527

void sys_err(const char *str)
{
   perror(str);
   exit(1);
}


int main(int argc, char*argv[])
{
    int cfd;
    int conter=10;
    char buf[BUFSIZ];

    struct sockaddr_in serv_addr;

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr.s_addr);

    cfd=socket(AF_INET,SOCK_STREAM,0);
    if(cfd==-1)
    {
      sys_err("socket error");
    }


    int ret=connect(cfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    if(ret!=0)
        sys_err("connect err");

    while(--conter)
    {
        write(cfd,"hello\n",6);
        ret=read(cfd,buf,sizeof(buf));
        write(STDOUT_FILENO,buf,ret);
        sleep(2);   
    }
    
    close(cfd);
    return 0;
}

注释:
1.exit()为C++的退出函数,声明于stdlib.h中,对于C++其标准的头文件为cstdlib,声明为

void exit(int value);

exit的功能为,退出当前运行的程序,并将参数value返回给主调进程。

在main中return v;的效果 与exit(v);相同。

exit(1)和exit(-1)是分别返回1和-1到主调程序。exit(0)则是返回0。

exit(0)表示程序正常退出,非0表示非正常退出.

2.argc是命令行总的参数个数

argv[]为保存命令行参数的字符串指针,其中第0个参数是程序的全名,以后的参数为命令行后面跟的用户输入的参数.

2. 自定义话题与消息实现通信

  1. 自定义话题消息
    写msg文件,自动编译生成头文件

  2. 创建发布者代码

/**
 * 该例程将发布/person_info话题,自定义消息类型learning_topic::Person
 */

#include <ros/ros.h>
#include "learning_topic/Person.h"  //功能包/头文件,自己创建的用引号

int main(int argc, char **argv)
{
    // ROS节点初始化
    ros::init(argc, argv, "person_publisher");  //节点名字
    // 创建节点句柄
    ros::NodeHandle n;
    // 创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person(自己创建的),队列长度10
    ros::Publisher person_info_pub = n.advertise<learning_topic::Person>("/person_info", 10);

    // 设置循环的频率
    ros::Rate loop_rate(1);
   int count = 0;
    while (ros::ok())
    {
        // 初始化learning_topic::Person类型的消息
             learning_topic::Person person_msg;   //创建消息类型的对象
                  person_msg.name = "Tom";
                  person_msg.age  = 18;
                  person_msg.sex  = learning_topic::Person::male;
        // 发布消息
                  person_info_pub.publish(person_msg);  //发布者person_info_pub发布消息person_msg
     ROS_INFO("Publish Person Info: name:%s  age:%d  sex:%d",
                                     person_msg.name.c_str(), person_msg.age, person_msg.sex);

        // 按照循环频率延时
       loop_rate.sleep();
    }

    return 0;
}
  1. 创建订阅者代码
/**
 * 该例程将订阅/person_info话题,自定义消息类型learning_topic::Person
 */
#include <ros/ros.h>
#include "learning_topic/Person.h"
 
// 接收到订阅的消息后,会进入消息回调函数
void personInfoCallback(const learning_topic::Person::ConstPtr& msg)
{
    // 将接收到的消息打印出来
    ROS_INFO("Subcribe Person Info: name:%s  age:%d  sex:%d",
                           msg->name.c_str(), msg->age, msg->sex);
}

int main(int argc, char **argv)
{
    // 初始化ROS节点
    ros::init(argc, argv, "person_subscriber");  //节点名字
    // 创建节点句柄
    ros::NodeHandle n;
    // 创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback
    ros::Subscriber person_info_sub = n.subscribe("/person_info", 10, personInfoCallback);
 
    // 循环等待回调函数
    ros::spin();
    
    return 0;
}
  1. 配置CMakeLists.txt中的编译规则

  2. 编译并运行

思考:msg文件怎么写,消息类型头文件怎么生成?

3. ROS自定义话题与消息实现TCP外部通信

  1. 自定义话题消息

https://blog.csdn.net/weixin_43795921/article/details/85839333

  1. 创建发布者代码
//TCP server端
#include <ros/ros.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include "std_msgs/String.h"  // 功能包/头文件

using namespace std;

void error(const char *msg) {
    perror(msg);
    exit(1);
}

int main (int argc, char** argv)
{
  //节点初始化
  ros::init(argc, argv, "server_node");

  //创建节点句柄
  ros::NodeHandle nh;

  //创建一个publisher
  ros::Publisher server_pub = nh.advertise<std_msgs::String>("/server_messages/", 1000);

  //Testing package is working fine
  int sockfd, newsockfd, portno; //Socket file descriptors and port number
  socklen_t clilen; //object clilen of type socklen_t
  char buffer[256]; //buffer array of size 256
  struct sockaddr_in serv_addr, cli_addr; ///two objects to store client and server address
  
  //初始化std_msgs::String类型的消息
  std_msgs::String message;
  std::stringstream ss;
  int n;
  ros::Duration d(0.01); // 100Hz
  if (argc < 2) {
    fprintf(stderr,"ERROR, no port provided\n");
    exit(1);
  }
  portno = atoi(argv[1]);
  cout << "Hello there! This node is listening on port " << portno << " for incoming connections" << endl;
  sockfd = socket(AF_INET, SOCK_STREAM, 0);//套接字返回文件描述符
  if (sockfd < 0)
      error("ERROR opening socket");
  int enable = 1;
  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
      error("setsockopt(SO_REUSEADDR) failed");
  bzero((char *) &serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = INADDR_ANY;
  serv_addr.sin_port = htons(portno);
  if (bind(sockfd, (struct sockaddr *) &serv_addr,
            sizeof(serv_addr)) < 0)
            error("ERROR on binding");
  listen(sockfd,5);
  clilen = sizeof(cli_addr);
  newsockfd = accept(sockfd,
              (struct sockaddr *) &cli_addr,
              &clilen);
  if (newsockfd < 0)
       error("ERROR on accept");
  while(ros::ok()) {
     ss.str(std::string()); //Clear contents of string stream
     bzero(buffer,256);
     n = read(newsockfd,buffer,255);
     if (n < 0) error("ERROR reading from socket");
     // printf("Here is the message: %s\n",buffer);
     ss << buffer;
     message.data = ss.str();
     ROS_INFO("%s", message.data.c_str());

     //发布消息
     server_pub.publish(message);

     n = write(newsockfd,"I got your message",18);
     if (n < 0) error("ERROR writing to socket");
     //close(newsockfd);
     //close(sockfd);
     //ros::spinOnce();
     //d.sleep();
  }
  return 0;
}
  1. 创建订阅者代码
//TCP Client端
#include <ros/ros.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include "std_msgs/String.h"

#define MESSAGE_FREQ 1

void error(const char *msg) {
    perror(msg);
    exit(0);
}

class Listener {
private:
    char topic_message[256] = { 0 };
public:
    void callback(const std_msgs::String::ConstPtr& msg);
    char* getMessageValue();
};

void Listener::callback(const std_msgs::String::ConstPtr& msg) {
    memset(topic_message, 0, 256);
    strcpy(topic_message, msg->data.c_str());
    ROS_INFO("I heard:[%s]", msg->data.c_str());
}

char* Listener::getMessageValue() {
    return topic_message;
}

int main(int argc, char *argv[]) {
	ros::init(argc, argv, "client_node");
	ros::NodeHandle nh;
    ros::Rate loop_rate(MESSAGE_FREQ); // set the rate as defined in the macro MESSAGE_FREQ
	Listener listener;
        ros::Subscriber client_sub = nh.subscribe("/client_messages", 1, &Listener::callback, &listener);
    int sockfd, portno, n, choice = 1;
    struct sockaddr_in serv_addr;
    struct hostent *server;
    char buffer[256];
    bool echoMode = false;
    if (argc < 3) {
       fprintf(stderr,"Usage: $ rosrun comm_tcp client_node <hostname> <port> --arguments\nArguments:\n -e : Echo mode\n");
       exit(0);
    }
    if (argc > 3)
		if (strcmp(argv[3], "-e") == 0)
			echoMode = true;
    portno = atoi(argv[2]);
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) 
        error("ERROR opening socket");
    server = gethostbyname(argv[1]);
    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr, 
         (char *)&serv_addr.sin_addr.s_addr,
         server->h_length);
    serv_addr.sin_port = htons(portno);
    if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0) 
        error("ERROR connecting");
    std::cout << "How do you want the client to behave?:\n1. Be able to send messages manually\n2. Subscribe to /client_messages and send whatever's available there\nYour choice:";
    std::cin >> choice;
	while(ros::ok()) {
        bzero(buffer,256);
        if (choice == 1) {
            printf("Please enter the message: ");
            fgets(buffer,255,stdin);
        } else if (choice == 2) {
            strcpy(buffer, listener.getMessageValue());
            loop_rate.sleep();
        }
	    n = write(sockfd,buffer,strlen(buffer));
	    if (n < 0) 
	         error("ERROR writing to socket");
	    if (echoMode) {
			bzero(buffer, 256);
		    n = read(sockfd,buffer,255);
		    if (n < 0)
				error("ERROR reading reply");
		    printf("%s\n", buffer);
	    }
	    ros::spinOnce();
	}
	return 0;
}

Step4.配置代码编译规则

Step5.编译并运行

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值