在Winsock(Socket API)中,用户可以使用TCP/IP地址家族中统一的套接字地址结构解决TCP/IP寻址中可能出现的问题。该套接字地址结构定义如下:
struct sockaddr_in{
short sin_family; //指定地址家族即地址格式
unsigned sin_port; //端口号码
structin_addr sin_addr; //IP地址
char sin_zero[8]; //留做备用,需要指定为0
};
在这个结构中,成员sin_family指定使用该套接字地址的地址家族。在这里必须设置为AF_INET,表示程序所使用的地址家族是TCP/IP。
该结构成员变量sin_addr表示32位的IP地址结构,其结构定义如下:
struct in_addr{
union{
struct{
unsignedchar s_b1,s_b2,s_b3,s_b4; //用4个u_char字符描述IP地址
}S_un_b;
struct{
unsigned short s_w1,s_w2; //用2个u_short类型描述IP地址
}S_un_w;
unsigned long S_addr; //用1个u_long类型描述IP地址
}S_un;
};
通常,用户在网络编程中使用1个u_long类型的字符进行描述IP地址。例如,使用IP地址结构in_addr进行描述IP地址为“192.168.1.1”,代码如下:
sockaddr_in addr;
addr.sin_addr.S_un.S_addr=inet_addr(“192.168.1.1”);
在程序中首先定义sockaddr_in结构对象,然后为IP地址结构in_addr中的成员S_addr赋值。因为结构成员S_addr所描述的IP地址均为网络字节顺序,所以程序调用inet_addr()函数将字符串IP转化为网络字节顺序排列的IP地址。
在Socket套接字编程中,传输数据的排列顺序以网络字节顺序和主机字节顺序为主。通常情况下,如果用户将数据通过网络发送时,需要将数据转换成以网络字节顺序排列,否则可能造成数据损坏。如果用户是将网络中收到的数据存储在本机上,那么需要将数据转换成以主机字节顺序排列。从数据存储角度来讲,网络字节顺序即将数据中最重要的字节首先存储,而主机字节顺序则将不重要的字节首先存储。
注意:IP地址结构in_addr中的成员S_addr的值均是以网络字节顺序排列。
在Winsock中提供了几个关于网络字节顺序与主机字节顺序之间转换的函数,函数定义如下:
u_short htons(u_short hostshort); //将一个u_short类型的IP地址从主机字节顺序转换到网络字节顺序
u_long htonl(u_long hostlong); //将一个u_long类型的IP地址从主机字节顺序转换到网络字节顺序
u_long ntohl(u_long netlong); //将一个u_long类型的IP地址从网络字节顺序转换到主机字节顺序
u_short ntohs(u_short netshort); //将一个u_short类型的IP地址从网络字节顺序转换到主机字节顺序
unsigned long inet_addr(const char FAR*r cp); //将一个字符串IP转换到以网络字节顺序排列的IP地址
char FAR* inet_ntoa(struct in_addr in); //将一个以网络字节顺序排列的IP地址转换为一个字符串IP地址
由于所有的Winsock函数都是从动态链接库WS2_32.DLL中导出,但默认情况下,编译器并没有于该库进行连接,所以必须手动添加。
#include "winsock2.h"
#pragma comment(lib, "ws2_32.lib")
用户必须首先从动态库中调用函数WSAStartup()对该库进行初始化,之后才能从该库中继续正确调用其他的Winsock函数,否则,将出现错误。函数WSAStartup()的原型如下:
int WSAStartup(WORDwVersionRequested,LPWSADATA lpWSAData);
该函数调用成功返回0。参数wVersionRequested表示套接字库的版本号。例如,当前套接字版本号为2.0,则将该参数设置为2.0,代码如下:
WORD wVersionRequested=MAKEWORD(2,0);
参数lpWSAData指向结构体WSADATA的指针变量,表示获取到的套接字详细信息,该结构体定义如下;
Typedef struct WSAData{
WORDwVersion; //库文件建议应用程序使用的版本号
WORDwHighVersion; //库文件支持的最高版本
charszDescription[WSADESCRIPTION_LEN+1]; //描述库文件的字符串
charszSystemStatus[WSASYS_STATUS_LEN+1]; //系统状态字符串
unsignedshort iMaxSockets; //同时支持的最大套接字数
unsignedshort iMaxUdpDg; //已废弃
charFAR* lpVendorInfo; //已废弃
}WSADATA,FAR*LPWSADATA;
用户初始化套接字库,代码如下:
WSAData data; //定义WSAData变量
WORD wVersionRequested=MAKEWORD(2,0); //定义套接字库版本号
::WSAStartip(wVersionRequested,&data); //初始化套接字库
当程序退出时,用户还应该调用函数WSACleanup释放该套接字,代码如下:
::WSACleanup();
在Socket API中,创建套接字句柄的函数是socket(),该函数原型如下:
SOCKET socket(
intaf, //指定套接字所使用的地址格式
inttype, //套接字类型
intprotocol, //如果参数type已指定套接字类型为TCP或UDP,则该参数可以设置为0
);
该函数执行成功,将返回新创建的套接字句柄,参数type的取值如下表:
套接字取值类型 |
含义 |
SOCK_STREAM |
创建流式套接字(基于TCP协议) |
SOCK_DGRAM |
创建数据报套接字(基于UDP协议) |
SOCK_RAM |
创建原始套接字 |
例如,创建流式套接字,代码如下:
SCOKET s; //定义套接字句柄
s=::socket(AF_INET,SOCK_STREAM,0); //创建并返回套接字句柄
对于服务器而言,套接字创建成功后,还应该将套接字与地址结构信息相关联,实现这一功能的函数是bind(),该函数原型如下:
int bind(
SOCKETs, //套接字句柄
conststruct sockaddr FAR* name, //地址结构信息
intnamelen //地址结构的大小
);
该函数调用成功返回0。将套接字句柄绑定到本地地址的代码如下:
```
sockaddr_in addr; //定义套接字地址结构变量
addr.sin_family = AF_INET; //指定地址家族为TCP/IP
addr.sin_port = htons(4999); //指定端口
addr.sin_addr.s_addr = INADDR_ANY; //表示服务器可以接收任何计算机发来的请求
retVal = bind(sServer,(LPSOCKADDR)&addrServ, sizeof(SOCKADDR_IN));//绑定套接字到指定地址结构
当服务器程序将套接字句柄绑定套接字地址成功时,则调用函数listen()实现监听端口的功能,该函数原型如下:
int listen(
SOCKETs, //实现监听功能的套接字
intbacklog //指定监听的最大连接数量
);
该函数仅被用于流式套接字上。如果多个客户端同时向服务器发出连接请求,并且超过了最大监听数,则客户端返回错误代码,例如,程序在已创建的套接字s上进行监听,代码如下:
···
::listen(s,5); //在套接字上进行监听,并将最大监听数指定为5
客户端程序连接服务器使用函数connect()实现,函数原型如下:
int connect(
SOCKETs, //套接字句柄
conststruct sockaddr FAR* name, //将要连接的服务器地址信息结构指针
intnamelen //地址信息结构体长度
);
例如,客户端使用该函数连接地址为“127.0.0.1”,端口为80的服务器,代码如下:
sockaddr_in addr; //定义套接字地址结构变量
addr.sin_family=AF_INET; //指定地址家族为TCP/IP
addr.sin_port=htons(80); //指定端口号
addr.sin_addr.s_addr=inet_addr(“127.0.0.1”); //指定服务器地址
SOCKET s;
s=socket(AF_INET,SOCK_STREAM,0); //创建并返回套接字句柄
::connect(s,(sockaddr)&addr,sizeof(addr)); //连接服务器
如果服务器接收到客户端的连接请求,则可以调用函数accept()接受该请求,函数原型如下:
SOCKET accept(
SOCKET s, //创建套接字句柄
structsockaddr FAR* addr, //获取对方的地址信息
intFAR* addrlen //地址长度
);
该函数调用成功后,返回一个新的套接字句柄,用于通讯双方数据的传输。
当用户使用Winsock编程时,都是调用函数send()和recv()进行数据的发送和接收,函数原型如下:
int send(SOCKET s,const char FAR* buf,intlen,int flags); //发送数据
int recv(SOCKET s,char FAR* buf,int len,intflags); //接收数据
两个函数的各个参数以及表示的意义均相同。参数buf是指向数据缓冲区的指针变量,参数flags通常设置为0。
注意:如果服务器使用上面的函数进行数据收发,则参数s应该为监听函数返回的新套接字句柄。如果客户端使用以上函数进行数据收发,则参数s应该为客户端创建的套接字句柄。
当套接字使用完毕或程序退出时,用户应该调用函数closesocket()关闭套接字句柄,函数原型如下:
int closesockeet(
SOCKETs //将关闭的套接字句柄
);
参数s表示将要关闭的套接字句柄,例如,用户关闭前面创建的套接字句柄,代码如下:
::closesocket(s);