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内容:
a2.txt内容:
运行结果:
客户端:
服务器端:
运行后:
b1.txt的内容:
b2.txt的内容:
分析:客户端设置了两个指针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