Sockets有两种模式,同步模式和异步模式。创建一个Socket后默认是同步的,可以通过ioctlsocket的FIONBIO命令设置异步模式。单独使用同步模式,效率会提高,但是不易控制连接的断开;单独使用异步模式,只有轮询,以控制但是效率自然低下。Windows Sockets提供了五种I/O模型,有效地提高了效率。当然,这五种模型主要是针对服务器的,因为服务器需要维护多个客户端的链路。
1,Select模型
Select模型使用select函数检测Socket集合中的若干个Socket是否有关心的事件(可读、可写、发生异常),根据检测结果执行相关操作。这种模式使得可以用select函数同步检测事件,然后就可确保相关的I/O操作可以立即完成,提高了效率。代码框架如下:
int g_iTotalConn = 0;
SOCKET g_CliSocketArr[FD_SETSIZE];
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
...
bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));
listen(sListen, 3);
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
while (TRUE)
{
sClient = accept(sListen, (sockaddr*)&client, &iAddrSize);
//新连接,状态回调函数
g_CliSocketArr[g_iTotalConn++] = sClient;
}
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
int i;
fd_set fdread;
int ret;
struct timeval tv = {1, 0};
char szMessage[MSGSIZE];
while (TRUE)
{
FD_ZERO(&fdread);
for (i = 0; i < g_iTotalConn; i++)
{
FD_SET(g_CliSocketArr[i], &fdread);
}
// We only care read event
ret = select(0, &fdread, NULL, NULL, &tv);
if (ret == 0)
{
// Time expired
continue;
}
for (i = 0; i < g_iTotalConn; i++)
{
if (FD_ISSET(g_CliSocketArr[i], &fdread))
{
// A read event happened on g_CliSocketArr
ret = recv(g_CliSocketArr[i], szMessage, MSGSIZE, 0);
if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
{
// Client socket closed
//连接断开,状态回调函数
closesocket(g_CliSocketArr[i]);
if (i < g_iTotalConn-1)
{
g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn];
}
}
else
{
// We reveived a message from client
// 新数据,数据回调函数
}
}
}
}
}
2:AsyncSelect模型
这种模型通过Windows的消息传递方式通知客户关心的事件发生。因而必须创建一个窗口,在窗口的消息回调函数中处理自定义消息。具体可以在窗口的Create消息中创建Socket,并绑定Socket关心的事件与此窗口的处理消息。然后再此处理消息中进行相关操作。代码框架如下:
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
WSADATA wsaData;
static SOCKET sListen;
SOCKET sClient;
SOCKADDR_IN local, client;
int ret, iAddrSize = sizeof(client);
char szMessage[MSGSIZE];
switch (message)
{
case WM_CREATE:
WSAStartup(0x0202, &wsaData);
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
...
bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));
listen(sListen, 3);
WSAAsyncSelect(sListen, hwnd, WM_SOCKET, FD_ACCEPT);
return 0;
case WM_SOCKET:
if (WSAGETSELECTERROR(lParam))
{
//连接断开,状态回调函数
closesocket(wParam);
break;
}
switch (WSAGETSELECTEVENT(lParam))
{
case FD_ACCEPT:
// Accept a connection from client
sClient = accept(wParam, (sockaddr*)&client, &iAddrSize);
//新连接,状态回调函数
// Associate client socket with FD_READ and FD_CLOSE event
WSAAsyncSelect(sClient, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
break;
case FD_READ:
ret = recv(wParam, szMessage, MSGSIZE, 0);
if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
{
//连接断开,状态回调函数
closesocket(wParam);
}
else
{
//新数据,数据回调函数
}
break;
case FD_CLOSE:
//连接关闭,状态回调函数
closesocket(wParam);
break;
}
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
3:EventSelect模型
这种模型将用户关心的事件和某个内核事件对象关联起来。当事件到达是自动向事件发送信号。因而不用创建窗口,代码框架如下:
int g_iTotalConn = 0;
SOCKET g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
...
bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));
listen(sListen, 3);
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
while (TRUE)
{
sClient = accept(sListen, (sockaddr*)&client, &iAddrSize);
//新连接,状态回调函数
g_CliSocketArr[g_iTotalConn] = sClient;
g_CliEventArr[g_iTotalConn] = WSACreateEvent();
WSAEventSelect(g_CliSocketArr[g_iTotalConn], g_CliEventArr[g_iTotalConn], FD_READ|FD_CLOSE);
g_iTotalConn++;
}
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
int ret, index;
WSANETWORKEVENTS NetworkEvents;
char szMessage[MSGSIZE];
while (TRUE)
{
ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
{
continue;
}
index = ret - WSA_WAIT_EVENT_0;
WSAEnumNetworkEvents(g_CliSocketArr[index], g_CliEventArr[index], &NetworkEvents);
if (NetworkEvents.lNetworkEvents & FD_READ)
{
// Receive message from client
ret = recv(g_CliSocketArr[index], szMessage, MSGSIZE, 0);
if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
{
//连接关闭,状态回调函数
Cleanup(index);
}
else
{
//收到数据,数据回调函数
}
}
if (NetworkEvents.lNetworkEvents & FD_CLOSE)
{
//连接关闭,状态回调函数
Cleanup(index);
}
}
return 0;
}
void Cleanup(int index)
{
closesocket(g_CliSocketArr[index]);
WSACloseEvent(g_CliEventArr[index]);
if (index < g_iTotalConn-1)
{
g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn-1];
g_CliEventArr[index] = g_CliEventArr[g_iTotalConn-1];
}
g_iTotalConn--;
}
4:重叠模式
重叠模式类似重叠I/O,内部使用异步Socket模式操作,可以通过事件或回调函数通知客户操作完成。
通过事件通知的代码框架如下:
typedef struct
{
WSAOVERLAPPED overlap;
WSABUF Buffer;
char szMessage[MSGSIZE];
DWORD NumberOfBytesRecvd;
DWORD Flags;
} PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;
int g_iTotalConn = 0;
SOCKET g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
LPPER_IO_OPERATION_DATA g_pPerIoDataArr[MAXIMUM_WAIT_OBJECTS];
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
...
bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));
listen(sListen, 3);
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
while (TRUE)
{
// Accept a connection
sClient = accept(sListen, (sockaddr*)&client, &iAddrSize);
//新连接,状态回调函数
g_CliSocketArr[g_iTotalConn] = sClient;
g_pPerIoDataArr[g_iTotalConn] = (LPPER_IO_OPERATION_DATA)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
sizeof(PER_IO_OPERATION_DATA));
g_pPerIoDataArr[g_iTotalConn]->Buffer.len = MSGSIZE;
g_pPerIoDataArr[g_iTotalConn]->Buffer.buf = g_pPerIoDataArr[g_iTotalConn]->szMessage;
g_CliEventArr[g_iTotalConn] = g_pPerIoDataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent();
WSARecv(g_CliSocketArr[g_iTotalConn],
&g_pPerIoDataArr[g_iTotalConn]->Buffer,
1,
&g_pPerIoDataArr[g_iTotalConn]->NumberOfBytesRecvd,
&g_pPerIoDataArr[g_iTotalConn]->Flags,
&g_pPerIoDataArr[g_iTotalConn]->overlap,
NULL);
g_iTotalConn++;
}
closesocket(sListen);
WSACleanup();
return 0;
}
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
int ret, index;
DWORD cbTransferred;
while (TRUE)
{
ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
{
continue;
}
index = ret - WSA_WAIT_EVENT_0;
WSAResetEvent(g_CliEventArr[index]);
WSAGetOverlappedResult(g_CliSocketArr[index],
&g_pPerIoDataArr[index]->overlap,
&cbTransferred,
TRUE,
&g_pPerIoDataArr[g_iTotalConn]->Flags);
if (cbTransferred == 0)
{
// 连接断开,状态回调函数
Cleanup(index);
}
else
{
// 新数据,数据回调函数
// Launch another asynchronous operation
WSARecv(g_CliSocketArr[index],
&g_pPerIoDataArr[index]->Buffer,
1,
&g_pPerIoDataArr[index]->NumberOfBytesRecvd,
&g_pPerIoDataArr[index]->Flags,
&g_pPerIoDataArr[index]->overlap,
NULL);
}
}
return 0;
}
void Cleanup(int index)
{
closesocket(g_CliSocketArr[index]);
WSACloseEvent(g_CliEventArr[index]);
HeapFree(GetProcessHeap(), 0, g_pPerIoDataArr[index]);
if (index < g_iTotalConn-1)
{
g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn-1];
g_CliEventArr[index] = g_CliEventArr[g_iTotalConn-1];
g_pPerIoDataArr[index] = g_pPerIoDataArr[g_iTotalConn-1];
}
g_pPerIoDataArr[--g_iTotalConn] = NULL;
}
回调函数代码框架:
SOCKET g_sNewClientConnection;
BOOL g_bNewConnectionArrived = FALSE;
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
...
bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));
listen(sListen, 3);
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
while (TRUE)
{
// Accept a connection
g_sNewClientConnection = accept(sListen, (sockaddr*)&client, &iAddrSize);
g_bNewConnectionArrived = TRUE;
//新连接,状态回调函数
}
return 0;
}
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
LPPER_IO_OPERATION_DATA lpPerIOData = NULL;
while (TRUE)
{
if (g_bNewConnectionArrived)
{
// Launch an asynchronous operation for new arrived connection
lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
sizeof(PER_IO_OPERATION_DATA));
lpPerIOData->Buffer.len = MSGSIZE;
lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
lpPerIOData->sClient = g_sNewClientConnection;
WSARecv(lpPerIOData->sClient,
&lpPerIOData->Buffer,
1,
&lpPerIOData->NumberOfBytesRecvd,
&lpPerIOData->Flags,
&lpPerIOData->overlap,
CompletionRoutine);
g_bNewConnectionArrived = FALSE;
}
SleepEx(1000, TRUE);
}
return 0;
}
void CALLBACK CompletionRoutine(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
{
LPPER_IO_OPERATION_DATA lpPerIOData = (LPPER_IO_OPERATION_DATA)lpOverlapped;
if (dwError != 0 || cbTransferred == 0)
{
// 连接断开,状态回调函数
closesocket(lpPerIOData->sClient);
HeapFree(GetProcessHeap(), 0, lpPerIOData);
}
else
{
//新数据,数据回调函数
// Launch another asynchronous operation
memset(&lpPerIOData->overlap, 0, sizeof(WSAOVERLAPPED));
lpPerIOData->Buffer.len = MSGSIZE;
lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
WSARecv(lpPerIOData->sClient,
&lpPerIOData->Buffer,
1,
&lpPerIOData->NumberOfBytesRecvd,
&lpPerIOData->Flags,
&lpPerIOData->overlap,
CompletionRoutine);
}
}
5:完成端口模型
完成端口模型应该是效率最高的I/O模型了,其他模型要么通过内核通知,要么为每个客户创建一个服务线程,通过检测通知。完成端口基于重叠I/O,采用了线程池技术,对于客户数目众多,变化频率高的服务器很实用。代码框架如下:
typedef enum
{
RECV_POSTED
} OPERATION_TYPE;
typedef struct
{
WSAOVERLAPPED overlap;
WSABUF Buffer;
char szMessage[MSGSIZE];
DWORD NumberOfBytesRecvd;
DWORD Flags;
OPERATION_TYPE OperationType;
} PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;
CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
GetSystemInfo(&sysinfo);
for (i = 0; i < sysinfo.dwNumberOfProcessors; i++)
{
CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &dwThreadId);
}
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
...
bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));
listen(sListen, 3);
while (TRUE)
{
// Accept a connection
sClient = accept(sListen, (sockaddr*)&client, &iAddrSize);
//新连接,状态回调函数
// Associate the newly arrived client socket with completion port
CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)sClient, 0);
// Launch an asynchronous operation for new arrived connection
lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
sizeof(PER_IO_OPERATION_DATA));
lpPerIOData->Buffer.len = MSGSIZE;
lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
lpPerIOData->OperationType = RECV_POSTED;
WSARecv(sClient,
&lpPerIOData->Buffer,
1,
&lpPerIOData->NumberOfBytesRecvd,
&lpPerIOData->Flags,
&lpPerIOData->overlap,
NULL);
}
PostQueuedCompletionStatus(CompletionPort, 0xFFFFFFFF, 0, NULL);
CloseHandle(CompletionPort);
closesocket(sListen);
WSACleanup();
return 0;
}
DWORD WINAPI WorkerThread(LPVOID CompletionPortID)
{
HANDLE CompletionPort = (HANDLE)CompletionPortID;
DWORD dwBytesTransferred;
SOCKET sClient;
LPPER_IO_OPERATION_DATA lpPerIOData = NULL;
while (TRUE)
{
GetQueuedCompletionStatus(
CompletionPort,
&dwBytesTransferred,
(DWORD*)&sClient,
(LPOVERLAPPED*)&lpPerIOData,
INFINITE);
if (dwBytesTransferred == 0xFFFFFFFF)
{
return 0;
}
if (lpPerIOData->OperationType == RECV_POSTED)
{
if (dwBytesTransferred == 0)
{
// 连接断开,状态回调函数
closesocket(sClient);
HeapFree(GetProcessHeap(), 0, lpPerIOData);
}
else
{
//新数据,数据回调函数
// Launch another asynchronous operation for sClient
memset(lpPerIOData, 0, sizeof(PER_IO_OPERATION_DATA));
lpPerIOData->Buffer.len = MSGSIZE;
lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
lpPerIOData->OperationType = RECV_POSTED;
WSARecv(sClient,
&lpPerIOData->Buffer,
1,
&lpPerIOData->NumberOfBytesRecvd,
&lpPerIOData->Flags,
&lpPerIOData->overlap,
NULL);
}
}
}
return 0;
}