对于多字节数据在内存中有两种存储方式:
Little-endian:低字节在前,高字节在后;
Big-endian:高字节在前,低字节在后
网络协议在处理多字节整数时,采用的是高端字节序,在编程时一定要考虑主机字节顺序与网络字节顺序的相互转换。
1 WinSock基本
1.1 加载/卸载WinSock库
如果装载Winsock1,那么必须include<Winsock1.h>,同时建立与Wsock32.lib的链接关系。
如果装载Winsock2,那么必须include<Winsock2.h>,同时建立与Ws_32.lib的链接关系。
在调用一个Winsock函数之前,必须先加载一个Winsock的动态链接库:
intWSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData )
参数wVersionRequested是Winsock的版本号,最新版本是2.2。高位字节指定副版本,低位字节指定主版本,可用宏MAKEWORD(x,y)(其中,x是高位字节,y是低位字节)。如果需要Winsock2.2,指定0x0202或使用宏MAKEWORD(2,2)。
WSAStartup加载的Winsock库的信息存放到参数lpWSAData指向的结构体WSADATA中,可以设置为NULL。
卸载Winsock库:
intWSACleanup(void);
注意:每次调用WSAStartup( ),都需要调用相应的WSACleanup()。
1.2 SOCKET介绍
Win32中套接字不同于文件描述符,是一个独立的类型SOCKET,由两个函数建立的:
SOCKETWSASocket( int af, int type, intprotocol,
LPWSAPROTOCOL_INFOlpProtocolInfo,
GROUP g, DWORD dwFlags );
SOCKETsocket( int af, int type, int protocol );
参数af是协议的地址家族;参数type是协议的套接字类型;参数protocol指定了协议字段。
协议 | 地址家族 |
| 套接字类型 | 协议字段 |
IP | AF_INET | TCP | SOCK_STREAM | IPPROTO_IP |
UDP | SOCK_DGRAM | IPPROTO_UDP | ||
Raw sockets | SOCK_RAW | IPPROTO_RAW IPPROTO_ICMP |
参数g始终为0,因为目前尚无支持套接字组的Winsock版本。
参数dwFlags可以指定标志:WSA_FLAG_OVERLAPPED(默认设置)
参数lpProtocolInfo用来指定创建套接字的类型,可以设置为NULL。
1.3 WinSock的地址信息
Winsock通过sockaddr_in结构来指定IP地址和服务端口信息:
structsockaddr
{
unsigned short sa_family; /* 地址协议族,一般为AF_INET*/
char sa_data[14]; /* 14字节的协议地址,包含该socket的IP地址和端口号 */
};
另外还有一种结构类型sockaddr_in:
structsockaddr_in
{
short int sin_family; /* 地址族 */
unsigned short int sin_port; /* 网络字节顺序的端口号*/
struct in_addr sin_addr; /* IP地址 */
unsigned char sin_zero[8]; /* 填充0 以保持与结构sockaddr同样大小 */
};
sockaddr_in结构使用更为方便,sockaddr_in.sin_zero应该用bzero( )或memset( )将其置为0。sockaddr_in的指针和sockaddr的指针可以通用。
structin_addr
{
unsigned long s_addr; /* 网络字节顺序的IP地址*/
};
可以将s_addr指定为htonl( INADDR_ANY ),表示的是由系统自己指定IP地址。
在选择端口sin_port时,必须特别小心:
1.0~1023由IANA控制,是为固定服务保留的;
2.1024~49151是IANA列出来的、已注册的端口,供普通用户的普通用户进程或程序使用;
3.49152~65535是动态或私用端口,IANA这些端口上没有注册服务,可自由使用。
1.4 地址格式转换函数
inet_addr( )可以把一个点式IP地址转换成一个网络字节顺序的IP地址:
unsignedlong inet_addr( const char FAR *cp );
inet_nota( )可以把一个网络字节顺序的IP地址转换成一个点式IP地址:
char*FAR inet_ntoa( struct in_addr in );
1.5 网络字节顺序转换函数
主机字节顺序---->网络字节顺序:
u_longhtonl( u_long hostlong );
intWSAHtonl( SOCKET s, u_long hostlong, u_long*lpnetlong );
u_shorthtons( u_short hostshort );
intWSAHtons( SOCKET s, u_short hostshort, u_short* lpnetshort );
网络字节顺序---->主机字节顺序:
u_longntohl( u_long netlong );
intWSANtohl( SOCKET s, u_long netlong, u_long* lphostlong );
u_shortntohs( u_short netshort );
intWSANtohs( SOCKET s, u_short netshort, u_short* lphostshort );
1.6 名字解析
structhostent
{
char FAR* h_name; // 主机名
char FAR FAR ** h_aliases; // 主机别名数组
short h_addrtype; // 主机地址类型
short h_length; // 主机地址长度(字节)
char FAR FAR ** h_addr_list; // 主机地址数组,其中的地址是网络字节顺序。
}
1.6.1 主机名---->主机地址
structhostent * FAR gethostbyname( const char* name );
HANDLEWSAAsyncGetHostByName( HWND hWnd, unsigned int wMsg,
const char* name, char* buf, int buflen );
WSAAsyncGetHostByNameAPI( )是gethostbyname( )的异步版,在结束时利用Windows消息向应用程序发出通知:
参数name可以是主机名,也可以是点式IP地址。
参数bWnd指定异步请求结束时,收到消息的窗口句柄。
参数wMsg是异步请求结束时,收到的窗口消息。
参数buf指向接收hostent数据的存储区。
参数buflen指定buf存储区的大小,推荐使用MAXGETHOSTSTRUCT。
1.6.2 主机地址---->主机名
structHOSTENT* FAR gethostbyaddr( const char* addr, int len, int type );
HANDLEWSAAsyncGetHostByAddr( HWND hWnd, unsignedint wMsg, const char* addr,
int len, int type, char* buf, int buflen );
WSAAsyncGetHostByAddrAPI( )是gethostbyaddr( )的异步版,在结束时利用Windows消息向应用程序发出通知:
参数addr是指向主机地址的指针,该主机地址按照网络字节顺序。
参数len指定参数addr的字节长度。
参数type为AF_INET。
1.7 获取Winsock函数的错误代码
intWSAGetLastError(void);
发生错误之后调用WSAGetLastError( ),就会返回发生错误的完整代码。
1.8 套接字的状态
套接字的初始状态都是CLOSED
MSL代表Maximum Segment Lifetime