菜单

示例程序

程序简要说明

示例程序主要为通过绑定 RT-net 网卡对数据进行收发操作,采用 linux 通用的 socket 接口
示例程序由回环服务器和客户端两部分组成,通过两套不同的数据发送接收接口完成回环功能

主要通信代码示例

注意本示例程序采用基于 raw_socket 网络链路层协议通信,linux 运行时需添加 root 权限,否则程序会异常退出(无法创建 raw_socket 文件描述符),echo service 接收并过滤特殊 client 发来的报文,并回传对应特殊报文供 client 端解析,上图中 client 通过 service 回传的报文解析出对应的协议内容为 1,2,3,4 依次递增数
Services

c 复制代码
static bool FilterData(uint8_t* data, ssize_t size) {
  if (size < 16) {
    return false;
  }
  return data[12] == 0x88 && data[13] == 0x8e && data[14] == 0x01 && data[15] == 0x01;
}

static void* Services(void* args) {
  (void)args;
  // args 为入参,实际为 rtnet 网卡名称,类型为字符数组(char*)
  char* networkCard = (char*)args;
    
  // 必要数据变量准备 如:sockfd, sockaddr_ll 结构体等
  uint8_t mac_src[ETH_ALEN] = {0};
  uint8_t mac_dst[ETH_ALEN] = {0};
  ssize_t len = 0;
  int sockfd = -1;
  uint8_t buffer[K_MAX_BUFFER_SIZE] = {0};
  struct sockaddr_ll sll;

  // 通过 if_nametoindex 系统调用可以通过 rtnet 网卡名称返回正确的网卡索引并保存
  memset(&sll, 0, sizeof(struct sockaddr_ll));
  sll.sll_ifindex = (int)if_nametoindex(networkCard);

  // 创建 raw socket 文件描述符,用于发送和接受数据
  sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
  if (sockfd < 0) {
    perror("sock fd create failed");
    return NULL;
  }

  while (!g_exit_falg) {
    struct sockaddr_ll tmp_sll;
    uint32_t length;
    // 接收 raw socket 数据,并存入 buffer 中,并缓存此次接受数据的 网卡属性到 tmp_sll 结构当中
    len = recvfrom(sockfd, buffer, K_MAX_BUFFER_SIZE, K_RECV_FLAGS, (struct sockaddr *)&tmp_sll, &length);
    if (len < 0) {
      // 处理系统报错
      perror("recvfrom failed");
      break;
    }

    // 过滤数据,确定可以解析自定义类型数据
    if (!FilterData(buffer, len)) {
      continue;
    }
    memset(mac_src, 0, ETH_ALEN);
    memset(mac_dst, 0, ETH_ALEN);
    for (int i = 0; i < ETH_ALEN; ++i) {
      mac_src[i] = buffer[i];
      mac_dst[i] = buffer[i + ETH_ALEN];
    }
    // 修改数据包
    FixPacket(mac_src, mac_dst);
    // 将修改的数据包完整回传至 client 
    len = sendto(sockfd, s_packet, len, K_SEND_FLAGS, (struct sockaddr*)&tmp_sll, sizeof(tmp_sll));
    if (len < 0) {
      perror("sendto failed");
      break;
    }
  }
  close(sockfd);
}

service 启动参数为实时网卡名称

shell 复制代码
    sudo ./rtnet_raw_socket rteth0    

运行结果:

terminal 复制代码
root@sinsegye-sx21:/home/sinsegye/projects/rtnet# ./rtnet_service
args is Network Card name.
        example: ./rtnet_service rteth0
root@sinsegye-sx21:/home/sinsegye/projects/rtnet# ./rtnet_service rteth0
will pthread_create()
pthread_join() before

Client

c 复制代码
static void* Client(void* args) {
  (void)args;
  // args 为入参,实际为 rtnet 网卡名称,类型为字符数组(char*)
  char* networkCard = (char*)args;
    
  // 必要数据变量准备 如:sockfd, sockaddr_ll 结构体等
  ssize_t len = 0;
  int sockfd = -1;
  uint8_t buffer[K_MAX_BUFFER_SIZE] = {0};
  struct sockaddr_ll sll;

  memset(&sll, 0, sizeof(struct sockaddr_ll));
  // 通过 if_nametoindex 系统调用可以通过 rtnet 网卡名称返回正确的网卡索引并保存
  sll.sll_ifindex = (int)if_nametoindex(network_card);

  // 创建 raw socket 文件描述符,用于发送和接受数据
  sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
  if (sockfd < 0) {
    perror("sock fd create failed");
    return;
  }
  FixPacket(s_mac_src, s_mac_dst);

  while (true) {
    len = sendto(sockfd, s_packet, K_MAX_PACKET_SIZE, K_SEND_FLAGS, (struct sockaddr*)&sll, sizeof(sll));
    if (len < 0) {
      perror("sendto failed");
      break;
    }

    while (true) {
      // 接收 raw socket 数据,并存入 buffer 中
      len = recvfrom(sockfd, buffer, K_MAX_BUFFER_SIZE, K_RECV_FLAGS, NULL, NULL);
      if (len < 0) {
        perror("recvfrom failed");
        return;
      }
      // 过滤数据,确定可以解析自定义类型数据
      if (!FilterData(buffer, len)) {
        continue;
      }
      // 解析数据
      ParseData(buffer, len);
      break;
    }
    // 控制发送频率
    sleep(1);
  }
}

// main 入口函数将目标机器的 mac 地址以及本机的 mac 地址以及本机的 rtnet 网卡名称依次传入程序
int main(int argc, char** argv) {
  if (argc < 4) {
    Help();
    return 0;
  }

  // 解析 mac 地址
  if (!ParseAddress(argv[1], s_mac_src)) {
    printf("parse src mac failed, src mac %s error\n", argv[1]);
    return 0;
  }
  if (!ParseAddress(argv[2], s_mac_dst)) {
    printf("parse dst mac failed, src mac %s error\n", argv[2]);
    return 0;
  }
  
  // 其他处理 ...

  return 0;
}

client 启动参数分别为 本机 mac 地址,对端 services mac 地址,本机用于网络通信的网卡名称

shell 复制代码
    sudo ./rtnet_client c8:6b:bc:80:0a:ce C8:6B:BC:80:08:EE  enp1s0f2

运行结果:

terminal 复制代码
sinsegye@sinsegye-sx21:~/projects/rtnet$ sudo ./rtnet_client c8:6b:bc:80:0a:ce c8:6b:bc:80:08:ee enp1s0f2
mac src : c8:6b:bc:80:0a:ce
mac dst : c8:6b:bc:80:08:ee
sendto success
Dst mac is "c8:6b:bc:80:0a:ce", Src mac is "c8:6b:bc:80:08:ee", len:4, times:   1
sendto success
Dst mac is "c8:6b:bc:80:0a:ce", Src mac is "c8:6b:bc:80:08:ee", len:4, times:   2
sendto success
Dst mac is "c8:6b:bc:80:0a:ce", Src mac is "c8:6b:bc:80:08:ee", len:4, times:   3
sendto success
Dst mac is "c8:6b:bc:80:0a:ce", Src mac is "c8:6b:bc:80:08:ee", len:4, times:   4
sendto success
Dst mac is "c8:6b:bc:80:0a:ce", Src mac is "c8:6b:bc:80:08:ee", len:4, times:   5
sendto success
Dst mac is "c8:6b:bc:80:0a:ce", Src mac is "c8:6b:bc:80:08:ee", len:4, times:   6
sendto success
最近修改: 2025-07-24