一、头文件及Library
头文件:Winsock2.h #include <Winsock2.h>
Library:Ws2_32.lib
二、主要函数
1. int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
wVersionRequested :指定准备加载Windows sockets 动态库的版本。
lpWSAData :指向WSADATA结构体的指针,用于返回被加载动态库的有关信息。
返回值:函数成功返回0,否则返回一个错误代码(可用WSAGetLastError()获得)。
作用:加载Windows sockets 动态库(dll)。
示例:WSADATA wsaData;
if( WSAStartup( MAKEWORD(2,2) , &wsaData)!=0 )
{
cout<<"找不到可使用的sockets dll。"<<endl;
}
2. SOCKET socket(int af, int type, int protocol );
af :协议的地址家族。一般为 AF_INET。
type :套接字类型。包括 SOCK_STREAM,SOCK_DGRAM 和 SOCK_RAM 三种。
protocol :使用协议。如 TCP(IPPROTO_TCP) 或 UDP(IPPROTO_UDP)
返回值:函数成功返回一个新建的套接字句柄,否则返回 INVALID_SOCKET,可用WSAGetLastError()获得错误代码。
作用:新建一个socket。
示例:SOCKET sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sListen == INVALID_SOCKET)
{
cout<<"创建套接字失败!"<<endl;
}
3. int getsockopt(SOCKET s, int level, int optname, char FAR *optval, int FAR *optlen );
s :套接字。
level :选项级别。有 SOL_SOCKET 和 IPPROTO_TCP 两个级别。
optname :套接字选项名称。
optval :接收数据缓冲区。
optlen :缓冲区大小。
返回值:函数成功返回0,否则返回 SOCKET_ERROR,可用WSAGetLastError()获得错误代码。
作用:获取 socket 选项信息。
示例:unsigned int nSendBuf;
int nOptLen = sizeof(nSendBuf);
if( getsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&nSendBuf, &nOptLen) == SOCKET_ERROR)
{
cout<<"获取发送缓冲区大小失败!"<<endl;
}
4. int setsockopt(SOCKET s, int level, int optname, char FAR *optval, int FAR *optlen );
s :套接字。
level :选项级别。有 SOL_SOCKET 和 IPPROTO_TCP 两个级别。
optname :套接字选项名称。
optval :设置数据缓冲区。
optlen :缓冲区大小。
返回值:函数成功返回0,否则返回 SOCKET_ERROR,可用WSAGetLastError()获得错误代码。
作用:设置 socket 选项信息。
示例:nSendBuf *=10;
if( setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&nSendBuf, nOptLen) == SOCKET_ERROR)
{
cout<<"修改发送缓冲区大小失败!"<<endl;
}
5. int bind(SOCKET s, const struct sockaddr FAR *name, int namelen);
s :套接字
name :地址
namelen :参数name值的长度
返回值:函数成功返回0,否则返回 SOCKET_ERROR,可用WSAGetLastError()获得错误代码。
作用:将 socket 绑定到一个已知的地址。
示例:SOCKADDR_IN addrServ;
addrServ.sin_family = AF_INET;
addrServ.sin_addr.S_un.S_addr = INADDR_ANY;
addrServ.sin_port = htons(6280);
if( bind(sListen, (sockaddr *)&addrServ, sizeof(sockaddr)) == SOCKET_ERROR)
{
cout<<"绑定socket失败!"<<endl;
}
6. int listen(SOCKET s, int backlog);
s :套接字
backlog :指定等待连接队列的最大长度
返回值:函数成功返回0,否则返回 SOCKET_ERROR,可用WSAGetLastError()获得错误代码。
作用:将 socket 设置为监听模式。
示例:if(SOCKET_ERROR == listen(g_sListen,10))
{
cout<<"监听socket失败!"<<endl;
}
7. SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);
s :监听套接字
addr :返回请求连接主机的地址结构
addrlen :返回addr参数值的长度
返回值:函数成功返回一个新的 socket 句柄,否则返回 INVALID_SOCKET,可用WSAGetLastError()获得错误代码。
作用:接受一个连接请求。
示例:SOCKADDR_IN addrClient;
int addrLen = sizeof(addrClient);
if( accept(sListen, (sockaddr *)&addrClient, &addrLen) == INVALID_SOCKET)
{
//失败处理
}
注意:第三个参数addrlen很重要,在调用accept之前必须定义一个int变量并对其赋初值为sockaddr结构的长度,如int addrLen = sizeof(addrClient); 否则 accept 会失败!
8. int connect(SOCKET s, const struct sockaddr FAR *name, int namelen);
s :套接字
addr :服务器的地址结构体
addrlen :返回addr参数值的长度
返回值:函数成功返回0,否则返回 SOCKET_ERROR,可用WSAGetLastError()获得错误代码。
作用:连接服务器。
示例:SOCKADDR_IN addrServ;
addrServ.sin_family = AF_INET;
addrServ.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrServ.sin_port = htons(6280);
if( connect(sClient, (sockaddr *)&addrServ, sizeof(sockaddr)) == SOCKET_ERROR)
{
cout<<"连接服务器失败!"<<endl;
}
9. int recv(SOCKET s, char FAR *buf, int len, int flags); ——TCP使用
s :套接字
buf :接收缓冲区
len :缓冲区长度
flags :该参数影响函数的行为,可以是0、MSG_PEEK、MSG_OOB。
返回值:函数成功返回接收的字节数,否则返回 SOCKET_ERROR,可用WSAGetLastError()获得错误代码。
作用:接收数据。
示例:char RecvBuf[128];
if( recv(sClient, RecvBuf, 128, 0) == SOCKET_ERROR)
{
//失败处理
}
10. int send(SOCKET s, const char FAR *buf, int len, int flags); ——TCP使用
s :套接字
buf :发送缓冲区
len :缓冲区长度
flags :该参数影响函数的行为,可以是0、MSG_PEEK、MSG_OOB。
返回值:函数成功返回实际发送的字节数,否则返回 SOCKET_ERROR,可用WSAGetLastError()获得错误代码。
作用:发送数据。
示例:char SendBuf = "Hello, Client! " ;
if( send(sListen, SendBuf, strlen(SendBuf), 0) == SOCKET_ERROR)
{
//失败处理
}
11. int recvfrom(SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR *from, int FAR *fromlen); ——UDP使用
s :套接字
buf :接收缓冲区
len :缓冲区长度
flags :该参数影响函数的行为,可以是0、MSG_PEEK、MSG_OOB。
from:返回发送数据主机的地址结构体
fromlen :参数from值的长度
返回值:函数成功返回实际接收的字节数,否则返回 SOCKET_ERROR,可用WSAGetLastError()获得错误代码。
作用:接收数据。
示例:SOCKADDR_IN addrSender;
int addrLen = sizeof(addrSender);
char RecvBuf[128];
memset(RecvBuf, 0, 128);
if( recvfrom(s, RecvBuf, 128, 0, (sockaddr *)&addrSender, &addrLen) == SOCKET_ERROR)
{
//失败处理
}
12. int sendto(SOCKET s, const char FAR *buf, int len, int flags, const struct sockaddr FAR *to, int tolen); ——UDP使用
s :套接字
buf :发送缓冲区
len :缓冲区长度
flags :该参数影响函数的行为,可以是0、MSG_DONTROUTE、MSG_OOB。
to:要接收数据主机的地址结构体
tolen :参数to值的长度
返回值:函数成功返回实际发送的字节数,否则返回 SOCKET_ERROR,可用WSAGetLastError()获得错误代码。
作用:发送数据。
示例:SOCKADDR_IN addrRecver;
addrRecver.sin_family = AF_INET;
addrRecver.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrRecver.sin_port = htons(6280);
char *SendBuf = "Hello, socket !";
if( sendto(s, SendBuf, strlen(SendBuf), 0, (sockaddr *)&addrRecver, sizeof(sockaddr)) == SOCKET_ERROR)
{
//失败处理
}
13. int closesocket(SOCKET s );
s :套接字
返回值:函数成功返回0,否则返回 SOCKET_ERROR,可用WSAGetLastError()获得错误代码。
作用:关闭socket,释放所占资源。
示例:closesocket(sListen);
14. int WSACleanup (void);
返回值:函数成功返回0,否则返回 SOCKET_ERROR,可用WSAGetLastError()获得错误代码。
作用:终止socket Library(Ws_32.lib)的使用,释放资源。
示例:WSACleanup();
15. char FAR * inet_ntoa(struct in_addr in );
in :Internet 主机地址结构体
返回值:函数成功返回点分十进制的IP地址字符串,否则NULL。
作用:转换网络地址(IPv4)到一个点分十进制的字符串。
示例:char *pIPAddr = inet_ntoa(addrClient.sin_addr);
16. unsigned long inet_addr(const char FAR *cp);
cp :以点分十进制表示的IP地址字符串
返回值:函数成功返回一个表示相应网络地址的unsinged long值,否则返回0或INADDR_NONE。
作用:转换一个点分十进制的字符串到相应的网络地址(IPv4)。
示例:unsinged long ulAddr = inet_addr("127.0.0.1");
17. u_long htonl( u_long hostlong );
作用:转换一个u_long值从主机字节序到网络字节序。
u_short htons( u_short hostshort );
作用:转换一个u_short值从主机字节序到网络字节序。
18. u_long ntohl( u_long netlong );
作用:转换一个u_long值从网络字节序到主机字节序。
u_short ntohs( u_short netshort );
作用:转换一个u_short值从网络字节序到主机字节序。
三、sockets 的两种模式
1. 阻塞模式
在阻塞模式下,在I/O操作完成之前,执行的操作函数将会一直等待而不会立即返回,该函数所在的线程会阻塞在这里。
优点:简单,容易实现。
缺点:当同时处理大量套接字时,将无从下手,扩展性差。
2. 非阻塞模式
在非阻塞模式下,当socket执行操作时,不管调用的函数操作是否完成都会立即返回,函数所在的线程不会阻塞。
优点:能同时处理大量连接,在收发量不均、时间不定的情况下优势明显。
缺点:1. 需要不停地检查返回代码,浪费系统资源。
2. 需要编写更多的代码,使用较复杂。
四、Windows sockets I/O 模型
1. Select 模型
使用该模型使得Windows sockets 应用程序可以在同一时间内管理和控制多个套接字。该模型的核心是Select函数。在使用该函数时,还需要用到FD_SET、FD_ZERO、FD_ISSET和FD_CLR四个宏。
利用Select函数,Windows sockets 应用程序可以判断套接字上是否存在数据,或者能否向该套接字写入数据。
缺点:完成一次I/O操作需要经历两次Windows sockets 函数的调用,效率低下。
2. WSAAsyncSelect 模型
WSAAsyncSelect模型是Windows sockets 的一个异步I/O模型。利用该模型应用程序可以在一个套接字上,接收以Windows 消息为基础的网络事件。
Windows sockets 应用程序在创建套接字后,调用WSAAsyncSelect函数注册感兴趣的网络事件。当该事件发生时,Windows 窗口收到消息,然后应用程序就可以对接收到的网络事件进行处理。
特点:1. 异步模型
2. 基于Windows环境,必须创建窗口
3. 自动将套接字设置为非阻塞模式
3. WSAEventSelect 模型
WSAEventSelect 模型是Windows sockets 提供的另外一个异步I/O模型。该模型允许在一个或多个套接字上接收以事件为基础的网络事件通知。
Windows sockets 应用程序在创建套接字后,调用WSAEventSelect函数,将一个事件对象与网络事件集合关联起来。当网络事件发生时,应用程序以事件的形式接收网络事件通知。
WSAEventSelect模型与WSAAsyncSelect模型很相似,主要的区别是:网络事件发生时系统通知应用程序的形式不同。WSAAsyncSelect模型以消息的形式通知应用程序,而WSAEventSelect模型则以事件的形式通知。
WSAEventSelect模型的核心是WSAEventSelect()函数。应用WSAEventSelect模型开发Windows sockets 应用程序时,还需要用到WSACreateEvent()、WSAResetEvent()、WSACloseEvent()、WSAWaitForMultipleEvents()、WSAEnumNetworkEvents()等函数。
缺点:每个WSAEventSelect模型最多只能管理64个套接字,当应用程序需要管理多于64个套接字时,就需要创建额外线程。
4. 重叠I/O模型
重叠I/O模型是真正意义上的异步I/O模型。该模型以Win32重叠I/O机制为基础。
利用重叠I/O模型,应用程序能在一个重叠结构上一次投递一个或者多个I/O请求,当系统完成I/O操作之后通知应用程序。应用程序接收到通知后,对数据进行处理。
系统向应用程序发送通知的形式有两种:1. 事件通知 2. 完成例程。在发起Windows sockets 函数调用时,由应用程序设置接收I/O操作完成的通知形式。
重叠I/O模型可以使用Windows sockets 应用程序达到比较好的性能。
5. 完成端口模型
完成端口是Win32的一种核心对象。完成端口模型是一种真正意义上的异步模型,以Win32的重叠I/O机制为基础。
利用完成端口模型,套接字应用程序能够管理数百个甚至上千个套接字。应用程序创建一个Win32完成端口对象,通过指定一定数量的服务线程,为已经完成的重叠I/O操作提供服务。
完成端口的目标是实现高效的服务器程序,它克服了并发模型(服务器线程模型之一)的不足。其方法为:
1. 为完成端口指定并发线程的数量;
2. 在初始化套接字时创建一定数量的服务线程,即所谓的线程池。当客户端请求到来时,这些线程立即为之服务。
优点:1. 对服务器提供服务的客户端数量没有限制;
2. 具有很好的系统性能;
3. 具有很好的可扩展性。