Windows完成端口编程
目录
一 基本概念
二 OVERLAPPED数据结构
三 完成端口的内部机制
创建完成端口
完成端口线程的工作原理
线程间数据传递
线程的安全退出
一 基本概念
设备---windows操作系统上允许通信的任何东西,比如文件、目录、串行口、并行口、邮件槽、命名管道、无名管道、套接字、控制台、逻辑磁盘、物理 磁盘等。绝大多数与设备打交道的函数都是CreateFile/ReadFile/WriteFile等。所以我们不能看到**File函数就只想到文件 设备。
与设备通信有两种方式,同步方式和异步方式。同步方式下,当调用ReadFile函数时,函数会等待系统执行完所要求的工作,然后才返回;异步方式下,ReadFile这类函数会直接返回,系统自己去完成对设备的操作,然后以某种方式通知完成操作。
重叠I/O----顾名思义,当你调用了某个函数(比如ReadFile)就立刻返回做自己的其他动作的时候,同时系统也在对I/0设备进行你要求的操 作,在这段时间内你的程序和系统的内部动作是重叠的,因此有更好的性能。所以,重叠I/O是用于异步方式下使用I/O设备的。
重叠I/O需要使用的一个非常重要的数据结构OVERLAPPED。
完成端口---是一种WINDOWS内核对象。完成端口用于异步方式的重叠I/0情况下,当然重叠I/O不一定非使用完成端口不可,还有设备内核对象、事 件对象、告警I/0等。但是完成端口内部提供了线程池的管理,可以避免反复创建线程的开销,同时可以根据CPU的个数灵活的决定线程个数,而且可以让减少 线程调度的次数从而提高性能。
二 OVERLAPPED数据结构
typedef struct _OVERLAPPED
{
ULONG_PTR Internal;//被系统内部赋值,用来表示系统状态
ULONG_PTR InternalHigh;// 被系统内部赋值,传输的字节数
union {
struct
{
DWORD Offset;//和OffsetHigh合成一个64位的整数,用来表示从文件头部的多少字节开始
DWORD OffsetHigh;//操作,如果不是对文件I/O来操作,则必须设定为0
};
PVOID Pointer;
};
HANDLE hEvent;//如果不使用,就务必设为0,否则请赋一个有效的Event句柄
} OVERLAPPED, *LPOVERLAPPED;
下面是异步方式使用ReadFile的一个例子
OVERLAPPED Overlapped;
Overlapped.Offset=345;
Overlapped.OffsetHigh=0;
Overlapped.hEvent=0;
//假定其他参数都已经被初始化
ReadFile(hFile,buffer,sizeof(buffer),&dwNumBytesRead,&Overlapped);
这样就完成了异步方式读文件的操作,然后ReadFile函数返回,由操作系统做自己的事情吧
下面介绍几个与OVERLAPPED结构相关的函数
等待重叠I/0操作完成的函数
BOOL GetOverlappedResult (
ANDLE hFile,
LPOVERLAPPED lpOverlapped,//接受返回的重叠I/0结构
LPDWORD lpcbTransfer,//成功传输了多少字节数
BOOL fWait //TRUE只有当操作完成才返回,FALSE直接返回,如果操作没有完成,通过调//用GetLastError ( )函数会返回ERROR_IO_INCOMPLETE
);
宏HasOverlappedIoCompleted可以帮助我们测试重叠I/0操作是否完成,该宏对OVERLAPPED结构的Internal成员进行了测试,查看是否等于STATUS_PENDING值。
三 完成端口的内部机制
创建完成端口
完成端口是一个内核对象,使用时他总是要和至少一个有效的设备句柄进行关联,完成端口是一个复杂的内核对象,创建它的函数是:
HANDLE CreateIoCompletionPort(
IN HANDLE FileHandle,
IN HANDLE ExistingCompletionPort,
IN ULONG_PTR CompletionKey,
IN DWORD NumberOfConcurrentThreads );
通常创建工作分两步:
第一步,创建一个新的完成端口内核对象,可以使用下面的函数:
HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads)
{
return CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads);
};
第二步,将刚创建的完成端口和一个有效的设备句柄关联起来,可以使用下面的函数:
bool AssicoateDeviceWithCompletionPort(HANDLE hCompPort,HANDLE hDevice,DWORD dwCompKey)
{
HANDLE h=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0);
return h==hCompPort;
};
说明
1) CreateIoCompletionPort函数也可以一次性的既创建完成端口对象,又关联到一个有效的设备句柄
2) CompletionKey是一个可以自己定义的参数,我们可以把一个结构的地址赋给它,然后在合适的时候取出来使用,最好要保证结构里面的内存不是分配在栈上,除非你有十分的把握内存会保留到你要使用的那一刻。
3) NumberOfConcurrentThreads通常用来指定要允许同时运行的的线程的最大个数。通常我们指定为0,这样系统会根据CPU的个数来自动确定。
创建和关联的动作完成后,系统会将完成端口关联的设备句柄、完成键作为一条纪录加入到这个完成端口的设备列表中。如果你有多个完成端口,就会有多个对应的设备列表。如果设备句柄被关闭,则表中自动删除该纪录。
完成端口线程的工作原理
完成端口可以帮助我们管理线程池,但是线程池中的线程需要我们使用_beginthreadex来创建,凭什么通知完成端口管理我们的新线程呢?答案在函数GetQueuedCompletionStatus。该函数原型:
BOOL GetQueuedCompletionStatus(
IN HANDLE CompletionPort,
OUT LPDWORD lpNumberOfBytesTransferred,
OUT PULONG_PTR lpCompletionKey,
OUT LPOVERLAPPED *lpOverlapped,
IN DWORD dwMilliseconds
);
这个函数试图从指定的完成端口的I/0完成队列中抽取纪录。只有当重叠I/O动作完成的时候,完成队列中才有纪录。凡是调用这个函数的线程将被放入到完成端口的等待线程队列中,因此完成端口就可以在自己的线程池中帮助我们维护这个线程。
完 成端口的I/0完成队列中存放了当重叠I/0完成的结果---- 一条纪录,该纪录拥有四个字段,前三项就对应GetQueuedCompletionStatus函数的2、3、4参数,最后一个字段是错误信息 dwError。我们也可以通过调用PostQueudCompletionStatus模拟完成了一个重叠I/0操作。
当I/0完成队列中出现 了纪录,完成端口将会检查等待线程队列,该队列中的线程都是通过调用GetQueuedCompletionStatus函数使自己加入队列的。等待线程 队列很简单,只是保存了这些线程的ID。完成端口会按照后进先出的原则将一个线程队列的ID放入到释放线程列表中,同时该线程将从等待 GetQueuedCompletionStatus函数返回的睡眠状态中变为可调度状态等待CPU的调度。
基本上情况就是如此,所以我们的线程要想成为完成端口管理的线程,就必须要调用
GetQueuedCompletionStatus函数。出于性能的优化,实际上完成端口还维护了一个暂停线程列表,具体细节可以参考《Windows高级编程指南》,我们现在知道的知识,已经足够了。
线程间数据传递
线程间传递数据最常用的办法是在_beginthreadex函数中将参数传递给线程函数,或者使用全局变量。但是完成端口还有自己的传递数据的方法,答案就在于CompletionKey和OVERLAPPED参数。
CompletionKey 被保存在完成端口的设备表中,是和设备句柄一一对应的,我们可以将与设备句柄相关的数据保存到CompletionKey中,或者将 CompletionKey表示为结构指针,这样就可以传递更加丰富的内容。这些内容只能在一开始关联完成端口和设备句柄的时候做,因此不能在以后动态改 变。
OVERLAPPED参数是在每次调用ReadFile这样的支持重叠I/0的函数时传递给完成端口的。我们可以看到,如果我们不是对文件设 备做操作,该结构的成员变量就对我们几乎毫无作用。我们需要附加信息,可以创建自己的结构,然后将OVERLAPPED结构变量作为我们结构变量的第一个 成员,然后传递第一个成员变量的地址给ReadFile函数。因为类型匹配,当然可以通过编译。当GetQueuedCompletionStatus函 数返回时,我们可以获取到第一个成员变量的地址,然后一个简单的强制转换,我们就可以把它当作完整的自定义结构的指针使用,这样就可以传递很多附加的数据 了。太好了!只有一点要注意,如果跨线程传递,请注意将数据分配到堆上,并且接收端应该将数据用完后释放。我们通常需要将ReadFile这样的异步函数 的所需要的缓冲区放到我们自定义的结构中,这样当GetQueuedCompletionStatus被返回时,我们的自定义结构的缓冲区变量中就存放了 I/0操作的数据。
CompletionKey和OVERLAPPED参数,都可以通过GetQueuedCompletionStatus函数获得。
线程的安全退出
很多线程为了不止一次的执行异步数据处理,需要使用如下语句
while (true)
{
....
GetQueuedCompletionStatus(...);
}
那么如何退出呢,答案就在于上面曾提到的PostQueudCompletionStatus函数,我们可以用它发送一个自定义的包含了OVERLAPPED成员变量的结构地址,里面包含一个状态变量,当状态变量为退出标志时,线程就执行清除动作然后退出。
8.2.5完成端口模型
创建完成端口对象 CreateCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort, DWORD CompletionKey, DWORD NumberOfConcurrentThreads);
CompletionKey:则指定要与某个特定套接字句柄关联在一起的“单句柄数据”
1.工作者线程与完成端口
StartWinsock();
//step 1
//create an IO completion port.
CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0,0);
//step 2;
//Determine how many processors are on the system
GetSystemInfo(&SystemInfo);
//Step3
//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, &ThreadID);
//close the thread handle
CloseHandle(ThreadHandle);
}
//step 4
//create a listening socket
Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
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)
{
//step 5
//Accept connections and assign to completion port.
Accept = WSAAccept(Listen, NULL, NULL, NULL, 0);
//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;
//step 7
//Associate the accepted socket with the completion port.
CreateIoCompletionPort((HANDLE)Accept,CompletionPort, (DWORD)PerHandleData,0);
//step 8
//start processing IO on the accepted socket. post oen or more WSASend() or WSARecv() calls on the socket using overlapped IO
WSARecv(...)''
}
2.完成端口和重叠IO
需要由我们的应用程序负责在以后的某个时间,通过一个OVERLAPPED结构,来接收调用的结果。
BOOL GetQueuedCompletionStatus(HANDLE CompletionPort, LPDWORD lpNumberOfBytesTransferred, LPDWORD lpCompletionKey, LPOVERLAPPED* lpOverlapped, DWORD dwMilliseconds);
3.单句柄数据和单IO操作数据
typedef struct
{
OVERLAPPED Overlapped;
WSABUF DataBuf;
CHAR Buffer[DATA_BUFSIZE];
bool OperationType;
} PER_IO_OPERATION_DATA;
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)
{
HANDLE CompletionPort = (HANDLE).CompletionPortID;
DWORD ByteTransferred;
LPOVERLAPPED Overlapped;
LPPER_HANDLE_DATA PerHandleData;
LPPER_IO_OPERATION_DATA PerIoData;
DWORD SendBytes, RecvBytes;
DWORD Flags;
while (TRUE)
{
//Wait for IO to complete on any socket associated with the completion port
GetQueuedCompletionStatus(CompletionPort,&ByteTransferred, (LPDWORD)&PerHandleData,(LPOVERLAPPED*)&PerIoData, INFINITE);
//socket and clean up the per-handle data and per-io opertion data associated with the socket
if (BytesTransferred ==0&&(PerIoData->OperationType == RECV_POSTED||PerHandleData->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 withe IO operation.
closesocket(PerHandleData->Socket);
GlobalFree(PerHandleData);
GlobalFree(PerIoData);
continue;
}
//service the completed IO request, You can determine which IO request has just completed by looking at the OperationType field contained in the
//the per-io operation data.
if (PerIoData->OperationType==RECV_POSTED)
{
//Do sth with the received data in perIoData->Buffer
}
//post another WSASend or WSARecv operation .
//As an example , we will post another WSARecv() IO operation
Flags = 0;
//Set up the per-IO 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);
}
}
8.2.3WSAEventSelect
应用程序在一个或者多个套接字上,接收以事件为基础的网络事件通知。网络事件投递至一个事件对象句柄。
事件通知
WSAVENT WSACreateEvent(void);
BOOL WSAResetEvent(WSAEVENT hEvent);
BOOL WSACloseEvent(WSAEVENT hEvent);
int WSAEventSelect(SOCKET S, WSAEVENT hEventObject, long lNetworkEvents);
DWORD WSAWaitForMultipleEvents(DWORD cEvents, const WSAEVENT FAR* lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable);
Index = WSAWaitForMultipleEvents();
MyEvent = EventArray[index - WSA_WAIT_EVENT_0];
int WSAEnumNetworkEvents(SOCKET s, WSAEVET hEventObject, LPWSANETWORKEVENTS lpNetworkEvents);
SOCKET Socket[WSA_MAXIMUM_MAIT_EVENTS];
WSAEVENT Event[WSA_MAXIMUM_MAIT_EVENTS];
SOCKET Accept, Listen;
DWORD EventTotal = 0;
DWORD Index;
//Set up a TCP socket for listening on port 5150;
Listen = socket(PF_INET, SOCKET_STREAM, 0);
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5105);
bind(Listen, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr));
NewEvent = WSACreateEvent();
WSAEventSelect(Listen, NewEvent, FD_ACCEP|FD_CLOSE);
listen(Listen, 5);
Socket[EventTotal] = Listen;
Event[EventTotal] = NewEvent;
EventTotal++;
while(TRUE)
{
//Wait for network events on all sockets
Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
WSAEnumNetworkEvents(SocketArray[Index - WSAWAIT_EVENT_0],EventArray[Index_WSA_WAIT_EVENT_0], &NetworkEvents);
//check for FD_ACCEPT messages
if(NetWorkEvents.lNetworkEvents&FD_ACCEPT)
{
if(NetworkEvent.iErrorCode[FD_ACCEPT_BIT]!=0)
{
printf("FD_ACCEPT failed with error%d/n", NetWorkEvent.iErrorCode[FD_ACCEPT_BIT]);
break;
}
//Accept a new connection and it to the socket and event lists
Accept = accept(SocketArray[Index-WSA_WAIT_EVENT_0],NULL, NULL);
//We can not process more than WSA_MAXIMUM_WAIT_EVENTS sockets, so close the accepted socket
if(EventTotal > WSA_MAXIMUM_WAIT_EVENTS)
{
printf("Too many connecttions");
closesocket(Accept);
break;
}
NewEvent = WSACreateEvent();
WSAEventSelect(Accept, NewEvent, FD_READ|FD_WRITE|FD_CLOSE);
Event[EventTotal] = NewEvent;
Socket[EventTotal] = Accept;
EventTotal++;
printf("socket &d connected/n",ACCept);
}
//Process FD_READ notification
if(NewWorkEvent.iErrorCode[FD_READ_BIT]!0)
{
if(NetworkEvents.iErrorCode[FD_READ_BIT]!=0)
{
printf("FD_READ failed with error %dn", NetworkEvent.iErrorCode[FD_READ_BIT]);
break;
}
//Read data from socket
recv(Socket[Index - WSA_WAIT_EVENT_0], buffer, sizeof(buffer),0);
}
//Process FD_WRITE notification
if(NetworkEvents.lNetworkEvents&FD_WRITE)
{
if(NetworkEvent.iErrorCode[FD_WRITE_BIT]!=0)
{
printf("FD_WRITE failed with error %d/n",
NetworkEvents.iErrorCode[FD_WRITE_BIT]);
break;
}
send(Socket[Index - WSA_WAIT_EVENT_0], buffer, sizeof(buffer),0);
}
if(NetworkEvents.lNetworkEvents&FD_CLOSE)
{
if(NetworkEvent.iErrorCode[FD_CLOSE_BIT]!=0)
{
printf("FD_CLOSE failed with error &d/n",
NetworkEvents.iErrorCode[FD_CLOSE_BIT]);
break;
}
}
closesocket(Socket[index - WSA_WAIT_EVET_0]);
//Remove socket and associated event from the socket and evnet array and decrement eventtatal
CompressArrays(Event, socket,&EventTotal);
}
}
8.2.4 重叠模型
s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
1.事件通知 将win32事件对象与WSAOVERLAPPED结构关联在一起。
typedef struct WSAOVERLAPPED
{
DWORD Internal;
DWORD InternalHigh;
DWORD offset;
DWORD OffsetHigh;
WSAEVENT hEvent;
} WSAOVERLAPPED, FAR*LPWSAOVERLAPPED;
hEvent 它允许应用程序将一个事件对象句柄同一个套接字关联起来。
BOOL WSAGetOverlappedResult(SOCKET S, LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWari, LPDWORD lpdwFlags);
void main()
{
WSABUF DataBuf;
DWORD EventTotal = 0;
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAOVERLAPPED AcceptOverlapped;
SOCKET ListenSocket, AcceptSocket;
//step 1
//start winsock and set up a listening socket
//step 2
//accepte an inbound connection
AcceptSocket = accept(ListenSocket, NULL, NULL);
//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.
WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL);
//Process overlapped receives on the socket.
while (TRUE)
{
//step 5
//wait for overlapped io call to complete
Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
//Index should be 0 because we have 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(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;
WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL);
}
}
2.完成例程
在一个重叠IO请求完成时由系统调用的函数。他们的基本设计宗旨是通过调用者的线程,为一个已完成的I请求提供服务。除此之外,应用程序可通过完成例程,继续进行重叠IO处理。
void CALLBACK CompletionROUTINE(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags);
WSABUF DataBuf;
SOCKET AcceptSocket;
void main()
{
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
//To get the overlapped IO processing started, first submit an overlapped WSARecv() request.
Flags = 0;
ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
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, WorkerRoutine)==SOCKET_ERROR)
{
if (WSAGetLastError()!=WSA_IO_PENDING)
{
printf("WSARecv() failed with error%d/n",WSAGetLastError());
return;
}
}
//since the WSAWaitForMutipleEvents()API requires waiting on one or more event objects, we will have to create a dummy event object, Aa
//an alterative , we can use SleepEx() instead.
EventArray[0]=WSACreateEvent();
while (TRUE)
{
//step 5
Index = WSAWaitForMultipleEvents(1,EventArray, FALSE, WSA_INFINITE, FALSE);
//step 6
if (Index == WAIT_IO_COMPLETION)
{
//An overlapped request completion routine just completed ,continue servicing more completion rountines.
}
else
{
//A bad error occurred --stop processing! if we were also processing an event object, this could be 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 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() requst.
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("failed with error%d/n",WSAGetLastError());
return;
}
}
}
8.2.2 WSAAsyncSelect
接收以windows消息为基础的网络事件通知。
int WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent);
FD_READ, FD_WRITE, FD_ACCEPT, FD_CONNECT和FD_CLOSE.
从锁定变成非锁定状态。
#define WM_SOCKET WM_USER+1
#define
int WINAPI WinMain(IN HINSTANCE hInstance, IN HINSTANCE hPrevInstance, IN LPSTR lpCmdLine, IN int nShowCmd )
{
SOCKET Listen;
HWND Window;
sockaddr InternetAddr;
Window = CreateWindow();
WSAStartup(...);
Listen = socket(...);
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5150);
bind(Listen, (PSOCKADDR)&InternetAddr,sizeof(InternetAddr));
WSAAsyncSelect(Listen, Window, WM_SOCKET, FD_ACCEPT|FD_CLOSE);
listen(Listen, 5);
//Translate and dispatch window messages until the application terminates
}
BOOL CALLBACK ServerWinProc(HWND hDlg, WORD wMsg,WORD wParam, DWORD lParam)
{
SOCKET Accept;
switch(wMsg)
{
case WM_PAINT:
break;
case WM_SOCKET:
//Determine whether an error occurred on the socket by using the WSAGETSLECTERRROR() macro
if (WSAGETSELECTERROR(lParam))
{
closesocket(wParam);
break;
}
//Determine what event occurred on the socket
switch(WSAGETSELECTEVENT(lParam))
{
case FD_ACCEPT:
Accept = accept(wParam, NULL, NULL);
//Prepare accepted socket for read, write and close notification.
WSAAsyncSelect(Accept, hwnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE);
break;
case FD_READ:
//Receive data from the socket in wparam;
break;
case FD_WRITE:
//The socket in param is ready for sending data;
break;
case FD_CLOSE:
//The connection is now closed
closesocket(wParam);
break;
}
break;
}
return true;
}
产生FD_WRITE通知的三种条件
1.使用connect或者WASConnect,一个套接字首次建立了连接。
2.使用accept or WSAAccept,套接字被接受以后。
3.若send WSASend,sendto WSASendto操作失败,返回了WSAEWOULDBLOCK错误,而且缓冲区的空间变得可用。
8.2.3WSAEventSelect
应用程序在一个或者多个套接字上,接收以事件为基础的网络事件通知。网络事件投递至一个事件对象句柄。
事件通知
WSAVENT WSACreateEvent(void);
BOOL WSAResetEvent(WSAEVENT hEvent);
BOOL WSACloseEvent(WSAEVENT hEvent);
int WSAEventSelect(SOCKET S, WSAEVENT hEventObject, long lNetworkEvents);
DWORD WSAWaitForMultipleEvents(DWORD cEvents, const WSAEVENT FAR* lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable);
Index = WSAWaitForMultipleEvents();
MyEvent = EventArray[index - WSA_WAIT_EVENT_0];
int WSAEnumNetworkEvents(SOCKET s, WSAEVET hEventObject, LPWSANETWORKEVENTS lpNetworkEvents);
SOCKET Socket[WSA_MAXIMUM_MAIT_EVENTS];
WSAEVENT Event[WSA_MAXIMUM_MAIT_EVENTS];
SOCKET Accept, Listen;
DWORD EventTotal = 0;
DWORD Index;
//Set up a TCP socket for listening on port 5150;
Listen = socket(PF_INET, SOCKET_STREAM, 0);
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5105);
bind(Listen, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr));
NewEvent = WSACreateEvent();
WSAEventSelect(Listen, NewEvent, FD_ACCEP|FD_CLOSE);
listen(Listen, 5);
Socket[EventTotal] = Listen;
Event[EventTotal] = NewEvent;
EventTotal++;
while(TRUE)
{
//Wait for network events on all sockets
Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
WSAEnumNetworkEvents(SocketArray[Index - WSAWAIT_EVENT_0],EventArray[Index_WSA_WAIT_EVENT_0], &NetworkEvents);
//check for FD_ACCEPT messages
if(NetWorkEvents.lNetworkEvents&FD_ACCEPT)
{
if(NetworkEvent.iErrorCode[FD_ACCEPT_BIT]!=0)
{
printf("FD_ACCEPT failed with error%d/n", NetWorkEvent.iErrorCode[FD_ACCEPT_BIT]);
break;
}
//Accept a new connection and it to the socket and event lists
Accept = accept(SocketArray[Index-WSA_WAIT_EVENT_0],NULL, NULL);
//We can not process more than WSA_MAXIMUM_WAIT_EVENTS sockets, so close the accepted socket
if(EventTotal > WSA_MAXIMUM_WAIT_EVENTS)
{
printf("Too many connecttions");
closesocket(Accept);
break;
}
NewEvent = WSACreateEvent();
WSAEventSelect(Accept, NewEvent, FD_READ|FD_WRITE|FD_CLOSE);
Event[EventTotal] = NewEvent;
Socket[EventTotal] = Accept;
EventTotal++;
printf("socket &d connected/n",ACCept);
}
//Process FD_READ notification
if(NewWorkEvent.iErrorCode[FD_READ_BIT]!0)
{
if(NetworkEvents.iErrorCode[FD_READ_BIT]!=0)
{
printf("FD_READ failed with error %dn", NetworkEvent.iErrorCode[FD_READ_BIT]);
break;
}
//Read data from socket
recv(Socket[Index - WSA_WAIT_EVENT_0], buffer, sizeof(buffer),0);
}
//Process FD_WRITE notification
if(NetworkEvents.lNetworkEvents&FD_WRITE)
{
if(NetworkEvent.iErrorCode[FD_WRITE_BIT]!=0)
{
printf("FD_WRITE failed with error %d/n",
NetworkEvents.iErrorCode[FD_WRITE_BIT]);
break;
}
send(Socket[Index - WSA_WAIT_EVENT_0], buffer, sizeof(buffer),0);
}
if(NetworkEvents.lNetworkEvents&FD_CLOSE)
{
if(NetworkEvent.iErrorCode[FD_CLOSE_BIT]!=0)
{
printf("FD_CLOSE failed with error &d/n",
NetworkEvents.iErrorCode[FD_CLOSE_BIT]);
break;
}
}
closesocket(Socket[index - WSA_WAIT_EVET_0]);
//Remove socket and associated event from the socket and evnet array and decrement eventtatal
CompressArrays(Event, socket,&EventTotal);
}
}
第8章 WinsockIO方法
套接字模式:锁定和非锁定
8.1套接字模式
生产者-消费者
8.1.2非锁定模式
SOCKET s;
unsigned long ul = 1;
int nRet;
s = socket(AF_INET, SOCK_STREAM, 0);
nRet = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
if(nRet == SOCKET_ERROR)
{
....
}
8.2.1 select模型
利用这个函数,我们判断套接字上是否存在数据,或者能否向一个套接字写入数据。
int select(int nfds,
fd_set FAR* readfds,
fd_set FAR* writefds,
fd_set FAR* exceptfds,
const struct timeval FAR* timeout);
FD_CLR(s,*set);从set中删除套接字s。
FD_ISSET(s,*set);检查s是否在set中。
FD_SET(s,*set);将s加入集合set。
FD_ZERO(*set);将set初始化。
SOCKET s;
fd_set fdread;
int ret;
//create a socket and accept a connection
//Manage I/O on the socket
while(1)
{
FD_ZERO(&fdread);
FD_SET(s,&fdread);
if((ret = select(0, &fdread, NULL, NULL, NULL)== SOCKET_ERROR)
{ //ERROR }
if(ret >0)
{
if(FD_ISSER(s, &fdread))
{
//a read event has occurred on socket s.
}
}
}
第7章 Winsock基础
7.1Winsock的初始化
int WSAStartup(WORD wVersionRequested, LPWSASATA lpWSData);
7.2错误检查和控制
int WSAGetLastError(void);
7.3面向连接的协议
7.3.1服务器API函数
1.bind 将指定的套接字同一个已经知道的地址绑定在一起。
2.listen(SOCKET s, int backlog);
3.accept和WSAAccept(SOCKET s, struct socket FAR*addr, int FAR* addrlen);
addr 返回客户机的ip地址信息
accept返回一个新的套接字,它对应于已经接受的那个客户机连接,对于该客户机后续的所有操作,都使用这个新套接字。
原来的套接字仍然用于接受客户机连接而处于监听模式。
int CALLBACK ConditionFunc(
LPWSABUF lpCallerId,
LPWSABUF lpCallerData,
LPQQS lpSOOS,
LPQOS lpGOOS,
LPWSABUF lpCalleeId,
LPWSABUF lpCalleeData,
GROUP FAR*g,
DWORD dwCallbackData);
lpCallerId: 指定建立连接的协议 包含建立连接的那个客户机的IP地址。
lpCallerData:包含了随连接一道,由客户机发出的任何连接数据。
lpSQOS,lpGQOS:对客户机请求的任何一个服务质量参数进行指定。
lpCalleeId:结构中包含与客户机需要与之连接的本地地址。
服务器将传递给条件的参数处理完之后,必须指出CF_ACCEPT, CF_REJECT , CF_DEFER.
3.1IP
网际协议:lan wan 是一个无连接的协议。
6.1.1TCP
Transmission Control Protocol,
6.1.2UDP
user Datagram Protocal
6.1.3定址
struct socketaddr_in
{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
端口分为:已知端口 已注册端口和动态端口
0~1023固定服务
1024~49151已注册端口供普通用户进程使用
49152~65535动态端口
unsiged long inet_addr(const char FAR* cp);//把点式的ip地址转换成一个32位的无符号的长整数。
1.特殊地址
INADDR_ANy 允许服务器应用监听主机计算机上面每个网络接口上客户机活动。一般情况下,在该地址绑定套接字和本地接口时,网络应用才利用这个地址来监听连接。
INADDR_BROADCAST用于在一个IP网络中发送广播UDP数据报。
2.字节排序
u_long htonl u_long ntohl
int WSAHtonl int WSANtohl
u_short htons(); u_short ntohs
int WSAHtons(); int WSANtohs
SOCKADDR_IN InternetAddr;
INT nPortId = 5150;
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = inet_addr("136.149.3.29");
InternetAddr.sin_port = htons(nPortId);
6.1.4创建套接字
s = socket(AF_INET, SOCK_STREAM,0);
s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WAS_FLAG_OVERLAPPED);
6.1.5名字解析
gethostbyname
WSAAsynGetHostByName
gethostbyaddr
WSAAsynGetHostByName
6.2红外线套接字
IrSock
6.3IPX/SPX
"互联网包交换"IPX
第五章 网络原理和协议
5.1协议的特征
5.1.1 面向消息
对每个离散写命令来说,如果传送协议把他们当做一条 独立的消息在网上传送,我们就说该协议是面向协议的。在接收端接收数据时,返回的数据是发送端写入的一条离散消息。这成为:保护消息边界。
无 保护消息边界的协议成为“基于流的协议”,这个术语常用来指代附加特性,流服务的定义是连续的数据传输;不管消息边界是否存在,接收端都会尽量地读取有效 数据。对发送端来说 ,意味着允许将原始消息分解成小消息或把几条消息积累在一起,形成一个较大的数据包。对接收端来说,则是数据一到达网络堆栈,网络堆栈就开始读取它,并将 它缓存下来等候进程处理。是否将各个对立的数据包累积在一起受许多因素的影响,比如最大传输单元或Nagle算法。
伪流(pseudo-stream):使用的协议是基于消息的,发送的数据在各自独立的数据包内,但在接收端可以把这些消息缓存在一起,这样接收端便可以读取任意大小的数据块。
5.1.2面向连接和无连接
5.1.3可靠性与次序性 可靠性和次序性两者不能兼得。
51.4从容关闭 面向连接 TCP协议连接双方都必须执行一次关闭以便完全中断连接。发送方FIN->接收方ACK->发送方
5.1.5广播数据 即数据从一个工作站发出,局域网内的其他所有工作站都能收到它。每台机器都必须对该消息进行处理。网络堆栈。
5.1.6多播数据 是指一个进程发送数据的能力,这些数据即将由一个或者多个接收端进行接收。多播要求对收发数据感兴趣的所有主机加入一个特定的组,视频会议。
5.1.7服务质量(Qos)实时视频流式传输
5.1.8部分消息 大消息
5.1.9路由选择的考虑
5.3winsock2协议信息
WSAEnumProtocals()
WSAStartup()
int WSACleanup()
SOCKET socket(int af, int type, int protocol);
原始套接字 允许你把其他协议封装在UDP数据包中,比如“互联网控制消息协议”(ICMP)。ICMP的目的是投递互联网主机间的控制、错误和信息类型消息。
Winsock API和OSI模型
每个传输协议都会提供一种传输数据的方法,但他们本身又是另一个网络协议的成员,而网络协议位于网络层,比如UDP和TCP就是传输协议但两者又都属于因特网协议IP。
winsockAPI安装在“会话层”和“传输层”之间。提供了一种为制定传输层协议打开、计算和关闭会话的能力。
第四章 命名管道 -
第四章 命名管道
是一种简单的进程之间通信的机制。
4.1实施细节
命名管道是围绕Windows文件系统设计的一种技术,采用命名管道文件系统(Named Pipe File System NPFS)接口。
它依赖MSNP重定向器再网上进行命名管道数据的发送和接收。
4.1.1命名规范
//server/Pipe/[path]name
4.1.2字节模式和消息模式
字节模式:在任何一个特定的时间段内,客户服务器不知道有多少字节从管道读入和写入。
消息模式:每次在管道发送一条消息后,必须作为一条完整的消息读入。
4.1.3应用程序的编译
winbase.h+kernel32.lib
4.1.4错误代码
winerror.h
4.2客户机服务器基础
4.2.1服务器细节
1)CreateNamedPipe
2)ConnectNamedPipe监听客户机的连接
3)ReadFile WriteFile
4)DisconnectNamedPipe()
5)CloseHandle()
void main()
{
HANDLE ThreadHandle;
int i;
DOWRD ThreadId;
for(i=0; i<5;i++)
{
if((ThreadHandle = CreateThread(NULL, 0, PipeInstanceProc,
NULL, 0, &ThreadId)) == NULL)
{
return;
}
CloseHandle(ThreadHandle)
_getch();
}
}
DWORD WINAPI PipeInstanceProc(LPVOID lpParameter)
{
HANDLE PipeHandle;
DWORD BytesRead;
DWORD BytesWrite;
CHAR Buffer[256];
if((PipeHandle = CreateNamedPiped("////.//PIPE//jim ",
PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE|PIPE_READMODE_BYTE,
NUM_PIPES,0,0,1000, NULL))==INVALID_HANDLE_VALUE)
{return 0;}
while(1)
{
if(ConnectNamedPipe(PipeHandle, NULL)==0)
{ break;}
while(ReadFile(PipeHandle, Buffer, sizeof(Buffer),&BytesRead,NULL)>0)
{
}
if(DisconnectNamedPipe(PipeHandle)==0)
{return;}
CloseHandle(PipeHandle);
}
}
2.重叠IO
允许Win32API函数在发出IO之后以一部方式工作。
4.2.3客户机细节
1)WaitNamedPipe
2)CreateFile
3)WriteFile&ReadFile
4)CloseHanlde
4.3OtherAPI
CallNamedPipe
TransactNamedPipe
SetNamedPipeHandleState
GetNamedPipeHandleState
第三章 邮槽 -
,或跨越整个网络的不同计算机进程之间,协助进行消息的传输,客户服务器模式单向消息传递。
3.1邮槽的细节
邮槽必须依赖windows重定向器,通过一个邮槽文件系统(Mailslot File System,MSFS),来创建及标识邮槽。
消息通常是无连接的传输方式发送,但亦可强迫windows重定定向器在windows NT和windows 2000上使用面向连接的
的方式传输。
3.1.1邮槽的名字
//server/Mailslot/[path]name
3.1.2 消息的长度
3.1.3 应用程序的编译
windows.h/winbase.h & Kernel32.lib
3.2基本客户机/服务器
3.2.1邮槽服务器
1) CreateMailslot()
2) ReadFile()//在默认情况下,处于暂停状态,直到有数据可以读入为止。
3) CloseFile()
3.2.2客户机
1) CreateFile()针对邮槽打开指向它的的一个引用句柄。
2) WriteFile()
3) CloseFile()
第二章 重定向器 -
windows使应用程序能通过操作系统内建的文件系统服务在网络上通信。“网络操作系统”。若应用程序希望访问本地系统中的文件,需要依赖操作系统来满足
I/O请求。我们称之为本地I/O。例如,在一个应用程序打开或关闭文件时, 需要由操作系统来决定如何访问包含了制定文件内容的一个设备。找到设备后,
I/O请求会被转发给一个本地设备驱动程序。通过网络来访问一个设备也是同样。然而,IO请求必须通过网络会被转发给对应的远程设备。我们称之为
“IO重定向(IO redirection)”.
如何将普通的IO请求重定向到远程设备。
通用命名规范 Universal Naming Convention UNC
多UNC提供者(Multiple UNC Provider)MUP
2.1 通用命名规范
它最大的特点是不必制定或引用一个已映射到远程文件系统的本地驱动器字母。“与驱动器字母无关”。它的格式:
//[服务器]/[共享名]/[路径]
2.2MUP
网络提供者Network Provider,其实就是一种服务,可通过网络硬件访问位于一台远程计算机上的资源(如文件和打印机)。网络
提供者如“Microsoft网络用户”。还有例如Novell公司的Novell Client v3.10 for Windows 95/98.
MUP的基本任务是决定具体由哪个网络提供者来满足一个UNC请求。为作出这个决定,MUP需将请求中提到的UNC名字发给已经安装好的每一个提供者
(以并行方式)。若某个网络提供这表明自己能够提供UNC名字牵涉到的那一种服务,MUP便会将请求中剩余的部分发给他。如果有多个提供者都表示能够服务一个UNC
请求,MUP便会根据优先级挑选最恰当的一个提供者。
2.3网络提供者
网络提供者只是一种服务,通过网络硬件来访问位于远程计算机上的共享资源,比如文件和打印机。这正是网络操作系统的一种核心功能。网络
提供者具备最主要的功能之一便是将本地磁盘标识符如(E)重定向至远程机器上的一个磁盘目录。
2.4重定向器简介
重定向器由网络提供者展示给用于接收和处理远程IO服务请求的操作系统。要做到这一点他需要格式化服务请求消息,再将其发给远程计算机的
重定向器服务器服务。远程机器的重定向器服务器收到这个请求之后,会发出本地IO请求的方式,来满足这一请求。
MSNP提供了一个特殊的重定向器,可直接与网络传输层和NetBIOS打交道,以便在客户机与服务器之间建立通信。
2.5服务器消息块
SMB:包含三个基本组件:命令代码、命令特有的和用户数据。
SMB协议采用非常简单的“客户机请求/服务器相应”传输模型。MSNP重定向器创建一个SMB结合时,需要再命令代码字段中指定一个
特定的请求。若命令要求的是发送数据,比如写指令,数据便会随结构一道传送出去。随后,SMB结构会通过一种想TCP/IP这样的传输协议传给一个远程工作站的服务器服务。
远程工作站的服务器服务会对收到的客户机请求进行处理,然后将一个SMB相应数据结构传回客户机。
WINS:windows互联网命名服务器。
NBTstat-n 查看本机已经注册的NetBIOS名字列表。
windows最有特色的一个网络提供者称为“microsoft网络用户”,以前叫做“Microsoft网络提供者(MSNP)”
2.6 安全问题
本地的安全。访问权限:读、写、执行。windows NT和windows2000是通过安全描述符和访问令牌来实现安全的。
第一部分传统网络API
NetBIOS:和winsock类似,也是一种与协议无关的网络API。他提供了异步调用,同时兼容OS/2、DOS等操作系统。
重定向器:提供了与传输无关的文件输入输出方式。
邮槽:是一种简单的接口,可在windows机器之间实现广播和单向数据通信。
命名管道:可建立一种双向信道。提供了对windows安全通信的支持。
第一章 NetBIOS(Newwork Basic Input/Output System)
OSI(开放系统互连)网络模型
应用层 为用户提供相应的界面,以便使用提供的联网功能
表示层 完成数据的格式化
会话层 控制两个主机间的通信链路(开放、操作和关闭)
传输层 提供数据传输服务(可靠与不可靠)
网络层 在两个主机之间提供一套定址/寻址机制,同时负责数据包的路由选择
数据链路层 控制连个主机间的物理通信链路:同时还要负责对数据整形,以便在物理媒体上传输
物理层 物理媒体负责以一系列电子信号的形式传出数据
NetBIOS主要在会话和传输层发挥作用。
1.1.1 LANA(LAN adapter)编号 每张物理网卡被分配的独一无二的值,通常在0-9之间。每个LANA编号对应于网卡和传输协议的唯一组合。
1.1.2 NetBIOS名字 对一个进程来说,他会注册自己希望与其通信的每个LANA编号。一个NetBIOS名字长度为16个字符。 在Win32环境中,针对每个可用的LANA编号,每个进程都会
为其维持一张BetBIOS名字表。
1.1.3 NetBIOS特性:同时提供面向链接和无链接服务。
1.2 NetBIOS编程基础
UCHAR Netbios(PNCB pNCB);//NCB 网络控制块
Nb30.h , Netapi32.lib
1.3 常规BetBIOS例程
winsock -
CDragListBox
In addition to proving the functionality of a windows list box, the CDragListBox class allows
the user to move list box items.
修改socket属性
int TimeOut=6000; //设置发送超时6秒
if(::setsockopt(sock,SOL_SOCKET,SO_SNDTIMEO,(char *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR)
{
::closesocket (sock);
continue;
}
TimeOut=6000;//设置接收超时6秒
if(::setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,(char *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR)
{
::closesocket (sock);
continue;
}
//设置非阻塞方式连接
unsigned long ul = 1;
ret = ioctlsocket(sock, FIONBIO, (unsigned long*)&ul);
if(ret==SOCKET_ERROR)
{
::closesocket (sock);
continue;
}
重叠I/O -
重叠模型就是让应用程序使用重叠数据结构(WSAOVERLAPPED),一次投递一个或者多个winsock i/0请求。针对这些请求,在他们完成之后,应用程序会收到通知,于是就可以用自己的代码来处理这些数据了。
有两个方法来管理重叠i/o请求的完成情况(就是说接到重叠操作完成的通知): 时间通知 和 完成例程。
基 于事件通知:就是把windows事件和WSAOVERLAPPED结构关联在一起。使用WSASend, WSASendto,WSARecvFrom。可以把重叠结构与这些函数绑定在一起,提交请求,其他的事情就交给重叠结构去操心了。这样我们就可以坐享其 成了,等到重叠结构完成之后,自然会有与之对应的事件来通知我们。我们根据重叠操作的结果取得我们需要的数据。
1。WSAOVERLAPPED
WSAEVENT event;
WSAOVERLAPPED AcceptOverlapped;
event = WSACreateEvent();
ZeroMemory(&Acceptoverlapped, sizeof(WSAOVERLAPPED));
AccepOverlapped.hEvent = event;
2. WSARecv系列函数
SOCKET S;
WSABUF DataBuf;
char buffer[5095];
ZeroMemorry(buffer,5095);
DataBuf.len = 5095;
DWORD dwBufferCount = 1, dwRecvBytes = 0, Flags = 0;
WSAOVERLAPPED AcceptOVerlapped;
WSAEVENT event;
event = WSACreateEvent();
ZeroMemory(&Acceptoverlapped, sizeof(WSAOVERLAPPED));
AcceptOverlapped.hEvent = event;
WSARecv(s, &DataBuf, dwBufferCount,&dwRecvBytes, &Flags, &AcceptOverlapped,NULL);
3.WSAWaitForMultipleEvents
等待事件的触发。
4.WSAGetOverlappedResult
查询重叠操作的结果。
[1] 定义变量
#define DATA_BUFSIZE 4096
SOCKET ListenSocket;//监听套节字
Socket AcceptSocket;//与客户端通信的套节字
WSAOVERLAPPED AcceptOverlapped;//重叠结构
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];//用来通知重叠操作完成的事件句柄数组
WSABUF DataBuf[WSA_MAXIMUM_WAIT_EVENTS];
DWORD dwEventTotal = 0;//事件总数
DWORD dwRecvBytes = 0;//接收的字符长度
Flags= 0 ;
[2] 创建套节字并在指定的端口上监听连接请求。
WSADATA wsaData;
WSAStartup();
ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCKADDR_IN ServerAddr;
serverAddr.sin.family = AF_INET;
serverAddr.sin.addr.S_un.S_addr = htonl(INNADDR_ANY);
serverAddr.sin_port = htons(11111);
bind(ListenSocket, &serveraddr, sizeof());
listen(Listensocket,5);
[3] 接收一个入站的连接请求。
SOCKADDR_IN clientAddr;
int addr_length = sizeof(clientAddr);
AcceptSocket= accept(ListenSocket, &clientsocket, &addr_length);
LPCTSTR lpIP = inet_ntoa(clientAddr.sin_addr);//client ip
UINT nPort = clientAddr.sin_port;//client port
[4]建立并初始化重叠结构
EventArray[dwEventTotal] = WSACreateEvent;
ZeroMemory(&Acceptoverlapped, sizeof(WSAOVERLAPPED));
AcceptOverlapped.hEvent = EventArray[dwEventTotal];//关联事件
char buffer[DATA_BUFSIZE];
ZeroMemory(buffer, DATA_BUFSIZE);
DataBuffer.len = DATA_BUFFER;//初始化一个WSABUF结构
DataBuffer.buf = buffer;
dwEventTotal++;
[5] 以WSAOVERLAPPED为参数,在套节字上投递WSARecv请求。
http://blog.csdn.net/tjb_1216/article/details/4628957