《网络应用程序设计》——UDP协议ICMP协议的理解及UDP套接口和原始套接口

UDP协议ICMP协议的理解及UDP套接口和原始套接口

1.使用UDP编程服务器端和客户端程序
(1)要求:客户端将文件A1和A2,内容交替发送给服务器;服务器大小写转换后交替传给客户端;客户端接受后存为B1和B2。

文件头:
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <time.h>
#include <signal.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>  /*for toupper*/
#define SERVPORT 3333	
#define NDG 2000
#define DGLEN 1400

主要代码:

客户端:(udpcli.c)
void dg_cli(FILE *fp,FILE *fp1,int sockfd,const struct sockaddr* pservaddr,socklen_t servlen)//缺乏流量控制
{
	int n,i=0,flag,empty1=0,empty2=0;
	char sendline[1024],recvline[1025];
	FILE *fp2,*fp3;
	if ((fp2 = fopen("b1.txt","r+")) == NULL) {
        perror("Open file failed\n");  
        exit(0);  
    }
	if ((fp3 = fopen("b2.txt","r+")) == NULL) {
        perror("Open file failed\n");  
        exit(0);  
    }
	for(;;){
		flag=i%2;
		if(flag == 0)
	   {
		   if(fgets(sendline,10,fp)!=NULL){
		    printf("send file A1: %s\n",sendline);
		    sendto(sockfd,sendline,strlen(sendline),0,pservaddr,servlen);
		    n=recvfrom(sockfd,recvline,1024,0,NULL,NULL);
		    recvline[n]='\0';
		    printf("received 1(saved in B1):");
		    fputs(recvline,stdout);  //输出到屏幕
			printf("\n");
		    fwrite(recvline,sizeof(char),strlen(recvline),fp2);//写到B1文件夹
	        }
			else empty1=1;
		   i++;
	    }
	    else if(flag == 1)
	    {
			if(fgets(sendline,10,fp1)!=NULL){
		    printf("send file A2: %s\n",sendline);
		    sendto(sockfd,sendline,strlen(sendline),0,pservaddr,servlen);
		    n=recvfrom(sockfd,recvline,1024,0,NULL,NULL);
		    recvline[n]='\0';
		    printf("received 2(saved in B2):");
		    fputs(recvline,stdout);
		    printf("\n");
		    fwrite(recvline,sizeof(char),strlen(recvline),fp3);//写到B2文件夹
		    }
			else empty2=1;
			i++;
	   }
	   if(empty1==1&&empty2==1) {
		   printf("end of send!\n");
		   break;
	   }
	} 
	fclose(fp2);
	fclose(fp3);
}
int main(int argc,char*argv[]){
	int sockfd;
 	struct sockaddr_in servaddr;
 	char buff[1024];
 	int n,len;
 	FILE *fp,*fp1; 
	if ((fp = fopen("a1.txt","r")) == NULL) {
        perror("Open file failed\n");  
        exit(0);  
    }
	if ((fp1 = fopen("a2.txt","r")) == NULL) { 
        perror("Open file failed\n");  
        exit(0);  
    }
	bzero(&servaddr,sizeof(servaddr));
 	servaddr.sin_family=AF_INET;
  	inet_aton("127.0.0.1",&servaddr.sin_addr);
 	servaddr.sin_port=htons(SERVPORT);
 	
 	sockfd=socket(AF_INET,SOCK_DGRAM,0);
	if(sockfd<0){
		fprintf(stderr,"Socket error");
		exit(1);
 	}
	
	dg_cli(fp,fp1,sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
	fclose(fp);
	fclose(fp1);
	exit(0);
}
服务器端:(udpser.c)
static int count;
static void recvfrom_int(int);
void udps_respon(int sockfd,struct sockaddr* pcliaddr,socklen_t clilen){
	int n;
  	char msg[1024];
  	socklen_t len;
	int j=1,i;
 	for(;;){
		len=clilen;
		n=recvfrom(sockfd,msg,1024,0,pcliaddr,&len);
 		/* 响应客户机请求 */
 		msg[n]='\0';
		printf("received message %d:%s \n",j,msg);
		for (i = 0; i < n; i++){  //大小写转换
			if(msg[i]>='A'&&msg[i]<='Z') 
				msg[i]+=32;
			else if(msg[i]>='a'&&msg[i]<='z')
		        msg[i]-=32;
		}
 		printf("send message %d:%s \n",j,msg);
 		sendto(sockfd,msg,n,0,pcliaddr,len);
		j++;
  	}
}
int main(int argc,char*argv[]){
	int sockfd;
 	struct sockaddr_in servaddr,cliaddr;
 	/* 创建一个UDP数据报类型的套接字 */
 	sockfd=socket(AF_INET,SOCK_DGRAM,0);
	if(sockfd<0){
		fprintf(stderr,"Socket error");
		exit(1);
 	}
	bzero(&servaddr,sizeof(servaddr));
 	servaddr.sin_family=AF_INET;
 	servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
 	servaddr.sin_port=htons(SERVPORT);

	/* 服务器为套接字绑定一个端口号 */
 	if(bind(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0){
		fprintf(stderr,"Bind error");
		exit(1);
	}
	/* 调用通信函数与客户端进行通信 */
	udps_respon(sockfd,(struct sockaddr *)&cliaddr,sizeof(cliaddr));
   	close(sockfd); /* 关闭套接字 */
}

a1.txt内容:
a1
a2.txt内容:
a2
运行结果:
客户端:
在这里插入图片描述
服务器端:
在这里插入图片描述
运行后:
b1.txt的内容:
b1
b2.txt的内容:
b2
分析:客户端设置了两个指针fp,fp1用来读a1,a2文件,并且设置了每次读取的大小为10,所以并没有一次性读完整个文件内容(图示a1,a2两个文件里字符串的长度均大于10)。客户端使用累加器i,当i为偶数,则标志位flag=0时,客户端读取a1文件内容;当i为奇数,则标志位flag=1时,客户端读取a2文件内容,实现交替发生给服务器端,服务器端将字符串转换为大写后返回给客户端,客户端将a1文件内容经转换后收到的内容保存在b1里,a2则保存在b2里。
由于a2文件里的字符串长度比a1文件里的大,所以我们可以看到运行结果中的后半部分都是在发送a2文件的内容。

(2)要求:采用连接UDP套接口方式,实现(1)的功能,启动2个服务器,在一个客户端上分别往两个服务器发送A1和A2文件
主要代码:

客户端:(ljudpcli.c)
void dg_cliconn(FILE *fp,FILE *fps, int sockfd, const struct sockaddr* pservaddr, socklen_t servlen)//已连接UDP套接口
{
	int	n;
	char	sendline[1024], recvline[1025];
    memset(sendline,0,sizeof(sendline));
	memset(recvline,0,sizeof(recvline));
	connect(sockfd, (struct sockaddr*) pservaddr, servlen);
	while (fgets(sendline, 10, fp) != NULL) {
//连接没有在相应地址与端口上运行服务程序的,write返回ICMP错误;TCP客户进程调用connect时候就返回同样错误
		write(sockfd, sendline, strlen(sendline));
		n=0;
		n = read(sockfd, recvline, 1024);
		recvline[n] = 0;	/* null terminate */
		printf("received !\n");
		fputs(recvline, stdout);
		printf("\n");
		fwrite(recvline,sizeof(char),strlen(recvline),fps);
	}
	fclose(fp);
	fclose(fps);
	printf("end!\n");
}
int main(int argc,char*argv[]){
	int sockfd1,sockfd2;
 	struct sockaddr_in servaddr1,servaddr2;
	bzero(&servaddr1,sizeof(servaddr1));
 	servaddr1.sin_family=AF_INET;
  	inet_aton("127.0.0.1",&servaddr1.sin_addr);
 	servaddr1.sin_port=htons(3333);
	bzero(&servaddr2,sizeof(servaddr2));
 	servaddr2.sin_family=AF_INET;
  	inet_aton("127.0.0.1",&servaddr2.sin_addr);
 	servaddr2.sin_port=htons(3334);
 	
	FILE *fp,*fp1,*fp2,*fp3; 
	if ((fp = fopen("a1.txt","r")) == NULL) {
        perror("Open file failed\n");  
        exit(0);  
    }
	if ((fp1 = fopen("a2.txt","r")) == NULL) { 
        perror("Open file failed\n");  
        exit(0);  
    }
	if ((fp2 = fopen("b1.txt","r+")) == NULL) {
        perror("Open file failed\n");  
        exit(0);  
    }
	if ((fp3 = fopen("b2.txt","r+")) == NULL) {
        perror("Open file failed\n");  
        exit(0);  
    }
 	sockfd1=socket(AF_INET,SOCK_DGRAM,0);
	if(sockfd1<0){
		fprintf(stderr,"Socket1 error");
		exit(1);
 	}
	sockfd2=socket(AF_INET,SOCK_DGRAM,0);
	if(sockfd2<0)
{
		fprintf(stderr,"Socket2 error");
		exit(1);
 	}
	printf("connect to server1... \n");
    dg_cliconn(fp,fp2,sockfd1,(struct sockaddr *)&servaddr1,sizeof(servaddr1));
	printf("connect to server2... \n");
	dg_cliconn(fp1,fp3,sockfd2,(struct sockaddr *)&servaddr2,sizeof(servaddr2));
	close(sockfd1);
	close(sockfd2);
	exit(0);
}
服务器端:
服务器1代码与(1)中服务器代码一致。服务器2只是将SERVPORT更改为“#define SERVPORT 3334”。

运行结果:
客户端:
在这里插入图片描述
服务器1和服务器2:
在这里插入图片描述
在这里插入图片描述
分析:主要就是创建了两个套接口。通过两次调用函数来向两个服务器发送文件内容,发送哪个文件的内容是通过函数的参数来控制,并分别传入对应的服务器套接口。

(3)采用复杂UDP并发服务器完成一项编程工作。
主要代码:

客户端:(bfudpc.c)
//./client ip port  
int main(int argc, const char *argv[])  
{  
    int sockfd;  
    int n;  
    char buf[1024];  
    struct sockaddr_in server_addr;  
    struct sockaddr_in peer_addr;  
    int addrlen = sizeof(struct sockaddr);  
    if(argc < 3)  
    {  
        fprintf(stderr,"Usage : %s ip port.\n",argv[0]);  
        exit(EXIT_FAILURE);  
    }  
  
    sockfd = socket(AF_INET,SOCK_DGRAM,0);  
if(sockfd < 0)
{  
        perror("socket");  
    }  

    bzero(&server_addr,sizeof(server_addr));  
    server_addr.sin_family = AF_INET;  
    server_addr.sin_port   = htons(atoi(argv[2]));  
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);  
    while(1)  
    {  
        printf(">");  
        fgets(buf,1024,stdin);  
        buf[strlen(buf)-1] = '\0';  
  
        n = sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&server_addr,addrlen);  
        if(n < 0)
{  
            perror("recvfrom");  
        }     
        printf("*********************************\n");  
        printf("Ip:%s.\n",inet_ntoa(server_addr.sin_addr));  
        printf("Port:%d.\n",ntohs(server_addr.sin_port)); 
	    printf("Send %d bytes : %s\n",n,buf);  
        memset(buf,0,sizeof(buf));  
          
        n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&server_addr,&addrlen);  
        if(n < 0)
{  
            perror("recvfrom");  
        }  
        buf[n] = '\0';  
        printf("Rcv %d bytes : %s\n",n,buf);  
        printf("*********************************\n");    
    }  
    return 0;  
} 
服务器端:(bfudps.c)
void handler(int signum)  
{  
    waitpid(-1,NULL,WNOHANG);  
    return;  
}  
int do_client(struct sockaddr_in peer_addr,char *buf)  
{  
    int n,i;  
    int sockfd;  
    int addrlen = sizeof(struct sockaddr);  
      
    sockfd = socket(AF_INET,SOCK_DGRAM,0);  
if(sockfd < 0)
{  
        perror("socket");  
      
    }  
    printf("*********************************\n");  
    printf("Ip:%s.\n",inet_ntoa(peer_addr.sin_addr));  
    printf("Port:%d.\n",ntohs(peer_addr.sin_port));  
    printf("Rcv %d bytes : %s\n",strlen(buf),buf);  
    printf("*********************************\n");  
    //发给客户端  
    sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));  
    while(1)  
    {  
        n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&peer_addr,&addrlen);  
        if(n < 0)
{  
            perror("recvfrom");  
        }      		
        buf[n] = '\0'; 
        printf("*********************************\n"); 
		printf("Ip:%s.\n",inet_ntoa(peer_addr.sin_addr));
		printf("Port:%d.\n",ntohs(peer_addr.sin_port)); 
		printf("Rcv %d bytes : %s\n",strlen(buf),buf); 
        for(i=0;i<n;i++)
			buf[i]=toupper(buf[i]);
        printf("Send %d bytes : %s\n",strlen(buf),buf); 
        printf("*********************************\n");  
      
        sendto(sockfd,buf,n,0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));  
  
        if(strncmp(buf,"quit",4) == 0)  
            break;  
    }  
  
    return 0;  
}  
  
//./server ip port  
int main(int argc, const char *argv[])  
{  
    int sockfd;  
    int n;  
    int pid;  
    char buf[1024];  
    struct sockaddr_in server_addr;  
    struct sockaddr_in peer_addr;  
    int addrlen = sizeof(struct sockaddr);  
  
    if(argc < 3)  
    {  
        fprintf(stderr,"Usage : %s ip port.\n",argv[0]);  
        exit(EXIT_FAILURE);  
    }  
  
    if(signal(SIGCHLD,handler) == SIG_ERR)  
    {  
        perror("signal");  
    }  
  
    sockfd = socket(AF_INET,SOCK_DGRAM,0);  
if(sockfd < 0)
{  
        perror("socket");  
    }  
  
    //绑定地址  
    bzero(&server_addr,sizeof(server_addr));  
    server_addr.sin_family = AF_INET;  
    server_addr.sin_port   = htons(atoi(argv[2]));  
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);  
    if(bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)  
        perror("bind");  
      
while(1)
{  
        n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&peer_addr,&addrlen);  
		if(n < 0){  
            perror("recvfrom ");  
        }         
        buf[n] = '\0';  
          
        pid = fork();  
        if(pid < 0)
{  
            perror("Fail to fork");  
            exit(EXIT_FAILURE);  
        }  
        if(pid == 0)
{  
            close(sockfd);  
            do_client(peer_addr,buf);  
            exit(EXIT_SUCCESS);  
        }  
    }  
    return 0;  
} 

运行结果:
客户端:
在这里插入图片描述
服务器端:
在这里插入图片描述
分析:
服务器端和客户之间交互多个数据包,服务器端只有一个众所周知的端口(此处为8000),服务器为每个客户创建一个新的套接口,在其上绑定一个临时端口(此处为36213),然后用套接口和客户进行交互。
如上图所示,客户的第一个数据包是发给服务器的父进程,端口号为8000,接收到该客户的请求数据时,创建了一个子进程,在子进程中,创建新套接口,并发送给客户端来自服务器的第一个应答,接下来客户就与服务器(子进程)交换剩余数据包。此时父进程继续接受请求数据。
还有一个需要说明的是,图中客户端发送的第一条字符串,服务器端并没有返回一个转换成大写的字符串,这是因为客户端的第一条数据都是发给服务器端的父进程,服务器端收到这个请求后,才会创建子进程为其提供服务,客户端后面发送的字符串,都是该子进程转换后发回的。

2.使用原始套接口编写发送ICMP回射请求,并接受和分析回射应答数据
构建原始套接口,向目标机发送若干个ICMP回射请求,接受回射应答ICMP数据报,分析后输出到标准输出或指定文件。

主要代码:(ping1.c)
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <netdb.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#ifdef	HAVE_SOCKADDR_DL_STRUCT
#include	<net/if_dl.h>
#endif	
#define	BUFSIZE	1500
extern int	optind, opterr, optopt;
//全局变量
char	 sendbuf[BUFSIZE];
int	 datalen;	/*ICMP数据部分字节数*/
char	*host;//主机地址
int	 nsent;	/* 序列号,每发送一个数据报增一 */
pid_t	 pid;	/* 进程 PID */
int	 sockfd; //套接口
int	 verbose;//v选项
void proc_v4(char *, ssize_t, struct msghdr *, struct timeval *);//数据报处理函数
void send_v4(void);//数据报发送函数
void readloop(void);//数据报循环接受函数
void sig_alrm(int);//信号处理函数
void tv_sub(struct timeval *, struct timeval *);//计算往返时间RTT
uint16_t in_cksum(uint16_t *addr, int len);//计算校验和 16位值的二进制反码和

struct proto {  //处理ping功能的结构体
  void	 (*fproc)(char *, ssize_t, struct msghdr *, struct timeval *);//处理数据函数
  void	 (*fsend)(void);//发送数据 函数
  void	 (*finit)(void);//初始化函数
  struct sockaddr  *sasend;	/* sockaddr{} for send, from getaddrinfo */
  struct sockaddr  *sarecv;	/* sockaddr{} for receiving */
  socklen_t	    salen;	/* length of sockaddr{}s */
  int	   	    icmpproto;	/* IPPROTO_xxx value for ICMP */
} *pr;

struct addrinfo *//获取目标地址信息
Host_serv(const char *host, const char *serv, int family, int socktype)
{
	int	n;
	struct addrinfo	hints, *res;

	bzero(&hints, sizeof(struct addrinfo));
	hints.ai_flags = AI_CANONNAME;	/* always return canonical name */
	hints.ai_family = family;	/* 0, AF_INET, AF_INET6, etc. */
	hints.ai_socktype = socktype;	/* 0, SOCK_STREAM, SOCK_DGRAM, etc. */

	if ( (n = getaddrinfo(host, serv, &hints, &res)) != 0)
	{
	    fprintf(stderr,"host_serv error for %s, %s: %s",
	    (host == NULL) ? "(no hostname)" : host,
	    (serv == NULL) ? "(no service name)" : serv,
	    gai_strerror(n));
	    exit(1);
	}

	return(res);	/* return pointer to first on linked list */
}
char *
sock_ntop_host(const struct sockaddr *sa, socklen_t salen)
{
    static char str[128];	/* Unix domain is largest */

	switch (sa->sa_family) {
	case AF_INET: {
	    struct sockaddr_in	*sin = (struct sockaddr_in *) sa;

	    if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
	      return(NULL);
	      return(str);
	}

	default:
	     snprintf(str, sizeof(str), "sock_ntop_host: unknown AF_xxx: %d, len %d",
	     sa->sa_family, salen);
	     return(str);
	}
    return (NULL);
}
char *
Sock_ntop_host(const struct sockaddr *sa, socklen_t salen)
{
	char	*ptr;

	if ( (ptr = sock_ntop_host(sa, salen)) == NULL)
	{
	    fprintf(stderr,"sock_ntop_host error\n");	/* inet_ntop() sets errno */
	    exit(1);
	}
	
	return(ptr);
}
void
readloop(void)
{
	int	size;
	char recvbuf[BUFSIZE];
	char controlbuf[BUFSIZE];
	struct msghdr	msg;
	struct iovec	iov;
	ssize_t	n;
	struct timeval	tval;

	sockfd = socket(pr->sasend->sa_family, SOCK_RAW, pr->icmpproto); // 进程必须拥有超级用户特权才能创建原始套接字
	setuid(getuid());// 把进程的有效用户ID设置为实际用户ID,使得进程放弃对超级用户特权的拥有(防攻击)
	if (pr->finit)
	(*pr->finit)();

	size = 60 * 1024;	  /* 设置套接字接收缓冲区的大小,防止接收缓冲区溢出 */
	setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));

	sig_alrm(SIGALRM);	/* 每秒钟驱动一次*/
 /* 在一个原始套接字上读入收到的每个分组,显示ICMP回射应答 */
	iov.iov_base = recvbuf;
	iov.iov_len = sizeof(recvbuf);
	msg.msg_name = pr->sarecv;
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_control = controlbuf;
	for ( ; ; ) {
	    msg.msg_namelen = pr->salen;
	    msg.msg_controllen = sizeof(controlbuf);
	    n = recvmsg(sockfd, &msg, 0);// 读入回射到原始ICMP套接字的每个分组
	    if (n < 0) {
	        if (errno == EINTR)
	        continue;
	        else
	        {
	            fprintf(stderr,"recvmsg error\n");
	           exit(1);
	        }
	    }

	    gettimeofday(&tval, NULL);// 记录分组收取时刻,用于计算RTT
	    (*pr->fproc)(recvbuf, n, &msg, &tval);
	}
}
void
proc_v4(char *ptr, ssize_t len, struct msghdr *msg, struct timeval *tvrecv)
{
	int	hlen1, icmplen;
	double	rtt;
	struct ip	*ip;
	struct icmp	*icmp;
	struct timeval	*tvsend;

	ip = (struct ip *) ptr;	/* start of IP header */
	hlen1 = ip->ip_hl << 2;	/* length of IP header */
	if (ip->ip_p != IPPROTO_ICMP)// 检查ICMP标识符字段	
	    return;	/* not ICMP */

	icmp = (struct icmp *) (ptr + hlen1);	/* start of ICMP header */
	if ( (icmplen = len - hlen1) < 8)// 是否为完整ICMP数据
	    return;	/* malformed packet */

	if (icmp->icmp_type == ICMP_ECHOREPLY) {// ICMP消息的“类型”
	    if (icmp->icmp_id != pid)// ICMP消息的“标识符”
	     return;	/* not a response to our ECHO_REQUEST */
	    if (icmplen < 16)
	     return;	/* not enough data to use */

	    tvsend = (struct timeval *) icmp->icmp_data;// ICMP消息的“可选数据”	
	    tv_sub(tvrecv, tvsend);
	    rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0;

	    printf("%d bytes from %s: seq=%u, ttl=%d, rtt=%.3f ms\n",
	    icmplen, sock_ntop_host(pr->sarecv, pr->salen),
	    icmp->icmp_seq, ip->ip_ttl, rtt);

	} else if (verbose) {
	//接收到的数据不ICMP_ECHOREPLY	0 回射应答报文
	//可能为#define ICMP_DEST_UNREACH	3  目标不可达
	//对本地进行ping会收到#define ICMP_ECHO	8  回射请求报文
	    printf("  %d bytes from %s: type = %d, code = %d\n",
	    icmplen, sock_ntop_host(pr->sarecv, pr->salen),
	    icmp->icmp_type, icmp->icmp_code);
	}
}
/* 相减两个timeval时间,结果放到out中 */
void
tv_sub(struct timeval *out, struct timeval *in)
{
	if ( (out->tv_usec -= in->tv_usec) < 0) {	/* out -= in */
	    --out->tv_sec;
	    out->tv_usec += 1000000;
	}
	out->tv_sec -= in->tv_sec;
}
void
sig_alrm(int signo)
{
	(*pr->fsend)();

	alarm(1);
	return;
}
/* 发送ICMPv4回射请求消息。 
 *
 * 0_______7.8_____15.16_____________31
 * |__类型__|__代码__|_____校验和_____|
 * |______标识符_____|_____序列号_____|
 * |                                  |
 * .             可选数据             .
 * |__________________________________|
 *
 *        (图:ICMPv4消息的格式)
 ************************************************/
void
send_v4(void)
{
	int	len;
	/* 构造ICMPv4消息 */
	struct icmp	*icmp; 
	icmp = (struct icmp *) sendbuf;
	icmp->icmp_type = ICMP_ECHO;// 消息的“类型”
	icmp->icmp_code = 0;// 消息的“代码”值为0
	icmp->icmp_id = pid;// 消息的“标识符”使用ping进程的PID
	icmp->icmp_seq = nsent++; // 消息的“序列号”递增
	memset(icmp->icmp_data, 0xa5, datalen);	// 消息的“可选数据”填充0xa5
	gettimeofday((struct timeval *) icmp->icmp_data, NULL);// 消息的“可选数据”开始处存放发送时刻的8字节时间戳

	len = 8 + datalen;	// ICMP“首部”和“可选数据”的长度和
	icmp->icmp_cksum = 0;// 计算校验和之前,要将校验和字段置为0
	icmp->icmp_cksum = in_cksum((u_short *) icmp, len);

	sendto(sockfd, sendbuf, len, 0, pr->sasend, pr->salen);
}
uint16_t
in_cksum(uint16_t *addr, int len)
{
	int nleft = len;
	uint32_t	sum = 0;
	uint16_t	*w = addr;
	uint16_t	answer = 0;
	while (nleft > 1)  {
	    sum += *w++;
	    nleft -= 2;
	}
	/* 4mop up an odd byte, if necessary */
	if (nleft == 1) {
	    *(unsigned char *)(&answer) = *(unsigned char *)w ;
	    sum += answer;
	}
	/* 4add back carry outs from top 16 bits to low 16 bits */
	sum = (sum >> 16) + (sum & 0xffff);	/* sum的高16位与低16位第一次相加 */
	sum += (sum >> 16);	/* 将上一步可能产生的高16位进位再次与低16位累加*/
	answer = ~sum;	/* truncate to 16 bits */
	return(answer);
}
struct proto	proto_v4 = { proc_v4, send_v4, NULL, NULL, NULL, 0, IPPROTO_ICMP };//初始化处理结构体
int	datalen = 56;		/* 随同回射请求发送的可选ICMP数据量设置为56字节,导致84字节的IPv4数据报(20字节IP头,8字节ICMP头)*/
int
main(int argc, char **argv)
{
	int c;
	struct addrinfo	*ai;
	char *h;
	 /* getopt被用来解析命令行选项参数。调用一次,返回一个选项。
     * extern int opterr;   //当opterr=0时,getopt不向stderr输出错误信息
     * extern int optopt;   //未知选项存储在optopt中,并且getopt返回'?'
     * extern int optind;   //getopt要处理的argv中的下一个字符选项的索引
     * extern char *optarg; //选项的参数指针    
     * 当再也检查不到包含的选项时,getopt返回-1,同时optind储存第一个不包含选项的命令行参数的索引。*/
	opterr = 0;		/* don't want getopt() writing to stderr */
	while ( (c = getopt(argc, argv, "v")) != -1) {
		switch (c) {
		case 'v'://查找到v参数
			verbose++;
			break;

		case '?'://没有匹配的字符"v"
			fprintf(stderr,"unrecognized option: %c", c);
			exit(1);
		}
	}
	if (optind != argc-1)
		{
			fprintf(stderr,"usage: ping [ -v ] <hostname>");
			exit(1);
		}
		
	host = argv[optind];
	pid = getpid() & 0xffff;	/* ICMP ID field is 16 bits */
	signal(SIGALRM, sig_alrm);
	ai = Host_serv(host, NULL, 0, 0);//getaddrinfo()函数获取目标主机名等地址信息
	if(!ai)
		{
		 printf("host unknown! \n");
		 exit (1);
		}
	h = Sock_ntop_host(ai->ai_addr, ai->ai_addrlen);//地址转换成点分十进制,从套接字地址结构中得到主机IP信息
	printf("PING %s (%s): %d data bytes\n",
			ai->ai_canonname ? ai->ai_canonname : h,
			h, datalen);

		/* 根据协议地址初始化全局变量pr */
	if (ai->ai_family == AF_INET) {
		pr = &proto_v4;//只使用了IPv4的地址,若增加条件,可以处理IPv6的地址
	} else
		{
			fprintf(stderr,"unknown address family %d", ai->ai_family);
			exit(1);
		}

	pr->sasend = ai->ai_addr;
	pr->sarecv = calloc(1, ai->ai_addrlen);
	pr->salen = ai->ai_addrlen;

	readloop();//循环处理收到信息
	exit(0);
}

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

参考文献
[1]《网络应用程序设计》,西安电子科技大学出版社,方敏、张彤编著
[2]Linux 网络编程——并发服务器的三种实现模型
https://blog.csdn.net/jason_huzhe/article/details/52352015
[3]并发服务器
https://www.cnblogs.com/renzhuang/articles/6950687.html

发布了34 篇原创文章 · 获赞 51 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_39480875/article/details/86746215