2007年4月3日星期二

I/O完成端口模型

[网络]I/O完成端口模型
参考了很多资料,组织这些教程,也是个复习和再学习的过程,目的是共享还有以备以后可以作为复习的纲要。这几天看了很多资料,发现这些资料的行文有2种表达发式,一种是最常见的:从技术的源头说起,直到未来发展的趋势。还有一种比较开门见山,可能功利了些:首先这种技术是为了解决什么问题,用在什么地方,有什么好处,怎么用,呵呵:)整个一个倒叙,要是再结合项目的实践就更好了,个人比较喜欢这种表达发式,所以这篇文章的结构也是这么组织的。




问题
现在网络游戏很流行,上万个玩家同时在线的情况很常见,网游服务器如何处理这么巨量的数据?!要是读过了"Winsock I/O方法"这篇文章,可以了解到套接字I/O模型中的:select,WSAAsyncSelect,WSAEventSelect,Overlapped I/O模型一次最多都只能支持6 4个套接字!这些模型显然不能胜任。而Winsock I/O模型中的"Completion port",可以同时管理数百乃至上千个套接字,辅以集群技术和提升硬件配置等措施,应付网游肯定可以了。

对IOCP的评价
I/O完成端口可能是Win32提供的最复杂的内核对象。
[Advanced Windows 3rd] Jeffrey Richter
这是[IOCP]实现高容量网络服务器的最佳方法。
[Windows Sockets2.0:Write Scalable Winsock Apps Using Completion Ports]
Microsoft Corporation
完成端口模型提供了最好的伸缩性。这个模型非常适用来处理数百乃至上千个套接字。
[Windows网络编程2nd] Anthony Jones & Jim Ohlund
I/O completion ports特别显得重要,因为它们是唯一适用于高负载服务器[必须同时维护许多连接线路]的一个技术。Completion ports利用一些线程,帮助平衡由I/O请求所引起的负载。这样的架构特别适合用在**P系统中产生的"scalable"服务器。
[Win32多线程程序设计] Jim Beveridge & Robert Wiener
IOCP的限制
该模型只适用于Windows NT和Windows 2000操作系统

什么是IOCP?

IOCP全称I/O Completion Port,中文译为I/O完成端口。所谓"完成端口",实际是Win32、Windows NT以及Windows 2000采用的一种I/O构造机制,除套接字句柄之外,实际上还可接受其他东西。IOCP是一个异步I/O的API,它可以高效地将I/O事件通知给应用程序。从本质上说,完成端口模型要求我们创建一个Win32完成端口对象,通过指定数量的线程,对重叠I/O请求进行管理,以便为已经完成的重叠I/O请求提供服务。IOCP只不过是用来进行读写操作,和文件I/O有些类似。

使用IOCP

Microsoft为IOCP提供了相应的API函数,主要的就两个: CreateIoCompletionPort( ... ), GetQueuedCompletionStatus( ... )。

1.首先要创建一个I/O完成端口对象,用它面向任意数量的套接字句柄,管理多个I/O请求。
HANDLE CreateIoCompletionPort(
HANDLE FileHandle,//既然要处理网络事件,那也就是将客户的socket作为HANDLE传进去
HANDLE ExistingCompletionPort,//是一个现有的完成端口
DWORD CompletionKey,
DWORD NumberOfConcurrentThreads//并发线程的数量
);
*NumberOfConcurrentThreads告诉系统一个完成端口上同时允许运行的线程最大数。在默认情况下,所开线程数和CPU数量相同,经验公式: 线程数 = CPU数 * 2 + 2。若将该参数设为0,表明系统内安装了多少个处理器,便允许同时运行多少个线程!
此函数有2种用法,不同的作用:
1.创建一个完成端口对象
hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
2.将一个句柄同完成端口关联到一起
CreateIoCompletionPort((HANDLE)hAccept,hCompletionPort,(DWORD)PerHandleData,0);

2. 接收有关I / O操作完成情况的通知
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort,//线程要监视哪一个完成端口
LPDWORD lpNumberOfBytes,//接收实际传输的字节数
PULONG_PTR lpCompletionKey,//为原先传递进入CreateCompletionPort函数的套接字返回"单句柄数据"
LPOVERLAPPED *lpOverlapped, //接收完成的I/O操作的重叠结果
DWORD dwMilliseconds //指定等待一个完成数据包在完成端口上出现的时间。
);
一个工作者线程从G e tQueuedCompletionStatus这个API调用接收到I/O完成通知后,在lpCompletionKey和lpOverlapped参数中,会包含一些必要的套接字信息。利用这些信息,可通过完成端口,继续在一个套接字上的I/O处理。
lpCompletionKey参数包含了"单句柄数据",因为在一个套接字首次与完成端口关联到一起的时候,那些数据便与一个特定的套接字句柄对应起来了。通常情况下,应用程序会将与I / O请求有关的套接字句柄保存在这里。
lpOverlapped参数则包含了一个OVERLAPPED结构,在它后面跟随"单I / O操作数据"。单I / O操作数据可以是追加到一个OVERLAPPED结构末尾的、任意数量的字节。要想做到这一点,一个简单的方法是定义一个结构,然后将OVERLAPPED结构作为新结构的第一个元素使用。

同时运行了一个或多个线程,在几个不同的套接字上执行I/O操作的时候。要避免的一个重要问题是在进行重叠I/O操作的同时,强行释放一个OVERLAPPED结构。要想避免出现这种情况,最好的办法是针对每个套接字句柄,调用closesocket函数,任何尚未进行的重叠I/O操作都会完成。一旦所有套接字句柄都已关闭,便需在完成端口上, 终止所有工作者线程的运行。要想做到这一点,需要使用PostQueuedCompletionStatus函数,向每个工作者线程都发送一个特殊的完成数据包。该函数会指示每个线程都"立即结束并退出"。

大致的流程:

1.初始化Winsock
2.创建一个完成端口
3.根据服务器线程数创建一定量的线程数
4.准备好一个socket进行bind然后listen
5.进入循环accept等待客户请求
6.创建一个数据结构容纳socket和其他相关信息
7.将连进来的socket同完成端口相关联
8.投递一个准备接受的请求
以后就不断的重复5至8的过程

示例
1.
HANDLE InitializeThreads()
{
HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (hCompletionPort == NULL) return NULL;
for (i = 0; i < g_dwNumThreads; i++)
{
hThread = CreateThread(NULL, 0, WorkerThreadProc, hCompletionPort, CREATE_SUSPENDED, &dwThreadId);
if (hThread == NULL){
CloseHandle(hCompletionPort);
return NULL;
}
SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL);
ResumeThread(hThread);
CloseHandle(hThread);
}
return hCompletionPort;
};

2.
g_hCompletionPort = InitializeThreads();
AcceptClients();
3.
BOOL AcceptClients()
{
...
g_nNumSocket = EnumNetworkAdapter();
if (g_nNumSocket == 0) return FALSE;

for (int nIndex = 0; nIndex < g_nNumSocket ; nIndex++)
{
...
g_interface[nIndex].hSocket = socket(AF_INET, SOCK_STREAM, 0);
if (g_interface[nIndex].hSocket == INVALID_SOCKET) return FALSE;
nError = bind(g_interface[nIndex].hSocket, (LPSOCKADDR)&sin, sizeof(sin));
nError = listen(g_interface[nIndex].hSocket, 5);
...
}
while ( TRUE )
{
for (i = 0; i < g_nNumSocket; i++)
{
FD_SET(g_interface[i].hSocket, &readfds);
}
nRet = select(0, &readfds, NULL, NULL, NULL);
if ((nRet == 0) || (nRet == SOCKET_ERROR)) continue;

for (i = 0; i < g_nNumSocket; i++)
{
if (!FD_ISSET(g_interface[i].hSocket, &readfds))
continue;
hSocket = accept(g_interface[i].hSocket, NULL, NULL);
...
g_hCompletionPort = CreateIoCompletionPort(
(HANDLE)hSocket,
g_hCompletionPort,
lpClientContext->dwVerifyClientContext,
0);
if (g_hCompletionPort == NULL){
CloseClient(lpClientContext);
continue;
}
...
nError = setsockopt(hSocket, SOL_SOCKET, SO_SNDBUF, (char *)&nZero, sizeof(nZero));
if (nError == SOCKET_ERROR)
{
PostQueuedCompletionStatus(g_hCompletionPort, 0, (DWORD)lpClientContext->dwVerifyClientContext, NULL);
continue;
}
...
}
return TRUE;
}
4.
DWORD WINAPI WorkerThreadProc(LPVOID lpCmpltPort)
{
HANDLE hCompletionPort = (HANDLE)lpCmpltPort;
...
while (TRUE)
{
GetQueuedCompletionStatus(hCompletionPort,dwIoSize,&dwClientNo,&lpOverlapped,INFINITE);
...
}
}
- 作者: XZXBLOGCHINA 2004年12月27日, 星期一 17:24  回复(0) |  引用(0) 加入博采

[网络]Winsock I/O方法 主要介绍:套接字模式、套接字I/O模型


套接字模式:锁定、非锁定
套接字I/O模型: select(选择)
WSAAsyncSelect(异步选择)
WSAEventSelect(事件选择)
Overlapped I/O(重叠式I / O)
Completion port(完成端口)



一、 简介

套接字模型的出现,是为了解决套接字模式存在的某些限制。
所有Wi n d o w s平台都支持套接字以锁定或非锁定方式工作。然而,并非每种平台都支持每一种I / O模型。
操作系统对套接字I / O模型的支持情况

平台
select
WSAAsync
WSAEventSelect
Overlapped
Completion Port

Windows CE
支持
不支持
不支持
不支持
不支持

Windows 95(Winsock 1)
支持
支持
不支持
不支持
不支持

Windows 95(Winsock 2)
支持
支持
支持
支持
不支持

Windows 98
支持
支持
支持
支持
不支持

Windows NT
支持
支持
支持
支持
支持

Windows 2000
支持
支持
支持
支持
支持



二、 套接字模式

在锁定模式下,在I / O操作完成前,执行操作的Winsock函数(比如send和recv)会一直等候下去,不会立即返回程序(将控制权交还给程序)。而在非锁定模式下,Winsock函数无论如何都会立即返回。

锁定模式
处在锁定模式的套接字,因为在一个锁定套接字上调用任何一个Winsock API函数,都会产生相同的后果—耗费或长或短的时间"等待"。

非锁定模式
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)
{
//Failed to put the socket into nonblocking mode
}

三、 套接字I/O模型

1. select模型
select函数,可以判断套接字上是否存在数据,或者能否向一个套接字写入数据。
处于锁定模式时,防止I / O调用(如send或recv),进入"锁定"状态;同时防止在套处于非锁定模式时,防止产生WSAEWOULDBLOCK错误。
除非满足事先用参数规定的条件,否则select函数会在进行I / O操作时锁定。

select的函数原型:
int select(
int nfds,//会被忽略, 为了保持与早期的B e r k e l e y套接字应用程序的兼容
fd_set FAR * readfds,//检查可读性
fd_set FAR * writefds,//检查可写性
fd_set FAR * exceptfds,//于例外数据
const struct timeval FAR * timeout//最多等待I / O操作完成多久的时间。如是空指针,无限期等
);
//fd_set数据类型代表着一系列特定套接字的集合
Wi n s o c k提供下列宏对f d _ s e t进行处理与检查:
■ FD_CLR(s, *set):从s e t中删除套接字s。
■ FD_ISSET(s, *set):检查s是否s e t集合的一名成员;如答案是肯定的是,则返回T R U E。
■ FD_SET(s, *set):将套接字s加入集合s e t。
■ F D _ Z E R O ( * s e t ):将s e t初始化成空集合。

步骤:
1) 使用F D _ Z E R O宏,初始化自己感兴趣的每一个f d _ s e t。
2) 使用F D _ S E T宏,将套接字句柄分配给自己感兴趣的每个f d _ s e t。
3) 调用s e l e c t函数,然后等待在指定的f d _ s e t集合中,I / O活动设置好一个或多个套接字句柄。s e l e c t完成后,会返回在所有f d _ s e t集合中设置的套接字句柄总数,并对每个集合进行相应的更新。
4) 根据s e l e c t的返回值,我们的应用程序便可判断出哪些套接字存在着尚未完成的I / O操作—具体的方法是使用F D _ I S S E T宏,对每个f d _ s e t集合进行检查。
5) 知道了每个集合中"待决"的I / O操作之后,对I / O进行处理,然后返回步骤1 ),继续进行s e l e c t处理。
s e l e c t返回后,它会修改每个f d _ s e t结构,删除那些不存在待决I / O操作的套接字句柄。这正是我们在上述的步骤( 4 )中,为何要使用F D _ I S S E T宏来判断一个特定的套接字是否仍在集合中的原因。
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);
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
}
}
}

2. WSAAsyncSelect
有用的异步I / O模型,利用这个模型,应用程序可在一个套接字上,接收以Windows消息为基础的网络事件通知。
函数原型:
int WSAAsyncSelect(
SOCKET s,
HWND hWnd,//收到通知消息的那个窗口
unsigned int wMsg, //指定在发生网络事件时,打算接收的消息
long lEvent//位掩码,对应于一系列网络事件的组合
);

用于WSAAsyncSelect函数的网络事件:
FD_READ 应用程序想要接收有关是否可读的通知,以便读入数据
FD_WRITE 应用程序想要接收有关是否可写的通知,以便写入数据
FD_OOB 应用程序想接收是否有带外(OOB)数据抵达的通知
FD_ACCEPT 应用程序想接收与进入连接有关的通知
FD_CONNECT 应用程序想接收与一次连接或者多点join操作完成的通知
FD_CLOSE 应用程序想接收与套接字关闭有关的通知
FD_QOS 应用程序想接收套接字"服务质量"(QoS)发生更改的通知
FD_GROUP_QOS 应用程序想接收套接字组"服务质量"发生更改的通知(为未来套接字组的使用保留)
FD_ROUTING_INTERFACE_CHANGE 应用程序想接收在指定的方向上,与路由接口发生变化的通知
FD_ADDRESS_LIST_CHANGE 应用程序想接收针对套接字的协议家族,本地地址列表发生变化的通知

窗口例程,函数原型:
LRESULT CALLBACK WindowProc(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);
WSAAsyncSelect调用中定义的消息。wParam参数指定在其上面发生了一个网络事件的套接字。假若同时为这个窗口例程分配了多个套接字,这个参数的重要性便显示出来了。在lParam参数中,包含了两方面重要的信息。其中, lParam的低位字指定了已经发生的网络事件,而lParam的高位字包含了可能出现的任何错误代码。
网络事件消息抵达一个窗口例程后,应用程序首先应检查lParam的高字位,以判断是否在套接字上发生了一个网络错误。这里有一个特殊的宏: WSAGETSELECTERROR,可用它返回高字位包含的错误信息。若应用程序发现套接字上没有产生任何错误,接着便应调查到底是哪个网络事件类型,造成了这条Windows消息的触发—具体的做法便是读取lParam之低字位的内容。此时可使用另一个特殊的宏:WSAGETSELECTEVENT,用它返回lParam的低字部分。

3.WSAEventSelect
异步I / O模型, 网络事件投递至一个事件对象句柄,而非投递至一个窗口例程。

1. 针对打算使用的每一个套接字,首先创建一个事件对象。
WSAEVENT WSACreateEvent(void);
*相关的函数:BOOL WSAResetEvent(WSAEVENT hEvent);BOOL WSACloseEvent(WSAEVENT hEvent);
WSACreateEvent创建的事件拥有两种工作状态:signaled和nonsignaled,以及两种工作模式:manual reset和auto reset。

2. 将其与某个套接字关联在一起,同时注册自己感兴趣的网络事件类型。
int WSAEventSelect(Socket s,WSAEVENT hEventObject,long lNetworkEvents);

3. 套接字同一个事件对象句柄关联在一起后,应用程序便可开始I/O处理;方法是等待网络事件触发事件对象句柄的工作状态。WSAWaitForMultipleEvents函数的设计宗旨便是用来等待一个或多个事件对象句柄,并在事先指定的一个或所有句柄进入"已传信"状态后,或在超过了一个规定的时间周期后,立即返回。
DWORD WSAWaitForMultipleEvents(
DWORD cEvents,
const WSAEVENT FAR * lphEvents,
BOOL fWaitAll,
DWORD dwTimeOut,
BOOL fAlertable
);
WSAWaitForMultipleEvents只能支持由WSA_MAXIMUM_WAIT_EVENTS对象规定的一个最大值,在此定义成64个。因此,针对发出WSAWaitForMultipleEvents调用的每个线程,该I/O模型一次最多都只能支持6 4个套接字。假如想让这个模型同时管理不止64个套接字,必须创建额外的工作者线程,以便等待更多的事件对象。

4. 知道了造成网络事件的套接字后,接下来调用WSAEnumNetworkEvents函数,调查发生了什么类型的网络事件。
int WSAEnumNetworkEvents(SOCKET s,WSAEVENT hEventObject,LPWSANETWORKEVENTS lpNetworkEvents);

4. Overlapped I/O
重叠I / O(Overlapped I/O)模型使应用程序能达到更佳的系统性能。
重叠模型的基本设计原理便是让应用程序使用一个重叠的数据结构,一次投递一个或多个Winsock I/O请求。
首先使用WSA_FLAG_OVERLAPPED这个标志,创建一个套接字。s = WSASocket(AF_INET, SOCK_STREAM, 0 ,NULL,0,WSA_FLAG_OVERLAPPED);创建套接字的时候,假如使用的是socket函数,而非WSASocket函数,那么会默认设置WSA_FLAG_OVERLAPPED标志。成功建好一个套接字,同时将其与一个本地接口绑定到一起后,便可开始进行重叠I / O 操作,方法是调用下述的Wi n s o c k 函数,同时指定一个WSAOVERLAPPED结构(可选):
■ WSASend
■ WSASendTo
■ WSARecv
■ WSARecvFrom
■ WSAIoctl
■ AcceptEx
■ TrnasmitFile
步骤:
1) 创建一个套接字,开始在指定的端口上监听连接请求。
2) 接受一个进入的连接请求。
3) 为接受的套接字新建一个WSAOVERLAPPED结构,并为该结构分配一个事件对象句柄。也将事件对象句柄分配给一个事件数组,以便稍后由WSAWaitForMultipleEvents函数使用。
4) 在套接字上投递一个异步WSARecv请求,指定参数为WSAOVERLAPPED结构。注意函数通常会以失败告终,返回SOCKET_ERROR错误状态WSA_IO_PENDING(I/O操作尚未完成)。
5) 使用步骤3 )的事件数组,调用WSAWaitForMultipleEvents函数,并等待与重叠调用关联在一起的事件进入"已传信"状态(换言之,等待那个事件的"触发")。
6) WSAWaitForMultipleEvents函数完成后,针对事件数组,调用WSAResetEvent(重设事件)函数,从而重设事件对象,并对完成的重叠请求进行处理。
7) 使用WSAGetOverlappedResult函数,判断重叠调用的返回状态是什么。
8) 在套接字上投递另一个重叠WSARecv请求。
9) 重复步骤5 ) ~ 8 )。

void main(void)
{
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:Accept 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);
While(TRUE)
{
//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){
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;
WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped,NULL);
}
}
在Windows NT和Windows 2000中,重叠I/O模型也允许应用程序以一种重叠方式,实现对连接的接受。具体的做法是在监听套接字上调用AcceptEx函数。

完成例程
"完成例程"是我们的应用程序用来管理完成的重叠I / O请求的另一种方法。完成例程其实就是一些函数。设计宗旨是通过调用者的线程,为一个已完成的I / O请求提供服务。
void CALLBACK CompletionROUTINE(
DWORD dwError, //表明了一个重叠操作(由l p O v e r l a p p e d指定)的完成状态是什么。
DWORD cbTransferred,//实际传输的字节量
LPWSAOVERLAPPED lpOverlapped,
DWORD dwFlags // 0
);
在用一个完成例程提交的重叠请求,与用一个事件对象提交的重叠请求之间,存在着一项非常重要的区别。WSAOVERLAPPED结构的事件字段hEvent并未使用;
步骤:
1) 新建一个套接字,开始在指定端口上,监听一个进入的连接。
2) 接受一个进入的连接请求。
3) 为接受的套接字创建一个WSAOVERLAPPED结构。
4) 在套接字上投递一个异步WSARecv请求,方法是将WSAOVERLAPPED指定成为参数,同时提供一个完成例程。
5) 在将fAlertable参数设为TRUE的前提下,调用WSAWaitForMultipleEvents,并等待一个重叠I/O请求完成。重叠请求完成后,完成例程会自动执行,而且WSAWaitForMultipleEven ts会返回一个WSA_IO_COMPLETION。在完成例程内,可随一个完成例程一道,投递另一个重叠WSARecv请求。
6) 检查WSAWaitForMultipleEvents是否返回WSA_IO_COMPLETION。
7) 重复步骤5 )和6 )。

SOCKET AcceptSocket;
WSABUF DataBuf;
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){
return;
}
//Since 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] = WSACreatedEvent();
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 rouines.
break;
}
else{
//A bad error occurred - stop processing.
Return;
}
}
}
}

void CALLBACK WorkerRoutine(DWORD Error,DWORD BytesTransferred,LPWSAOVERLAPPED overlapped,DWORD InFlags)
{
DWORD SendBytes, RecvBytes;
DWORD Flags;
If(Error!=0||BytesTransferred == 0){
//a bad error occurred on the socket
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 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){
return;
}
}
}

5、完成端口模型
"完成端口"模型是迄今为止最为复杂的一种I / O模型。然而,假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!但不幸的是,该模型只适用于Windows NT和Windows 2000操作系统。因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的C P U数量的增多,应用程序的性能也可以线性提升,才应考虑采用"完成端口"模型。要记住的一个基本准则是,假如要为Windows NT或Windows 2000开发高性能的服务器应用,同时希望为大量套接字I / O请求提供服务(We b服务器便是这方面的典型例子),那么I / O完成端口模型便是最佳选择!
[将单独一章讲解完成端口模型。xzx 2004-12-24]
- 作者: XZXBLOGCHINA 2004年12月24日, 星期五 12:03  回复(1) |  引用(0) 加入博采

[网络]Winsock支持的协议 主要介绍:IP、红外线套接字、IPX/SPX、NetBIOS、AppleTalk、ATM



一、IP
网际协议( Internet Protocol)是一个无连接的协议,不能保证数据投递万无一失。两个比它高级的协议( T C P和U D P)用于依赖I P协议的数据通信。

TCP
传输控制协议(Transmission Control Protocol), 面向连接的通信,提供可靠无错的数据传输。连接一旦建立,就可双向字节流交换数据。

UDP用户数据报协议(User Datagram Protocol),无连接通信,不保障可靠数据的传输,但能够向若干个目标发送数据,接收发自若干个源的数据。

定址计算机都分配有一个I P v 4地址,用一个3 2位数来表示。当需通过T C P或U D P通信时,必须指定服务器的I P地址和服务端口号。
Wi n s o c k中,通过S O C K A D D R _ I N结构来指定地址和端口信息,该结构的格式如下:
Struct sockaddr_in
{
short sin_family;//必须设为A F _ I N E T,以使用I P地址家族
u_short sin_port;//端口
struct in_addr sin_addr;//把一个I P地址保存为一个4字节的数,无符号长整数类型
char sin_zero[8];//充当填充项,使结构和S O C K A D D R结构的长度一样
};

端口分为三类:"已知"端口、已注册端口、动态端口。
■ 0 ~ 1 0 2 3由I A N A控制,是为固定服务保留的。
■ 1 0 2 4 ~ 4 9 1 5 1是I A N A列出来的、已注册的端口,供普通用户的普通用户进程或程序使用。
■ 4 9 1 5 2 ~ 6 5 5 3 5是动态和(或)私用端口。


二、红外线套接字
红外线套接字,I r S o c k,通过红外串口通信。可在Windows 98和Windows 2000上使用。

Typedef struct sockaddr_irda{
u_short irdaAddressFamily;//A F _ I R D A
u_char irdaDeviceID[4];//唯一性地标识特定服务所运行的设备
char irdaServiceName[25];//服务名
}SOCKADDR_IRDA;

irdaDeviceID:
在建立I r S o c k服务器时,这个字段是忽略不计的。但是对客户机而言,却非常重要,因为它指定的是准备连接的那个I r D A设备(也可能有若干个)。
irdaServiceName:
服务名,应用要么利用这项服务对其本身进行注册,要么试着与这项服务建立连接。

红外线设备列举、创建一个客户机或服务器:
由于红外线设备的使用地点不固定,因此,必须有一种方法,可以动态地把特定范围内的所有可用红外线设备列举出来。
SOCKET sock;
DEVICELIST devList;
DWORD dwListLen=sizeof(DEVICELIST);
Sock=WSASocket(AF_IRDA,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
...
devList.numDevice=0;
dwRet=getsockopt(sock,SOL_IRLMP,IRLMP_ENUMDEVICES,(char *)&devList,&dwListLen);

建立I r S o c k服务器:
1) 建立一个地址家族A F _ I R D A套接字和套接字类型S O C K _ S T R E A M。
2) 用服务器的服务名填写一个S O C K A D D R _ I R D A结构。
3) 利用套接字句柄和S O C K A D D R _ I R D A结构调用b i n d。
4) 利用套接字句柄和b a c k l o g边限调用l i s t e n。
5) 为接入客户机锁定一个a c c e p t调用。

建立I r S o c k客户机:
1) 建立地址家族A F _ I R D A套接字和套接字类型S O C K - S T R E A M。
2) 调用g e t s o c k o p t函数,列举所有可用的红外线设备。
3) 针对返回的每个设备,利用设备I D和准备连接的服务名填写一个SOCKADDR_ IRDA结构。
4) 利用套接字句柄和S O C K A D D R _ I R D A结构,调用c o n n e c t函数。针对步骤3)中所填的
结构,重复步骤4),直到连接成功。


三、IPX/SPX
互联网包交换协议,无连接通信,一般为承担Novell NetWa r e客户机/服务器联网服务的计算机所用。
如果应用程序需要数据投递保证,但仍坚持使用I P X,它就会选用一个比I P X高级的协议,比如说"顺序分组交换"S P X和SPX II协议,这两个协议中, S P X包通过I P X发送。


四、NetBIOS
五、AppleTalk
如果不与苹果公司的M A C机通信,你可能不会选择A p p l e Ta l k协议。从某种程度上来说,A p p l e Ta l k与N e t B I O S类似,因为两者都是针对每一个进程来进行名字注册的。

六、ATM
异步传输模式协议, AT M通常用于L A N和WA N上的高速联网,也用于各种类型的通信,比如说要求高速通信的语音、视频和数据等。一般说来, AT M利用网络上的虚拟连接(V C)来提供服务质量( Q O S)保证。Wi n s o c k能够通过AT M地址家族来使用AT M网络上的虚拟连接。AT M网络一般由通过交换机(它们将AT M网络桥接在一起)连接的端点(或计算机)构成。

ATM是一个媒体类型,而不是一个真正的协议。也就是说, AT M类似于直接在以太网上写入以太帧。和以太网一样, AT M协议没有提供流控制。它是一个面向连接的协议,要么提供消息模式,要么提供流模式。如果数据不能快速发送出去,发送应用则可能溢出本地缓冲。同样地,接收应用必须频繁投递收到的数据:否则,接收缓冲填满之时,任何一个另外接入的数据都可能被丢弃。如果需要流控制,方法之一是在AT M上使用I P协议,这样一来,应用便紧跟在上面描述的I P地址家族之后。

没有评论: