【部落格407】用socket程式設計實現ping命令
阿新 • • 發佈:2020-12-28
技術標籤:linux
內容:使用socket程式設計實現ping命令
socket程式設計原始碼:
/*
** time:2020.12.27
** 功能:實現ping命令
** 環境:ubuntu
*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<netinet/ip_icmp.h>
#include<signal.h>
#include<arpa/inet.h>
#include<sys/time.h>
#include<string.h>
#include<netdb.h>
#include<pthread.h>
#define PACK_SIZE 4096
//使用的結構宣告
char send_packet[PACK_SIZE]; //傳送報文結構
char recv_packet[PACK_SIZE]; //接收報文結構
int sockfd; //套接字
pid_t pid; //程序號
int begin=0,end=0; //記錄發收的包的數量
int datalen=56; //icmp資料長度,注意完成資料報文最小為64bytes,不是56bytes
struct timeval recv_time; //記錄某一刻的時間
struct sockaddr_in from;
//使用的函式宣告
void* send_data(void* arg); //發資料函式
void* recv_data(void* arg); //收資料函式
unsigned short get_cksum(unsigned char*,int); //校驗和計算
void time_sub(struct timeval*,struct timeval*); //計算時間差
//各個函式實現
//報文傳送函式
void* send_data(void* arg)
{
int packetsize;
int i=0;
for(;i<4;i++)
{
packetsize=pack(i)
if(sendto(sockfd,send_packet,packetsize,0,(struct sockaddr*)&from,sizeof(from))<0) //傳送報文
{
//失敗
perror("sendto");
continue;
}
else
{
begin++; //成功後,休眠1s
sleep(1);
}
}
return NULL;
}
//報文接收資料
void* recv_data(void* arg)
{
int n,fromlen;
fromlen=sizeof(from);
int i=0;
while(i<4)
{
if((n=recvfrom(sockfd,recv_packet,sizeof(recv_packet),0,(struct sockaddr*)&from,&fromlen))<0) //收資料
{
continue;
}
if(unpack(recv_packet,n)==-1) //解包失敗
{
i++;
continue;
}
else
{
end++; //成功
}
}
}
//校驗和計算
unsigned short get_cksum(unsigned char* data,int len) //求檢驗和
{
int sum=0;
int odd=(len&0x01);
while(len&0xfffe)
{
sum+=*(unsigned short*)data;
data+=2;
len-=2;
}
if(odd)
{
unsigned short tmp=((*data)<<8)&0xff00;
sum+=tmp;
}
sum=(sum>>16)+(sum&0xffff);
sum+=(sum>>16);
return ~sum;
}
//構建包
int pack(int pack_no)
{
int size;
struct icmp* myicmp;
myicmp=(struct icmp*)send_packet;
//對相應欄位填充
myicmp->icmp_type=ICMP_ECHO;
myicmp->icmp_code=0;
myicmp->icmp_cksum=0;
myicmp->icmp_seq=pack_no;
myicmp->icmp_id=pid;
size=8+datalen; //總的icmp大小
//將時間資訊放入data欄位
struct timeval*send_time=(struct timeval*)myicmp->icmp_data;
gettimeofday(send_time,NULL);
//注意檢驗和放最後,因為資料也要進行校驗
myicmp->icmp_cksum=get_cksum(send_packet,size);
return size;
}
//拆解包
int unpack(char* buf,int len)
{
int ip_len;
struct ip* myip;
struct icmp* myicmp;
myip=(struct ip*)buf;
ip_len=myip->ip_hl<<2; //IP首部長度*4
myicmp=(struct icmp*)(buf+ip_len); //獲得ICMP報文
len-=ip_len;
if(len<8) //ICMP固定長度為8,小於即錯誤
{
printf("icmp packet is small than 8\n");
return -1;
}
// printf("i am coming\n");
if(myicmp->icmp_type==ICMP_ECHOREPLY && myicmp->icmp_id==pid) //判斷是不是想要的包
{
// printf("i am coming\n");
gettimeofday(&recv_time,NULL);
struct timeval* sendTime=(struct timeval*)myicmp->icmp_data;
time_sub(&recv_time,sendTime); //計算時間差放到recv_time裡面
double rtt=recv_time.tv_sec*1000+recv_time.tv_usec/1000; //以ms為單位
//列印相關資訊
printf("%d byte from %s:icmp_seq=%u ttl=%d rtt=%fms\n",len,inet_ntoa(from.sin_addr),myicmp->icmp_seq,myip->ip_ttl,rtt);
}
else
//不是想要的包
return -1;
}
//時間差計算
void time_sub(struct timeval* end,struct timeval* begin)
{
end->tv_sec-=begin->tv_sec;
end->tv_usec-=begin->tv_usec;
}
//主函式
int main(int argc,char* argv[])
{
if(argc<2)
{
printf("usage:%s hostname/ip\n",argv[0]);
exit(-1);
}
if((sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP))<0) //建立原始套接字
{
perror("socket");
exit(-1);
}
memset(&from,0,sizeof(from));
if((from.sin_addr.s_addr=inet_addr(argv[1]))==INADDR_NONE)//如果傳入的不是ip
{
struct hostent* hptr=NULL;
//轉換失敗
if((hptr=gethostbyname(argv[1]))==NULL)
{
perror("gethostbyname");
exit(-1);
}
memcpy(&from.sin_addr,hptr->h_addr,sizeof(hptr->h_addr));
}
else
{
from.sin_addr.s_addr=inet_addr(argv[1]);
}
from.sin_family=AF_INET;
pid=getpid();
printf("ping %s,%d data in icmp packets\n",argv[1],datalen);
pthread_t id1,id2;
//建立傳送函式執行緒
if(pthread_create(&id1,NULL,send_data,NULL)<0)
{
perror("pthread_create");
return -2;
}
//建立接收函式執行緒
if(pthread_create(&id2,NULL,recv_data,NULL)<0)
{
perror("pthread_create");
return -3;
}
//回收執行緒,防止執行緒洩露
pthread_join(id1,NULL);
pthread_join(id2,NULL);
close(sockfd);
printf("ping %s is complete,%d was sended and %d was received\n",argv[1],begin,end);
return 0;
}
編譯並執行:
gcc -o pingbyme pingbyme.c -lpthread
sudo ./pingbyme www.baidu.com
執行結構展示:
我的ping展示:
linux自帶的ping: