ping程序-c语言实现
来源:互联网 发布:什么是半结构化数据 编辑:程序博客网 时间:2024/06/09 17:52
创建时间: 2017-08-17
最后修改时间: 2017-08-17因个人水平有限,文章中存在不足,错误之处,还望指正
实验环境
Linux 2.6.32
gcc version 4.4.6 20120305 (Red Hat 4.4.6-4) (GCC)
引言
本ping程序并没有提供网络上公开可得的ping程序那样强大的功能(支持众多不同的选项),而是仅实现最基本的功能来了解我们所关注的问题:网络编程的概念和技巧。
ping程序简介
ping程序可以用来测试网络上的节点是否可达或网络连接速度。该程序发送一个ICMP回显请求给目的端,并等待其返回ICMP回显应答。
一般来说,如果不能ping到某主机,那么就不能Telnet到该主机。但这并不是绝对的,随着Internet安全意识的增强,出现了提供访问控制的路由器和防火墙,一台主机的可达性可能不只取决于IP层是否可达,还取决于使用何种协议以及端口。ping程序运行结果显示某台主机不可达,但我们却可以用Telnet远程登录该主机。
IP和ICMP协议简介
IP(Internet Protocol),网际协议,是TCP/IP协议族中最为核心的协议。所有TCP、UDP、ICMP及IGMP数据都以IP数据报格式传输。
它提供不可靠、无连接的数据报传送服务。
不可靠的意思是它不能保证IP数据报能成功地到达目的地。如果发生某种错误时,如某个路由器的缓冲区已满,IP有一个简单的错误处理方法:丢弃该数据报,然后发送ICMP消息给源端。任何要求的可靠性必须由上层服务来提供,如TCP。
无连接的意思是IP并不维护任何关于后续数据报信息。每个数据报的处理是相互独立的。这说明,IP数据报可以不按发送顺序接收。如果源端向目的端发送两个连续的数据报A、B,每个数据报都是独立进行路由选择,可能选择不同的线路,因此B可能在A之前先到达。
IP首部
0 4 8 16 19 32 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|Version| IHL |Type Of Service| Total Length(in bytes) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Identification |Flags| Fragment Offset |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Time To Live | Protocol | Header Checksum |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Source Ipv4 Address |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Destination Ipv4 Address |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+~ Options (if any) ~+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+~ Data ~+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ IPv4首部
ICMP(Internet Control Message Protocol),Internet控制消息协议,是TCP/IP协议族的一个子协议,属于网络层协议,用于传递差错报文以及其他需要注意的信息。
ICMP报文在IP数据报内部被传输的。如下:
|------------------------- IP数据报 ------------------------|+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| IP首部 | ICMP报文 |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
ICMP报文有许多种类型,这里只列出我们所关注的报文格式:
0 8 16 32 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Type | Code | Checksum |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Identifier | Sequence Number |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+~ Option Data ~+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ICMP回显请求和回显应答报文首部Type ICMP报文类型Code 代码Checksum 检验和Identifier 标识符,由源端设定,应答报文的Identifier与请求报文的保持一致Sequence Number 序列号,由源端设定,一般是递增的,应答报文的Sequence Number与请求报文的保持一致Option Data 数据,应答报文的Option Data与请求报文的保持一致
与IP和ICMP首部相关的结构
在/usr/include/目录下可找到这些头文件。
ip结构体在头文件 “netinet/ip.h”中定义如下:
struct ip{#if __BYTE_ORDER == __LITTLE_ENDIAN unsigned int ip_hl:4; /* header length */ unsigned int ip_v:4; /* version */#endif#if __BYTE_ORDER == __BIG_ENDIAN unsigned int ip_v:4; /* version */ unsigned int ip_hl:4; /* header length */#endif u_int8_t ip_tos; /* type of service */ u_short ip_len; /* total length */ u_short ip_id; /* identification */ u_short ip_off; /* fragment offset field */#define IP_RF 0x8000 /* reserved fragment flag */#define IP_DF 0x4000 /* dont fragment flag */#define IP_MF 0x2000 /* more fragments flag */#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ u_int8_t ip_ttl; /* time to live */ u_int8_t ip_p; /* protocol */ u_short ip_sum; /* checksum */ struct in_addr ip_src, ip_dst; /* source and dest address */};
icmp结构体在头文件”netinet/ip_icmp.h”中定义如下:
struct icmp{ u_int8_t icmp_type; /* type of message, see below */ u_int8_t icmp_code; /* type sub code */ u_int16_t icmp_cksum; /* ones complement checksum of struct */ union { u_char ih_pptr; /* ICMP_PARAMPROB */ struct in_addr ih_gwaddr; /* gateway address */ struct ih_idseq /* echo datagram */ { u_int16_t icd_id; u_int16_t icd_seq; } ih_idseq; u_int32_t ih_void; /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */ struct ih_pmtu { u_int16_t ipm_void; u_int16_t ipm_nextmtu; } ih_pmtu; struct ih_rtradv { u_int8_t irt_num_addrs; u_int8_t irt_wpa; u_int16_t irt_lifetime; } ih_rtradv; } icmp_hun;#define icmp_pptr icmp_hun.ih_pptr#define icmp_gwaddr icmp_hun.ih_gwaddr#define icmp_id icmp_hun.ih_idseq.icd_id#define icmp_seq icmp_hun.ih_idseq.icd_seq#define icmp_void icmp_hun.ih_void#define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void#define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu#define icmp_num_addrs icmp_hun.ih_rtradv.irt_num_addrs#define icmp_wpa icmp_hun.ih_rtradv.irt_wpa#define icmp_lifetime icmp_hun.ih_rtradv.irt_lifetime union { struct { u_int32_t its_otime; u_int32_t its_rtime; u_int32_t its_ttime; } id_ts; struct { struct ip idi_ip; /* options and then 64 bits of data */ } id_ip; struct icmp_ra_addr id_radv; u_int32_t id_mask; u_int8_t id_data[1]; } icmp_dun;#define icmp_otime icmp_dun.id_ts.its_otime#define icmp_rtime icmp_dun.id_ts.its_rtime#define icmp_ttime icmp_dun.id_ts.its_ttime#define icmp_ip icmp_dun.id_ip.idi_ip#define icmp_radv icmp_dun.id_radv#define icmp_mask icmp_dun.id_mask#define icmp_data icmp_dun.id_data};
c语言实现
该实现主要划分成三大部分:主体结构、ICMP请求发送和ICMP应答处理。在主体结构部分中主要是处理用户参数并创建一个原始套接字;在ICMP请求发送部分中主要是构建一个ICMP回显请求报文并发送到目的端;在ICMP应答处理部分中主要是解析报文,计算并打印ICMP应答信息。
宏定义、全局变量定义以及函数声明:
#define EXIT_ERR 1 /* 异常退出状态码 */#define BUFSIZE 1500 /* 缓冲区大小 */ #define SEQSIZE 100 /* 已发送的icmp回显请求报文的seq数组大小 */#define ECHO_RATE 1 /* 报文发送速率,单位(s) */#define ICMP_HLEN 8 /* icmp报文头部长度 *//********** 全局变量 **********/int sockfd; /* 套接字变量 */pid_t pid; /* 进程ID */int nsend, nrecv; /* 发送报文数和接收报文数 */char sendbuf[BUFSIZE];int seqarr[SEQSIZE]; /* 已发送的icmp回显请求报文的seq数组 */struct sockaddr_in destaddr; /* 目标主机信息 */struct hostent *desthostent; int datalen = 56;/********** 函数声明 **********/int seq_init(int seqarr[], int len);int seq_add(int seq, int seqarr[], int len);int seq_del(int seq, int seqarr[], int len);uint16_t in_cksum(uint16_t *addr, int len);void icmp_echo(void);void proc_echoreply(void);void statistics(void);
主体结构:
1、处理用户输入的参数,并将其转换成对应的结构;
2、创建一个原始套接字,这需要超级用户特权。创建好后放弃超级用户特权;
3、修改套接字缓冲区大小;
int main(int argc, char *argv[]){ int size; struct sigaction act; in_addr_t saddr; if (argc < 2) { printf("usage: ping <host> \n"); exit(EXIT_ERR); } /* 初始化目标地址信息 */ if ((saddr = inet_addr(argv[1])) == INADDR_NONE) { if ((desthostent = gethostbyname(argv[1])) == NULL) { printf("unknow host: %s \n", argv[1]); exit(EXIT_ERR); } else { memmove(&saddr, desthostent->h_addr_list[0], desthostent->h_length); } } bzero(&destaddr, sizeof(destaddr)); destaddr.sin_family = AF_INET; destaddr.sin_addr.s_addr = saddr;; /* 创建原始套接字后,放弃超级用户特权(创建原始套接字需要超级用户特权) */ if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) { perror("socket error"); exit(EXIT_ERR); } setuid(getuid()); /* 修改套接字的接收缓冲区大小 */ size = 60 * 1024; setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); printf("PING %s (%s) %d bytes of data. \n", desthostent==NULL? inet_ntoa(destaddr.sin_addr):desthostent->h_name, inet_ntoa(destaddr.sin_addr), datalen);/* /* 设定闹钟 */ alarm(ECHO_RATE); /* 处理回显应答 */ proc_echoreply(); */ return 0;}
ICMP回显请求发送:
1、设置一个闹钟,每1s调用一次icmp_echo函数发送一个回显请求报文;
int main(int argc, char *argv[]){ ... /* 建立SIGALRM信号处理函数 */ sigemptyset(&act.sa_mask); act.sa_handler = sig_alrm; act.sa_flags = 0; sigaction(SIGALRM, &act, NULL); ... alarm(ECHO_RATE);}/* * SIGALRM信号处理函数 */void sig_alrm(int signo){ icmp_echo(); alarm(ECHO_RATE); return ;}/* * icmp报文发送函数 */void icmp_echo(void){ struct icmp *icmp; size_t len; /* 如果已发送报文序号数组已满,则向自身发送SIGINT信号 */ if (seq_add(nsend, seqarr, SEQSIZE) < 0) { raise(SIGINT); pause(); return ; } icmp = (struct icmp *) sendbuf; icmp->icmp_type = ICMP_ECHO; icmp->icmp_code = 0; icmp->icmp_id = pid; icmp->icmp_seq = nsend++; memset(icmp->icmp_data, 0xa5, datalen); gettimeofday((struct timeval *) icmp->icmp_data, NULL); len = ICMP_HLEN + datalen; icmp->icmp_cksum = 0; icmp->icmp_cksum = in_cksum((u_short *) icmp, len); if (sendto(sockfd, sendbuf, len, 0, (struct sockaddr *) &destaddr, sizeof(destaddr)) < 0) { perror("sendto error"); exit(EXIT_ERR); }}
ICMP报文处理:
1、读取套接字,并过滤掉不要的数据;
2、计算并打印数据;
/* * 处理接收到的icmp响应报文函数 */void proc_echoreply(void){ char recvbuf[BUFSIZE]; ssize_t n; double rtt; struct ip *ip; struct icmp *icmp; int iphlen, icmplen; struct timeval *tvsend, *tvrecv; struct timeval tv; for (;;) { if ((n = recv(sockfd, recvbuf, BUFSIZE, 0)) < 0) { if (errno == EINTR) continue; else { perror("recv error"); exit(EXIT_ERR); } } /* 获取数据接收到的时间 */ gettimeofday(&tv, NULL); tvrecv = &tv; /* 解析报文 */ ip = (struct ip *) recvbuf; iphlen = ip->ip_hl << 2; /* 获取ip报文头部长度 */ if (ip->ip_p != IPPROTO_ICMP) continue; icmp = (struct icmp *) (recvbuf + iphlen); if ((icmplen = n - iphlen) < ICMP_HLEN) continue; if (icmp->icmp_type != ICMP_ECHOREPLY) continue; if (icmp->icmp_id != pid) continue; if (icmplen < 16) continue; /* 若报文的序号不存在已发送数组中,则不计数 */ if (seq_del(icmp->icmp_seq, seqarr, SEQSIZE) != -1) { nrecv++; } /* 计算rtt */ tvsend = (struct timeval *) icmp->icmp_data; if ((tvrecv->tv_usec -= tvsend->tv_usec ) < 0) { --tvrecv->tv_sec; tvrecv->tv_usec += 1000000; } tvrecv->tv_sec -= tvsend->tv_sec; rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0; printf("%d bytes from %s: icmp_seq=%d, ttl-%d, rtt=%.3f ms \n", icmplen, inet_ntoa(ip->ip_src), icmp->icmp_seq, ip->ip_ttl, rtt); }}
统计ICMP报文收发结果:
1、设置SIGINT信号处理函数,当收到SIGINT信号时调用statistic函数统计结果,并结束程序;
int main(int argc, char *argv[]){ ... /* 建立SIGINT信号处理函数 */ sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_handler = sig_int; sigaction(SIGINT, &act, NULL); ...}/* * SIGINT信号处理函数 */void sig_int(int signo){ statistics(); exit(0);}/* * 统计报文发送和接收的结果 */void statistics(void){ int nloss; nloss = nsend - nrecv; printf("\n"); printf("--- %s ping statistics --- \n", inet_ntoa(destaddr.sin_addr)); printf("%d packets transmitted, %d received, %.1f%% packet loss \n", nsend, nrecv, (double) nloss / nsend * 100);}
完整源码:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <strings.h>#include <unistd.h>#include <errno.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/ip.h>#include <netinet/ip_icmp.h>#include <netdb.h>#include <sys/signal.h>#define EXIT_ERR 1 /* 异常退出状态吗 */#define BUFSIZE 1500 /* 缓冲区大小 */ #define SEQSIZE 100 /* 已发送的icmp回显请求报文的seq数组大小 */#define ECHO_RATE 1 /* 报文发送速率,单位(s) */#define ICMP_HLEN 8 /* icmp报文头部长度 *//********** 全局变量 **********/int sockfd; /* 套接字变量 */pid_t pid; /* 进程ID */int nsend, nrecv; /* 发送报文数和接收报文数 */char sendbuf[BUFSIZE];int seqarr[SEQSIZE]; /* 已发送的icmp回显请求报文的seq数组 */struct sockaddr_in destaddr; /* 目标主机信息 */struct hostent *desthostent; int datalen = 56;/********** 函数声明 **********/int seq_init(int seqarr[], int len);int seq_add(int seq, int seqarr[], int len);int seq_del(int seq, int seqarr[], int len);uint16_t in_cksum(uint16_t *addr, int len);void icmp_echo(void);void proc_echoreply(void);void statistics(void);/* * 初始化数组 */int seq_init(int seqarr[], int len){ int i; for (i=0; i<len; i++) seqarr[i] = -1; return 0;}/* * 添加一个元素到数组中,若成功,则返回该下标,否则返回-1 */int seq_add(int seq, int seqarr[], int len){ int i; for (i=0; i<len; i++) if (seqarr[i] == -1) { seqarr[i] = seq; break; } if (i >= len) return -1; return i;}/* * 删除数组中对应的元素,若成功,则返回该下标,否则返回-1 */int seq_del(int seq, int seqarr[], int len){ int i; for (i=0; i<len; i++) { if (seqarr[i] == seq) { seqarr[i] = -1; break; } } if (i >= len) return -1; return i;}/* * 计算校验和 */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; } if (nleft == 1) { *(unsigned char *) (&answer) = *(unsigned char *) w; sum += answer; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); answer = ~sum; return answer;}/* * icmp报文发送函数 */void icmp_echo(void){ struct icmp *icmp; size_t len; /* 如果已发送报文序号数组已满,则向自身发送SIGINT信号 */ if (seq_add(nsend, seqarr, SEQSIZE) < 0) { raise(SIGINT); pause(); return ; } icmp = (struct icmp *) sendbuf; icmp->icmp_type = ICMP_ECHO; icmp->icmp_code = 0; icmp->icmp_id = pid; icmp->icmp_seq = nsend++; memset(icmp->icmp_data, 0xa5, datalen); gettimeofday((struct timeval *) icmp->icmp_data, NULL); len = ICMP_HLEN + datalen; icmp->icmp_cksum = 0; icmp->icmp_cksum = in_cksum((u_short *) icmp, len); if (sendto(sockfd, sendbuf, len, 0, (struct sockaddr *) &destaddr, sizeof(destaddr)) < 0) { perror("sendto error"); exit(EXIT_ERR); }}/* * 处理接收到的icmp响应报文函数 */void proc_echoreply(void){ char recvbuf[BUFSIZE]; ssize_t n; double rtt; struct ip *ip; struct icmp *icmp; int iphlen, icmplen; struct timeval *tvsend, *tvrecv; struct timeval tv; for (;;) { if ((n = recv(sockfd, recvbuf, BUFSIZE, 0)) < 0) { if (errno == EINTR) continue; else { perror("recv error"); exit(EXIT_ERR); } } /* 获取数据接收到的时间 */ gettimeofday(&tv, NULL); tvrecv = &tv; /* 解析报文 */ ip = (struct ip *) recvbuf; iphlen = ip->ip_hl << 2; /* 获取ip报文头部长度 */ if (ip->ip_p != IPPROTO_ICMP) continue; icmp = (struct icmp *) (recvbuf + iphlen); if ((icmplen = n - iphlen) < 8) continue; if (icmp->icmp_type != ICMP_ECHOREPLY) continue; if (icmp->icmp_id != pid) continue; if (icmplen < 16) continue; /* 若报文的序号不存在已发送数组中,则不计数 */ if (seq_del(icmp->icmp_seq, seqarr, SEQSIZE) != -1) { nrecv++; } /* 计算rtt */ tvsend = (struct timeval *) icmp->icmp_data; if ((tvrecv->tv_usec -= tvsend->tv_usec ) < 0) { --tvrecv->tv_sec; tvrecv->tv_usec += 1000000; } tvrecv->tv_sec -= tvsend->tv_sec; rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0; printf("%d bytes from %s: icmp_seq=%d, ttl-%d, rtt=%.3f ms \n", icmplen, inet_ntoa(ip->ip_src), icmp->icmp_seq, ip->ip_ttl, rtt); }}/* * 统计报文发送和接收的结果 */void statistics(void){ int nloss; nloss = nsend - nrecv; printf("\n"); printf("--- %s ping statistics --- \n", inet_ntoa(destaddr.sin_addr)); printf("%d packets transmitted, %d received, %.1f%% packet loss \n", nsend, nrecv, (double) nloss / nsend * 100);}/* * SIGALRM信号处理函数 */void sig_alrm(int signo){ icmp_echo(); alarm(ECHO_RATE); return ;}/* * SIGINT信号处理函数 */void sig_int(int signo){ statistics(); exit(0);}int main(int argc, char *argv[]){ int size; struct sigaction act; in_addr_t saddr; if (argc < 2) { printf("usage: ping <ip> \n"); exit(EXIT_ERR); } /* 初始化目标地址信息 */ if ((saddr = inet_addr(argv[1])) == INADDR_NONE) { if ((desthostent = gethostbyname(argv[1])) == NULL) { printf("unknow host: %s \n", argv[1]); exit(EXIT_ERR); } else { memmove(&saddr, desthostent->h_addr_list[0], desthostent->h_length); } } bzero(&destaddr, sizeof(destaddr)); destaddr.sin_family = AF_INET; destaddr.sin_addr.s_addr = saddr;; /* 创建原始套接字后,放弃超级用户特权(创建原始套接字需要超级用户特权) */ if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) { perror("socket error"); exit(EXIT_ERR); } setuid(getuid()); /* 修改套接字的接收缓冲区大小 */ size = 60 * 1024; setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); /* 建立SIGALRM,SIGINT信号处理函数 */ sigemptyset(&act.sa_mask); act.sa_handler = sig_alrm; act.sa_flags = 0; sigaction(SIGALRM, &act, NULL); act.sa_handler = sig_int; sigaction(SIGINT, &act, NULL); /* 初始化已发送报文序号数组 */ seq_init(seqarr, SEQSIZE); printf("PING %s (%s) %d bytes of data. \n", desthostent==NULL? inet_ntoa(destaddr.sin_addr):desthostent->h_name, inet_ntoa(destaddr.sin_addr), datalen); alarm(ECHO_RATE); proc_echoreply(); return 0;}
运行结果
[root@localhost test]# ./a.out 192.168.15.4PING 192.168.15.4 (192.168.15.4) 56 bytes of data. 64 bytes from 192.168.15.4: icmp_seq=0, ttl-64, rtt=1.710 ms 64 bytes from 192.168.15.4: icmp_seq=1, ttl-64, rtt=0.425 ms 64 bytes from 192.168.15.4: icmp_seq=2, ttl-64, rtt=0.879 ms 64 bytes from 192.168.15.4: icmp_seq=3, ttl-64, rtt=0.703 ms ^C--- 192.168.15.4 ping statistics --- 4 packets transmitted, 4 received, 0.0% packet loss [root@localhost test]# arp -a # ARP高速缓存? (192.168.15.4) at 00:0c:29:06:00:8c [ether] on eth0? (192.168.15.1) at 00:50:56:c0:00:08 [ether] on eth0[root@localhost test]#
扩展知识
- 第一个ICMP消息的rtt几乎总是比后面的时间要长
在分析ping的输出时,发现第一条输出几乎总是比后面的输出的rtt时间要长,这是因为源端会先发送ARP请求来确定目的端的MAC地址(如果目的端MAC不存在于ARP高速缓存中),这个过程耗费了一定时间。之后发送到相同目的端时就不用再发送ARP请求了。
参考
《UNIX网络编程 卷1:套接字联网API》
《TCP/IP详解 卷1:协议》
Fireplusplus-用C语言实现Ping命令:http://blog.csdn.net/qq_33724710/article/details/51576444
- C语言实现PING程序
- ping程序-c语言实现
- C 语言实现 ping 程序
- 用C语言实现Ping程序功能
- 用C语言实现Ping程序功能
- 用C语言实现ping程序
- 用C语言实现Ping程序功能
- 用C语言实现Ping程序功能
- 用C语言实现Ping程序功能
- 基于C语言实现的Ping程序
- 基于C语言实现的Ping程序
- 用C语言实现Ping程序功能
- 用C语言实现Ping程序功能
- 用C语言实现Ping程序功能
- 用C语言实现Ping程序功能
- 用C语言实现Ping程序功能
- 用C语言实现Ping程序功能
- 用C语言实现Ping程序功能
- glViewport函数和glOrtho函数的理解
- webpack hello.js hello.bundle.js 报错 'webpack' 不是内部或外部命令
- Spring Web MVC
- uva 1354 Mobile Computing
- 注解
- ping程序-c语言实现
- Android打造不一样的EmptyView
- Java并发编程:线程池的使用
- 装饰器,高阶函数,嵌套函数
- 设计模式(4)
- 说说ASP.Net Core 2.0中的Razor Page
- 小K的农场(差分约束)
- 21.driverbase-多线程PsCreateSystemThread
- base64编码和解码