Linux下CAN通信C++类封装

最近项目需求在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)

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux C Socket封装是一种将Socket编程相关的方法进行封装,以便更方便地使用Socket进行网络通信的技术。通过封装,我们可以更加简洁地编写网络通信的代码,并提供更高级别的接口来处理常见的网络操作,从而提高开发效率。 封装可以包含一些建立连接、发送和接收数据的方法,以及处理错误和异常的功能。通过封装好的接口,我们可以通过调用中的方法来创建Socket、绑定地址和端口、监听和接受客户端连接、发送和接收数据等操作。这样,我们可以将底层Socket编程的复杂性隐藏起来,让用户更加关注业务逻辑的实现。 封装还可以提供一些高级功能,如处理多个连接的并发处理、实现客户端和服务器端之间的数据交互等。它们会提供一些与网络通信相关的事件处理函数,用来处理不同情况下的事件,如读取数据、写入数据、连接断开等。 使用封装可以大大简化Socket编程的过程,减少代码的冗余和复杂性。封装会将底层的Socket接口进行封装,提供更加易用和高效的功能接口,使得开发人员可以更加专注于业务逻辑的实现,提高开发效率。 总之,封装是一种将Socket编程相关的方法进行封装,提供更加简洁和高级的接口来处理网络通信的技术。它可以简化Socket编程的过程,提高开发效率,同时还可以提供一些高级功能来处理并发、数据交互等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值