为什么要采用Socket模型,而不直接使用Socket?
原因源于recv()方法是堵塞式的,当多个客户端连接服务器时,其中一个socket的recv调用时,会产生堵塞,使其他链接不能继续。
这样我们又想到用多线程来实现,每个socket链接使用一个线程,这样效率十分低下,根本不可能应对负荷较大的情况。
于是便有了各种模型的解决方法,总之都是为了实现多个线程同时访问时不产生堵塞。此篇文章介绍其中两种常见的模型,Select模型和异步I/O模型。
select的函数原型
int select(
int nfds, //Winsock中此参数无意义
fd_set* readfds, //进行可读检测的Socket
fd_set* writefds, //进行可写检测的Socket
fd_set* exceptfds, //进行异常检测的Socket
const struct timeval* timeout //非阻塞模式中设置最大等待时间
);
Select模型的客户端:
#include <WinSock.h>
#pragma comment(lib, "ws2_32.lib")
#define MAXSOCKETCON 200
SOCKET ClientSockets[MAXSOCKETCON];
int NowClientSocket = 0;
SOCKET socketServer;
int CreateServerSocket(int Port)
{
int iErrCode;
WSADATA wsaData;
iErrCode = WSAStartup(0x0202, &wsaData);
int iRes;
//创建Socket
socketServer = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
//绑定
SOCKADDR_IN addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
//addr.sin_addr.s_addr = 0x010000ff;
addr.sin_port = htons(Port);
iRes = bind(socketServer,(LPSOCKADDR)&addr,sizeof(addr));
if(iRes == SOCKET_ERROR)
{
closesocket(socketServer);
return -1;
}
iRes = listen(socketServer, MAXSOCKETCON);
if(iRes == SOCKET_ERROR)
{
closesocket(socketServer);
return -1;
}
return 1;
}
DWORD WINAPI RecvProc(LPVOID pParam)
{
char Data[256];
int iBuffer = 0;
TIMEVAL TimeOut = {1, 0};
while (1)
{
if(NowClientSocket == 0)
{
continue;
}
fd_set ReadSet;
FD_ZERO(&ReadSet);
for (int i = 0; i<NowClientSocket;i++)
{
FD_SET(ClientSockets[i], &ReadSet);
}
select(0, &ReadSet, NULL, NULL, &TimeOut);
for (int i=0; i<NowClientSocket;i++)
{
if(FD_ISSET(ClientSockets[i], &ReadSet))
{
iBuffer = recv(ClientSockets[i], Data, 256, 0);
if(iBuffer>0)
{
printf("(%d)%s", i , Data);
}
}
}
}
}
DWORD WINAPI AcceptProc(LPVOID pParam)
{
SOCKADDR_IN addr;
int addrLen = sizeof(addr);
SOCKET tempSocket;
while (1)
{
tempSocket = accept(socketServer, (sockaddr*)&addr, &addrLen);
if (tempSocket == INVALID_SOCKET)
{
return 0;
}
ClientSockets[NowClientSocket] = tempSocket;
NowClientSocket++;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
if(CreateServerSocket(6565)<1)
return 0;
CreateThread(NULL, NULL, &AcceptProc, NULL, NULL, NULL);
CreateThread(NULL, NULL, &RecvProc, NULL, NULL, NULL);
while(1)
{
Sleep(1000);
}
return 0;
}
Winsock提供了一个有用的异步I/O模型。利用这个模型,应用程序可在一个套接字上,接收以Windows消息为基础的网络事件通知:
- 首先必须创建一个窗口,再为该窗口提供一个窗口例程支持函数(Winproc)。
- 设置好窗口的框架后,便可开始创建套接字。
- 建好一个套接字后调用WSAAsyncSelect函数。 WSAAsyncSelect函数定义如下:
int WSAAsyncSelect(
SOCKET s, //检查的套接字
HWND hWnd, //接收消息的窗口句柄
unsigned int uMsg, //发生网络事件时要接收的消息
long lEvent //网络事件
);
应用:在CreateSocketServer中最后调用:WSAAsyncSelect(sktServer, hWnd, WM_SOCKET, FD_ACCEPT|FD_READ|FD_WRITE|FD_CLOSE);
在window的消息处理函数中:
case WM_SOCKET:
switch(lParam)
{
case FD_ACCEPT:
tempSocket = accept(wParam, (sockaddr*)&ADDR, &AddrLen);
if(tempSocket != INVALID_SOCKET)
{
ClientSockets[NowClientCount++] = tempSocket;
}
break;
case FD_READ:
iBuffer = recv(wParam, Buffer, 256, 0);
if(iBuffer > 0)
{
Sleep(0);
}
break;
}