最近项目需求在Linux下使用CAN通信进行数据报文收发,目前现成的可用库太少了,所以自己封装一个。
思路就是简单的 SocketCAN,将发送和接收放到两个子线程里。
目前协议部分还没有优化,后续会优化八个字节以上的报文发送协议。
//
// Created by zty19 on 24-5-26.
//
#ifndef CANMOOS_CANMOOS_H
#define CANMOOS_CANMOOS_H
#include <cstring>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <unistd.h>
#include <atomic>
#include <fcntl.h>
#include <sys/select.h>
class CANInterface {
public:
//构造函数
explicit CANInterface() noexcept = default; //默认构造函数
//值传递构造
explicit CANInterface(std::string interface_name_) noexcept:
interface_name(std::move(interface_name_)), socket_fd(-1) {}
//C风格字符串构造
explicit CANInterface(const char *interface_name_) noexcept:
interface_name(std::string(interface_name_)), socket_fd(-1) {}
//指定线程构造
explicit CANInterface(std::string interface_name_, std::thread read_, std::thread write_) noexcept:
interface_name(std::move(interface_name_)), can_read_thread(std::move(read_)),
can_write_thread(std::move(write_)), socket_fd(-1) {}
//禁止拷贝
CANInterface(const CANInterface &) = delete;
CANInterface &operator=(CANInterface &) = delete;
//禁止移动构造
CANInterface(CANInterface &&CANInterface_) noexcept = delete;
//析构函数
~CANInterface() {
stop();
if (socket_fd != -1)
close(socket_fd);
}
public:
//成员函数
bool start(); //初始化can文件描述符
void stop(); //释放线程
void send(const struct can_frame &send_frame); //将can消息帧写入发送消息队列
private:
//CAN硬件设置
std::string interface_name; //接口名可指定类似CAN0 || CAN1等
int socket_fd = -1; //绑定的套接字文件描述符
std::deque<struct can_frame> write_queue; //写入消息队列,等待写入线程
//CAN线程管理
std::thread can_read_thread;
std::thread can_write_thread;
std::mutex msg_queue_mutex;
std::condition_variable cv;
bool exit_flag = false; //CAN写线程退出标志位
bool exit_flag_ = exit_flag; //CAN读线程退出标志位
void readCan_thread(); //CAN读线程
void writeCan_thread(); //CAN写线程
};
bool CANInterface::start() {
if (exit_flag) return false;
//创建套接字
if ((socket_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
perror("Socket create fail.\n");
return false;
}
//指定CAN接口
struct ifreq ifr{};
strcpy(ifr.ifr_name, interface_name.c_str());
ioctl(socket_fd, SIOCGIFINDEX, &ifr);
//配置地址
struct sockaddr_can addr{};
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
//套接字绑定至硬件接口
if (bind(socket_fd, reinterpret_cast<sockaddr *>(&addr), sizeof(addr))) {
perror("Bind fail.\n");
return false;
}
/* int flags = fcntl(socket_fd, F_GETFL, 0);
fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK); //开启非阻塞IO*/
//开启收发线程
can_read_thread = std::thread(&CANInterface::readCan_thread, this);
can_write_thread = std::thread(&CANInterface::writeCan_thread, this);
return true;
}
void CANInterface::stop() {
{
std::unique_lock<std::mutex> lock(msg_queue_mutex);
exit_flag = true;
cv.notify_all();
}
exit_flag_ = true;
if (can_read_thread.joinable())
can_read_thread.join();
if (can_write_thread.joinable())
can_write_thread.join();
}
void CANInterface::send(const struct can_frame &send_frame) {
{
std::lock_guard<std::mutex> lock(msg_queue_mutex);
write_queue.emplace_back(send_frame);
write_queue.shrink_to_fit();
}
cv.notify_all();
}
void CANInterface::readCan_thread() {
struct can_frame read_buff{};
while (true) {
if (exit_flag_) break;
int nbytes = read(socket_fd, &read_buff, sizeof(struct can_frame));
if (nbytes > 0) {
printf("CAN ID: 0X%X DLC: %d \n", read_buff.can_id, read_buff.can_dlc);
printf("0X");
for (const auto &i: read_buff.data) printf("%X ", i);
printf("\n");
}
}
}
void CANInterface::writeCan_thread() {
while (true) {
struct can_frame frame{};
{
std::unique_lock<std::mutex> lock(msg_queue_mutex);
cv.wait(lock, [this] { return !write_queue.empty() || exit_flag; });
if (write_queue.empty() && exit_flag) break;
if (!write_queue.empty()) {
frame = write_queue.front();
write_queue.pop_front();
write_queue.shrink_to_fit();
}
}
if (frame.can_dlc != 0) {
int nbytes = write(socket_fd, &frame, sizeof(struct can_frame));
if (nbytes < 0) {
perror("CAN write error.\n");
}
//std::cout << "Send CAN frame : ID = " << std::hex << frame.can_id << std::dec << std::endl;
}
}
}
#endif //CANMOOS_CANMOOS_H
测试代码如下:
#include <iostream>
#include "CANMOOS.h"
int main() {
CANInterface can("vcan0");
if (!can.start()) {
std::cerr << "Failed to start CAN interface" << std::endl;
return 1;
}
struct can_frame frame{};
frame.can_id = 0x123;
frame.can_dlc = 8;
std::memcpy(frame.data, "\x01\x02\x03\x04\x05\x06\x07\x08", 8);
for (int i = 0; i < 1000000; ++i) {
can.send(frame);
}
//can.stop();
return 0;
}
cmake:
cmake_minimum_required(VERSION 3.16)
project(CANMOOS)
#使用C11标准
set(CMAKE_CXX_STANDARD 11)
#编译标志加入多线程
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
#添加可执行程序
add_executable(CANMOOS main.cpp
CANMOOS.h)