Linux环境下C语言实现ping命令

Linux环境下C语言实现ping命令

涉及的知识点

Linux信号量的使用
SIGALRM信号是操作系统中的其中一个信号。他的作用是设置进程隔多久后会收到一个SIGALRM信号

#include <unistd.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <signal.h> void handle_alarm() 
{ exit(0); 
} 
int main(int argc, char *argv[]) 
{ signal(SIGALRM, handle_alarm); alarm(10); while(1) {} 
} 

进程在10秒或10秒之后触发SIGALRM信号,然后执行信号处理函数,最后退出。

setitimer函数的使用
setitimer函数用于设置定时器,并指定定时器到期后所产生的信号行为。其函数原型如下:

#include <sys/time.h>//int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
/*
该函数接受三个参数:which:指定定时器类型,可以是以下两个值之一:ITIMER_REAL:真实时间定时器,使用系统实时时间,到期后将发送SIGALRM信号。
ITIMER_VIRTUAL:虚拟时间定时器,使用进程运行时间,到期后将发送SIGVTALRM信号。
ITIMER_PROF:以两者之和作为时间基准,到期后将发送SIGPROF信号。
new_value:一个指向struct itimerval结构体的指针,用于指定新的定时器值(启动时间和间隔时间)。it_value字段表示定时器首次到期的时间间隔。
it_interval字段表示定时器循环触发的时间间隔。
old_value:一个指向struct itimerval结构体的指针,用于获取旧的定时器值,即之前设置的定时器值。如果不需要获取旧的定时器值,则可以传入NULL。调用setitimer函数后,会根据new_value所指定的定时器值来启动或修改定时器。到期后,系统会发送相应的信号,并根据信号处理机制执行相应的操作。可以使用signal函数或sigaction函数来捕获并处理定时器到期产生的信号。以下是一个示例代码,演示如何使用setitimer函数设置一个定时器:
*/#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>void timer_handler(int signum) {printf("Timer expired!\n");
}int main() {struct itimerval timer;// 设置首次触发定时器为1秒后,定时器到期后每10秒触发一次timer.it_interval.tv_sec = 10;timer.it_interval.tv_usec = 0;timer.it_value.tv_sec = 1;timer.it_value.tv_usec = 0;// 注册信号处理函数signal(SIGALRM, timer_handler);// 启动定时器setitimer(ITIMER_REAL, &timer, NULL);// 死循环,等待定时器到期while (1);return 0;
}
/*
上述代码中,我们设置了一个定时器,其初次到期时间为1秒后,之后每10秒触发一次。当定时器到期时,
会触发SIGALRM信号,并通过注册的信号处理函数timer_handler进行处理。在示例代码中,我们只是简单地打印一条信息并没有做其他处理。
*/
#define ICMP_ECHOREPLY 0 /* Echo应答*/  
#define ICMP_ECHO   /*Echo请求*/  #define BUFSIZE 1500    /*发送缓存最大值*/  
#define DEFAULT_LEN 56  /**ping消息数据默认大小/  /*数据类型别名*/  
typedef unsigned char u8;  
typedef unsigned short u16;  
typedef unsigned int u32;  /*ICMP消息头部*/  
struct icmphdr 
{  u8 type;     /*定义消息类型*/  u8 code;    /*定义消息代码*/  u16 checksum;   /*定义校验*/  union{  struct{  u16 id;  u16 sequence;  }echo;  u32 gateway;  struct{  u16 unsed;  u16 mtu;  }frag; /*pmtu实现*/  }un;  /*ICMP数据占位符*/  u8 data[0];  
#define icmp_id un.echo.id  
#define icmp_seq un.echo.sequence  
};  
#define ICMP_HSIZE sizeof(struct icmphdr)  
/*定义一个IP消息头部结构体*/  
struct iphdr {  u8 hlen:4, ver:4;   /*定义4位首部长度,和IP版本号为IPV4*/  u8 tos;				/*8位服务类型TOS*/  u16 tot_len;		/*16位总长度*/  u16 id;				/*16位标志位*/  u16 frag_off;		/*3位标志位*/  u8 ttl;				/*8位生存周期*/  u8 protocol;		/*8位协议*/  u16 check;			/*16位IP首部校验和*/  u32 saddr;			/*32位源IP地址*/  u32 daddr;			/*32位目的IP地址*/  
};  char *hostname;				/*被ping的主机名*/  
int datalen = DEFAULT_LEN;  /*ICMP消息携带的数据长度*/  
char sendbuf[BUFSIZE];      /*发送字符串数组*/   
char recvbuf[BUFSIZE];      /*接收字符串数组*/  
int nsent;					/*发送的ICMP消息序号*/  
int nrecv;					/*接收的ICMP消息序号*/  
pid_t pid;					/*ping程序的进程PID*/  
struct timeval recvtime;    /*收到ICMP应答的时间戳*/  
int sockfd;					/*发送和接收原始套接字*/  
struct sockaddr_in dest;    /*被ping的主机IP*/  
struct sockaddr_in from;    /*发送ping应答消息的主机IP*/  
struct sigaction act_alarm;  
struct sigaction act_int;  /*函数原型*/  
void alarm_handler(int);		/*SIGALRM处理程序*/  
void int_handler(int);			/*SIGINT处理程序*/  
void set_sighandler();			/*设置信号处理程序*/  
void send_ping();				/*发送ping消息*/  
void recv_reply();				/*接收ping应答*/  
u16 checksum(u8 *buf, int len); /*计算校验和*/  
int handle_pkt();				/*ICMP应答消息处理*/  
void get_statistics(int, int);  /*统计ping命令的检测结果*/  
void bail(const char *);		/*错误报告*/
#include<stdio.h>  
#include<stdlib.h>  
#include<sys/time.h>  /*是Linux系统的日期时间头文件*/  
#include<unistd.h>    /* 是POSIX标准定义的unix类系统定义符号常量的头文件,包含了许多UNIX系统服务的函数原型,例如read函数、write函数和getpid函数*/  
#include<string.h>  
#include<sys/socket.h>    /*对与引用socket函数必须*/  
#include<sys/types.h>  
#include<netdb.h> /*定义了与网络有关的结构,变量类型,宏,函数。函数gethostbyname()用*/  
#include<errno.h> /*sys/types.h中文名称为基本系统数据类型*/  
#include<arpa/inet.h> /*inet_ntoa()和inet_addr()这两个函数,包含在 arpa/inet.h*/  
#include<signal.h>    /*进程对信号进行处理*/  
#include<netinet/in.h>    /*互联网地址族*/  #include"test.h"  
#define IP_HSIZE sizeof(struct iphdr)   /*定义IP_HSIZE为ip头部长度*/  
#define IPVERSION  4   /*定义IPVERSION为4,指出用ipv4*/  /*设置的时间是一个结构体,倒计时设置,重复倒时,超时值设为1秒*/  
struct itimerval val_alarm = {.it_interval.tv_sec = 1,      .it_interval.tv_usec = 0,  .it_value.tv_sec = 0,  .it_value.tv_usec = 1  
};  /*argc表示隐形程序命令行中参数的数目,argv是一个指向字符串数组指针,其中每一个字符对应一个参数*/
int main(int argc,char **argv)  
{  struct hostent		*host; /*该结构体属于include<netdb.h>*/   int					on = 1;  if( argc < 2)/*判断是否输入了地址*/ {       printf("Usage: %s hostname\n",argv[0]);  exit(1);  }  /*gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的结构指针,*/ //if((host = getaddrinfo(argv[1])) == NULL)if((host = gethostbyname(argv[1])) == NULL){     printf("usage:%s hostname/IP address\n", argv[0]);exit(1);  }  hostname = argv[1];	/*取出地址名*/  memset(&dest,0,sizeof dest);	/*将dest中前sizeof(dest)个字节替换为0并返回s,此处为初始化,给最大内存清零*/  dest.sin_family=PF_INET;		/*PF_INET为IPV4,internet协议,在<netinet/in.h>中,地址族*/   dest.sin_port=ntohs(0);			/*端口号,ntohs()返回一个以主机字节顺序表达的数。*/  dest.sin_addr=*(struct in_addr *)host->h_addr_list[0];/*host->h_addr_list[0]是地址的指针.返回IP地址,初始化*/  /*PF_INEI套接字协议族,SOCK_RAW套接字类型,IPPROTO_ICMP使用协议,调用socket函数来创建一个能够进行网络通信的套接字。这里判断是否创建成功*/ if((sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0){  perror("RAW socket created error");  exit(1);  }  /*设置当前套接字选项特定属性值,sockfd套接字,IPPROTO_IP协议层为IP层,IP_HDRINCL套接字选项条目,套接字接收缓冲区指针,sizeof(on)缓冲区长度的长度*/ setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));   /*getuid()函数返回一个调用程序的真实用户ID,setuid()是让普通用户可以以root用户的角色运行只有root帐号才能运行的程序或命令。*/ //setuid(getuid()); //pid = getpid(); /*getpid函数用来取得目前进程的进程识别码*/  set_sighandler();/*对信号处理*/  printf("Ping %s(%s): %d bytes data in ICMP packets.\n", argv[1], inet_ntoa(dest.sin_addr), datalen);  //alarm(1);if((setitimer(ITIMER_REAL, &val_alarm, NULL)) == -1) /*定时函数*/  {bail("setitimer fails.");  }recv_reply(); /*接收ping应答*/  return 0;  
}  /*发送ping消息*/  
void send_ping(void)  
{  struct iphdr		*ip_hdr;   /*iphdr为IP头部结构体*/  struct icmphdr		*icmp_hdr;   /*icmphdr为ICMP头部结构体*/  int					len;  int					len1;  /*ip头部结构体变量初始化*/  ip_hdr=(struct iphdr *)sendbuf; /*字符串指针*/     ip_hdr->hlen=sizeof(struct iphdr)>>2;  /*头部长度*/  ip_hdr->ver=IPVERSION;   /*版本*/  ip_hdr->tos=0;   /*服务类型*/  ip_hdr->tot_len=IP_HSIZE+ICMP_HSIZE+datalen; /*报文头部加数据的总长度*/  ip_hdr->id=0;    /*初始化报文标识*/  ip_hdr->frag_off=0;  /*设置flag标记为0*/  ip_hdr->protocol=IPPROTO_ICMP;/*运用的协议为ICMP协议*/  ip_hdr->ttl=255; /*一个封包在网络上可以存活的时间*/  ip_hdr->daddr=dest.sin_addr.s_addr;  /*目的地址*/  len1=ip_hdr->hlen<<2;  /*ip数据长度*/  /*ICMP头部结构体变量初始化*/  icmp_hdr=(struct icmphdr *)(sendbuf+len1);  /*字符串指针*/  icmp_hdr->type=8;    /*初始化ICMP消息类型type*/  icmp_hdr->code=0;    /*初始化消息代码code*/  icmp_hdr->icmp_id=pid;   /*把进程标识码初始给icmp_id*/  icmp_hdr->icmp_seq=nsent++;  /*发送的ICMP消息序号赋值给icmp序号*/      memset(icmp_hdr->data,0xff,datalen);  /*将datalen中前datalen个字节替换为0xff并返回icmp_hdr-dat*/    gettimeofday((struct timeval *)icmp_hdr->data,NULL); /* 获取当前时间*/  len=ip_hdr->tot_len; /*报文总长度赋值给len变量*/  icmp_hdr->checksum=0;    /*初始化*/  icmp_hdr->checksum=checksum((u8 *)icmp_hdr,len);  /*计算校验和*/  sendto(sockfd,sendbuf,len,0,(struct sockaddr *)&dest,sizeof (dest)); /*经socket传送数据*/  
}  /*接收程序发出的ping命令的应答*/  
void recv_reply()  
{  int			n;  int			len;  int			errno;  n = 0;nrecv = 0;  len = sizeof(from);   /*发送ping应答消息的主机IP*/  while(nrecv < 6){  /*经socket接收数据,如果正确接收返回接收到的字节数,失败返回0.*/if((n=recvfrom(sockfd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&from, &len))<0){   if(errno==EINTR)  /*EINTR表示信号中断*/  continue;  bail("recvfrom error");  }  gettimeofday(&recvtime, NULL);   /*记录收到应答的时间*/  if(handle_pkt())    /*接收到错误的ICMP应答信息*/  continue;  nrecv++;  }  //get_statistics(nsent, nrecv);     /*统计ping命令的检测结果*/  
}  /*计算校验和*/  
u16 checksum(u8 *buf,int len)  
{  u32 sum	= 0;  u16 *cbuf;  cbuf = (u16 *)buf;  while(len > 1){  sum += *cbuf++;  len -= 2;  }  if(len){sum += *(u8 *)cbuf;  }sum = (sum >> 16) + (sum & 0xffff);  sum += (sum >> 16);  return ~sum;  
}  /*ICMP应答消息处理*/  
int handle_pkt()  
{  struct iphdr		*ip;  struct icmphdr		*icmp;  int					ip_hlen;  u16					ip_datalen; /*ip数据长度*/  double				rtt; /* 往返时间*/  struct timeval		*sendtime;  ip = (struct iphdr *)recvbuf;  ip_hlen = ip->hlen << 2;  ip_datalen = ntohs(ip->tot_len) - ip_hlen;  icmp = (struct icmphdr *)(recvbuf + ip_hlen);  if(checksum((u8 *)icmp, ip_datalen)) /*计算校验和*/  return -1;  if(icmp->icmp_id != pid)  return -1;  sendtime = (struct timeval *)icmp->data; /*发送时间*/  rtt = ((&recvtime)->tv_sec - sendtime->tv_sec) * 1000 + ((&recvtime)->tv_usec - sendtime->tv_usec)/1000.0; /* 往返时间*/  /*打印结果*/  printf("%d bytes from %s:icmp_seq=%u ttl=%d rtt=%.3f ms\n",  \ip_datalen,					/*IP数据长度*/  inet_ntoa(from.sin_addr),   /*目的ip地址*/  icmp->icmp_seq,				/*icmp报文序列号*/  ip->ttl,					/*生存时间*/  rtt);						/*往返时间*/  return 0;  
}  /*设置信号处理程序*/  
void set_sighandler()  
{  act_alarm.sa_handler = alarm_handler;  /*sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum指所要捕获信号或忽略的信号,&act代表新设置的信号共用体,NULL代表之前设置的信号处理结构体。这里判断对信号的处理是否成功。*/if(sigaction(SIGALRM, &act_alarm, NULL) == -1)    {bail("SIGALRM handler setting fails.");  }act_int.sa_handler = int_handler;  if(sigaction(SIGINT, &act_int, NULL) == -1)  {bail("SIGALRM handler setting fails.");  }
}  /*统计ping命令的检测结果*/  
void get_statistics(int nsent,int nrecv)  
{  printf("--- %s ping statistics ---\n",inet_ntoa(dest.sin_addr)); /*将网络地址转换成“.”点隔的字符串格式。*/  printf("%d packets transmitted, %d received, %0.0f%% ""packet loss\n",  \nsent,nrecv,1.0*(nsent-nrecv)/nsent*100);  
}  /*错误报告*/  
void bail(const char * on_what)  
{  /*:向指定的文件写入一个字符串(不写入字符串结束标记符‘\0’)。成功写入一个字符串后,文件的位置指针会自动后移,函数返回值为0;否则返回EOR(符号常量,其值为-1)。*/ fputs(strerror(errno),stderr);   fputs(":",stderr);  fputs(on_what,stderr);  fputc('\n',stderr); /*送一个字符到一个流中*/  exit(1);  
}  /*SIGINT(中断信号)处理程序*/  
void int_handler(int sig)  
{  printf("Timer123 expired!\n");get_statistics(nsent,nrecv);    /*统计ping命令的检测结果*/  close(sockfd);  /*关闭网络套接字*/  exit(1);  
}  /*SIGALRM(终止进程)处理程序*/  
void alarm_handler(int signo)  
{  send_ping();    /*发送ping消息*/  }

运行结果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2809036.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

SSM---Mybatis查询数据库的功能

Mybatis查询数据库的功能流程&#xff1a; 在maven中加入mybatis依赖&#xff0c;mysql驱动依赖创建一张student表创建表对应的实体类&#xff1a;student类&#xff0c;用来保存表中的每行数据创建持久层的DAO接口&#xff0c;用来定义操作数据库的方法创建这个表对应的sql映…

前端发送请求,明明登录进去了,为什么获取用户信息不行,后端总是识别不到token——跨域的问题

跨域问题 今天在对接前后端的时候&#xff0c;发现明明系统的登录接口都是好的&#xff0c;但是偏偏就是获取不到用户的信息&#xff0c;后端总是报错说读取不到有效的token。 总是说请求头中读取的token是null 在经过不断的排查和上网需求帮助的时候&#xff0c;我总结了以下…

JavaWeb——007MYSQL(DQL多表设计)

# 数据库开发-MySQL 一级目录二级目录三级目录 1. 数据库操作-DQL1.1 介绍1.2 语法1.3 基本查询1.4 条件查询1.5 聚合函数1.6 分组查询1.7 排序查询1.8 分页查询1.9 案例1.9.1 案例一1.9.2 案例二 2. 多表设计2.1 一对多2.1.1 表设计2.1.2 外键约束 2.2 一对一2.3 多对多2.4 案…

ROS2高效学习第四章 -- ros2 topic 编程之自定义 msg

ros2 topic 编程之自定义 msg 1 前言和资料2 正文2.1 两种自定义 msg 方式的讨论2.2 自定义 msg 独立存在2.2.1 自定义 msg 包&#xff08;diy_interface&#xff09;2.2.2 pubsub_cpp 收发自定义 msg2.2.3 pubsub_py 收发自定义 msg 2.3 自定义 msg 放在模块包里&#xff08;p…

逻辑思维1000题丨【Exercise 1】解析

目录 声明 解析 声明 解析网上是搜不到的&#xff0c;100%原创。本专栏只会讲解重难点题目&#xff0c;简单题目不做讲解。 解析 2.What is the missing number? 1 12 13 124 15 1236 17 1248 139 12510 111 1234612 113 12714 13515 &#xff1f; 观察每个数字的末尾分别是…

Redis高并发分布锁实战

Redis高并发分布锁实战 问题场景 场景一: 没有捕获异常 // 仅仅加锁 // 读取 stock15 Boolean ret stringRedisTemplate.opsForValue().setIfAbsent("lock_key", "1"); // jedis.setnx(k,v) // TODO 业务代码 stock-- stringRedisTemplate.delete(&quo…

HarmonyOS服务卡片开发指导(Stage模型)概述

服务卡片概述 服务卡片&#xff08;以下简称“卡片”&#xff09;是一种界面展示形式&#xff0c;可以将应用的重要信息或操作前置到卡片&#xff0c;以达到服务直达、减少体验层级的目的。卡片常用于嵌入到其他应用&#xff08;当前卡片使用方只支持系统应用&#xff0c;如桌…

Android Gradle 开发与应用 (一) : Gradle基础

1. Gradle是什么 Gradle是一个通用的构建工具&#xff0c;支持诸多主要的 IDE&#xff0c;包括 Android Studio、IntelliJ IDEA、Visual Studio 等 Gradle 的底层实现(核心引擎和框架)其实是用 Java 编写的开发者通常使用 Groovy 或 Kotlin 来编写构建脚本 1.1 那么为什么Gra…

你真的了解@Async吗?

你真的了解Async吗&#xff1f; 使用场景&#xff1a; 开发中会碰到一些耗时较长或者不需要立即得到执行结果的逻辑&#xff0c;比如消息推送、商品同步等都可以使用异步方法&#xff0c;这时我们可以用到Async。但是直接使用 Async 会有风险&#xff0c;当我们没有指定线程池…

速度提高100倍 - 扩展 RAG 应用程序,以实现数十亿个嵌入,并行计算余弦相似度

原文链接&#xff1a;100x Faster — Scaling Your RAG App for Billions of Embeddings 2024 年 2 月 15 日 RAG应用程序最大的问题之一是它们的计算检索时间。想象一下&#xff0c;你有一个向量数据库&#xff0c;包含一万亿条Embedding向量的记录。当您尝试将用户查询与一…

数字化转型导师坚鹏:政府数字化转型智慧城市类案例研究

政府数字化转型智慧城市类案例研究 课程背景&#xff1a; 很多地方政府存在以下问题&#xff1a; 不清楚政府数字化转型的智慧城市类成功案例 不清楚政府数字化转型的城市大脑类成功案例 不清楚政府数字化转型的综合实践类成功案例 课程特色&#xff1a; 针对性强 …

LDRA Testbed软件静态分析_软件质量度量

系列文章目录 LDRA Testbed软件静态分析_操作指南 LDRA Testbed软件静态分析_自动提取静态分析数据生成文档 LDRA Testbed软件静态分析_Jenkins持续集成_(1)自动进行静态分析的环境搭建 LDRA Testbed软件静态分析_Jenkins持续集成_(2)配置邮件自动发送静态分析结果 LDRA Testb…

LeetCode--代码详解 236. 二叉树的最近公共祖先

236. 二叉树的最近公共祖先 题目 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&am…

python 循环语句 while 循环

while循环 Python 编程中 while 语句用于循环执行程序&#xff0c;即在某条件下&#xff0c;循环执行某段程序&#xff0c;以处理需要重复处理的相同任务。其基本形式为&#xff1a; while 判断条件(condition)&#xff1a; 执行语句(statements)…… 执行语句可以是单个语句…

遥感影像目标检测:从CNN(Faster-RCNN)到Transformer(DETR)

我国高分辨率对地观测系统重大专项已全面启动&#xff0c;高空间、高光谱、高时间分辨率和宽地面覆盖于一体的全球天空地一体化立体对地观测网逐步形成&#xff0c;将成为保障国家安全的基础性和战略性资源。未来10年全球每天获取的观测数据将超过10PB&#xff0c;遥感大数据时…

【数据结构】周末作业

1.new(struct list_head*)malloc(sizeof(struct list_head*)); if(newNULL) { printf("失败\n"); return; } new->nextprev->next; prev->nextnew; return; 2.struct list_head* pprev->next; prev->nextp->next; p->next->prevpr…

Oracle conn / as sysdba遇到ORA-01031: insufficient privileges错误

背景 oracle 突然挂了&#xff0c;处于锁定状态&#xff0c;然后打算重新启动一下子。 遂 sqlplus /nolog conn / as sysdba 然后就出现了以下错误。。 ORA-01031: insufficient privileges 1.查了一圈&#xff0c;有说是 计算机 》 管理》本地用户和组》组》ORA_DBA&am…

python 3.11中安装sympy(符号工具包)

1.python环境&#xff1a; 2.安装遇到问题&#xff1a; … 3.升级pip cmd命令行中&#xff0c;执行如下命令&#xff1a; python.exe -m pip installl --upgrade pip 4.再次安装sympy cmd命令行中&#xff0c;执行如下命令&#xff1a; pip install sympy 5.简单应用 对…

kuka示教器嵌套UR界面操作ros中rviz的UR机器人

摘要 本例展示了用QT增加一个网页视图&#xff0c;背景是kuka示教器界面&#xff0c;中间增加UR的VNC网页界面显示。本人博客中一起有写过ros2运行UR的操作。 ros2 UR10仿真包运行_基于ros的ur仿真-CSDN博客 效果如下&#xff1a; 1.打开UR机器人的ros2仿真文件 sudo su ros2…

杀疯啦!yolov9+bytetrack的目标跟踪实现

目录 YOLOV9介绍 bytetrack介绍&#xff1a; yolov9结合bytetrack实现目标跟踪 效果展示 训练与预测 UI设计 其他功能展示 完整代码实现UI界面 此次yolov9bytetrack不论是准确率还是稳定性&#xff0c;都超越了之前的yolobytetrack系列。 YOLOV9介绍 在目标检测…