Linux网络编程之原始套接字ping协议实现Word文档格式.docx
《Linux网络编程之原始套接字ping协议实现Word文档格式.docx》由会员分享,可在线阅读,更多相关《Linux网络编程之原始套接字ping协议实现Word文档格式.docx(10页珍藏版)》请在冰豆网上搜索。
![Linux网络编程之原始套接字ping协议实现Word文档格式.docx](https://file1.bdocx.com/fileroot1/2022-12/16/b3b30543-7311-4226-9f5f-506d651b2510/b3b30543-7311-4226-9f5f-506d651b25101.gif)
//校验和
union{
structih_idseq{//显示数据报
u_int16_ticd_id;
//数据报所在进程的ID
u_int16_6icd_seq;
//数据报序号
}ih_idseq;
}icmp_hun;
#defineicmp_idicmp_hun.ih_idseq.icd_id
#defineicmp_seqicmp_hun.ih_idseq.icd_seq
union{
u_int8_tid_data[i];
//数据
}icmp_dun;
#defineicmp_dataicmp_dun.id_data
}
**/
typedefstructpingm_packet{
structtimevaltv_begin;
//发送的时间
structtimevaltv_end;
//接收到响应包的时间
shortseq;
//序号值
intflag;
//1表示已经发送但没有接收到回应包,0表示接收到响应包
}pingm_packet;
//保存已经发送包的状态值
staticpingm_packetpingpacket[128];
//定义一个包数组
staticpingm_packet*icmp_findpacket(intseq);
staticunsignedshorticmp_cksum(unsignedchar*data,intlen);
staticstructtimevalicmp_tvsub(structtimevalend,structtimevalbegin);
staticvoidicmp_statistics(void);
staticvoidicmp_pack(structicmp*icmph,intseq,structtimeval*tv,intlength);
staticinticmp_unpack(char*buf,intlen);
staticvoid*icmp_recv(void*argv);
staticvoid*icmp_send(void*argv);
staticvoidicmp_sigint(intsigno);
staticvoidicmp_usage();
#defineK1024
#defineBUFFERSIZE512
staticunsignedcharsend_buff[BUFFERSIZE];
//定义发送缓冲区的大小
staticunsignedcharrecv_buff[2*K];
//定义接收缓冲区的大小,为防止接收端溢出,接收缓冲区稍微大一些
staticstructsockaddr_indest;
//目的地址
staticintrawsock=0;
//原始套接字描述符
staticpid_tpid=0;
//进程id
staticintalive=0;
//是否接收到退出信号
staticshortpacket_send=0;
//已经发送的数据包数目
staticshortpacket_recv=0;
//已经接收的数据报数目
staticchardest_str[80];
//目的主机字符串
staticstructtimevaltv_begin,tv_end,tv_interval;
//本程序开始发送,结束时间和时间间隔
staticvoidicmp_usage(){
printf("
pingaaa.bbb.ccc.ddd\n"
);
//计算ICMP首部校验和
staticunsignedshorticmp_cksum(unsignedchar*data,intlen){
intsum=0;
intodd=len&
0x01;
unsignedshort*value=(unsignedshort*)data;
while(len&
0xfffe){
sum+=*(unsignedshort*)data;
data+=2;
len-=2;
if(odd){
unsignedshorttmp=((*data)<
<
8)&
0xff00;
sum+=tmp;
sum=(sum>
>
16)+(sum&
0xffff);
sum+=(sum>
16);
return~sum;
//设置ICMP报头
staticvoidicmp_pack(structicmp*icmph,intseq,structtimeval*tv,intlength){
unsignedchari=0;
icmph->
icmp_type=ICMP_ECHO;
//ICMP回显请求
icmp_code=0;
//code为0
icmp_cksum=0;
//cksum值
icmp_seq=htons(seq);
//数据报的序列号
icmp_id=htons(pid&
//数据报的ID
for(i=0;
i<
length;
i++){
icmph->
icmp_data[i]=htons(i);
//注意主机字节序转换成网络字节序
}//发送的数据
//计算校验和
icmp_cksum=icmp_cksum((unsignedchar*)icmph,length+8);
//计算时间差函数
staticstructtimevalicmp_tvsub(structtimevalend,structtimevalbegin){
structtimevaltv;
tv.tv_sec=end.tv_sec-begin.tv_sec;
tv.tv_usec=end.tv_usec-begin.tv_usec;
if(tv.tv_usec<
0){
tv.tv_sec--;
tv.tv_usec+=1000000;
returntv;
//发送报文
staticvoid*icmp_send(void*argv){
tv.tv_usec=0;
tv.tv_sec=1;
//每隔一秒发送报文
gettimeofday(&
tv_begin,NULL);
//保存程序开始发送数据的时间
while(alive){
memset(send_buff,0,sizeof(send_buff));
intsize=0;
tv,NULL);
//当前包发送的时间
icmp_pack((structicmp*)send_buff,packet_send,&
tv,203);
//packet_send为发送包的序号,发送的数据长度为64个字节,填充ICMP首部信息
size=sendto(rawsock,send_buff,203+8,0,(structsockaddr*)&
dest,sizeof(dest));
//dest为ICMP包发送的目的地址
if(size<
perror("
sendtoerror"
continue;
}
else{
//在发送包的状态数组找一空闲位置记录发送状态信息
pingm_packet*packet=icmp_findpacket(-1);
if(packet){
packet->
seq=packet_send;
flag=1;
packet->
packet_send++;
//发送序号+1
sleep
(1);
//寻找一个空闲位置,seq=-1表示空闲位置
staticpingm_packet*icmp_findpacket(intseq){
inti=0;
pingm_packet*found=NULL;
if(seq==-1){
for(i=0;
128;
if(pingpacket[i].flag==0){
found=&
pingpacket[i];
break;
}elseif(seq>
=0){//查找对应seq的数据包
if(pingpacket[i].seq==seq){
break;
returnfound;
//获得ICMP接收报文,buf存放的除去了以太网部分的IP数据报文,len为数据长度,ip_hl标识IP头部长度以4字节为单位,获得ICMP数据报后判断是否为ICMP_ECHOREPLY并检查是否为本进程的ID
staticinticmp_unpack(char*buf,intlen){
inti,iphdrlen;
structip*ip=NULL;
structicmp*icmp=NULL;
intrtt;
//计算往返时延
ip=(structip*)buf;
iphdrlen=ip->
ip_hl*4;
//IP头部长度
icmp=(structicmp*)(buf+iphdrlen);
//ICMP报文的地址
len-=iphdrlen;
//ICMP报文的长度,ICMP报文至少8个字节
if(len<
8){
printf("
ICMPpackets\'
slengthislessthan8\n"
return-1;
//判断ICMP报文的类型是否为ICMP_ECHOREPLY并且为本进程的PID
if((icmp->
icmp_type==ICMP_ECHOREPLY)&
&
(icmp->
icmp_id==pid)){
structtimevaltv_interval,tv_recv,tv_send;
//在发送数组中查找已经发送的包
pingm_packet*packet=icmp_findpacket(ntohs(icmp->
icmp_seq));
//网络字节序转换成主机字节序
if(packet==NULL){
return-1;
flag=0;
//表示已经响应了
//本包的发送时间
tv_send=packet->
tv_begin;
//读取收到响应包的时间
gettimeofday(&
tv_recv,NULL);
tv_interval=icmp_tvsub(tv_recv,tv_send);
//计算往返时延,即RTT
rtt=tv_interval.tv_sec*1000+tv_interval.tv_usec/1000;
//打印ICMP段长度,源IP,包的序列号,TTL,时间差
%dbytefrom%s:
icmp_seq=%uttl=%drtt=%dms\n"
len,inet_ntoa(ip->
ip_src),icmp->
icmp_seq,ip->
ip_ttl,rtt);
packet_recv++;
//接收包的数量加1
else{
//接收报文
staticvoid*icmp_recv(void*argv){
structtimevaltv;
tv.tv_usec=200;
//轮循时间
tv.tv_sec=0;
fd_setreadfd;
while(alive){
intret=0;
tv.tv_usec=200;
tv.tv_sec=0;
FD_ZERO(&
readfd);
FD_SET(rawsock,&
ret=select(rawsock+1,&
readfd,NULL,NULL,&
tv);
intfromlen=0;
structsockaddrfrom;
switch(ret){
case-1:
//发生错误
case0:
//超时
//printf("
timeout\n"
default:
//收到数据包
fromlen=sizeof(from);
intsize=recvfrom(rawsock,recv_buff,sizeof(recv_buff),0,(structsockaddr*)&
from,&
fromlen);
//利用原始套接字,原始套接字与IP层网络协议栈核心打交道
if(errno==EINTR){
recvfromerror"
//解包,得到RTT
ret=icmp_unpack(recv_buff,size);
if(ret==-1){
//统计数据结果,成功发送的报文数量,成功接收的报文数量,丢失报文百分比和程序总共运行时间
staticvoidicmp_statistics(void){
longtime=(tv_interval.tv_sec*1000)+(tv_interval.tv_usec/1000);
---%spingstatistics---\n"
dest_str);
//目的IP
%dpacketstransmitted,%drecevied,%d%cpacketloss,time%dms\n"
packet_send,packet_recv,(packet_send-packet_recv)*100/packet_send,'
%'
time);
//信号处理函数
staticvoidicmp_sigint(intsigno){
alive=0;
//alive=0程序将会终止
tv_end,NULL);
//程序结束时间
tv_interval=icmp_tvsub(tv_end,tv_begin);
//计算程序一共运行了多长时间
return;
//主函数实现
intmain(intargc,char*argv[]){
structhostent*host=NULL;
structprotoent*protocol=NULL;
charprotoname[]="
icmp"
;
unsignedlonginaddr=1;
intsize=128*K;
intret;
if(argc<
2){
icmp_usage();
//获取协议类型ICMP,协议类型的值作为设置原始套接字的第3个参数,type类型下的具体协议值不止一个,当type为SOCK_RAW
protocol=getprotobyname(protoname);
if(protocol==NULL){
getprotobyname()"
//复制目的地址
memcpy(dest_str,argv[1],strlen(argv[1])+1);
memset(pingpacket,0,sizeof(pingm_packet)*128);
//pingpacket数组初始化
//建立原始套接字
rawsock=socket(AF_INET,SOCK_RAW,protocol->
p_proto);
if(rawsock<
rawsockerror"
//得到程序的pid
pid=getuid();
//增大接收端缓冲区防止接收的包被覆盖
ret=setsockopt(rawsock,SOL_SOCKET,SO_RCVBUF,&
size,sizeof(size));
SO_RCVBUFERROR"
//输入的目的IP
inaddr=inet_addr(argv[1]);
//转换成二进制IP
bzero(&
dest.sin_family=AF_INET;
//设置地址族
if(inaddr==INADDR_NONE){
//输入的是DNS
host=gethostbyname(argv[1]);
if(host==NULL){
gethostbyname"
memcpy((char*)&
dest.sin_addr,host->
h_addr,host->
h_length);
dest.sin_addr,&
inaddr,sizeof(inaddr));
inaddr=dest.sin_addr.s_addr;
//由于是ICMP不涉及到端口绑定
PING%s(%d.%d.%d.%d)56(84)bytesofdata.\n"
dest_str,(inaddr&
0x000000FF)>
0,(inaddr&
0x0000FF00)>
8,(inaddr&
0x00FF0000)>
16,(inaddr&
0xFF000000)>
24);
signal(SIGINT,icmp_sigint);
alive=1;
//定义两个线程,分别用于发送数据与接收数据
pthread_tsend_id,recv_id;
interr=0;
err=pthread_create(&
send_id,NULL,icmp_send,NULL);
if(err<
recv_id,NULL,icmp_recv,NULL);
pthread_join(send_id,NULL);
//等待子线程结束send
pthread_join(recv_id,NULL);
//等待子线程的结束recv
close(rawsock);
icmp_statistics();
return0;
运行结果:
PING222.27.253.1(222.27.253.1)56(84)bytesofdata.
60bytefrom222.27.253.1:
icmp_seq=0ttl=255rtt=5ms
icmp_seq=1ttl=255rtt=12ms
icmp_seq=2ttl=255rtt=5ms
icmp_seq=3ttl=255rtt=7ms
icmp_seq=4ttl=255rtt=2ms
icmp_seq=5ttl=255rtt=23ms
icmp_seq=6ttl=255rtt=27ms
icmp_seq=7ttl=255rtt=10ms
icmp_seq=8ttl=255rtt=17ms
icmp_seq