Autoware源码学习笔记(一):Vehicle_socket

Autoware 安装(源码)过程 与 踩坑记录(Ubuntu18.04) ubuntu 18.04 autoware 源码安装 与 踩坑记录 阅读详情

本文为博主原创内容,未经博主允许不得转载。尊重他人知识成果,共同建立良好学习分享氛围,谢谢!

一、前言

  Vehicle_socket这个Package包含有vehicle_receiver和vehicle_sender两个node,主要是用于autoware平台应用车辆和Android设备的数据传输和通信,其中还涉及到了线程、客户机和服务器之间TCP/IP通信的知识和常用函数。我之前对这方面也不了解,很多内容也是现学现用的,如有错误,还希望各位提出指正。

  看源代码之前,先附上一张图,讲述的是客户机和服务器通信的基本框架思路,以便于更好的帮助理解源代码。
在这里插入图片描述

图1 客户机和服务器通信的基本框架

  博主主要对vehicle_receiver的函数和代码做了详细注释和解释,而vehicle_sender中的基本思路和使用函数与vehicle_receiver基本相似,故vehicle_sender中重复使用的函数没有加以说明。

二、vehicle_receiver

  在以autoware平台应用车辆作为服务器端,Android设备作为客户端的基础上,vehicle_receiver节点建立基本的TCP/IP通信机制,等待Android设备客户端向车辆服务器端发起通信请求,若请求则读取服务器端的数据,并按照一定的格式解析消息内容。其中主要包括了main函数、receiverCaller函数、getCanValue函数和parseCanValue函数。

2.1 main函数

  main函数主要创建了vehicle_receiver节点,然后创建mode_pub和can_pub两个publisher,随后创建一个线程,创建线程成功后,新创建的线程从指定的起始地址执行线程函数receiverCaller,而原来的线程则继续运行下一行代码。

#include <ros/ros.h>
#include <tablet_socket_msgs/mode_info.h>
#include "autoware_can_msgs/CANInfo.h"//先从自定义的文件中找头文件,如果找不到再从函数库中寻找文件。
#include <netinet/in.h>
#include <pthread.h>//在Linux编写多线程程序需要包含该头文件
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>//要使用istringstream、getline等类创建对象就必须包含该头文件
#include <string>
#include <vector>//在函数中使用vector函数,需添加该头文件

#define CAN_KEY_MODE (0)
#define CAN_KEY_TIME (1)
#define CAN_KEY_VELOC (2)
#define CAN_KEY_ANGLE (3)
#define CAN_KEY_TORQUE (4)
#define CAN_KEY_ACCEL (5)
#define CAN_KEY_BRAKE (6)
#define CAN_KEY_SHIFT (7)

static ros::Publisher can_pub;//创建publisher对象can_pub
static ros::Publisher mode_pub;//创建publisher对象mode_pub
static int mode;

int main(int argc, char **argv)
{
  ros::init(argc, argv, "vehicle_receiver");//ros定义名字为vehicle_receiver的节点
  ros::NodeHandle nh;//ros实例化句柄对象nh
  
  std::cout << "vehicle receiver" << std::endl;
  
  can_pub = nh.advertise<autoware_can_msgs::CANInfo>("can_info", 100);//定义"can_info"topic的内容
  mode_pub = nh.advertise<tablet_socket_msgs::mode_info>("mode_info", 100);//定义"mode_info"topic的内容
  
  pthread_t th;//声明线程ID
  int ret = pthread_create(&th, nullptr, receiverCaller, nullptr);//创建线程
  /*
    原型:int  pthread_create((pthread_t  *thread,  pthread_attr_t  *attr,  void  *(*start_routine)(void  *),  void  *arg)
    头文件:#include  <pthread.h>
    功能:创建线程(实际上就是确定调用该线程函数的入口点),在线程创建以后,就开始运行相关的线程函数。
    说明:thread:线程标识符;
          attr:线程属性设置;
          start_routine:线程函数的起始地址;
          arg:传递给start_routine的参数;
          返回值:若成功,返回0;若出错,返回-1。
  */

  if (ret != 0)
  {
    std::perror("pthread_create");
    /*
    perror(s) 用来将上一个函数发生错误的原因输出到标准设备。参数s所指的字符串会先打印出,后面再加上错误原因字符串。
    此错误原因依照全局变量errno的值来决定要输出的字符串。在库函数中有个errno变量,每个errno值对应着以字符串表示的错误类型。
    当你调用"某些"函数出错时,该函数已经重新设置了errno的值。perror函数只是将你输入的一些信息和现在的errno所对应的错误一起输出。
    */
    std::exit(1);
    /*
    每个进程都会有一个返回值的. 进程开始时是由系统的一个启动函数掉用了main函数的:
    int   nMainRetVal   =   main(); 
    当从main函数退出后,启动函数便调用exit函数,并且把nMainRetVa传递给它. 所以,任何时候都会调用exit函数的。
    正常情况下,main函数不会调用exit函数的,而是由return 0 返回值给nMainRetVal的,exit再接收这个值作为参数的。
    所以,正常情况下是以exit(0)退出的,但是如果你的程序发生异常,你可以在main函数中调用exit(1),强制退出程序,强制终止进程.其中1表示不正常退出。    
    */
  }

  ret = pthread_detach(th);
  /*
  从状态上实现线程分离,注意不是指该线程独自占用地址空间。分离成功返回0,分离失败返回错误号。
  线程分离状态:指定该状态,线程主动与主控线程断开关系。
  线程结束后(不会产生僵尸线程),其退出状态不由其他线程获取,而直接自己自动释放(自己清理掉PCB的残留资源)。网络、多线程服务器常用。  
  */

  if (ret != 0)
  {
    std::perror("pthread_detach");
    std::exit(1);
  }

  ros::spin();
  return 0;
}

2.2 receiverCaller函数

  receiverCaller函数主要通过socket函数创建服务器端的流式套接字,然后使用bind函数将该套接字和指定地址相连接,随后使用listen函数表示服务器已做好准备,可以通信,最后利用accept函数接纳客户端请求并返回客户端套接字标识,并把该标识作为参数传递给线程函数getCanValue。

static void *receiverCaller(void *unused)
{
  constexpr int listen_port = 10000;
  /*
  constexpr是C++11中新增的关键字,其语义是“常量表达式”,其是在编译期就可以计算出结果的表达式。
  常量表达式的好处:
  1、允许一些计算只在编译时进行一次,而不是每次程序运行时;
  2、编译器可以在编译期对constexpr的代码进行非常大的优化,比如将用到的constexpr表达式都直接替换成最终结果等;
  3、是一种很强的约束,更好地保证程序的正确语义不被破坏;
  4、相比宏来说,没有额外的开销,但更安全可靠。
  */

  int sock = socket(AF_INET, SOCK_STREAM, 0);
  /*
    socket函数的使用方法如下:
  int socket(int domain, int type, int protocol),若返回值为非负描述符,则表示成功,若返回值为-1,则表示出错。
  在参数表中,domain指定使用何种的地址类型,比较常用的有:
    PF_INET, AF_INET: Ipv4网络协议;
    PF_INET6, AF_INET6: Ipv6网络协议。
  type参数的作用是设置通信的协议类型,可能的取值如下所示:
    SOCK_STREAM: 提供面向连接的稳定数据传输,即TCP协议。其应用在C语言socket编程中,在进行网络连接前,需要用socket函数向系统申请一个通信端口;
    OOB: 在所有数据传送前必须使用connect()来建立连接状态;
    SOCK_DGRAM: 使用不连续不可靠的数据包连接;
    SOCK_SEQPACKET: 提供连续可靠的数据包连接;
    SOCK_RAW: 提供原始网络协议存取;
    SOCK_RDM: 提供可靠的数据包连接;
    SOCK_PACKET: 与网络驱动程序直接通信。
  参数protocol用来指定socket所使用的传输协议编号。这一参数通常不具体设置,一般设置为0即可。
  */

  if (sock == -1)
  {
    std::perror("socket");
    return nullptr;
  }

  sockaddr_in client;//sockaddr_in是系统封装的一个结构体,这里实例化一个client来存放地址信息。
  socklen_t len = sizeof(client);//socklen_t是一种数据类型,它其实和int差不多,在32位机下,size_t和int的长度相同,都是32 bits,但在64位机下,size_t(32bits)和int(64 bits)的长度是不一样的。
  sockaddr_in addr;//实例化一个addr来存放地址信息

  std::memset(&addr, 0, sizeof(sockaddr_in));//从addr的初始地址开始到后面的sizeof(sockaddr_in)个字节用0替换并返回addr。这个函数在socket中多用于清空数组

  addr.sin_family = PF_INET;
  addr.sin_port = htons(listen_port);
  addr.sin_addr.s_addr = INADDR_ANY;
    /*
    sockaddr_in是系统封装的一个结构体,具体包含了成员变量:sin_family、sin_port、sin_addr、sin_zero等等。
    sin_family主要用于定义地址族,PF_INET代表TCP/IP
    sin_port主要用来保存端口号,将监听套接字的端口设置为listen_port,htons表示host to network short,用来进行主机字节序和网络字节序的转换。
    sin_addr主要用来保存IP地址信息,INADDR_ANY转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。
        比如一台电脑有3块网卡,分别连接三个网络,那么这台电脑就有3个ip地址了,如果某个应用程序需要监听某个端口,那他要监听哪个网卡地址的端口呢?
        如果绑定某个具体的ip地址,你只能监听你所设置的ip地址所在的网卡的端口,其它两块网卡无法监听端口,如果我需要三个网卡都监听,那就需要绑定3个ip,也就等于需要管理3个套接字进行数据交换,这样岂不是很繁琐?
        所以出现INADDR_ANY,你只需绑定INADDR_ANY,管理一个套接字就行,不管数据是从哪个网卡过来的,只要是绑定的端口号过来的数据,都可以接收到。
    sin_zero无特殊含义,只是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。用来将sockaddr_in结构填充到与struct
        sockaddr同样的长度,可以用bzero()或memset()函数将其置为零。  
    */

  // make it available immediately to connect
  // setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(yes));
  int ret = bind(sock, (sockaddr *)&addr, sizeof(addr));//bind函数把名字和套接字相关联
  /*
  int bind(int sockfd, const struct sockaddr *addr,socklen_t *addrlen);
  当用socket()函数创建套接字以后,套接字在名称空间(网络地址族)中存在,但没有任何地址给它赋值。
  bind()把用addr指定的地址赋值给用文件描述符代表的套接字sockfd。addrlen指定了以addr所指向的地址结构体的字节长度。
  一般来说,该操作称为“给套接字命名”。
  */
  
  if (ret == -1)
  {
    std::perror("bind");
    goto error;
  }

  ret = listen(sock, 5);
  /*
  int PASCAL FAR listen (SOCKET s, int backlog);
  第一个参数是服务端套接字,你要聆听,总得出来说个话啊,好,就指定你了;
  第二个参数是等待连接队列的最大长度,比方说,你将backlog定为10, 当有15个连接请求的时候,前面10个连接请求就被放置在请求队列中,后面5个请求被拒绝。
  再看函数的返回值,成功返回0, 失败返回-1。
  */

  if (ret == -1)
  {
    std::perror("listen");
    goto error;
  }

  while (true)
  {
    // get connect to android
    std::cout << "Waiting access..." << std::endl;
    int *client_sock = new int();
    *client_sock = accept(sock, reinterpret_cast<sockaddr *>(&client), &len);
    /*
    unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len);
    接纳客户端请求的函数是accept,其指定服务端去接受客户端的连接,接收后,返回了客户端套接字的标识,
    并且获得了客户端套接字的“地方”(包括客户端IP和端口信息等)。
    第一个参数用来标识服务端套接字(也就是listen函数中设置为监听状态的套接字);
    第二个参数是用来保存客户端套接字对应的“地方”(包括客户端IP和端口信息等);
    第三个参数是“地方”的占地大小。返回值对应客户端套接字标识。
    */
    
    if (*client_sock == -1)
    {
      std::perror("accept");
      break;
    }

    std::cout << "Get connect" << std::endl;

    pthread_t th;
    if (pthread_create(&th, nullptr, getCanValue, static_cast<void *>(client_sock)))
        //static_cast用法:static_cast < type-id > ( expression ),该运算符把expression转换为type - id类型。
    {
      std::perror("pthread_create");
      break;
    }

    ret = pthread_detach(th);
    if (ret != 0)
    {
      std::perror("pthread_detach");
      break;
    }
  }

error:
  close(sock);
  return nullptr;
}

2.3 getCanValue函数

  getCanValue函数主要是客户端通过recv函数从服务器端读取CAN原始数据,然后关闭客户端套接字,随后parseCanValue函数解析CAN原始数据,最后定义can_info和mode_info两个topic的结构内容。

static void *getCanValue(void *arg)
{
  int *client_sockp = static_cast<int *>(arg);
  int sock = *client_sockp;
  delete client_sockp;

  char recvdata[1024];
  std::string can_data("");
  constexpr int LIMIT = 1024 * 1024;

  while (true)
  {
    ssize_t n = recv(sock, recvdata, sizeof(recvdata), 0);
    /*
    recv函数 int recv( SOCKET s,     char FAR *buf,      int len,     int flags     );  
    不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。
    该函数的第一个参数指定接收端套接字描述符;
    第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
    第三个参数指明buf的长度;
    第四个参数一般置0。
    这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,
    如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,
    如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,
    如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,只到 协议把数据接收完毕。
    当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,
    所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),
    recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0
    */

    if (n < 0)
    {
      std::perror("recv");
      can_data = "";
      break;
    }
    else if (n == 0)
    {
      break;
    }
    can_data.append(recvdata, n);
    /*
    从字符串recvdata的第0到第n个字符连接在string字符串can_data后面
    */

    // recv data is bigger than 1M,return error
    if (can_data.size() > LIMIT)
    {
      std::cerr << "recv data is too big." << std::endl;
      can_data = "";
      break;
    }
  }

  if (close(sock) < 0)
  {
    std::perror("close");
    return nullptr;
  }

  if (can_data.empty())
    return nullptr;

  autoware_can_msgs::CANInfo can_msg;
  bool ret = parseCanValue(can_data, can_msg);
  if (!ret)
    return nullptr;

  can_msg.header.frame_id = "/can";
  can_msg.header.stamp = ros::Time::now();
  can_pub.publish(can_msg);

  tablet_socket_msgs::mode_info mode_msg;
  mode_msg.header.frame_id = "/mode";
  mode_msg.header.stamp = ros::Time::now();
  mode_msg.mode = mode;
  mode_pub.publish(mode_msg);

  return nullptr;
}

2.4 parseCanValue函数

  parseCanValue函数主要解析客户端通过recv函数从服务器端读取到的数据,并更新can_msg和mode的内容。

static bool parseCanValue(const std::string &can_data, autoware_can_msgs::CANInfo &msg)
{
  std::istringstream ss(can_data);
  std::vector<std::string> columns;//初始化列表空对象columns

  std::string column;
  while (std::getline(ss, column, ','))
      /*
      getline()的原型:getline(istream &is,string &str,char delim)
      istream &is表示一个输入流,譬如cin,string表示把从输入流读入的字符串存放在这个字符串中(&str其实就是一个变量)
      char delim是终止符(默认为回车,还可以是别的符号,如#,*之类的都可以)
      而对于while(getline(cin,str))来讲,while语句的真实判断对象是cin,也就是当前是否存在有效的输入流,如果存在就不会结束循环
      */
  {
    columns.push_back(column);//列表尾部插入column
  }

  for (std::size_t i = 0; i < columns.size(); i += 2)
      /*
      size_t是一种“整型”类型,里面保存的是一个整数,就像int, long那样。这种整数用来记录一个大小(size)。
      size_t的全称应该是size type,就是说“一种用来记录大小的数据类型”。
      */
  {
    int key = std::stoi(columns[i]);//stoi()可以将string转化为int。
    switch (key)
    {
      case CAN_KEY_MODE:
        mode = std::stoi(columns[i + 1]);
        if (mode & 0x1)
        {
          msg.devmode = 1;
        }
        else
        {
          msg.devmode = 0;
        }

        if (mode & 0x2)
        {
          msg.strmode = 1;
        }
        else
        {
          msg.strmode = 0;
        }
        break;
      case CAN_KEY_TIME:
        msg.tm = columns[i + 1].substr(1, columns[i + 1].length() - 2);  // skip '
        break;
      case CAN_KEY_VELOC:
        msg.speed = std::stod(columns[i + 1]);
        break;
      case CAN_KEY_ANGLE:
        msg.angle = std::stod(columns[i + 1]);
        break;
      case CAN_KEY_TORQUE:
        msg.torque = std::stoi(columns[i + 1]);
        break;
      case CAN_KEY_ACCEL:
        msg.drivepedal = std::stoi(columns[i + 1]);
        break;
      case CAN_KEY_BRAKE:
        msg.brakepedal = std::stoi(columns[i + 1]);
        break;
      case CAN_KEY_SHIFT:
        msg.driveshift = std::stoi(columns[i + 1]);
        break;
      default:
        std::cout << "Warning: unknown key : " << key << std::endl;
    }
  }

  return true;
}

三、vehicle_sender

  在以autoware平台应用车辆作为服务器端,Android设备作为客户端的基础上,vehicle_sender节点建立基本的TCP/IP通信机制,等待Android设备客户端向车辆服务器端发起通信请求,若请求则把服务器端数据发送给客户端。其中主要包括了main函数、vehicleCmdCallback函数、receiverCaller函数和sendCommand函数。

3.1 main函数

  main函数主要创建了vehicle_sender节点,然后创建sub这个subscriber和回调函数vehicleCmdCallback,随后创建一个线程,创建线程成功后,新创建的线程从指定的起始地址执行线程函数receiverCaller,而原来的线程则继续运行下一行代码,最后通过阻塞函数反复调用队列中可执行的回调函数vehicleCmdCallback。

#include <ros/ros.h>
#include "autoware_msgs/VehicleCmd.h"
#include "autoware_msgs/Gear.h"
#include <iostream>
#include <string>
#include <cstdio>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>

struct CommandData
{
  double linear_x;
  double angular_z;
  int modeValue;
  int gearValue;
  int lampValue;
  int accellValue;
  int brakeValue;
  int steerValue;
  double linear_velocity;
  double steering_angle;

  void reset();
};

enum class ZMPGear
{
  Drive = 1,
  Reverse = 2,
  Low = 3,
  Neutral = 4,
  Park = 5,
};

void CommandData::reset()
{
  linear_x = 0;
  angular_z = 0;
  modeValue = 0;
  gearValue = 0;
  lampValue = 0;
  accellValue = 0;
  brakeValue = 0;
  steerValue = 0;
  linear_velocity = -1;
  steering_angle = 0;
}

static CommandData command_data;

int main(int argc, char** argv)
{
  ros::init(argc, argv, "vehicle_sender");
  ros::NodeHandle nh;

  std::cout << "vehicle sender" << std::endl;
  ros::Subscriber sub = nh.subscribe("/vehicle_cmd", 1, vehicleCmdCallback);

  command_data.reset();

  pthread_t th;
  if (pthread_create(&th, nullptr, receiverCaller, nullptr) != 0)
  {
    std::perror("pthread_create");
    std::exit(1);
  }

  if (pthread_detach(th) != 0)
  {
    std::perror("pthread_detach");
    std::exit(1);
  }

  ros::spin();//阻塞,反复查看有无可执行的回调函数
  return 0;
}

3.2 vehicleCmdCallback函数

  vehicleCmdCallback函数主要是定义command_data的内容,这个command_data之后要从服务器端发送给客户端。

static void vehicleCmdCallback(const autoware_msgs::VehicleCmd& msg)
{
  command_data.linear_x = msg.twist_cmd.twist.linear.x;
  command_data.angular_z = msg.twist_cmd.twist.angular.z;
  command_data.modeValue = msg.mode;
  if (msg.gear_cmd.gear == autoware_msgs::Gear::DRIVE)
  {
    command_data.gearValue = static_cast<int>(ZMPGear::Drive);
  }
  else if (msg.gear_cmd.gear == autoware_msgs::Gear::REVERSE)
  {
    command_data.gearValue = static_cast<int>(ZMPGear::Reverse);
  }
  else if (msg.gear_cmd.gear == autoware_msgs::Gear::LOW)
  {
    command_data.gearValue = static_cast<int>(ZMPGear::Low);
  }
  else if (msg.gear_cmd.gear == autoware_msgs::Gear::NEUTRAL)
  {
    command_data.gearValue = static_cast<int>(ZMPGear::Neutral);
  }
  else if (msg.gear_cmd.gear == autoware_msgs::Gear::PARK)
  {
    command_data.gearValue = static_cast<int>(ZMPGear::Park);
  }

  if (msg.lamp_cmd.l == 0 && msg.lamp_cmd.r == 0)
  {
    command_data.lampValue = 0;
  }
  else if (msg.lamp_cmd.l == 1 && msg.lamp_cmd.r == 0)
  {
    command_data.lampValue = 1;
  }
  else if (msg.lamp_cmd.l == 0 && msg.lamp_cmd.r == 1)
  {
    command_data.lampValue = 2;
  }
  else if (msg.lamp_cmd.l == 1 && msg.lamp_cmd.r == 1)
  {
    command_data.lampValue = 3;
  }
  command_data.accellValue = msg.accel_cmd.accel;
  command_data.steerValue = msg.steer_cmd.steer;
  command_data.brakeValue = msg.brake_cmd.brake;
  command_data.linear_velocity = msg.ctrl_cmd.linear_velocity;
  command_data.steering_angle = msg.ctrl_cmd.steering_angle;
}

3.3 receiverCaller函数

  receiverCaller函数又创建了一个服务器端的流式套接字,同样通过socket函数创建服务器端的流式套接字,然后使用bind函数将该套接字和指定地址相连接,随后使用listen函数表示服务器已做好准备,可以通信,最后利用accept函数接纳客户端请求并返回客户端套接字标识,并把该标识作为参数传递给线程函数sendCommand。

static void* receiverCaller(void* unused)
{
  constexpr int listen_port = 10001;

  int sock = socket(AF_INET, SOCK_STREAM, 0);
  if (sock == -1)
  {
    std::perror("socket");
    return nullptr;
  }

  sockaddr_in addr;
  sockaddr_in client;
  socklen_t len = sizeof(client);

  std::memset(&addr, 0, sizeof(sockaddr_in));
  addr.sin_family = PF_INET;
  addr.sin_port = htons(listen_port);
  addr.sin_addr.s_addr = INADDR_ANY;

  int ret = bind(sock, (struct sockaddr*)&addr, sizeof(addr));
  if (ret == -1)
  {
    std::perror("bind");
    goto error;
  }

  ret = listen(sock, 20);
  if (ret == -1)
  {
    std::perror("listen");
    goto error;
  }

  while (true)
  {
    // get connect to android
    std::cout << "Waiting access..." << std::endl;

    int* client_sock = new int();
    *client_sock = accept(sock, reinterpret_cast<sockaddr*>(&client), &len);
    if (*client_sock == -1)
    {
      std::perror("accept");
      break;
    }

    std::cout << "get connect." << std::endl;

    pthread_t th;
    if (pthread_create(&th, nullptr, sendCommand, static_cast<void*>(client_sock)) != 0)
    {
      std::perror("pthread_create");
      break;
    }

    if (pthread_detach(th) != 0)
    {
      std::perror("pthread_detach");
      break;
    }
  }

error:
  close(sock);
  return nullptr;
}

3.4 sendCommand函数

  sendCommand函数主要使用write函数把服务器端的数据往客户端写入。

static void* sendCommand(void* arg)
{
  int* client_sockp = static_cast<int*>(arg);
  int client_sock = *client_sockp;
  delete client_sockp;

  std::ostringstream oss;
  oss << command_data.linear_x << ",";
  oss << command_data.angular_z << ",";
  oss << command_data.modeValue << ",";
  oss << command_data.gearValue << ",";
  oss << command_data.accellValue << ",";
  oss << command_data.brakeValue << ",";
  oss << command_data.steerValue << ",";
  oss << command_data.linear_velocity << ",";
  oss << command_data.steering_angle << ",";
  oss << command_data.lampValue;

  std::string cmd(oss.str());
  ssize_t n = write(client_sock, cmd.c_str(), cmd.size());
  /*
  ssize_t write(int filedes, const void *buf, size_t nbytes);
  返回值:写入文件的字节数(成功);-1(出错)
  write 函数向 filedes 中写入 nbytes 字节数据,数据来源为 buf 。
  返回值一般总是等于 nbytes,否则就是出错了。常见的出错原因是磁盘空间满了或者超过了文件大小限制。
  */

  if (n < 0)
  {
    std::perror("write");
    return nullptr;
  }

  if (close(client_sock) == -1)
  {
    std::perror("close");
    return nullptr;
  }

  std::cout << "cmd: " << cmd << ", size: " << cmd.size() << std::endl;
  return nullptr;
}
【AUTOSAR COM 2.通信协议栈进阶介绍】 com 通信栈 阅读详情

相关推荐

WSL2部署Apollo自动驾驶仿真平台的工程实践

WSL2(Windows Subsystem for Linux)是微软推出的轻量级Linux兼容层,其底层基于Hyper-V虚拟化技术,提供接近原生Linux的运行环境。在自动驾驶开发中,Apollo框架依赖严格的实时性、POSIX兼容性与GPU/NPU硬件直通能力,而WSL2通过内核定制、网络模式重构与内存盘优化等手段,可支撑高精度传感器仿真与全链路闭环验证。本文聚焦于Apollo11级工程部署,涵盖WSL2内核手动升级、Ubuntu 22.04.5 cloud image离线导入、dedicated网

weixin_30596343的博客 413

socketcan的测试例程

使用的是autoware源码中的socketcan程序驱动 发送例程 #include <socketcan/socket_can_receiver.hpp> #include <socketcan/socket_can_sender.hpp> #include <chrono> #include <memory> #include<io...

u012084226的博客 922

Autoware显示用户界面细节

Runtime Manager Runtime Manager是runtime_manager包中包含的Python脚本($HOME/Autoware/ros/src/util/packages/runtime_manager/scripts/runtime_manager_dalog.py)。可以通过以下命令启动: $ rosrun runtime_manager runtime_manager...

jianxuezixuan的博客 5119

Autoware.auto源码安装

Autoware是世界上第一个由Autoware基金会托管的用于自动驾驶汽车的“一体化”开源软件。基于 ROS 2 的 Autoware.Auto 项目是基于 ROS 1 的 Autoware.AI 项目的下一代继承者。

旧势力底层学习者 2248

AUTOWARE下底盘CAN通讯实现

[无人驾驶系列] autoware与底盘CAN通讯实现 本文介绍在使用autoware时,如何将autoware发送的消息传输给底盘,实现对线控底盘的控制。 准备工作: 1、系统具备ROS和autoware 2、根据之前的教程安装好了ros_canopen 1、订阅话题 话题:/vehicle_cmd 消息类型:autoware_msgs/VehicleCmd 在这个消息类型中有很多的数据,控制线控底盘仅需取x轴方向的线速度和z轴方向的角速度。 线速度(m/s)、角速度(rad/s) speed_ms =

weixin_51948102的博客 3738

Autoware源码分析——astar_avoid

概要 本文是对autoware中waypoint_planner功能包的相关文件组成和具体节点的分析。由于程序比较复杂,我认为还存在一些不完整的地方,之后也会继续分析,继续更新。有任何错误或不足之处,望指正! waypoint_planner功能包的概述及整体结构 Waypoint planner can be alternatively used to generate a set of waypoints to the destination. This package differs from la

One step, One punch 4163

CARLA仿真核心:世界与客户端的架构原理与工程实践

CARLA并非普通游戏引擎改件,而是一个面向自动驾驶研发的工业级闭环仿真平台。其运行本质依赖‘世界(World)’与‘客户端(Client)’这对核心概念构成的空间-控制二元体:世界是承载车辆、行人、天气等全状态的独立UE4渲染与物理进程;客户端则是具备连接管理、状态缓存和序列化代理能力的TCP会话中枢。理解二者间的契约关系——如单客户端单世界绑定、同步模式下server时钟主权、传感器生命周期强绑定——是规避连接超时、Actor失效、数据丢失等高频问题的技术前提。本文基于CARLA 0.9.15 LTS实

davy57345的博客 536

ubuntu18.04 ros melodic Autoware1.14源码编译安装(完整版,全过程)

查看推荐显卡找到recommended推荐的版本本机是470版本如果安装失败,需要在安装之前进行指令。

justinyjf的博客 3433

技术人择业指南:互联网、芯片、通信、车企、国企技术栈与成长路径深度对比

在技术领域,编程语言和工具链是工程师的核心技能,其选择与应用深度直接影响职业发展路径。从原理上看,C++、Python、Java等语言在不同行业扮演着截然不同的角色,这背后是各领域对性能、可靠性、迭代速度和安全规范等技术价值的差异化追求。例如,互联网行业追求高并发与快速迭代,而芯片设计则聚焦于PPA(性能、功耗、面积)和极致的可靠性。这些技术差异最终映射到具体的应用场景:互联网构建分布式服务,芯片设计硬件IP核,通信优化协议栈,车企开发车规级嵌入式系统,国企则专注于大型系统工程。对于面临**行业选择困境**

weixin_30784501的博客 475

IEEE802.11bf: WLAN Sensing Standard

下表为IEEE802.11bf 定义的使用情境 (Use Cases)

qinsd19的博客 1057

不用买VR设备!用UE5内置模拟器开发虚拟现实项目的完整教程

本文提供了一套无需购买昂贵VR硬件的虚拟现实开发最佳实践方案。详细介绍了如何利用虚幻引擎5内置的XR模拟器,从零开始配置开发环境、模拟操作、构建场景并实现核心交互逻辑。通过这套纯软件方案,开发者可以高效学习VR开发核心,大幅降低入门门槛和成本。

weixin_29019241的博客 621

Autoware 技术代码解读(三)

全局路径规划在车辆启动或重启的时候被确定,局部路径根据车辆的状态进行实时更新。例如,如果车辆在障碍物前或停止线前,车辆状态变为“stop”,那么车辆的速度就被规划为0。Autoware分为Autoware.core 和Autoware.universe 两个版本,其中Autoware.core 是稳定版,Autoware.universe 是开发者版本。检测模块使用摄像头和激光雷达,结合传感器融合算法和深度学习网络进行目标检测。的整体框架和模块主要包括感知和规划两大部分。

lovely_yoshino的博客 1万+

分析以太网帧结构_100G以太网接口调试初体验

是的,标题没有多敲一个“0”,明人不说暗话,就是在FPGA上实现100G以太网接口设计。正好参与了实验室某项目中的100G接口的调试,早想写个专栏文章了,由于调试过程中发现了一些硬件问题,导致迟迟没有往前推进,现在问题解决了之后可以简单分享一下100G以太网接口的调试初体验了~(好像DDR4读写测试系列文章还没搞完 )这篇初体验暂时不分多个章节了,基本设计流程与之前发布的“10G以太网接口”系列文...

weixin_39574050的博客 1603

Autoware源码总结

AutowareAutoware的整体架构如下图,主要包括传感器sensing、高精地图map data、车辆接口vehicle interface、感知perception(动态障碍物检测detection、跟踪tracking、预测prediction;

weixin_41245988的博客 1274

Openwork 入门指南:开源 AI 桌面助手快速上手

Openwork是一款开源的AI桌面代理工具,提供类似Claude Cowork的免费替代方案。它支持本地运行,确保隐私安全,并能高效处理文件整理、文档读写等任务。支持多种AI模型,通过MIT许可完全免费。安装简单,仅需运行命令行或下载桌面版,需配置API Key后即可使用。操作直观,支持多步骤规划和权限控制,适合自动化重复工作。目前主要支持macOS,未来将扩展至Windows/Linux。建议从简单任务入手,逐步掌握这一高效的数字助手工具。

3803

0018-量化第五天:QMT—日内回转回测注释

日内做T算法框架

m0_68750710的博客 4149

从零开始理解Autoware:ROS 2通信接口设计与实现

你是否在使用Autoware时遇到过节点通信异常?是否想知道自动驾驶系统中传感器数据如何高效传输?本文将带你深入了解Autoware基于ROS 2的通信协议设计,掌握接口使用方法,解决实际开发中的数据传输问题。 读完本文你将学到: - Autoware通信架构的核心组件 - ROS 2接口在自动驾驶中的应用场景 - 如何自定义消息类型实现模块间通信 - 通信性能优化的实用技巧 ## Autow

gitblog_00135的博客 814

保姆级教程:用sw_urdf_exporter插件将Solidworks机械臂模型转为ROS可用的URDF

本文提供了一份详细的保姆级教程,指导用户使用sw_urdf_exporter插件将Solidworks机械臂模型转换为ROS可用的URDF文件。从环境准备、插件配置到模型预处理和关键参数设置,再到ROS环境集成测试和性能优化,全面覆盖了机械臂URDF转换的全流程。特别适合机器人工程师和ROS开发者快速实现Solidworks模型到ROS环境的无缝迁移。

weixin_29300931的博客 400

Autoware 1.14 源码编译+感知SSD+YOLOv3+Point Pillar模块运行(步骤+报错+解决方法记录)

Autoware 1.14 源码编译+感知SSD+YOLOv3+Point Pillar模块运行(步骤+报错解决汇总)显示rostopic之间的关系图:range_vision_fusion_01:安装caffe(其实貌似不需要安装,ssdcaffe会安装caffe的)一条命令装caffe测试:运行SSD1. 安装/编译ssdcaffe其中: 如何更改Makefile.config:SSD的caffe在编译时,成功之后,给出的命令需要逐步执行:生成一个适合你的列表创建一个表格设定内容居中、居左、居右Smar

tiatiatiatia的博客 1万+

【算能】TPU 指令集架构深度解析

TPU指令集以领域专用性与数据流驱动为核心,通过精简指令集、硬件固化逻辑与高并行架构,实现张量计算的极致性能。开发者通常无需直接编写指令,但理解其设计原理对性能调优(如内存布局优化、指令调度)至关重要。算能TPU通过开放指令描述与编译器工具链,为垂直行业提供了更高灵活性的定制空间,而谷歌TPU则依赖编译器的全自动化优化,更适合大规模标准模型部署。未来,随着稀疏计算、光子计算等技术的引入,TPU指令集将进一步向高能效与高泛化性演进。

浩瀚之水的专栏 1183

17track单号查询逆向分析

这就说明该网站很有可能存在其它检测点,而检测本地替换最大可能的就是检测了代码格式化或者代码长度。也一样,要么导致网站卡死,要么会导致网页加载异常,这里可能就难到不少人了,让我们一步一步解决。而这个站主要是检测了代码格式化,无限循环检测造成了网站卡死,经多次测试发现检测点共三处。方法中代码很长,但是经调试后发现,它其实只返回一个固定的值,这里直接写死即可。同样的,虽然看起来很长,但是跟过之后就会发现流程很简单,这里主要就是对。追加了一些值,是一条很长的语句,整理代码。,核心流程也很简单,稍微跟一下就出来了,

pyzzd的博客 1624

(十二)学习笔记autoware源码core_planning(velocity_set)

1.libvelocity_set #include <waypoint_planner/velocity_set/libvelocity_set.h> // extract edge points from zebra zone std::vector<geometry_msgs::Point> removeNeedlessPoints(std::vector<geometry_msgs::Point> &area_points) { area_poi

weixin_44439003的博客 1985

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

autoware.universe源码略读(3.5)--perception:compare_map_segmentation/crosswalk_traffic_light_estimator

autoware.universe感知模块中的地面分割以及人行道红绿灯的状态判断

weixin_45432823的博客 1257

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

【自动驾驶】Autoware.Universe 开发概述 ② ( Autoware.Universe 安装简介 | Docker 安装方式 | 源码安装方式 | 数字孪生仿真 CARLA 仿真 )

一、Autoware.Universe 安装简介 1、Autoware Universe Docker 安装方式 2、Autoware Universe 源码 安装方式 3、Docker 与 源码 安装方式对比 二、Autoware.Universe 仿真 / 评估 教程 1、仿真概述 2、数字孪生仿真 - CARLA 仿真

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 3268

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

(三)学习笔记autoware源码core_planning(waypoint_loader)

1.waypoint_loader_node #include <ros/ros.h> #include "waypoint_loader_core.h" int main(int argc, char** argv) { ros::init(argc, argv, "waypoint_loader"); waypoint_maker::WaypointLoaderNode wln; wln.run(); return 0; } /*首先是其main函数,main函数在

weixin_44439003的博客 1297

autoware.universe源码略读(2.1)--localization:ekf_localizer

autoware.universe定位模块中的EKF部分

weixin_45432823的博客 2247
上一篇: 为什么要用Linux系统?
下一篇: 基于Python的RRT算法实现
Fermans
博客等级 码龄9年 1346粉丝 5原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值