canutils-4.0.6-candump源码解析

一、candump源码解析

#include <can_config.h>

#include <errno.h>
#include <getopt.h>
#include <libgen.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <stdint.h>

#include <net/if.h>

#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h>

#include <linux/can.h>
#include <linux/can/raw.h>

extern int optind, opterr, optopt;

static int  s = -1; 
static int  running = 1;

enum {
    VERSION_OPTION = CHAR_MAX + 1,
    FILTER_OPTION,
};

static void print_usage(char *prg)
{
        fprintf(stderr, "Usage: %s [<can-interface>] [Options]\n"
        "Options:\n"
        " -f, --family=FAMILY\t"    "protocol family (default PF_CAN = %d)\n"//指定协议族
        " -t, --type=TYPE\t"        "socket type, see man 2 socket (default SOCK_RAW = %d)\n"//指定socket类型
        " -p, --protocol=PROTO\t"   "CAN protocol (default CAN_RAW = %d)\n"//指定特殊协议
        "     --filter=id:mask[:id:mask]...\n"//指定id和验收屏蔽
        "\t\t\t"            "apply filter\n"
        " -h, --help\t\t"       "this help\n"//使用说明  这里注释错误,-h无法被解析请使用长选项
        " -o <filename>\t\t"        "output into filename\n" //将输出重定向到文件
        " -d\t\t\t"         "daemonize\n"//后台运行,保持当前目录不变,将标准输入、标准输出和标准错误重定向到/dev/null;
        "     --version\t\t"        "print version information and exit\n",//查看版本号
        prg, PF_CAN, SOCK_RAW, CAN_RAW);
}

static void sigterm(int signo)
{
    running = 0;
}

static struct can_filter *filter = NULL;
static int filter_count = 0;

#if 0
/usr/include/linux/can.h
/**
 * struct can_filter - CAN ID based filter in can_register().
 * @can_id:   relevant bits of CAN ID which are not masked out.没有被屏蔽的CAN ID的相关位
 * @can_mask: CAN mask (see description)
 *
 * Description:
 * A filter matches, when
 *
 *          <received_can_id> & mask == can_id & mask
 *
 * The filter can be inverted (CAN_INV_FILTER bit set in can_id) or it can
 * filter for error message frames (CAN_ERR_FLAG bit set in mask).
 */过滤器可以倒置(CAN_INV_FILTER位设置在can_id中)或它可以过滤错误消息帧(CAN_ERR_FLAG位设置在mask中)struct can_filter {
    canid_t can_id;
    canid_t can_mask;
};
#endif

int add_filter(u_int32_t id, u_int32_t mask)
{
    filter = realloc(filter, sizeof(struct can_filter) * (filter_count + 1));//动态调整内存,
    if(!filter)
        return -1;

    filter[filter_count].can_id = id;
    filter[filter_count].can_mask = mask;
    filter_count++;

    printf("id: 0x%08x mask: 0x%08x\n",id,mask);
    return 0;
}

#define BUF_SIZ (255)

int main(int argc, char **argv)
{
    //下面这三个结构体在cansend解析中分析过
    struct can_frame frame;
    struct ifreq ifr;
    struct sockaddr_can addr;
    FILE *out = stdout;
    char *interface = "can0";
    char *optout = NULL;
    char *ptr;
    char buf[BUF_SIZ];
    int family = PF_CAN, type = SOCK_RAW, proto = CAN_RAW;
    int n = 0, err;
    int nbytes, i;
    int opt, optdaemon = 0;
    uint32_t id, mask;

    signal(SIGPIPE, SIG_IGN);

    struct option       long_options[] = {// 长选项 
        { "help", no_argument, 0, 'h' },//--help不需要参数
        { "family", required_argument, 0, 'f' },
        { "protocol", required_argument, 0, 'p' },
        { "type", required_argument, 0, 't' },
        { "filter", required_argument, 0, FILTER_OPTION },//--filter需要参数
        { "version", no_argument, 0, VERSION_OPTION},//--version不需要参数
        { 0, 0, 0, 0},
    };
    //短选项-f -t -p -o 都必须使用参数 -d不需要参数
    while ((opt = getopt_long(argc, argv, "f:t:p:o:d", long_options, NULL)) != -1) {
        switch (opt) {
        case 'd':
            optdaemon++; //后台运行,保持当前目录不变,将标准输入、标准输出和标准错误重定向到/dev/null;
            break;

        case 'h'://打印使用说明
            print_usage(basename(argv[0]));
            exit(0);

        case 'f'://指定协议族
            family = strtoul(optarg, NULL, 0);
            break;

        case 't'://执行协议类型
            type = strtoul(optarg, NULL, 0);
            break;

        case 'p'://指定特殊协议
            proto = strtoul(optarg, NULL, 0);
            break;

        case 'o'://指定保存输出信息的文件
            optout = optarg;
            break;

        case FILTER_OPTION:  //指定id和mask 将其保存到can_filter
            ptr = optarg;
            while(1) {
                id = strtoul(ptr, NULL, 0);//解析ID
                ptr = strchr(ptr, ':');//得到“:”字符地址
                if(!ptr) {
                    fprintf(stderr, "filter must be applied in the form id:mask[:id:mask]...\n");
                    exit(1);
                }
                ptr++;//得到“:”字符后mask字符首地址
                mask = strtoul(ptr, NULL, 0);//得到mask
                ptr = strchr(ptr, ':');//同上,如果存在第二个: 说明是双滤波
                add_filter(id,mask);//把得到的id和mask添加到can_filter结构体
                if(!ptr)//说明没有后续的id和mask
                    break;
                ptr++;//得到第二个id字符首地址
            }
            break;

        case VERSION_OPTION://版本号
            printf("candump %s\n",VERSION);
            exit(0);

        default:
            fprintf(stderr, "Unknown option %c\n", opt);
            break;
        }
    }

    if (optind != argc)  //代表指定了设备
        interface = argv[optind];//第一个不是选项的参数就是设备名,

    printf("interface = %s, family = %d, type = %d, proto = %d\n",
           interface, family, type, proto);

    if ((s = socket(family, type, proto)) < 0) {
        perror("socket");
        return 1;
    }

    addr.can_family = family;
    strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
    if (ioctl(s, SIOCGIFINDEX, &ifr)) {//获取接口索引,需要通过name从网络名字空间进行匹配因此需要提前设置好name
        perror("ioctl");
        return 1;
    }
    addr.can_ifindex = ifr.ifr_ifindex;

    if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind");
        return 1;
    }

    if (filter) {
        //SOL_CAN_RAW的用法 见官方文档./Documentation/networking/can.rst
        //此滤波为软件滤波,因此可以设置任意个
        if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, filter,//设置接受滤波器
                   filter_count * sizeof(struct can_filter)) != 0) {
            perror("setsockopt");
            exit(1);
        }
    }

    if (optdaemon)
        daemon(1, 0);//后台运行,保持当前目录不变,将标准输入、标准输出和标准错误重定向到/dev/null;
    else {
        signal(SIGTERM, sigterm);//更改信号处理函数用来停止接收
        signal(SIGHUP, sigterm);
    }

    if (optout) {
        out = fopen(optout, "a");
        if (!out) {
            perror("fopen");
            exit (EXIT_FAILURE);
        }
    }

#if 0
/**
 * struct can_frame - basic CAN frame structure
 * @can_id:  CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
 * @can_dlc: frame payload length in byte (0 .. 8) aka data length code
 *           N.B. the DLC field from ISO 11898-1 Chapter 8.4.2.3 has a 1:1
 *           mapping of the 'data length code' to the real payload length
 * @__pad:   padding
 * @__res0:  reserved / padding
 * @__res1:  reserved / padding
 * @data:    CAN frame payload (up to 8 byte)
 */
struct can_frame {
    canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */
    __u8    can_dlc; /* frame payload length in byte (0 .. CAN_MAX_DLEN) */
    __u8    __pad;   /* padding */
    __u8    __res0;  /* reserved / padding */
    __u8    __res1;  /* reserved / padding */
    __u8    data[CAN_MAX_DLEN] __attribute__((aligned(8)));
};

#endif
    while (running) {
        if ((nbytes = read(s, &frame, sizeof(struct can_frame))) < 0) {
            perror("read");
            return 1;
        } else {
            if (frame.can_id & CAN_EFF_FLAG)//扩展帧
                n = snprintf(buf, BUF_SIZ, "<0x%08x> ", frame.can_id & CAN_EFF_MASK);
            else//标准帧
                n = snprintf(buf, BUF_SIZ, "<0x%03x> ", frame.can_id & CAN_SFF_MASK);

            n += snprintf(buf + n, BUF_SIZ - n, "[%d] ", frame.can_dlc);//数据长度
            for (i = 0; i < frame.can_dlc; i++) {
                n += snprintf(buf + n, BUF_SIZ - n, "%02x ", frame.data[i]);//数据
            }
            if (frame.can_id & CAN_RTR_FLAG)//远程帧
                n += snprintf(buf + n, BUF_SIZ - n, "remote request");

            fprintf(out, "%s\n", buf);//输出到标准输出或者文件

            do {
                err = fflush(out);//刷新输出缓冲区
                if (err == -1 && errno == EPIPE) {
                    err = -EPIPE;
                    fclose(out);
                    out = fopen(optout, "a");
                    if (!out)
                        exit (EXIT_FAILURE);
                }
            } while (err == -EPIPE);

            n = 0;
        }
    }

    exit (EXIT_SUCCESS);
}

二、candump使用说明

candump can0/1/2/3

  1.  -f   <FAMILY>          指定协议族 default PF_CAN
     --family=FAMILY
    
  2.  -t   <TYPE>             指定socket类型
     --type=TYPE
    
  3.  -p  <PROTO>           指定特殊协议
     --protocol=PROTO
    
  4.  --filter=id:mask[:id:mask]...  指定id和验收屏蔽,软件滤波,因此可以设置任意个
    
  5.  --help  使用说明
    
  6.  -o	<filename> 将输出重定向到文件<filename>
    
  7.  -d 后台运行,保持当前目录不变,将标准输入、标准输出和标准错误重定向到/dev/null;
    
  8.  --version  查看版本号
    

注:使用SIGTERM 和SIGHUP信号可以停止程序接收数据,使程序结束退出,否则会一直循环接收

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值