第一章 计算机网络基础 1.1 网络的概念和组成 连接到Internet的所有的这些设备称为主机 或终端系统
终端系统由通信链接连在一起 。常见的通信链接有双绞线 、同轴电缆 、光纤 ,负责传输原始的比特流
不是单一通过通信链接连接,通过中介交换设备 间接相连,这些中介交换设备称为包交换器 ,传输的信息块称为包 ,当今Internet上最基本的两种是交换机 、路由器
ISP——Internet Service Provider TCP——Transfer Control Protocol IP——Internet Protocol
还有许多专用网络,这些网络称为企业内部互联网
1.2 计算机网络参考模型 现代网络通信结构以层划分,各层和各层协议的集合称为网络体系 ,特定系统使用的一组协议称为协议堆栈
OSI模型 重点
1.3 网络程序寻址方式 存在竞争信道时,解决此问题的协议是MAC(Medium Access Control) 子层
IP地址的分类
IP地址根据网络号和主机号来分,分为A、B、C三类及特殊地址D、E。
全0和全1的都保留不用。 全0——本网络和主机,全1——广播地址指定网络中所有主机
在IP地址3种主要类型里,各保留了3个区域作为私有地址,其地址范围如下:
A类地址:10.0.0.0~10.255.255.255
B类地址:172.16.0.0~172.31.255.255
C类地址:192.168.0.0~192.168.255.255
第二章 Winsock编程接口 2.1 Winsock 库 Winsock库有两个版本,目前使用Winsock2 使用时
包含头文件winsock2.h
添加WS2_32.lib 库的链接
封装CInitSock 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <winsock2.h> #pragma comment(lib, "WS2_32" ) class CInitSock { public : CInitSock (BYTE minorVer=2 , BYTE majorVer = 2 ) { WSADATA wsaData; WORD sockVersion = MAKEWORD (minorVer, majorVer) if (::WSAStartup (sockVersion, &wsaData) != 0 ) { exit (0 ); } } ~CInitSock () { ::WSACleanup (); } }
构造函数 CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
:这是类的构造函数,它接受两个参数,分别是用于指定Winsock版本的minor版本号和major版本号。如果未提供这些参数,默认使用版本号2.0。构造函数首先声明一个WSADATA
结构,该结构用于接收Winsock初始化信息。然后,使用MAKEWORD
宏将minor和major版本号组合成一个WORD
类型的版本号。接着,调用WSAStartup
函数初始化Winsock。如果初始化失败,程序调用exit(0)
来退出程序。
析构函数 ~CInitSock()
:这是类的析构函数,用于在对象生命周期结束时清理Winsock资源。在这里,它调用WSACleanup
函数来释放Winsock占用的资源。
2.2 Winsock的寻址方式和字节顺序 2.2.1 Winsock 寻址 Winsock要兼容多个协议,因此它有采取通用的地址结构sockaddr
1 2 3 4 5 struct sockaddr { u_short sa_family; char sa_data[14 ]; }
Winsock已经定义了sockaddr结构的TCP/IP 版本 —— sockaddr_in 结构
1 2 3 4 5 6 7 struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8 ]; }
端口号可分为下面三个范围
0~1023 由IANA管理,保留为公共的服务使用
1024~49151 普通用户注册的端口号
4915265535 动态和/或 私有的端口号 普通用户的应用程序选择102449151 当然 49152~65535也可以
sin_addr 存储IP地址 是一个联合,如下
1 2 3 4 5 6 7 8 9 struct in_addr { 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; } }
IP地址转换:应用程序可以使用inet_addr 函数将一个点分十进制格式的IP地址字符串转换成32位二进制数表示的IP地址;inet_ntoa 是inet_addr 的逆函数,可以将IP的32位二进制格式转换为字符串格式。inet_addr 返回的二进制是以网络顺序存储的(大尾)
2.2.2 字节顺序
TCP/IP 统一采用大尾 方式传输,也称为网络字节顺序
2.3 Winsock 编程详解 2.3.1 Winsock编程流程 1、套接字的创建和关闭
使用套接字之前,需调用socket函数创建一个套接字对象
1 2 3 4 5 SOCKET socket ( int af, int type, int protocol )
type常见参数
SOCK_STREAM: 流式套接字,TCP
SOCK_DGRAM: 数据包套接字,UDP
SOCK_RAW: 原始套接字,Winsock不处理,程序自行处理数据包及协议首部
2.3.2 典型过程图
2.3.4 UDP编程 1、UDP编程流程
服务器端程序设计流程如下
创建套接字-socket
绑定IP地址和端口-bind
收发数据-sendto/recvfrom
关闭连接-closesocket
客户端程序设计流程如下
创建套接字-socket
收发数据-sendto/recvfrom
关闭连接-closesocket
第三章 Windows套接字 I/O 模型
Winsock提供了一些 I/O 模型帮助应用程序以异步方式 在一个或多个套接字上管理 I/O,这样的I/O有6种:阻塞(blocking)、选择(select)、异步选择(WSAAsyncSelect)、事件通知(WSAEventSelect)、重叠(overlapped)、完成端口(completion port)模型-不考
3.1 套接字模式
Winsock以两种模式执行I/O操作:阻塞和非阻塞
3.1.1 阻塞模式 套接字创建时,默认的工作模式
3.1.2 非阻塞模式 3.2 选择(Select)模型
select模型,使用select函数管理I/O,目的是允许应用程序有能力管理多个套接字,让套接字调用的程序是阻塞的,非阻塞的都行
3.2.1 select函数 1 2 3 4 5 6 7 int select ( int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timeval* timeout ) ;
函数调用成功,返回发送网络事件的所有套接字数量的总和,超出时间限制返回0,失败返回SOCKET_ERROR
1、套接字集合
fd_set 结构如下 typedef struct fd_set { u_int fd_count; // 下面数组的大小 SOCKET fd_array[FD_SETSIZE]; // 套接字句柄数组 }fd_set;
下面是Winsock定义的4个操作fd_set 套接字集合的宏
1 2 3 4 FD_ZERO (*set); FD_CLR (s, *set); FD_ISSET (s, *set); FD_SET (s, *set);
2、网络事件
传递给select函数的三个fds集合中的套接字被标识的条件
readfds集合:
数据可读
连接已经关闭,重启或中断
如果listen已经被调用,并且有一个连接未决,accept函数将成功
writefds集合:
数据能够发送
如果一个非阻塞连接调用正在被处理,连接已经成功
exceptfds集合:
如果一个非阻塞连接调用正在被处理,连接失败
OOB数据可读
测试套接字s是否可读,添加到readfds集合,等待select函数返回,还在说明可读了
3.2.2 应用举例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #include <stdio.h> #include "initsock.h" CInitSock c; int main () { USHORT nPort = 4567 ; SOCKET sListen = ::socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons (nPort); sin.sin_addr.S_un.S_addr = INADDR_ANY; if (::bind (sListen, (sockaddr*)&sin, sizeof (sin)) == SOCKET_ERROR) { printf (" Failed bind() \n" ); return -1 ; } ::listen (sListen, 5 ); fd_set fdSocket; FD_ZERO (&fdSocket); FD_SET (sListen, &fdSocket); while (TRUE) { fd_set fdRead = fdSocket; int nRet = ::select (0 , &fdRead, NULL , NULL , NULL ); if (nRet > 0 ) { for (int i = 0 ; i < (int )fdSocket.fd_count; i++) { if (FD_ISSET (fdSocket.fd_array[i], &fdRead)) { if (fdSocket.fd_array[i] == sListen) { if (fdSocket.fd_count < FD_SETSIZE) { sockaddr_in addrRemote; int nAddrLen = sizeof (addrRemote); SOCKET sNew = ::accept (sListen, (SOCKADDR*)&addrRemote, &nAddrLen); FD_SET (sNew, &fdSocket); printf ("接收到连接(%s)\n" , ::inet_ntoa (addrRemote.sin_addr)); } else { printf (" Too much connections! \n" ); continue ; } } else { char szText[256 ]; int nRecv = ::recv (fdSocket.fd_array[i], szText, sizeof (szText), 0 ); if (nRecv > 0 ) { szText[nRecv] = '\0' ; printf ("接收到数据: %s \n" , szText); } else { ::closesocket (fdSocket.fd_array[i]); FD_CLR (fdSocket.fd_array[i], &fdSocket); } } } } } else { printf (" Failed select() \n" ); break ; } } return 0 ; }
3.3 异步选择模型(WSAAsyncSelect) WSAAsyncSelect 模型允许应用程序以Windows消息 的形式接收网络事件通知,目的是适应Windows的消息驱动环境设置的,目前对性能要求不高的网络应用程序都采用WSAAsyncSelect 模型。MFC中的CSocket 也有采用
3.4 事件通知(WSAEventSelect) 也是异步事件通知I/O模型 允许应用程序在一个或多个套接字上接收基于事件的网络通知 并不依靠Windows的消息驱动机制,由事件对象的句柄通知
3.4.1 WSAEventSelect 函数 基本步骤
创建事件对象
事件对象与网络事件关联
网络事件发生,使事件对象受信,在事件对象上的等待函数就会执行,返回
调用WSAEnumNetworkEvents 函数 知道发生什么网络事件
创建事件对象
1 WSAEVENT WSACreateEvent (void ) ;
WSAEventSelect
1 2 3 4 5 int WSAEventSelect ( SOCKET s, WSAEVENT hEventObject, long INetworkEvents ) ;
WSAWaitForMultipleEvents 函数,等待
1 2 3 4 5 6 7 DWORD WSAWaitForMultipleEvents ( DWORD cEvents, const WSAEVENT* lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable ) ;
WSAWaitForMultipleEvents 最多支持WSA_MAXIMUM_WAIT_EVENTS 个对象 被定义为64 在一个线程中同一时间最多管理64个套接字 超出64需创建额外工作线程
返回值
超出时间,WSA_WAIT_TIMEOUT
有事件发生,返回事件对象
调用失败返回WSA_WAIT_FAILED
WSAEnumNetworkEvents
1 2 3 4 5 int WSAEnumNetworkEvents ( SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents ) ;
WSANETWORKEVENTS
1 2 3 4 typedef struct _WSANETWORKEVENTS { long INetworkEvents; int iErrorCode[FD_MAX_EVENTS]; } WSANETWORKEVENTS, *LPWSANETWORKEVENTS
3.4.2 应用举例 WSAEventSelect 编程的基本步骤如下
创建一个事件句柄表和一个对应的套接字句柄表
创建一个套接字,对应创建一个事件,放入步骤1创建的表中,并调用WSAEventSelect函数关联起来
调用WSAWaitForMultiple 在所有事件上等待,函数返回后遍历事件数组,调用WSAWaitForMultiple 函数判断每个事件是否受信
处理发生的网络事件,继续在事件上等待
3.5 重叠 OverLapped I/O 模型 该模型提供更好的性能。运行应用程序使用重叠数据结构一次投递一个或者多个的异步I/O请求
3.5.1 重叠I/0 函数 1、创建套接字
1 2 3 4 5 SOCKET WSASocket (int af, int type, int protocol, LPWSAPROTOCOL_INFO lpProtocolInfo, GROUP g, DWORDd dwFlags ) ;
创建监听套接字
1 SOCKET sListen = ::WSASocket (AF_INET, SOCK_STREAM, IP_PROTOCOL, NULL , 0 , WSA_FLAG_OVERLAPPPED);
2、传输数据
WSASend
1 2 3 4 5 6 7 8 9 int WSASend ( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags. LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine ) ;
3、接受连接 AcceptEx
1 2 3 4 5 6 7 8 9 10 BOOL AcceptEx ( SOCKET sListen Socket, SOCKET sAcceptSocket, PVOID lpOutputBuffer, DWORD dwReceiveDataLength, DWORD dwLocalAddressLength, DWORD dwRemoteAddressLength, LPDWORD lpdwBytesReceived, LPOVERLAJPJPED lpOverlapped ) ;
该函数声明在Mswsock.h中,需添加Mswsock.lib库
第五章 互联网广播和IP多播
在广播通信中,网络层提供了将一个节点发送到其他所有节点的服务:多播可以使用一个源节点发送封包的拷贝到其他多个网络节点的集合
5.1 套接字选项和I/O 控制命令 5.1.1 套接字选项 获取和设置套接字选项的函数是 getsockopt 、setsockopt 结构用法如下
1 2 3 4 5 6 7 8 9 int getsockopt ( SOCKET s, int level, int optname, char * optval, int * optlen ) ;int setsockopt (SOCKET s, int level, int optname, const char * optval, int optlen) ;
1、SOL_SOCKET级别 应用层
SO_BROADCAST: BOOL类型,设置套接字传输和接收广播消息。
SO_REUSEADDR: BOOL类型,值为TRUE,套接字可以绑定到一个已经被另一个套接字使用的地址,或者处于TIME_WAIT 的地址
SO_RCVBUF和SO_SNDBUF: int类型,获取或设置套接字内部为接收(发送)操作分配缓冲区的大小‘
SO_RCVTIMEO和SO_SNDTIMEO: int类型,获取或设置套接字接收(发送)的超时值(ms) 2、IPPROTO_IP级别 网络层
IP_HDRINCL: BOOL类型。值为TRUE,IP头和数据会一块提交给winsock发送调用,
IP_TTL: int类型。设置和获取IP头中的TTL参数。数据报使用TTL域来限制它能够经过的路由器数量,防止路由陷入死循环
5.1.2 I/O 控制命令 下面是一些常用的ioctl命令
FIONBIO: 将套接字置于非阻塞模式
SIO_RCVALL: 接收网络上所有的封包。抓包
嗅探器程序向套接字发送SIO_REVALL控制命令的代码
1 2 3 DWORD dwValue = 1 ; if (ioctlsocket (sRaw, SIO_RCVALL, &dwValue) != 0 ) return ;
5.2 广播通信 服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <stdio.h> #include <winsock2.h> #include "initsock.h" CInitSock c; int main () { SOCKET s = ::socket (AF_INET, SOCK_DGRAM, 0 ); BOOL bBroadCast = TRUE; ::setsockopt (s, SOL_SOCKET, SO_BROADCAST, (char *)&bBroadCast, sizeof (bBroadCast)); SOCKADDR_IN bcast; bcast.sin_family = AF_INET; bcast.sin_addr.S_un.S_addr = INADDR_BROADCAST; bcast.sin_port = htons (4567 ); printf (" 开始向4567端口发送广播数据... \n \n" ); char sz[] = "我的天 \n" ; while (TRUE) { ::sendto (s, sz, strlen (sz), 0 , (SOCKADDR*)&bcast, sizeof (bcast)); Sleep (5000 ); } return 0 ; }
接收端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <stdio.h> #include "initsock.h" CInitSock c; int main () { SOCKET s = ::socket (AF_INET, SOCK_DGRAM, 0 ); sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_addr.S_un.S_addr = INADDR_ANY; sin.sin_port = htons (4567 ); if (::bind (s, (sockaddr*)&sin, sizeof (sin)) == SOCKET_ERROR) { printf (" bind() failed \n" ); return 0 ; } printf (" 开始在4567端口接收广播数据... \n\n" ); SOCKADDR_IN addrRemote; int nLen = sizeof (addrRemote); char sz[256 ]; while (TRUE) { int nRet = ::recvfrom (s, sz, 256 , 0 , (sockaddr*)&addrRemote, &nLen); if (nRet > 0 ) { sz[nRet] = '\0' ; printf ("收到以下数据: \n%s\n\n" , sz); } } return 0 ; }
5.3 IP 多播 广播发送到所有节点,多播发送到一些节点的集合去,不发送全部
5.3.1 多播地址
5.3.3 使用IP多播 1、加入组和离开组
选项级别level :IPPROTO_IP
选项名称optname:IP_ADD_MEMBERSHIP、IP_DROP_MEMBERSHIP
请求该选项要带的数据:ip_mreq结构体
ip_mreq
1 2 3 4 5 typedef struct { struct in_addr imr_multiaddr; struct in_addr imr_interface; }ip_mreq;
加入示例
1 2 3 4 iq_mreq mcast; mcast.imr_interface.S_un.S_addr = INADDR_ANY; mcast.imr_multiaddr.S_un.S_addr = ::inet_addr ("234.5.6.7" ); int nRet = ::setsockopt (s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mcast, sizeof (mcast));
离开示例
1 2 3 4 iq_mreq mcast; mcast.imr_interface.S_un.S_addr = dwInterFace; mcast.imr_multiaddr.S_un.S_addr = dwMultiAddr int nRet = ::setsockopt (s, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char *)&mcast, sizeof (mcast));
3、发送多播数据
TTL的值的各个概念
初始0——多播封包限制在同一个主机
初始1——限制在同一个子网
初始32——限制在同一个站点
初始64——限制在同一个地区
初始128——限制在同一个大陆
初始255——无地区限制
第六章 原始套接字
允许访问底层传输协议
6.1 使用原始套接字 有两种类型,在IP头中预定义的,在IP头中使用自定义的
6.2 ICMP编程 6.2.1 ICMP与校验和的计算 1、ICMP格式
第一个域是消息类型,第二个域是代码,第三个域是校验和
类型
询问/错误类型
代码
描述
0
询问
0
回显应答
3
错误:目标不可达
0
网络不可达
8
询问
0
请求回显
11
错误:超时
0
传输过程中TTL等于0
2、校验和的计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 USHORT checksum (USHORT* buff, int size) { ungigned long cksum=0 ; while (size > 1 ) { cksum += *buffer++; size -= sizeof (USHORT); } if (size) { cksum += *(UCHAR*)buffer; } chsum = (chsum >> 16 ) + (cksum & 0xffff ); cksum += (cksum >> 16 ); return (USHORT)(~cksum); }
6.2.2 ping 程序实例 基本编程步骤如下
创建协议类型为IPPROTO_ICMP 的原始套接字,设置其属性
创建并初始化ICMP封包
调用sendto向远程主机发送ICMP请求
调用recvfrom函数接收ICMP响应
6.2.3 路由跟踪 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 #include "initsock.h" #include "protoinfo.h" #include "comm.h" #include <stdio.h> #include <windows.h> CInitSock c; typedef struct icmp_hdr { unsigned char icmp_type; unsigned char icmp_code; unsigned short icmp_checksum; unsigned short icmp_id; unsigned short icmp_sequence; unsigned long icmp_timestamp; } ICMP_HDR, *PICMP_HDR; int main () { char * szDestIp = "192.168.90.160" ; char recvBuf[1024 ] = {0 }; SOCKET sRaw = ::socket (AF_INET, SOCK_RAW, IPPROTO_ICMP); sockaddr_in in; in.sin_family = AF_INET; in.sin_port = 0 ; in.sin_addr.S_un.S_addr = INADDR_ANY; if (::bind (sRaw, (sockaddr*)&in, sizeof (in)) == SOCKET_ERROR) { printf ("bind() failed \n" ); printf ("bind() 失败,错误码:%d\n" , WSAGetLastError ()); system ("pause" ); return 0 ; } SetTimeout (sRaw, 5 *1000 ); SOCKET sSend = ::socket (AF_INET, SOCK_RAW, IPPROTO_ICMP); SOCKADDR_IN destAddr; destAddr.sin_family = AF_INET; destAddr.sin_addr.S_un.S_addr = ::inet_addr (szDestIp); destAddr.sin_port = ::htons (22 ); int nTTL = 1 ; int nRet; ICMP_HDR* pICMPHdr; int nTick; SOCKADDR_IN recvAddr; do { SetTTL (sSend, nTTL); nTick = ::GetTickCount (); nRet = ::sendto (sSend, "hello" , 5 , 0 , (sockaddr*)&destAddr, sizeof (destAddr)); if (nRet == SOCKET_ERROR) { printf (" sendto() failed \n" ); break ; } int nLen = sizeof (recvAddr); nRet = ::recvfrom (sRaw, recvBuf, 1024 , 0 , (sockaddr*)&recvAddr, &nLen); if (nRet == SOCKET_ERROR) { if (::WSAGetLastError () == WSAETIMEDOUT) { printf (" time out \n" ); break ; } else { printf (" recvfrom() failed \n" ); break ; } } pICMPHdr = (ICMP_HDR*)&recvBuf[20 ]; if (pICMPHdr->icmp_type != 11 && pICMPHdr->icmp_type != 3 && pICMPHdr->icmp_code != 3 ) { printf (" Unexpected Type: %d , code: %d \n" , pICMPHdr->icmp_type, pICMPHdr->icmp_code); } else { char * szIP = ::inet_ntoa (recvAddr.sin_addr); printf (" 第%d个路由器,IP地址:%s \n" , nTTL, szIP); printf (" 用时:%d毫秒 \n" , ::GetTickCount () - nTick); } if (destAddr.sin_addr.S_un.S_addr == recvAddr.sin_addr.S_un.S_addr) { printf ("目标可达 \n" ); break ; } printf ("//------------------------------------// \n" ); } while (nTTL++ < 20 ); ::closesocket (sRaw); ::closesocket (sSend); system ("pause" ); return 0 ; } BOOL SetTTL (SOCKET s, int nValue) { int result = setsockopt (s, IPPROTO_IP, 4 , (const char *)&nValue, sizeof (nValue)); if (result == SOCKET_ERROR) { printf ("setsockopt() 失败,错误码:%d\n" , WSAGetLastError ()); return FALSE; } return TRUE; } BOOL SetTimeout (SOCKET s, int nTime, BOOL bRecv ) { DWORD timeoutValue = static_cast <DWORD>(nTime); int option = bRecv ? SO_RCVTIMEO : SO_SNDTIMEO; int result = setsockopt (s, SOL_SOCKET, option, (const char *)&timeoutValue, sizeof (timeoutValue)); if (result == SOCKET_ERROR) { printf ("setsockopt() 失败,错误码:%d\n" , WSAGetLastError ()); return FALSE; } return TRUE; }
6.3 使用IP头包含选项 6.3.1 IP 数据报格式
Version——这4位指定IP数据报版本,对应IPv4来说,值为4
IHL(IP Header Length)——IP头不固定,需要4位确定数据开始部分,一般不包含该域,通常的IP数据报有20个字节的头长度
Type of serviec——区分不同类型的IP数据报
Total Length——IP数据报的总长度,IP头+数据,理论最长是65535字节,但数据报长度很少超过1500
Identification——标识已发送的数据报,发送一次封包,增加一次值
Flags和Fragment offset——给路由器的指令,DF代表不分割数据报,MF代表更多的分割
Time to live——TTL,超时检测,数据报被路由器处理,值减1,为0就丢弃
Protocol——IP数据报到达目的使用此域,指定数据部分传递给哪个传输层协议,6位TCP,17为UDP
Header checksum——头校验
Source address 和 Destination address——源IP、目的IP
Options——可添加额外信息,最多40字节
6.3.2 UDP数据报格式
Source port和Destination port——源端口,目的端口,都16位
UDP length——UDP长度
UDP checksum——UDP校验和
UDPHEADER描述UDP头
1 2 3 4 5 6 typedef struct _UDPHeader { USHORT sourcePort; USHORT destinationPort; USHORT len; USHORT checksum; };
6.4 网络嗅探器开发实例 6.4.1 嗅探器设计原理 TCP头格式
第十四章 Email协议及其编程
SMTP POP3 这两个协议 原理及其实现
14.1 概述 个人用户不能直接收发电子邮件,通过ISP主机的一个电子信箱负责接收
14.2 电子邮件介绍 电子邮件的格式由3部分组成:信头、信体、签名区
14.2.1 电子邮件的Internet地址 通用形式为:userid(用户标识)@doman(域名) ex: lrzshaha@qq.com
14.2.3 电子邮件的信头结构及分析 1、邮件的结构 (1) From: xx@xx (2) To: xx@xx (3) Subject: xxx (4) Date: Thu, 21 Dec 2023. 03:00:00 GMT+8 (5) (6) xxx (7) This mail is to explain you the mail format (8) —- (9) Thanks (10) lrui1
RFC822 要求 信头和信体间要有一个空行,就是(5)
14.3 SMTP协议原理介绍 SMTP基本命令
14.4 POP3协议原理介绍 14.4.1 POP3协议简介 POP——Post Office Protocol 邮局协议,使用TCP110端口,POP3仍然采用C/S模式
14.4.2 POP3工作原理 POP3协议有三种工作状态,确认状态、操作状态、更新状态
14.4.3 POP3命令原始码
参考文献 《Windows网络与通信程序设计(第3版)——陈香凝..》