### 程序简要说明

示例程序主要为通过绑定 `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
  ```