//-----------------------iphdr.h-----------------------//
//源码分析将忽略ipv6
//边界
对齐至字节
#include
pshpack1.h为官方头文件,不做赘述。
// 1 -- ipv4
头部
typedef
struct ip_hdr
{
unsigned char ip_verlen; //
前4位
IP
版本号
(IPv4
或者
IPv6)
//
后4位头部长度(32位,4字节)(1.1)
unsigned char ip_tos; //
前3位为优先级,后5位为服务类型(1.2)
unsigned short ip_totallength; // 16
位包总长度包括头部和数据(字节)(1.3)
unsigned short ip_id; // 16
位
ID
标识
unsigned short ip_offset; //
前3位为分段标识,后5位为分段偏移
unsigned char ip_ttl; //
该包可经过的路由器数量上限
unsigned char ip_protocol; //
协议类型(
TCP
,
UDP
,
ICMP
,
IGMP
等)
unsigned short ip_checksum; //
ipv4 头部的校验和
unsigned int ip_srcaddr; // ipv4
源地址
unsigned int ip_destaddr; // ipv4
目的地址
} IPV4_HDR, *PIPV4_HDR, FAR * LPIPV4_HDR;
此为IPv4头部定义。IPv4头部一般为20字节大小除非使用到选项位。
(1.1) 由于最大头部的限制,ping所具有路由记录功能在如今的网络拓扑中无法使用(只能记录9个ipv4地址)。 此功能 由traceroute替代。
(1.2) 以上参照CCNA Study Guide的说法。在TCP/IP illustrated Volume I中前3位被忽略,后4位分别代表minimize delay, maximize throughput, maximize reliability, 和minimize monetray cost,最后1位被置0并忽略。
(1.3) 理论上ipv4可以达到的最大长度为65536字节,除了在超级通道(hyperchannel)中出现这种最大传输单元外,在普通网络中通常只允许 8192字节大小的包传输。TCP为流协议没有大小限制,UDP最大包为512字节。一台主机一次可接收的最大数据包为576字节。
// 2 -- ipv4 选项头部
typedef
struct ipv4_option_hdr
{
unsigned char opt_code;
// ipv4 选项头类型
unsigned char opt_len;
// ipv4 选项头长度
unsigned char opt_ptr;
// ipv4 选项头指针
unsigned long opt_addr[9];
// ipv4 9个地址列表(2.1)
} IPV4_OPTION_HDR, *PIPV4_OPTION_HDR, FAR *LPIPV4_OPTION_HDR;
(2.1) 参照(1.1)
// 3 -- icmp 头部
typedef
struct icmp_hdr
{
unsigned char icmp_type;
// icmp 类型
unsigned char icmp_code;
// ipv4 码
unsigned short icmp_checksum;
// icmp 头部及数据校验和
unsigned short icmp_id;
// icmp id标识(3.1)
unsigned short icmp_sequence;
// icmp 序列号,请求回应消息对
} ICMP_HDR, *PICMP_HDR, FAR *LPICMP_HDR;
(3.1) id标识一般为发送icmp回显请求的进程号
// 4 -- udp 头部(此头部未在程序中用到)
typedef
struct udp_hdr
{
unsigned short src_portno;
// 源端口
unsigned short dst_portno;
// 目的端口
unsigned short udp_length;
// udp 包总长度(字节)
unsigned short udp_checksum;
// udp 头部以及数据校验和(4.1)
} UDP_HDR, *PUDP_HDR;
(4.1) UDP,TCP校验和都包括头部和数据。IP校验和只涉及头部。UDP校验和可选,TCP为强制。
// 5 -- ipv4 路径记录宏
#define
IP_RECORD_ROUTE 0x7
// icmp 类型和码(5.1)
#define
ICMPV4_ECHO_REQUEST_TYPE 8
// icmp 回显请求类型
#define
ICMPV4_ECHO_REQUEST_CODE 0
// icmp 回显请求码
#define
ICMPV4_ECHO_REPLY_TYPE 0
// icmp 回显回应类型
#define
ICMPV4_ECHO_REPLY_CODE 0
// icmp 回显回应码
#define
ICMPV4_MINIMUM_HEADER 8
// icmp 最小头部
(5.1) 参照TCP/IP Illustrated : Volume 1 Chapter 6 ICMP ICMP 消息类型
// 恢复默认对齐方式
#include
//-----------------------resolve.h---------------------//
#ifndef _RESOLVE_H_
#define
_RESOLVE_H_
// 在C++编译器中以C语言的方式编译
#ifdef
_cplusplus
extern "C" {
#endif
int
PrintAddress(SOCKADDR *sa, int salen);
int
FormatAddress(SOCKADDR *sa, int salen, char *addrbuf, int addrbuflen);
int
ReverseLookup(SOCKADDR *sa, int salen, char *namebuf, int namebuflen);
struct
addrinfo *ResolveAddress(char *addr, char *port, int af, int type, int proto);
#ifdef
_cplusplus
}
#endif
#endif
//-----------------------resolve.cpp---------------------//
// 6
#include
// socket 标准头文件
#include
// TCP/IP实现相关(6.1)
#include
// 提供安全的字符串操作(6.2)
#include
#include
#include
"resolve.h"
(6.1) 此头文件提供 getnameinfo,getaddrinfo函数。
(6.2) StringCchPrintf, StringCchCopy 具有相对于printf 和strcpy函数更多的缓冲区安全机制。
// 7
int
PrintAddress(SOCKADDR *sa, int salen)
{
char host[NI_MAXHOST],
serv[NI_MAXSERV];
int hostlen = NI_MAXHOST,
servlen = NI_MAXSERV,
rc;
rc = getnameinfo(
// 提供协议无关的名字解析(7.1)
sa,
salen,
host,
hostlen,
serv,
servlen,
NI_NUMERICHOST | NI_NUMERICSERV
);
if (rc != 0)
{
fprintf(stderr, "%s: getnameinfo failed: %d\n", __FILE__, rc);
return rc;
}
if (strcmp(serv, "0") != 0)
{
if (sa->sa_family == AF_INET)
printf("[%s]:%s", host, serv);
else
printf("%s:%s", host, serv);
}
else
printf("%s", host);
return NO_ERROR;
}
(7.1) host 缺省返回为一个在网络上的完整域名。serv返回为一个端口服务名。NI_NUMBERICHOST | NI_NUMBERICSERV 意味着返回以数字形式表示的host(IP地址)和serv(端口号)。
此函数与 ResolveAddress函数的区别为此函数将addrinfo结构中sockaddr翻译成为可读信息。
int
FormatAddress(SOCKADDR *sa, int salen, char *addrbuf, int addrbuflen)
{
char host[NI_MAXHOST],
serv[NI_MAXSERV];
int hostlen = NI_MAXHOST,
servlen = NI_MAXSERV,
rc;
HRESULT hRet;
rc = getnameinfo(
sa,
salen,
host,
hostlen,
serv,
servlen,
NI_NUMERICHOST | NI_NUMERICSERV
);
if (rc != 0)
{
fprintf(stderr, "%s: getnameinfo failed: %d\n", __FILE__, rc);
return rc;
}
if ( (strlen(host) + strlen(serv) + 1) > (unsigned)addrbuflen)
return WSAEFAULT;
addrbuf[0] = '\0';
if (sa->sa_family == AF_INET)
{
if(FAILED(hRet = StringCchPrintf(addrbuf, addrbuflen, "%s:%s", host, serv)))
{
fprintf(stderr,"%s StringCchPrintf failed: 0x%x\n",__FILE__,hRet);
return (int)hRet;
}
}
else if (sa->sa_family == AF_INET6)
{
if(FAILED(hRet = StringCchPrintf(addrbuf, addrbuflen, "[%s]:%s", host, serv)))
{
fprintf(stderr,"%s StringCchPrintf failed: 0x%x\n",__FILE__,hRet);
return (int)hRet;
}
}
return NO_ERROR;
}
以上代码将解析出的IP地址和端口号以 X:X的形式存放入字符串。
struct
addrinfo *ResolveAddress(char *addr, char *port, int af, int type, int proto)
{
struct addrinfo hints,
*res = NULL;
int rc;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = ((addr) ? 0 : AI_PASSIVE);
hints.ai_family = af;
hints.ai_socktype = type;
hints.ai_protocol = proto;
rc = getaddrinfo(
addr,
port,
&hints,
&res
);
if (rc != 0)
{
fprintf(stderr, "Invalid address %s, getaddrinfo failed: %d\n", addr, rc);
return NULL;
}
return res;
}
以上代码解析出域名将地址信息存入addrinfo结构。
int
ReverseLookup(SOCKADDR *sa, int salen, char *buf, int buflen)
{
char host[NI_MAXHOST];
int hostlen=NI_MAXHOST,
rc;
HRESULT hRet;
rc = getnameinfo(
sa,
salen,
host,
hostlen,
NULL,
0,
0
);
if (rc != 0)
{
fprintf(stderr, "getnameinfo failed: %d\n", rc);
return rc;
}
buf[0] = '\0';
if(FAILED(hRet = StringCchCopy(buf, buflen, host)))
{
fprintf(stderr,"StringCchCopy failed: 0x%x\n",hRet);
return (int)hRet;
}
return NO_ERROR;
}
DNS逆向查找(此函数未被调用过)。 此函数和PrintAddress 函数功能相近。
//-----------------------ping.cpp---------------------//
// 64位架构
#ifdef
_IA64_
#pragma warning (disable: 4267)
#endif
// 此宏定义使windows.h
剔除部分头文件,加快编译速度
#ifndef
WIN32_LEAN_AND_MEAN
#define
WIN32_LEAN_AND_MEAN
#endif
#include
#include
#include
#include
#include
"resolve.h"
#include
"iphdr.h"
#define
DEFAULT_DATA_SIZE 32 // icmp 数据段大小
#define
DEFAULT_SEND_COUNT 4 // ping 的次数
#define
DEFAULT_RECV_TIMEOUT 6000 // 接收超时
#define
DEFAULT_TTL 128
// 最大跳站术
#define
MAX_RECV_BUF_LEN 0xFFFF // 最大接收缓冲区大小
int
gAddressFamily=AF_UNSPEC,
gProtocol=IPPROTO_ICMP,
gTtl=DEFAULT_TTL;
int
gDataSize=DEFAULT_DATA_SIZE;
BOOL bRecordRoute=FALSE; // 是否记录路由
char
*gDestination=NULL,
recvbuf[MAX_RECV_BUF_LEN];
int
recvbuflen = MAX_RECV_BUF_LEN;
// ping 命令使用方法
void
usage(char *progname)
{
printf("usage: %s [options] \n" , progname);
printf(" host Remote machine to ping\n");
printf(" options: \n");
printf(" -a 4|6 Address family (default: AF_UNSPEC)\n");
printf(" -i ttl Time to live (default: 128) \n");
printf(" -l bytes Amount of data to send (default: 32) \n");
printf(" -r Record route (IPv4 only)\n");
return;
}
// 初始话icmp头部
void
InitIcmpHeader(char *buf, int datasize)
{
ICMP_HDR *icmp_hdr=NULL;
char *datapart=NULL;
// 详见icmp头部定义
icmp_hdr = (ICMP_HDR *)buf;
icmp_hdr->icmp_type = ICMPV4_ECHO_REQUEST_TYPE;
icmp_hdr->icmp_code = ICMPV4_ECHO_REQUEST_CODE;
icmp_hdr->icmp_id = (USHORT)GetCurrentProcessId();
//进程号
icmp_hdr->icmp_checksum = 0;
// 序列号未定义,校验和未计算
icmp_hdr->icmp_sequence = 0;
// 序列号置空
datapart = buf + sizeof(ICMP_HDR);
// 指针移至数据段头
memset(datapart, 'E', datasize);
// 填充数据段
}
// 计算校验和
USHORT checksum(USHORT *buffer, int size)
{
unsigned long cksum=0;
while (size > 1)
{
cksum += *buffer++;
size -= sizeof(USHORT);
}
if (size)
{
cksum += *(UCHAR*)buffer;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
return (USHORT)(~cksum);
}
// 传参解析
BOOL ValidateArgs(int argc, char **argv)
{
int i;
BOOL isValid = FALSE;
for(i=1; i < argc ;i++)
{
if ((argv[i][0] == '-') || (argv[i][0] == '/'))
{
switch (tolower(argv[i][1]))
{
case 'a':
if (i+1 >= argc)
{
usage(argv[0]);
goto CLEANUP;
}
if (argv[i+1][0] == '4')
gAddressFamily = AF_INET;
else if (argv[i+1][0] == '6')
gAddressFamily = AF_INET6;
else
{
usage(argv[0]);
goto CLEANUP;
}
i++;
break;
case 'i': // 设置最大跳值
if (i+1 >= argc)
{
usage(argv[0]);
goto CLEANUP;
}
gTtl = atoi(argv[++i]);
break;
case 'l': // icmp数据段大小设置
if (i+1 >= argc)
{
usage(argv[0]);
goto CLEANUP;
}
gDataSize = atoi(argv[++i]);
break;
case 'r': // 记录路由选项
bRecordRoute = TRUE;
break;
default:
usage(argv[0]);
goto CLEANUP;
}
}
else
{
gDestination = argv[i];
}
}
isValid = TRUE;
CLEANUP:
return isValid;
}
// 设置icmp序列号
void
SetIcmpSequence(char *buf)
{
ULONG sequence=0;
sequence = GetTickCount();
if (gAddressFamily == AF_INET)
{
ICMP_HDR *icmpv4=NULL;
icmpv4 = (ICMP_HDR *)buf;
icmpv4->icmp_sequence = (USHORT)sequence;
}
else if (gAddressFamily == AF_INET6)
{
ICMPV6_HDR *icmpv6=NULL;
ICMPV6_ECHO_REQUEST *req6=NULL;
icmpv6 = (ICMPV6_HDR *)buf;
req6 = (ICMPV6_ECHO_REQUEST *)(buf + sizeof(ICMPV6_HDR));
req6->icmp6_echo_sequence = (USHORT)sequence;
}
}
// 计算icmp校验和
void
ComputeIcmpChecksum(SOCKET s, char *buf, int packetlen, struct addrinfo *dest)
{
if (gAddressFamily == AF_INET)
{
ICMP_HDR *icmpv4=NULL;
icmpv4 = (ICMP_HDR *)buf;
icmpv4->icmp_checksum = 0;
icmpv4->icmp_checksum = checksum((USHORT *)buf, packetlen);
}
else if (gAddressFamily == AF_INET6)
{
ICMPV6_HDR *icmpv6=NULL;
icmpv6 = (ICMPV6_HDR *)buf;
icmpv6->icmp6_checksum = 0;
icmpv6->icmp6_checksum = ComputeIcmp6PseudoHeaderChecksum(
s,
buf,
packetlen,
dest
);
}
}
// 发布异步接收
int
PostRecvfrom(SOCKET s, char *buf, int buflen, SOCKADDR *from, int *fromlen, WSAOVERLAPPED *ol)
{
WSABUF wbuf;
DWORD flags,
bytes;
int rc;
wbuf.buf = buf;
wbuf.len = buflen;
flags = 0;
rc = WSARecvFrom(
// 通过重叠IO实现异步接收
s,
&wbuf,
1,
&bytes,
&flags,
from,
fromlen,
ol,
NULL
);
if (rc == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
fprintf(stderr, "WSARecvFrom failed: %d\n", WSAGetLastError());
return SOCKET_ERROR;
}
}
return NO_ERROR;
}
// 8
void
PrintPayload(char *buf, int bytes)
{
int hdrlen=0,
routes=0,
i;
UNREFERENCED_PARAMETER(bytes);
if (gAddressFamily == AF_INET)
{
SOCKADDR_IN hop;
IPV4_OPTION_HDR *v4opt=NULL;
IPV4_HDR *v4hdr=NULL;
hop.sin_family = (USHORT)gAddressFamily;
hop.sin_port = 0;
v4hdr = (IPV4_HDR *)buf;
hdrlen = (v4hdr->ip_verlen & 0x 0F ) * 4;
// 计算IP头部长度(8.1)
if (hdrlen > sizeof(IPV4_HDR))
// 如果长度大于无选项IP头部长度
{
// 选项头部指针
v4opt = (IPV4_OPTION_HDR *)(buf + sizeof(IPV4_HDR));
//计算路由数(8.2)
routes = (v4opt->opt_ptr / sizeof(ULONG)) - 1;
for(i=0; i < routes ;i++)
{
hop.sin_addr.s_addr = v4opt->opt_addr[i];
if (i == 0)
printf(" Route: ");
else
printf(" ");
PrintAddress((SOCKADDR *)&hop, sizeof(hop));
if (i < routes-1)
printf(" ->\n");
else
printf("\n");
}
}
}
return;
}
(8.1) (v4hdr->ip_verlen & 0x 0F ) * 4 ip_verlen前4位为版本号,后四位为头部长度(4字节一记,因此要乘以4)。ip_verlen & 0x0F, 前四位屏蔽,后四位保留。
(8.2) v4opt->opt_ptr 指向下一个可用地址,因此需要减一。
// 设置最大跳站数
int
SetTtl(SOCKET s, int ttl)
{
int optlevel = 0,
option = 0,
rc;
rc = NO_ERROR;
if (gAddressFamily == AF_INET)
{
optlevel = IPPROTO_IP;
option = IP_TTL;
}
else if (gAddressFamily == AF_INET6)
{
optlevel = IPPROTO_IPV6;
option = IPV6_UNICAST_HOPS;
}
else
{
rc = SOCKET_ERROR;
}
if (rc == NO_ERROR)
{
rc = setsockopt(
s,
optlevel,
option,
(char *)&ttl,
sizeof(ttl)
);
}
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "SetTtl: setsockopt failed: %d\n", WSAGetLastError());
}
return rc;
}
// 9
int
__cdecl main(int argc, char **argv)
{
WSADATA wsd;
WSAOVERLAPPED recvol;
// 重叠 IO
SOCKET s=INVALID_SOCKET;
char *icmpbuf=NULL;
struct addrinfo *dest=NULL,
*local=NULL;
IPV4_OPTION_HDR ipopt;
SOCKADDR_STORAGE from;
// socket地址存储结构(9.1)
DWORD bytes,
flags;
int packetlen=0,
fromlen,
time=0,
rc,
i,
status = 0;
recvol.hEvent = WSA_INVALID_EVENT;
// 分析输入参数
if (ValidateArgs(argc, argv) == FALSE)
{
status = -1;
goto EXIT;
}
// socket模块启动初始化
if ((rc = WSAStartup(MAKEWORD(2,2), &wsd)) != 0)
{
printf("WSAStartup() failed: %d\n", rc);
status = -1;
goto EXIT;
}
// 解析目的地址
dest = ResolveAddress(
gDestination,
"0",
gAddressFamily,
0,
0
);
if (dest == NULL)
{
printf("bad name %s\n", gDestination);
status = -1;
goto CLEANUP;
}
gAddressFamily = dest->ai_family;
if (gAddressFamily == AF_INET)
gProtocol = IPPROTO_ICMP;
else if (gAddressFamily == AF_INET6)
gProtocol = IPPROTO_ICMP6;
// 获得本地地址,绑定使用
local = ResolveAddress(
NULL,
"0",
gAddressFamily,
0,
0
);
if (local == NULL)
{
printf("Unable to obtain the bind address!\n");
status = -1;
goto CLEANUP;
}
//创建Raw套接字,protocol = IPPROTO_ICMP
s = socket(gAddressFamily, SOCK_RAW, gProtocol);
if (s == INVALID_SOCKET)
{
printf("socket failed: %d\n", WSAGetLastError());
status = -1;
goto CLEANUP;
}
SetTtl(s, gTtl); //设置最大跳站数为128
if (gAddressFamily == AF_INET)
packetlen += sizeof(ICMP_HDR);
else if (gAddressFamily == AF_INET6)
packetlen += sizeof(ICMPV6_HDR) + sizeof(ICMPV6_ECHO_REQUEST);
// packetlen 为 数据长度+ICMP头部长度
packetlen += gDataSize;
// 分配空间存储ICMP请求(9.2)
icmpbuf = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, packetlen);
if (icmpbuf == NULL)
{
fprintf(stderr, "HeapAlloc failed: %d\n", GetLastError());
status = -1;
goto CLEANUP;
}
// 初始化 ICMP 头部
if (gAddressFamily == AF_INET)
{
if (bRecordRoute)
// 如有路由记录功能,初始化选项数据段
{
ZeroMemory(&ipopt, sizeof(ipopt));
ipopt.opt_code = IP_RECORD_ROUTE; // 路由记录选项
ipopt.opt_ptr = 4; // 指向可用的地址,每个地址占用4字节,此指针指向第一个可用的存储偏移地址
ipopt.opt_len = 39; // 选项数据段长度
rc = setsockopt(s, IPPROTO_IP, IP_OPTIONS,
(char *)&ipopt, sizeof(ipopt));
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "setsockopt(IP_OPTIONS) failed: %d\n", WSAGetLastError());
status = -1;
goto CLEANUP;
}
}
InitIcmpHeader(icmpbuf, gDataSize);
}
else if (gAddressFamily == AF_INET6)
{
InitIcmp6Header(icmpbuf, gDataSize);
}
// 绑定地址,
此套接字可获得
外部单元向这个地址发送的数据
rc = bind(s, local->ai_addr, (int)local->ai_addrlen);
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "bind failed: %d\n", WSAGetLastError());
status = -1;
goto CLEANUP;
}
// 建立接收操作
memset(&recvol, 0, sizeof(recvol));
recvol.hEvent = WSACreateEvent();
// 事件初始化
if (recvol.hEvent == WSA_INVALID_EVENT)
{
fprintf(stderr, "WSACreateEvent failed: %d\n", WSAGetLastError());
status = -1;
goto CLEANUP;
}
// 异步接收
fromlen = sizeof(from);
PostRecvfrom(s, recvbuf, recvbuflen, (SOCKADDR *)&from, &fromlen, &recvol);
printf("\nPinging ");
PrintAddress(dest->ai_addr, (int)dest->ai_addrlen);
printf(" with %d bytes of data\n\n", gDataSize);
// ping 4 次
for(i=0; i < DEFAULT_SEND_COUNT ;i++)
{
// 设置序列号并计算校验和
SetIcmpSequence(icmpbuf);
ComputeIcmpChecksum(s, icmpbuf, packetlen, dest);
time = GetTickCount();
rc = sendto(
// 发送icmp请求
s,
icmpbuf,
packetlen,
0,
dest->ai_addr,
(int)dest->ai_addrlen
);
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "sendto failed: %d\n", WSAGetLastError());
status = -1;
goto CLEANUP;
}
// 等待ICMP回复(9.3)
rc = WaitForSingleObject((HANDLE)recvol.hEvent, DEFAULT_RECV_TIMEOUT);
if (rc == WAIT_FAILED)
{
fprintf(stderr, "WaitForSingleObject failed: %d\n", GetLastError());
status = -1;
goto CLEANUP;
}
else if (rc == WAIT_TIMEOUT)
{
printf("Request timed out.\n");
}
else
{
// 收到ICMP回复
rc = WSAGetOverlappedResult(
s,
&recvol,
&bytes,
FALSE,
&flags
);
if (rc == FALSE)
{
fprintf(stderr, "WSAGetOverlappedResult failed: %d\n", WSAGetLastError());
}
time = GetTickCount() - time;
WSAResetEvent(recvol.hEvent);
printf("Reply from ");
PrintAddress((SOCKADDR *)&from, fromlen);
if (time == 0)
printf(": bytes=%d time<1ms TTL=%d\n", gDataSize, gTtl);
else
printf(": bytes=%d time=%dms TTL=%d\n", gDataSize, time, gTtl);
PrintPayload(recvbuf, bytes);
if (i < DEFAULT_SEND_COUNT - 1)
{
fromlen = sizeof(from);
PostRecvfrom(s, recvbuf, recvbuflen, (SOCKADDR *)&from, &fromlen, &recvol);
}
}
Sleep(1000);
}
CLEANUP:
if (dest)
freeaddrinfo(dest);
if (local)
freeaddrinfo(local);
if (s != INVALID_SOCKET)
closesocket(s);
if (recvol.hEvent != WSA_INVALID_EVENT)
WSACloseEvent(recvol.hEvent);
if (icmpbuf)
HeapFree(GetProcessHeap(), 0, icmpbuf);
WSACleanup();
EXIT:
system("pause");
return status;
}
(9.1) sockaddr 以及 sockaddr_in比较:这两个结构大小相同全为16字节,可相互转化。唯一区别是sockaddr_in将内容分得更细,sockaddr结构一般作为参数带入,sockaddr_in结构在填值时使用。
struct
sockaddr {
u_short sa_family;
// 2字节
char sa_data[14];
// 14字节
};
typedef
struct addrinfo
{
int
ai_flags;
int
ai_family;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;
char * ai_canonname;
struct sockaddr * ai_addr;
struct addrinfo * ai_next;
};
struct
sockaddr_in {
short sin_family;
// 2字节
u_short sin_port;
// 2字节
struct in_addr sin_addr;
// 4字节
char sin_zero[8];
// 8字节填充
};
struct
in_addr {
// 4字节
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
};
(9.2) HeapAlloc 和 malloc的功能一样,都是在堆上面分配空间。HeapAlloc为Windows SDK中提供的函数,malloc是anci C中的函数。后者更有可移植性。
(9.3) 此程序首先发起异步接收后再发送ICMP请求包可以保证发送端不会漏掉任何ICMP回应。UDP协议下先发送后接收的方式会因为接收端运行速度慢的问题导致丢包。