经过一周的努力,终于实现啦如题所示的功能。
ros节点到基础使用ros节点发布与订阅的第一个例程,也就是learning_communication功能包,使用了listener 和 talker两个节点。其中使用talker节点作为socket的服务端,当talker接受到其他进程来到数据的时候,发布出去,listener订阅这个数据。
下面直接上代码:
listener.cpp
/**
* 该例程将订阅chatter话题,消息类型String
*/
#include "ros/ros.h"
#include "std_msgs/String.h"
// 接收到订阅的消息后,会进入消息回调函数
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
// 将接收到的消息打印出来
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "listener");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Subscriber,订阅名为chatter的topic,注册回调函数chatterCallback
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
// 循环等待回调函数
ros::spin();
return 0;
}
上述代码有点ros基础,应该都没有问题。接下来是talker.cpp
/**
* 该例程将发布chatter话题,消息类型String
*/
#include
#include "ros/ros.h"
#include "std_msgs/String.h"
//socket need xx.h
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFFER_SIZE 100
//服务器读写方法
void listen_data(int fd,int id)
{
//ros::init(argc, argv, "talker");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Publisher,发布名为chatter的topic,消息类型为std_msgs::String
ros::Publisher chatter_pub = n.advertise
// 设置循环的频率
ros::Rate loop_rate(10);
int count = 0;
char buffer[BUFFER_SIZE];
while(1) //一直处于监听客户端信息状态,知道客户端断开或客户端发出退出信息
{
memset(buffer,0,BUFFER_SIZE);
int len = recv(fd,buffer,BUFFER_SIZE,0);
if(len > 0) //如果接受到数据,则判断是否为退出信息
{
if(strcmp(buffer,"exit\n") == 0)
{
printf("id %d exited.\n",id);
break;
}
printf("ID_%d:%s\n",id,buffer); //输出第N 个用户,输出的信息
if (ros::ok())
{
// 初始化std_msgs::String类型的消息
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
//msg.data = ss.str();
msg.data = buffer;
// 发布消息
ROS_INFO("%s", msg.data.c_str());
chatter_pub.publish(msg);
// 循环等待回调函数
ros::spinOnce();
// 按照循环频率延时
loop_rate.sleep();
++count;
}
}
else //接收数据小于0,或等于0 则退出
{
printf("clinet %d close!\n",id);
break;
}
//如果服务端需要发送信息,此处添加发送信息
//memset(buffer,0,BUFFER_SIZE);
//scanf("%s",buffer);
//send(fd,buffer,BUFFER_SIZE,0);
}
close(fd); //关闭此客户端的连接的socket
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "talker");
int sockfd,new_fd;
struct sockaddr_in my_addr; //本地连接socked
struct sockaddr_in their_addr; //客户端连接socked
unsigned int numbytes,sin_size;
char buffer[BUFFER_SIZE];
static int i=0; //记录连接客户端数目,可以使用数组,结构体等数据类型记录客户端信息(IP,端口等)
//记录本地信息
my_addr.sin_family = AF_INET; //IPV4
my_addr.sin_port = htons(9999); //绑定端口9999,并将其转化为网络字节
my_addr.sin_addr.s_addr = INADDR_ANY; //指定接收IP为任意(如果指定IP,则只接收指定IP)
bzero(&(my_addr.sin_zero),0); //最后位置赋0,补位置
//设置socked
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
perror("socket error!\n");
exit(1);
}
//绑定socked
if((bind(sockfd,(struct sockaddr*)&my_addr,sizeof(my_addr))) < 0)
{
perror("bind error!\n");
exit(1);
}
//开启监听,可连接客户端最大为10个
if(listen(sockfd,10) == -1)
{
perror("listen error!\n");
exit(1);
}
//服务端一直运行,等待客户端连接
while(1)
{
sin_size = sizeof(struct sockaddr_in);
//等待客户端连接,连接后their_addr接收客户端的IP等信息,没有客户端连接,则一直等待
if((new_fd = accept(sockfd,(struct sockaddr*)(&their_addr),&sin_size)) < 0)
{
perror("accept error!\n");
exit(1);
}
//连接成功后,连接客户端数+1
i++;
//开启进程运行客户端
pid_t childid;
childid = fork(); //fork()函数有两个返回值,0为子进程,-1为错。子进程运行客户端
if(childid == 0)
{
close(sockfd); //子进程中不再需要sockfd去监听,这里释放它,只需要new_fd即可
listen_data(new_fd,i);
exit(0);
}
//父进程继续执行while,在accept()等待客户端。父进程的socked此时还在运行,没有关闭
//此处没有设置父进程退出的代码,因为假设服务器一直运行,如果需要服务器自动退出,可设置服务器
//等待连接的时间,如果一定时间没有客户端连接,可以退出等待,结束
}
//所有客户端
close(sockfd);
printf("server-------closed.\n");
//return 0 ;
/*
// ROS节点初始化
ros::init(argc, argv, "talker");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Publisher,发布名为chatter的topic,消息类型为std_msgs::String
ros::Publisher chatter_pub = n.advertise
// 设置循环的频率
ros::Rate loop_rate(10);
int count = 0;
while (ros::ok())
{
// 初始化std_msgs::String类型的消息
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
// 发布消息
ROS_INFO("%s", msg.data.c_str());
chatter_pub.publish(msg);
// 循环等待回调函数
ros::spinOnce();
// 按照循环频率延时
loop_rate.sleep();
++count;
}
*/
return 0;
}
上述代码基本流程是,使用ros::init(argc, argv, "talker"); 向rosmaster注册节点信息,然后建立一个socket的服务器,保持服务器一直运行,
if((new_fd = accept(sockfd,(struct sockaddr*)(&their_addr),&sin_size)) < 0)
{
perror("accept error!\n");
exit(1);
}
使用accept()函数,此处会阻塞,直到有客户端建立了链接。当客户端打开的时候,使用childid = fork(); 也是就fork()函数,来复制一份当前进程的数据,等价于开辟一个新的进程,具体原理,查阅fork函数。
当fork()函数成功之后,在子进程中,会返回0,在父进程中(也就是调用这个函数的进程)返回端口值,失败的话,会返回一个负值。
然后进入到void listen_data(int fd,int id)中,在这里,接受数据,并且建立一个消息发布器,每次接收到消息之后,就把数据发布出去。当客户端那边ctrl+C 之后,退出子进程,返回到父进程中继续等待客户端的到来。
下面是socket客户端到代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd,numbytes;
char buff[100];
struct sockaddr_in their_addr;
int i=0;
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(9999);
their_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //本次设置的是本地连接
bzero(&(their_addr.sin_zero),0);
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
perror("socket error!\n");
exit(1);
}
// 使用connect连接服务器,their_addr获取服务器信息
if(connect(sockfd,(struct sockaddr*)&their_addr,sizeof(struct sockaddr)) == -1)
{
perror("connect error!\n");
exit(1);
}
while(1)
{
//连接成功后
memset(buff,0,sizeof(buff));
printf("clinet----:");
scanf("%s",buff);
//客户端开始写入数据,*****此处buff需要和服务器中接收
if(send(sockfd,buff,sizeof(buff),0) == -1)
{
perror("send error \n");
exit(1);
}
}
close(sockfd);
return 0;
}
基本就这些,功能实现啦。