以下代码均摘自原版,文字部分是我的体会。
//--------------------------------------------------------------
//select情况下:
SOCKET s;
fd_set fdread;
int ret;
// Create a socket, and accept a connection
// Manage I/O on the socket
while(TRUE)
{
// Always clear the read set before calling
// select()
FD_ZERO(&fdread);
// Add socket s to the read set
FD_SET(s, &fdread);
FD_SET(s1, &fdread);
FD_SET(s2, &fdread);
//...
FD_SET(sn, &fdread);
if ((ret = select(0, &fdread, NULL, NULL, NULL))
== SOCKET_ERROR)
{
// Error condition
}
if (ret > 0)
{
// For this simple case, select() should return
// the value 1. An application dealing with
// more than one socket could get a value
// greater than 1. At this point, your
// application should check to see whether the
// socket is part of a set.
if (FD_ISSET(s, &fdread))
{
// A read event has occurred on socket s
}
if (FD_ISSET(s1, &fdread))
{
// A read event has occurred on socket s1
}
if (FD_ISSET(s2, &fdread))
{
// A read event has occurred on socket s2
}
//...
if (FD_ISSET(sn, &fdread))
{
// A read event has occurred on socket sn
}
//结论:
//可见,当有多个套接字的时候,这样的判断也是很费时的,
//fdread是一个数组,对数组的访问每一次都是按从0开始。
}
}
//--------------------------------------------------------------
//WSAAsyncSelect 情况下:
#define WM_SOCKET WM_USER + 1
#include <winsock2.h>
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow)
{
WSADATA wsd;
SOCKET Listen;
SOCKADDR_IN InternetAddr;
HWND Window;
// Create a window and assign the ServerWinProc
// below to it
Window = CreateWindow();
// Start Winsock and create a socket
WSAStartup(MAKEWORD(2,2), &wsd);
Listen = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Bind the socket to port 5150
// and begin listening for connections
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5150);
bind(Listen, (PSOCKADDR) &InternetAddr,
sizeof(InternetAddr));
// Set up window message notification on
// the new socket using the WM_SOCKET define
// above
WSAAsyncSelect(Listen, Window, WM_SOCKET,
FD_ACCEPT │ FD_CLOSE);
//经典的套接字模型是这里有个accept(),是每当有一个连接就转移给另一个套接字做,
//这是系统默认在处理ACCEPT;
//而这里,自己申请了对ACCEPT和CLOSE的处理,将处理例程写在窗口处理里。
listen(Listen, 5);
// Translate and dispatch window messages
// until the application terminates
while (1) {
// ...
}
}
BOOL CALLBACK ServerWinProc(HWND hDlg,UINT wMsg,
WPARAM wParam, LPARAM lParam)
{
SOCKET Accept;
//多个套接字的时候应该是一个数组
switch(wMsg)
{
case WM_PAINT:
// Process window paint messages
break;
case WM_SOCKET:
// Determine whether an error occurred on the
// socket by using the WSAGETSELECTERROR() macro
if (WSAGETSELECTERROR(lParam))
{
// Display the error and close the socket
closesocket( (SOCKET) wParam);
break;
}
// Determine what event occurred on the
// socket
switch(WSAGETSELECTEVENT(lParam))
{
case FD_ACCEPT:
// Accept an incoming connection
Accept = accept(wParam, NULL, NULL);
// Prepare accepted socket for read,
// write, and close notification
WSAAsyncSelect(Accept, hDlg, WM_SOCKET,
FD_READ │ FD_WRITE │ FD_CLOSE);
//有接入的情况下,申请对相关三个参数的处理
//wParam传递过来的时候就包含了套接字对象
break;
case FD_READ:
// Receive data from the socket in
// wParam
//recv(...);
break;
case FD_WRITE:
// The socket in wParam is ready
// for sending data
//send(...); 参数为:wParam,远程套接字,自己的buf
//(数据是在其他地方如文本框中输入的)...
break;
case FD_CLOSE:
// The connection is now closed
closesocket( (SOCKET)wParam);
break;
}
break;
}
return TRUE;
}
//总结:结构清晰
//总是需要一个窗体,它运用了成熟的窗口消息处理机制,是在不成熟的条件下,将现有的成熟技术融合,
//强调套接字事件与窗体通知消息的沟通。但是当消息量很大的时候,原有的窗口消息队列会有差的表现。
//--------------------------------------------------------------
//WSAEventSelect 情况下:
/*int WSAEventSelect(
SOCKET s,
WSAEVENT hEventObject,
long lNetworkEvents
);*/
SOCKET SocketArray [WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT EventArray [WSA_MAXIMUM_WAIT_EVENTS],
NewEvent;
SOCKADDR_IN InternetAddr;
SOCKET Accept, Listen;
DWORD EventTotal = 0;
DWORD Index, i;
// Set up a TCP socket for listening on port 5150
Listen = socket (PF_INET, SOCK_STREAM, 0);
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5150);
bind(Listen, (PSOCKADDR) &InternetAddr,
sizeof(InternetAddr));
NewEvent = WSACreateEvent();
WSAEventSelect(Listen, NewEvent,
FD_ACCEPT │ FD_CLOSE); //如果有多个套接字,需要多次调用WSAEventSelect,对每一个套接字进行设置。
listen(Listen, 5);
SocketArray[EventTotal] = Listen;
EventArray[EventTotal] = NewEvent;
EventTotal++;
while(TRUE)
{
// Wait for network events on all sockets
Index = WSAWaitForMultipleEvents(EventTotal,
EventArray, FALSE, WSA_INFINITE, FALSE);
Index = Index - WSA_WAIT_EVENT_0;
// Iterate through all events to see if more than one is signaled
for(i=Index; i < EventTotal ;i++)
{
Index = WSAWaitForMultipleEvents(1, &EventArray[i], TRUE, 1000, FALSE);
if ((Index == WSA_WAIT_FAILED) ││ (Index == WSA_WAIT_TIMEOUT))
continue;
else
{
Index = i;
WSAEnumNetworkEvents(
SocketArray[Index],
EventArray[Index],
&NetworkEvents);
//当确定了是哪个套接字有事件时,调用WSAEnumNetworkEvents确定在这个套接字上
//具体发生了什么网络事件
// Check for FD_ACCEPT messages
if (NetworkEvents.lNetworkEvents & FD_ACCEPT)
{
if (NetworkEvents.iErrorCode[FD_ACCEPT_BIT] != 0)
{
printf("FD_ACCEPT failed with error %d/n",
NetworkEvents.iErrorCode[FD_ACCEPT_BIT]);
break;
}
// Accept a new connection, and add it to the
// socket and event lists
Accept = accept(
SocketArray[Index],
NULL, NULL);
// We cannot process more than
// WSA_MAXIMUM_WAIT_EVENTS sockets, so close
// the accepted socket
if (EventTotal > WSA_MAXIMUM_WAIT_EVENTS)
{
printf("Too many connections");
closesocket(Accept);
break;
}
NewEvent = WSACreateEvent();
WSAEventSelect(Accept, NewEvent,
FD_READ │ FD_WRITE │ FD_CLOSE); //声明将要对三个网络事件处理
EventArray[EventTotal] = NewEvent;
SocketArray[EventTotal] = Accept;
EventTotal++;
printf("Socket %d connected/n", Accept);
}
// Process FD_READ notification
if (NetworkEvents.lNetworkEvents & FD_READ)
{
if (NetworkEvents.iErrorCode[FD_READ_BIT] != 0)
{
printf("FD_READ failed with error %d/n",
NetworkEvents.iErrorCode[FD_READ_BIT]);
break;
}
// Read data from the socket
recv(SocketArray[Index - WSA_WAIT_EVENT_0],
buffer, sizeof(buffer), 0);
}
// Process FD_WRITE notification
if (NetworkEvents.lNetworkEvents & FD_WRITE)
{
if (NetworkEvents.iErrorCode[FD_WRITE_BIT] != 0)
{
printf("FD_WRITE failed with error %d/n",
NetworkEvents.iErrorCode[FD_WRITE_BIT]);
break;
}
send(SocketArray[Index - WSA_WAIT_EVENT_0],
buffer, sizeof(buffer), 0);
}
if (NetworkEvents.lNetworkEvents & FD_CLOSE)
{
if (NetworkEvents.iErrorCode[FD_CLOSE_BIT] != 0)
{
printf("FD_CLOSE failed with error %d/n",
NetworkEvents.iErrorCode[FD_CLOSE_BIT]);
break;
}
closesocket(SocketArray[Index]);
// Remove socket and associated event from
// the Socket and Event arrays and decrement
// EventTotal
CompressArrays(EventArray, SocketArray, &EventTotal);
}
}
}
}
//总结:
//将感兴趣的网络事件和套接字联系起来,并通过EVENT通知例程,这也是对已有技术的运用,
//没有享受系统的机制。
//而且为了避免调用WSAWaitForMultipleEvents产生的事件饥饿,用了一个
//for(i=Index; i < EventTotal ;i++)
//强迫进行事件轮询,容易造成处理缓慢。而且还是可能造成先到的包后被处理。
//--------------------------------------------------------------
//Overlapped情况下:
//Event:
/*
typedef struct WSAOVERLAPPED
{
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent;
} WSAOVERLAPPED, FAR * LPWSAOVERLAPPED;
从结构可以看出系统对网络大流量的支持,直接将自建事件和缓冲区联系起来,系统自己做接受网络事件,处理缓冲区的内容。
*/
#define DATA_BUFSIZE 4096
void main(void)
{
WSABUF DataBuf;
char buffer[DATA_BUFSIZE];
DWORD EventTotal = 0,
RecvBytes=0,
Flags=0;
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAOVERLAPPED AcceptOverlapped;
SOCKET ListenSocket, AcceptSocket;
// Step 1:
// Start Winsock and set up a listening socket
...
// Step 2:
// Accept an inbound connection
AcceptSocket = accept(ListenSocket, NULL, NULL);
//将ACCEPT消息还是交给系统处理
// Step 3:
// Set up an overlapped structure
EventArray[EventTotal] = WSACreateEvent();
ZeroMemory(&AcceptOverlapped,sizeof(WSAOVERLAPPED));
AcceptOverlapped.hEvent = EventArray[EventTotal];
DataBuf.len = DATA_BUFSIZE;
DataBuf.buf = buffer;
EventTotal++;
// Step 4:
// Post a WSARecv request to begin receiving data
// on the socket
if (WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes,
&Flags, &AcceptOverlapped, NULL) == SOCKET_ERROR)
//注意在投递之前并没有表示它注册了什么网络事件,是否WSARecv函数里会与FD_READ联系,还不知
//具体哪种网络事件发生了,自制事件能得到通知。
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
// Error occurred
}
}
// Process overlapped receives on the socket
while(TRUE)
{
DWORD Index;
// Step 5:
// Wait for the overlapped I/O call to complete
Index = WSAWaitForMultipleEvents(EventTotal,
EventArray, FALSE, WSA_INFINITE, FALSE);
// Index should be 0 because we
// have only one event handle in EventArray
// Step 6:
// Reset the signaled event
WSAResetEvent(
EventArray[Index - WSA_WAIT_EVENT_0]);
// Step 7:
// Determine the status of the overlapped
// request
WSAGetOverlappedResult(AcceptSocket,
&AcceptOverlapped, &BytesTransferred,
FALSE, &Flags);
// First check to see whether the peer has closed
// the connection, and if so, close the
// socket
if (BytesTransferred == 0)
{
printf("Closing socket %d/n", AcceptSocket);
closesocket(AcceptSocket);
WSACloseEvent(
EventArray[Index - WSA_WAIT_EVENT_0]);
return;
}
// Do something with the received data
// DataBuf contains the received data
...
// Step 8:
// Post another WSARecv() request on the socket
Flags = 0;
ZeroMemory(&AcceptOverlapped,
sizeof(WSAOVERLAPPED));
AcceptOverlapped.hEvent = EventArray[Index -
WSA_WAIT_EVENT_0];
DataBuf.len = DATA_BUFSIZE;
DataBuf.buf = buffer;
if (WSARecv(AcceptSocket, &DataBuf, 1,
&RecvBytes, &Flags, &AcceptOverlapped,
NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
// Unexpected error
}
}
}
}
//总结:
//这里只是表示了如何处理接收数据,如果同时也有发送呢?调用WSASend(...),也在循环里等待一个
//事件数组?这样会和WSARecv的处理耗上,看来需要另一个线程
//专门处理。这个比WSAEventSelect好的地方在于:判断了事件以后,直接可以通过判断
//BytesTransferred来确定对Buf的处理,而不需要再调用一次Recv函数。
//需要分线程处理发送与接收下面是我理解中的WSARecv伪代码
WSARecv(socket s,char* buf,int i,int* len,bool* flag,WSAOVERLAPPED lap)
{
//分析套接字s的属性
if(有完成端口)
{
声明处理完成端口感兴趣的网络事件(这里是FD_READ);
//可能跳入另一个系统分配的线程工作,当事件产生时,再往下进行。而该线程马上返回
//以下处理部分很有可能是一个回调过程,不在真实的WSARecv里面。
写入LPPER_IO_DATA结构;//应该就是buf
将该结构添加进一个类似队列的结构中;//等待GetQueuedCompletionStatus调用
return;
}
//...
else
{
WSAEVENT event = WSACreatEvent();
WSAEventSelect(s, event, FD_READ); //声明将要响应FD_READ事件
//...不是用waitfor..等待event ;用系统特殊的方式,否则会阻塞
if (NetworkEvents.lNetworkEvents & FD_READ)
{
if (NetworkEvents.iErrorCode[FD_READ_BIT] != 0)
{
printf("FD_READ failed with error %d/n",
NetworkEvents.iErrorCode[FD_READ_BIT]);
break;
}
// Read data from the socket
recv(s,buf, sizeof(buf), 0);
WSAReSetEvent(lap.hEvent); //给予外部例程通知
}
}
//...
}
//--------------------------------------------------------------
//Overlapped情况下:
//Completion Routines:
/*
void CALLBACK CompletionROUTINE(
DWORD dwError,
DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped,
DWORD dwFlags
);
*/
#define DATA_BUFSIZE 4096
SOCKET AcceptSocket,
ListenSocket;
WSABUF DataBuf;
WSAEVENT EventArray[MAXIMUM_WAIT_OBJECTS];
DWORD Flags,
RecvBytes,
Index;
char buffer[DATA_BUFSIZE];
void main(void)
{
WSAOVERLAPPED Overlapped;
// Step 1:
// Start Winsock, and set up a listening socket
...
// Step 2:
// Accept a new connection
AcceptSocket = accept(ListenSocket, NULL, NULL);
// Step 3:
// Now that we have an accepted socket, start
// processing I/O using overlapped I/O with a
// completion routine. To get the overlapped I/O
// processing started, first submit an
// overlapped WSARecv() request.
Flags = 0;
ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
DataBuf.len = DATA_BUFSIZE;
DataBuf.buf = buffer;
// Step 4:
// Post an asynchronous WSARecv() request
// on the socket by specifying the WSAOVERLAPPED
// structure as a parameter, and supply
// the WorkerRoutine function below as the
// completion routine
if (WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes,
&Flags, &Overlapped, WorkerRoutine)
== SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
printf("WSARecv() failed with error %d/n",
WSAGetLastError());
return;
}
}
// Because the WSAWaitForMultipleEvents() API
// requires waiting on one or more event objects,
// we will have to create a dummy event object.
// As an alternative, we can use SleepEx()
// instead.
EventArray [0] = WSACreateEvent();
while(TRUE)
{
// Step 5:
Index = WSAWaitForMultipleEvents(1, EventArray,
FALSE, WSA_INFINITE, TRUE);
// Step 6:
if (Index == WAIT_IO_COMPLETION)
{
// An overlapped request completion routine
// just completed. Continue servicing
// more completion routines.
continue;
}
else
{
// A bad error occurred:óstop processing!
// If we were also processing an event
// object, this could be an index to
// the event array.
return;
}
}
}
void CALLBACK WorkerRoutine(DWORD Error,
DWORD BytesTransferred,
LPWSAOVERLAPPED Overlapped,
DWORD InFlags)
{
DWORD SendBytes, RecvBytes;
DWORD Flags;
if (Error != 0 ││ BytesTransferred == 0)
{
// Either a bad error occurred on the socket
// or the socket was closed by a peer
closesocket(AcceptSocket);
return;
}
// At this point, an overlapped WSARecv() request
// completed successfully. Now we can retrieve the
// received data that is contained in the variable
// DataBuf. After processing the received data, we
// need to post another overlapped WSARecv() or
// WSASend() request. For simplicity, we will post
// another WSARecv() request.
Flags = 0;
ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
DataBuf.len = DATA_BUFSIZE;
DataBuf.buf = buffer;
if (WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes,
&Flags, &Overlapped, WorkerRoutine)
== SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING )
{
printf("WSARecv() failed with error %d/n",
WSAGetLastError());
return;
}
}
}
//总结:
//其实质是对每一个套接字的特定网络事件注册回调例程,但是暂时还不知系统是否是创建多个
//回调例程,不过这样也是最完美的,每个例程处理一个事件,
//系统只承担调用开销,例程本身的处理能够最优化。还有源代码里没有把hEvent传入lap,有问题。
//--------------------------------------------------------------
//Completion Port 情况下:
/*
HANDLE CreateIoCompletionPort(
HANDLE FileHandle,
HANDLE ExistingCompletionPort,
DWORD CompletionKey,
DWORD NumberOfConcurrentThreads
);
*/
HANDLE CompletionPort;
WSADATA wsd;
SYSTEM_INFO SystemInfo;
SOCKADDR_IN InternetAddr;
SOCKET Listen;
int i;
typedef struct _PER_HANDLE_DATA
{
SOCKET Socket;
SOCKADDR_STORAGE ClientAddr;
// Other information useful to be associated with the handle
} PER_HANDLE_DATA, * LPPER_HANDLE_DATA; //传给线程的信息
// Load Winsock
StartWinsock(MAKEWORD(2,2), &wsd);
// Step 1:
// Create an I/O completion port
CompletionPort = CreateIoCompletionPort(
INVALID_HANDLE_VALUE, NULL, 0, 0);
// Step 2:
// Determine how many processors are on the system
GetSystemInfo(&SystemInfo);
// Step 3:
// Create worker threads based on the number of
// processors available on the system. For this
// simple case, we create one worker thread for each
// processor.
for(i = 0; i < SystemInfo.dwNumberOfProcessors; i++)
{
HANDLE ThreadHandle;
// Create a server worker thread, and pass the
// completion port to the thread. NOTE: the
// ServerWorkerThread procedure is not defined
// in this listing.
ThreadHandle = CreateThread(NULL, 0,
ServerWorkerThread, CompletionPort,
0, NULL); //此时工作线程被阻塞,因为它在等待CompletionPort里的参数。
//之所以要在这么早调用,可能给子线程充分的初始化时间
// Close the thread handle
CloseHandle(ThreadHandle);
}
// Step 4:
// Create a listening socket
Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,
WSA_FLAG_OVERLAPPED); //注意声明: 这里就将支持重叠I/O
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5150);
bind(Listen, (PSOCKADDR) &InternetAddr,sizeof(InternetAddr));
// Prepare socket for listening
listen(Listen, 5);
while(TRUE)
{
PER_HANDLE_DATA *PerHandleData=NULL;
SOCKADDR_IN saRemote;
SOCKET Accept;
int RemoteLen;
// Step 5:
// Accept connections and assign to the completion
// port
RemoteLen = sizeof(saRemote);
Accept = WSAAccept(Listen, (SOCKADDR *)&saRemote, &RemoteLen);
//没有任何特性的阻塞返回,
//每一次返回都把一个新的套接字赋给Accept
// Step 6:
// Create per-handle data information structure to
// associate with the socket
PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));
printf("Socket number %d connected/n", Accept);
PerHandleData->Socket = Accept;
memcpy(&PerHandleData->ClientAddr, &saRemote, RemoteLen);
// Step 7:
// Associate the accepted socket with the
// completion port
CreateIoCompletionPort((HANDLE) Accept,
CompletionPort, (DWORD) PerHandleData, 0); //一个完成端口可以支持多个套接字
// Step 8:
// Start processing I/O on the accepted socket.
// Post one or more WSASend() or WSARecv() calls
// on the socket using overlapped I/O.
WSARecv(...); //对刚刚设置的完成端口进行处理,放入队列等待例程处理。
}
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)
{
HANDLE CompletionPort = (HANDLE) CompletionPortID;
DWORD BytesTransferred;
LPOVERLAPPED Overlapped;
LPPER_HANDLE_DATA PerHandleData;
LPPER_IO_DATA PerIoData;
DWORD SendBytes, RecvBytes;
DWORD Flags;
while(TRUE)
{
// Wait for I/O to complete on any socket
// associated with the completion port
ret = GetQueuedCompletionStatus(CompletionPort,
&BytesTransferred,(LPDWORD)&PerHandleData,
(LPOVERLAPPED *) &PerIoData, INFINITE);
//堵塞在这里,该函数将取得PerIoData
// First check to see if an error has occurred
// on the socket; if so, close the
// socket and clean up the per-handle data
// and per-I/O operation data associated with
// the socket
if (BytesTransferred == 0 &&
(PerIoData->OperationType == RECV_POSTED ││
PerIoData->OperationType == SEND_POSTED))
{
// A zero BytesTransferred indicates that the
// socket has been closed by the peer, so
// you should close the socket. Note:
// Per-handle data was used to reference the
// socket associated with the I/O operation.
closesocket(PerHandleData->Socket);
GlobalFree(PerHandleData);
GlobalFree(PerIoData);
continue;
}
// Service the completed I/O request. You can
// determine which I/O request has just
// completed by looking at the OperationType
// field contained in the per-I/O operation data.
if (PerIoData->OperationType == RECV_POSTED)
{
// Do something with the received data
// in PerIoData->Buffer
}
// Post another WSASend or WSARecv operation.
// As an example, we will post another WSARecv()
// I/O operation.
Flags = 0;
// Set up the per-I/O operation data for the next
// overlapped call
ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
PerIoData->DataBuf.len = DATA_BUFSIZE;
PerIoData->DataBuf.buf = PerIoData->Buffer;
PerIoData->OperationType = RECV_POSTED;
WSARecv(PerHandleData->Socket,
&(PerIoData->DataBuf), 1, &RecvBytes,
&Flags, &(PerIoData->Overlapped), NULL); //进入一个循环处理FD_READ
}
}
//结论:
//完成端口把它自身与套接字联系起来,WSA系列函数会通过套接字得到完成端口。
//技术上还是往多线程参数传递上发展,但是系统的特别支持使得这种模型更加强大。
//不用太多的线程就可以实现。
//但是它的处理性能还是决定于工作例程的性能。