1. Socket套接字实现TCP/UDP通信
- 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;
}
- 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. 自定义话题与消息实现通信
-
自定义话题消息
写msg文件,自动编译生成头文件 -
创建发布者代码
/**
* 该例程将发布/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;
}
- 创建订阅者代码
/**
* 该例程将订阅/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;
}
-
配置CMakeLists.txt中的编译规则
-
编译并运行
思考:msg文件怎么写,消息类型头文件怎么生成?
3. ROS自定义话题与消息实现TCP外部通信
- 自定义话题消息
https://blog.csdn.net/weixin_43795921/article/details/85839333
- 创建发布者代码
//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;
}
- 创建订阅者代码
//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.编译并运行