>
int select(
WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_CONNECT │ FD_READ │ FD_WRITE │ FD_CLOSE);
LRESULT CALLBACK WindowProc(
int WSAEventSelect(
BOOL WSAResetEvent(WSAEVENT hEvent);
DWORD WSAWaitForMultipleEvents(
Index = WSAWaitForMultipleEvents(...);
int WSAEnumNetworkEvents(
typedef struct _WSANETWORKEVENTS
// Process FD_READ notification
其实上例中的for循环可以不存在,只不过如果那样的话,如果第一个socket一直有event发生的话,那么数组中后边的socket总是很不到机会.
typedef struct WSAOVERLAPPED
BOOL WSAGetOverlappedResult(
if ((NewConnection = accept(ListeningSocket, (SOCKADDR *) &ClientAddr,&ClientAddrLen)) == INVALID_SOCKET)
WINDOWS下的防火墙技术都被研究得比较透了
socket modes 和socket I/O models .
socket modes (每一个socket对象自己所具备的特性 ):
As we mentioned, Windows sockets perform I/O operations in two socket operating modes: blocking and non-blocking . In blocking mode, Winsock calls that perform I/O—such as send and recv— wait until the operation is complete before they return to the program. In non-blocking mode, the Winsock functions return immediately.
Once a socket is placed in non-blocking mode(by calling the ioctlsocket()), Winsock API calls that deal with sending and receiving data or connection management return immediately. In most cases, these calls fail with the error WSAEWOULDBLOCK , which means that the requested operation did not have time to complete during the call. For example, a call to recv returns WSAEWOULDBLOCK if no data is pending in the system's input buffer. Often additional calls to the same function are required until it encounters a successful return code.
socket I/O models(管理多个sockets的方法) :
对于一个socket,我们可以使用blocking or non-blocking mode去处理.
当有多个socket时怎么办?
1 可以使用blocking socket加多线程的方法来处理,即每一个线程来处理一个socket的操作. 这样做的缺点是太多的线程浪费系统资源而且影响系统效率.
2 如果使用non-blocking方式来处理, 但是随着sockets个数的增多, 程序要用不断循环的方法来调用函数直到他们成功, 程序变得无法管理.所以这个模式不存在.
3 select模型.
4 WSAAsyncSelect模型.
5 WSAEventSelect模型.
6 overlapped 模型.
7 completion port
详解模型:
1 blocking socket + 多线程.
这种模式的好处是简单,容易实现. 坏处是因为要为每个socket创建一个或者两个线程来对它进行处理.所以非常浪费系统资源, 这里所说的系统资源是内存, 包括物理内存和虚拟内存. 每创建一个线程,系统要为线程在虚拟内存中保留1M的栈(这个操作使用了虚拟内存,每一个只有2GB的地址空间是属于用户的.), 而且还要为其中的一些虚拟内存分配一部分物理内存(计算机上的RAM).况且在运行时由于频繁的线程切换会使CPU很累.
2 select模型
We call it the select model because it centers on using the select function to manage I/O.
The select function can be used to determine if there is data on a socket and if a socket can be written to. The reason for having this function is to prevent your application from blocking on an I/O bound call such as send or recv when a socket is in a blocking mode and to prevent the WSAEWOULDBLOCK error when a socket is in a non-blocking mode. The select function blocks for I/O operations until the conditions specified as parameters are met.
int select(
int nfds,
fd_set FAR * readfds,
fd_set FAR * writefds,
fd_set FAR * exceptfds,
const struct timeval FAR * timeout
);
书中说的不详细, 还有很多的疑问要在msdn中才能找到答案.
比如在 readfds中的socket, 在满足以下三种情况时,认为可以操作。
1 当该socket上有数据可以被读取的时候。2 当该socket被关闭的时候。 3 当有连接请求要连接到该socket 时。
那么在该模型中并没有任何数据用来区别这三种情况,这三种情况中的任意一个发生时,都只是返回一个1而已。
区分的方法是: 如果在readfds中的socket处于listen状态但是没有调用accept时,那么这时肯定是有连接请求到达该socket,此时去accept的话,不会blocking. 如果在readfds中的socket已经是accept过的了,那么肯定是1或者2,那么怎么区别这两种情况呢,方法是直接调用recv函数,如果是1的情况,会成功收到数据。如果是2的情况,会返回一个错误值。
怎么用这个模式来处理多个链接呢?
用一个链表来保存接收到的socket, 用多线程,其中一个线程负责accept链接,得到socket后加入到链表中去.另外一个线程中while(true)一直调用select. 在select之前,每次都检查链表,如果有新socket加入,则把它也加到fd_read中去.
3 WSAAsyncSelect模型
Winsock provides a useful asynchronous I/O model that allows an application to receive Windows message–based notification of network events on a socket.This is accomplished by calling the WSAAsyncSelect function after creating a socket.
To use the WSAAsyncSelect model, your application must first create a window using the CreateWindow function and supply a window procedure (winproc ) support function for it. You can also use a dialog box with a dialog procedure instead of a window because dialog boxes are windows.
Once you have set up the window infrastructure, you can begin creating sockets and turning on window message notification by calling the WSAAsyncSelect function, which is defined as
int WSAAsyncSelect( SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent );
The s parameter represents the socket we are interested in.
The hWnd parameter is a window handle identifying the window or the dialog box that receives a message when a network event occurs.
The wMsg parameter identifies the message to be received when a network event occurs.
The last parameter, lEvent , represents a bitmask that specifies a combination of network events
WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_CONNECT │ FD_READ │ FD_WRITE │ FD_CLOSE);
当有connect,或fd_read,或fd_write,或fd_close这类的网络事件发生的时候, hwnd会收到一个WM_SOCKET事件, 而且在其中又可以根据lparam
来判断是哪一种事件.
This allows our application to get connect, send, receive, and socket-closure network event notifications on socket s . It is impossible to register multiple events one at a time on the socket. Also note that once you turn on event notification on a socket, it remains on unless the socket is closed by a call to closesocket or the application changes the registered network event types by calling WSAAsyncSelect (again, on the socket). Setting the lEvent parameter to 0 effectively stops all network event notification on the socket.
When your application calls WSAAsyncSelect on a socket, the socket mode is automatically changed from blocking to the non-blocking mode that we described previously(why? ).
How to design the windows procedure?
A window procedure is normally defined as
LRESULT CALLBACK WindowProc(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);
The hWnd parameter is a handle to the window that invoked the window procedure.
The uMsg parameter indicates which message needs to be processed. In your case, you will be looking for the message defined in the WSAAsyncSelect call.
The wParam parameter identifies the socket on which a network event has occurred. This is important if you have more than one socket assigned to this window procedure.
The lParam parameter contains two important pieces of information—the low word of lParam specifies the network event that has occurred, and the high word of lParam contains any error code.
When network event messages arrive at a window procedure, the application should first check the lParam high-word bits to determine whether a network error has occurred on the socket. There is a special macro, WSAGETSELECTERROR , that returns the value of the high-word bits error information. After the application has verified that no error occurred on the socket, the application should determine which network event type caused the Windows message to fire by reading the low-word bits of lParam . Another special macro, WSAGETSELECTEVENT , returns the value of the low-word portion of lParam .
One final detail worth noting is how applications should process FD_WRITE event notifications. FD_WRITE notifications are sent under only three conditions:
-
After a socket is first connected with connect or WSAConnect
-
After a socket is accepted with accept or WSAAccept
-
When a send, WSASend , sendto , or WSASendTo operation fails with WSAEWOULDBLOCK and buffer space becomes available
Therefore, an application should assume that sends are always possible on a socket starting from the first FD_WRITE message and lasting until a send , WSASend , sendto , or WSASendTo returns the socket error WSAEWOULDBLOCK . After such failure, another FD_WRITE message notifies the application that sends are once again possible.
The WSAAsyncSelect model offers many advantages; foremost is the capability to handle many connections simultaneously without much overhead, unlike the select model's requirement of setting up the fd_set structures. The disadvantages are having to use a window if your application requires no windows (such as a service or console application). Also, having a single window procedure to service all the events on thousands of socket handles can become a performance bottleneck (meaning this model doesn't scale very well).
4 the WSAEventSelect model
Winsock provides another useful asynchronous event notification I/O model that is similar to the WSAAsyncSelect model that allows an application to receive event-based notification of network events on one or more sockets. This model is similar to the WSAAsyncSelect model because your application receives and processes the same network events listed in Table 5-3 that the WSAAsyncSelect model uses. The major difference with this model is that network events are notified via an event object handle instead of a window procedure.
The event notification model requires your application to create an event object for each socket used by calling the WSACreateEvent function, which is defined as WSAEVENT WSACreateEvent(void);
The WSACreateEvent function simply returns a manual reset event object handle. Once you have an event object handle, you have to associate it with a socket and register the network event types of interest
int WSAEventSelect(
SOCKET s,
WSAEVENT hEventObject,
long lNetworkEvents
);
The s parameter represents the socket of interest.
The hEventObject parameter represents the event object—obtained with WSACreateEvent— to associate with the socket.
The last parameter, lNetworkEvents , represents a bitmask that specifies a combination of network event types that the application is interested in.
The event created for WSAEventSelect has two operating states and two operating modes. The operating states are known as signaled and non-signaled . The operating modes are known as manual reset and auto reset .WSACreateEvent initially creates event handles in a non-signaled operating state with a manual reset operating mode.As network events trigger an event object associated with a socket, the operating state changes from non-signaled to signaled.
Because the event object is created in a manual reset mode, your application is responsible for changing the operating state from signaled to non-signaled after processing an I/O request. This can be accomplished by calling the WSAResetEvent function, which is defined as
BOOL WSAResetEvent(WSAEVENT hEvent);
The function takes an event handle as its only parameter and returns TRUE or FALSE based on the success or failure of the call.
When an application is finished with an event object, it should call the WSACloseEvent function to free the system resources used by an event handle. The WSACloseEvent function is defined as BOOL WSACloseEvent(WSAEVENT hEvent);
This function also takes an event handle as its only parameter and returns TRUE if successful or FALSE if the call fails.
Once a socket is associated with an event object handle, the application can begin processing I/O by waiting for network events to trigger the operating state of the event object handle. The WSAWaitForMultipleEvents function is designed to wait on one or more event object handles and returns either when one or all of the specified handles are in the signaled state or when a specified timeout interval expires. WSAWaitForMultipleEvents is defined as
DWORD WSAWaitForMultipleEvents(
DWORD cEvents,
const WSAEVENT FAR * lphEvents,
BOOL fWaitAll,
DWORD dwTimeout,
BOOL fAlertable
);
The cEvents and lphEvents parameters define an array of WSAEVENT objects in which cEvents is the number of event objects in the array and lphEvents is a pointer to the array. WSAWaitForMultipleEvents can support only a maximum of WSA_MAXIMUM_WAIT_EVENTS objects, which is defined as 64. Therefore, this I/O model is capable of supporting only a maximum of 64 sockets at a time for each thread that makes the WSAWaitForMultipleEvents call.
If you need to have this model manage more than 64 sockets, you should create additional worker threads to wait on more event objects.
The fWaitAll parameter specifies how WSAWaitForMultipleEvents waits for objects in the event array.
If TRUE , the function returns when all event objects in the lphEvents array are signaled. If FALSE , the function returns when any one of the event objects is signaled. In the latter case, the return value indicates which event object caused the function to return. Typically, applications set this parameter to FALSE and service one socket event at a time.
The dwTimeout parameter specifies how long (in milliseconds) WSAWaitForMultipleEvents will wait for a network event to occur.
The final parameter, fAlertable , can be ignored when you're using the WSAEventSelect model and should be set to FALSE . It is intended for use in processing completion routines in the overlapped I/O model, which will be described later in this chapter.
Note that by servicing signaled events one at a time (by setting the fWaitAll parameter to FALSE ), it is possible to starve sockets toward the end of the event array.(这是WSAEventSelect一项缺点)
This is clearly undesirable. Once an event within the loop is signaled and handled, all events in the array should be checked to see if they are signaled as well. This can be accomplished by using WSAWaitForMultipleEvents with each individual event handle after the first signaled event and specifying a dwTimeOut of zero.
When WSAWaitForMultipleEvents receives network event notification of an event object, it returns a value indicating the event object that caused the function to return. As a result, your application can determine which network event type is available on a particular socket by referencing the signaled event in the event array and matching it with the socket associated with the event. When you reference the events in the event array, you should reference them using the return value of WSAWaitForMultipleEvents minus the predefined value WSA_WAIT_EVENT_0 .
For example:
Index = WSAWaitForMultipleEvents(...);
MyEvent = EventArray[Index - WSA_WAIT_EVENT_0];
Once you have the socket that caused the network event, you can determine which
network events are available by calling the WSAEnumNetworkEvents
function,
which is defined as
int WSAEnumNetworkEvents(
SOCKET s,
WSAEVENT hEventObject,
LPWSANETWORKEVENTS lpNetworkEvents
);
The s parameter represents the socket that caused the network event, and the hEventObject parameter is an optional parameter representing an event handle identifying an associated event object to be reset. Because our event object is in a signaled state, we can pass it in and it will be set to a non-signaled state. The hEventObject parameter is optional in case you wish to reset the event manually via the WSAResetEvent function. The final parameter, lpNetworkEvents , takes a pointer to a WSANETWORKEVENTS structure, which is used to retrieve network event types that occurred on the socket and any associated error codes. The WSANETWORKEVENTS structure is defined as
typedef struct _WSANETWORKEVENTS
{
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
注意:
More than one network event type can occur whenever an event is signaled. For example, a busy server application might receive FD_READ and FD_WRITE notification at the same time.
The iErrorCode parameter is an array of error codes associated with the events in lNetworkEvents . For each network event type, there is a special event index similar to the event type names—except for an additional “_BIT” string appended to the event name. For example, for the FD_READ event type, the index identifier for the iErrorCode array is named FD_READ_BIT . The following code fragment demonstrates this for an FD_READ event:
// 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]);
}
}
After you process the events in the WSANETWORKEVENTS structure, your application should continue waiting for more network events on all of the available sockets. The following example demonstrates how to develop a server and manage event objects when using the WSAEventSelect I/O model. The code highlights the steps needed to develop a basic server application capable of managing one or more sockets at a time.
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);
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);
// 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); } } } }
其实上例中的for循环可以不存在,只不过如果那样的话,如果第一个socket一直有event发生的话,那么数组中后边的socket总是很不到机会.
The WSAEventSelect
model offers several
advantages. It is conceptually simple and it does not require a windowed
environment. The only drawback is its limitation of waiting on only 64 events at
a time, which necessitates managing a thread pool when dealing with many
sockets. Also, because many threads are required to handle a large number of
socket connections, this model does not scale as well as the overlapped models
其实这几个模型看过以后,只要经常的回来看看这几个示例代码就可以了.
线程池技术?
The Overlapped Model:
The overlapped I/O model in Winsock offers applications better system performance than any of the I/O models explained so far.
The overlapped model's basic design allows your application to post one or more asynchronous I/O requests at a time using an overlapped data structure.
At a later point, the application can service the submitted requests after they have completed. This model is available on all Windows platforms except Windows CE. The model's overall design is based on the Windows overlapped I/O mechanisms available for performing I/O operations on devices using the ReadFile and WriteFile functions.
To use the overlapped I/O model on a socket, you must first create a socket that has the overlapped flag set.
After you successfully create a socket and bind it to a local interface, overlapped I/O operations can commence by calling the Winsock functions listed below and specifying an optional WSAOVERLAPPED structure.
-
WSASend
-
WSASendTo
-
WSARecv
-
WSARecvFrom
-
WSAIoctl
-
WSARecvMsg
-
AcceptEx
-
ConnectEx
-
TransmitFile
-
TransmitPackets
-
DisconnectEx
-
WSANSPIoctl