﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-Thinking in C++-文章分类-socket</title><link>http://www.cppblog.com/yishanhante/category/3729.html</link><description /><language>zh-cn</language><lastBuildDate>Thu, 22 May 2008 18:40:12 GMT</lastBuildDate><pubDate>Thu, 22 May 2008 18:40:12 GMT</pubDate><ttl>60</ttl><item><title>Overlapped模型与CompletionPort模型</title><link>http://www.cppblog.com/yishanhante/articles/23125.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Sat, 28 Apr 2007 09:14:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/23125.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/23125.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/23125.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/23125.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/23125.html</trackback:ping><description><![CDATA[<strong>摘自《Visual C++网络游戏建模与实现》（苏羽、王媛媛编著）</strong>&nbsp;<br>Win32重叠I/O(Overloapped I/O)机制允许发起一个操作，然后在操作完成之后接受<br>到信息。对于那种需要很长时间才能完成的操作来说，重叠I/O机制尤其有用，因为发起<br>重叠操作的线程在重叠请求发出后就可以自由地做别的事情了。<br>&nbsp;&nbsp;&nbsp; 在Windows NT/2000上，提供真正可扩展的I/O模型就是使用完成端口（Completion <br>Port)的重叠I/O。<br>&nbsp;&nbsp;&nbsp; &#8230;&#8230;<br>&nbsp;&nbsp;&nbsp; 可以把完成端口看成系统维护的一个队列，操作系统把重叠I/O操作完成的事件通知<br>放到该队列里，由于是暴露&#8220;操作完成&#8221;的事件通知，所以命名为&#8220;完成端口&#8221;（Completion<br>Ports)。一个Socket被创建后，可以在任何时刻和一个完成端口联系起来。<br>&nbsp;&nbsp;&nbsp; 一般来说，一个应用程序可以创建多个工作线程来处理完成端口上的通知事件。工作<br>线程的数量依赖于程序的具体需要。但是在理想的情况下，应该对应一个CPU创建一个线<br>程。因为在完成端口理想模型中，每个线程都可以从系统获得一个&#8220;原子&#8221;性的时间片，轮<br>番运行并检查完成端口，线程的切换是额外的开销。在实际开发的时候，还要考虑这些线<br>程是否牵涉到其他堵塞操作的情况。如果某线程进行堵塞操作，系统则将其挂起，让别的<br>线程获得运行时间。因此，如果有这样的情况，可以多创建几个线程来尽量利用时间。<br>&nbsp;&nbsp;&nbsp; 应用完成端口分两步走：<br>&nbsp;&nbsp;&nbsp; 1. 创建完成端口句柄：<br>&nbsp;&nbsp;&nbsp; HANDLE hIocp;<br>&nbsp;&nbsp;&nbsp; hIocp=CreateIoCompletionPort(<br>&nbsp;&nbsp;&nbsp; INVALID_HANDLE_VALUE,<br>&nbsp;&nbsp;&nbsp; NULL,<br>&nbsp;&nbsp;&nbsp; (ULONG_PTR)0,<br>&nbsp;&nbsp;&nbsp; 0);<br>&nbsp;&nbsp;&nbsp; if(hIocp==NULL) {<br>&nbsp;&nbsp;&nbsp; //如果错误<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;&#8230;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; 注意在第1个参数(FileHandle)传入INVALID_FILE_HANDLE，第2个参数(ExistingCompletionPort)<br>传入NULL，系统将创建一个新的完成端口句柄，没有任何I/O句柄与其关联。
<p>&nbsp;&nbsp;&nbsp; 2. 完成端口创建成功后，在Socket和完成端口之间建立关联。再次调用CreateIoCompletionPort<br>函数，这一次在第1个参数FileHandle传入创建的Socket句柄，参数ExistingCompletionPort<br>为已经创建的完成端口句柄。<br>&nbsp;&nbsp;&nbsp; 以下代码创建了一个Socket并把它和完成端口联系起来。<br>&nbsp;&nbsp;&nbsp; SOCKET s;<br>&nbsp;&nbsp;&nbsp; s=Socket(AF_INET,SOCK_STREAM,0);<br>&nbsp;&nbsp;&nbsp; if(s==INVALID_SOCKET) {<br>&nbsp;&nbsp;&nbsp; if(CreateIoCompletionPort((HANDLE)s,<br>&nbsp;&nbsp;&nbsp; hIocp,<br>&nbsp;&nbsp;&nbsp; (ULONG_PTR)0,<br>&nbsp;&nbsp;&nbsp; 0)==NULL)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; //如果创建失败<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;&#8230;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; 到此为止，Socket已经成功和完成端口相关联。在此Socket进行的重叠I/O操作结果均<br>使用完成端口发出通知。</p>
<p>&nbsp;&nbsp;&nbsp; 注意：CreateIoCompletionPort函数的第3个参数允许开发人员传入一个类型为ULONG_PTR<br>的数据成员，我们把它称为完成键(Completion Key)，此数据成员可以设计为指向包含Socket<br>信息的一个结构体的一个指针，用来把相关的环境信息和Socket联系起来，每次完成通知来<br>到的同时，该环境信息也随着通知一起返回给开发人员。</p>
<p>&nbsp;&nbsp;&nbsp; 完成端口创建以及与Socket关联之后，就要创建一个或多个工作线程来处理完成通知，<br>每个线程都可以循环地调用GetQueuedCompletionStatus函数，检查完成端口上的通知事件。<br>&nbsp;&nbsp;&nbsp; 在举例说明一个典型的工作线程之前，我们先讨论一下重叠I/O的过程。到一个重叠I/O<br>被发起，一个Overlapped结构体的指针就要作为参数传递给系统。当操作完成时，<br>GetQueueCompletionStatus就可以返回指向同一个Overlapped结构的指针。为了辨认和定位<br>这个已完成的操作，开发人员最好定义自己的OVERLAPPED结构，以包含一些自己定义的关于<br>操作本身的额外信息。比如：<br>&nbsp;&nbsp;&nbsp; typedef struct _OVERLAPPELUS {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OVERLAPPED ol;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SOCKET s, sclient;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int OpCode;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSABUF wbuf;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD dwBytes, dwFlags;<br>&nbsp;&nbsp;&nbsp; } OVERLAPPELUS;<br>&nbsp;&nbsp;&nbsp; 此结构的第1个成员为默认的OVERLAPPED结构，第2和第3个为本地服务Socket和与该<br>操作相关的客户socket，第4个成员为操作类型，对于Socket，现在定义的有以下3种：<br>&nbsp;&nbsp;&nbsp; #define OP_READ 0<br>&nbsp;&nbsp;&nbsp; #define OP_WRITE 1<br>&nbsp;&nbsp;&nbsp; #define OP_ACCEPT 2<br>&nbsp;&nbsp;&nbsp; 然后还有应用程序的Socket缓冲区，操作数据量，标志位以及其他开发人员认为有用<br>的信息。<br>&nbsp;&nbsp;&nbsp; 当进行重叠I/O操作，把OVERLAPPELUS结构作为重叠I/O的参数lpOverlapp传递（如<br>WSASend，WASRecv，等函数的lpOverlapped参数，要求传入一个OVERLAPP结构的指针）。<br>&nbsp;&nbsp;&nbsp; 当操作完成后，GetQueuedCompletionStatus函数返回一个LPOVERLAPPED类型的指针，<br>这个指针其实是指向开发人员定义的扩展OVERLAPPELUS结构，包含着开发人员早先传入的<br>全部信息。</p>
<p>&nbsp;&nbsp;&nbsp; 注意：OVERLAPPED成员不一定要求是OVERLAPPELUS扩展结构的一个成员，在获得<br>OVERLAPPED指针之后，可以用CONTAINING_RECORD宏获得相应的扩展结构的指针。</p>
<p>&nbsp;&nbsp;&nbsp; 典型的Worker Thread结构：<br>&nbsp;&nbsp;&nbsp; DWORD WINAPI WorkerThread(LPVOID lpParam)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ULONG_PTR *PerHandleKey;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OVERLAPPED *Overlap;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OVERLAPPELUS *OverlapPlus, *newolp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD dwBytesXfered;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while(1)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret=GetQueuedCompletionStatus(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hIocp,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;dwBytesXfered,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (PULONG_PTR)&amp;PerHandleKey,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;Overlap,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; INFINITE);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(ret==0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //如果操作失败<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OverlapPlus=CONTATING_RECORD(Overlap, OVERLAPPELUS, ol);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; switch(OverlapPlus-&gt;OpCode)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case OP_ACCEPT:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CreateIoCompletionPort(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (HANDLE)OverlapPlus-&gt;sclient,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hIocp,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (ULONG_PTR)0,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; newolp=AllocateOverlappelus();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; newolp-&gt;s=OverlapPlus-&gt;sclient;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; newolp-&gt;OpCode=OP_READ;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PrepareSendBuffer(&amp;newolp-&gt;wbuf);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret=WSASend(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; newolp-&gt;s,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;newolp-&gt;wbuf,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;newolp-&gt;dwBytes,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;newolp.ol,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(ret==SOCKET_ERROR)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(WSAGetLastError()!=WSA_IO_PENDING)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //进行错误处理<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;&#8230;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FreeOverlappelus(OverlapPlus);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SetEvent(hAcceptThread);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case OP_READ:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; memset(&amp;OverlapPlus-&gt;ol,0,sizeof(OVERLAPPED));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret=WSARecv(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OverlapPlus-&gt;s,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;OverlapPlus-&gt;wbuf,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;OverlapPlus-&gt;dwBytes,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;OverlapPlus-&gt;dwFlags,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;OverlapPlus-&gt;ol,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(ret==SOCKET_ERROR)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(WSAGetLastError()!=WSA_IO_PENDING)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //错误处理<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;&#8230;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case OP_WRITE:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }/*switch结束*/<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }/*while结束*/<br>&nbsp;&nbsp;&nbsp; }/*WorkerThread结束*/<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; 注意：如果Overlapped操作立刻失败（比如，返回SOCKET_ERROR或其他非<br>&nbsp;&nbsp;&nbsp; WSA_IO_PENDING的错误），则没有任何完成通知事件会被放到完成端口队列里。反之，<br>&nbsp;&nbsp;&nbsp; 则一定有相应的通知事件被放到端口队列。</p>
<img src ="http://www.cppblog.com/yishanhante/aggbug/23125.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-04-28 17:14 <a href="http://www.cppblog.com/yishanhante/articles/23125.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>点对点多线程断点续传的实现[转]</title><link>http://www.cppblog.com/yishanhante/articles/22374.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Fri, 20 Apr 2007 07:18:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/22374.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/22374.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/22374.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/22374.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/22374.html</trackback:ping><description><![CDATA[<p>在如今的网络应用中，文件的传送是重要的功能之一，也是共享的基础。一些重要的协议像HTTP，FTP等都支持文件的传送。尤其是FTP，它的全称就是&#8220;文件传送协议&#8221;，当初的工程师设计这一协议就是为了解决网络间的文件传送问题，而且以其稳定，高速，简单而一直保持着很大的生命力。作为一个程序员，使用这些现有的协议传送文件相当简单，不过，它们只适用于服务器模式中。这样，当我们想在点与点之间传送文件就不适用了或相当麻烦，有一种大刀小用的意味。笔者一直想寻求一种简单有效，且具备多线程断点续传的方法来实现点与点之间的文件传送问题，经过大量的翻阅资料与测试，终于实现了，现把它共享出来，与大家分享。<br>我写了一个以此为基础的实用程序（网络传圣，包含源代码），可用了基于TCP/IP的电脑上，供大家学习。<br><br><img height=457 src="http://www.vckbase.net/document/journal/vckbase15/images/ChanSheng.gif" width=614><br>(本文源代码运行效果图)
<p><br>实现方法（VC＋＋，基于TCP/IP协议）如下：<br>仍釆用服务器与客户模式，需分别对其设计与编程。<br>服务器端较简单，主要就是加入待传文件，监听客户，和传送文件。而那些断点续传的功能，以及文件的管理都放在客户端上。<br><br><strong><img height=16 src="http://www.vckbase.net/document/image/paragraph.gif" width=14> 一、服务器端</strong><br><br>首先介绍服务器端：<br>最开始我们要定义一个简单的协议，也就是定义一个服务器端与客户端听得懂的语言。而为了把问题简化，我就让服务器只要听懂两句话，一就是客户说&#8220;我要读文件信息&#8221;，二就是&#8220;我准备好了，可以传文件了&#8221;。<br>由于要实现多线程，必须把功能独立出来，且包装成线程，首先建一个监听线程，主要负责接入客户，并启动另一个客户线程。我用VC++实现如下：<br></p>
<pre>DWORD WINAPI listenthread(LPVOID lpparam)
{
//由主函数传来的套接字
　　SOCKET  pthis=(SOCKET)lpparam;
//开始监听
int rc=listen(pthis,30);
//如果错就显示信息
if(rc&lt;0){
CString aaa;
aaa="listen错误\n";
AfxGetMainWnd()-&gt;SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);
aaa.ReleaseBuffer();
return 0;
}
//进入循环，并接收到来的套接字
while(1){
//新建一个套接字，用于客户端
SOCKET s1;
s1=accept(pthis,NULL,NULL);
　  //给主函数发有人联入消息
CString aa;
aa="一人联入！\n";
AfxGetMainWnd()-&gt;SendMessageToDescendants(WM_AGE1,(LPARAM)aa.GetBuffer(0),1);
aa.ReleaseBuffer();
DWORD dwthread;
//建立用户线程
::CreateThread(NULL,0,clientthread,(LPVOID)s1,0,&amp;dwthread);
}
return 0;
}
</pre>
接着我们来看用户线程：<br>先看<strong>文件消息类定义</strong>：<br>
<pre>struct fileinfo
{
int fileno;//文件号
int type;//客户端想说什么（前面那两句话，用1,2表示）
long len;//文件长度
int seek;//文件开始位置，用于多线程
char name[100];//文件名
};
</pre>
<strong>用户线程函数</strong>:
<pre>DWORD WINAPI clientthread(LPVOID lpparam)
{
//文件消息
fileinfo* fiinfo;
//接收缓存
char* m_buf;
m_buf=new char[100];
//监听函数传来的用户套接字
SOCKET  pthis=(SOCKET)lpparam;
//读传来的信息
int aa=readn(pthis,m_buf,100);
//如果有错就返回
if(aa&lt;0){
closesocket (pthis);
return -1;
}
//把传来的信息转为定义的文件信息
fiinfo=(fileinfo*)m_buf;
CString aaa;
//检验客户想说什么
switch(fiinfo-&gt;type)
{
//我要读文件信息
case 0:
//读文件
aa=sendn(pthis,(char*)zmfile,1080);
//有错
if(aa&lt;0){
closesocket (pthis);
return -1;
}
//发消息给主函数
aaa="收到LIST命令\n";
AfxGetMainWnd()-&gt;SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);
break;
//我准备好了，可以传文件了
case 2:
//发文件消息给主函数
aaa.Format("%s  文件被请求！%s\n",zmfile[fiinfo-&gt;fileno].name,nameph[fiinfo-&gt;fileno]);
AfxGetMainWnd()-&gt;SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);
//读文件，并传送
readfile(pthis,fiinfo-&gt;seek,fiinfo-&gt;len,fiinfo-&gt;fileno);
//听不懂你说什么
default:
aaa="接收协议错误！\n";
AfxGetMainWnd()-&gt;SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);
break;
}
return 0;
}</pre>
<strong>读文件函数</strong>
<pre>void readfile(SOCKET  so,int seek,int len,int fino)
{
//文件名
CString myname;
myname.Format("%s",nameph[fino]);
CFile myFile;
//打开文件
myFile.Open(myname, CFile::modeRead | CFile::typeBinary|CFile::shareDenyNone);
//传到指定位置　
myFile.Seek(seek,CFile::begin);
char m_buf[SIZE];
int len2;
int len1;
len1=len;
//开始接收，直到发完整个文件
while(len1&gt;0){
len2=len&gt;SIZE?SIZE:len;
myFile.Read(m_buf, len2);
int aa=sendn(so,m_buf,len2);
if(aa&lt;0){
closesocket (so);
break;
}
len1=len1-aa;
len=len-aa;
}
myFile.Close();
}
</pre>
<p>服务器端最要的功能各技术就是这些，下面介绍客户端。<br><br><strong><img height=16 src="http://www.vckbase.net/document/image/paragraph.gif" width=14> 二、客户端</strong><br><br>客户端最重要，也最复杂，它负责线程的管理，进度的记录等工作。<br><br>大概流程如下：<br>先连接服务器，接着发送命令1（给我文件信息），其中包括文件长度，名字等，然后根据长度决定分几个线程下载，并初使化下载进程，接着发送命令2（可以给我传文件了），并记录文件进程。最后，收尾。<br>这其中有一个十分重要的类，就是cdownload类，定义如下： </p>
<pre>class cdownload
{
public:
void createthread();//开线程
DWORD finish1();//完成线程
int sendlist();//发命令1
downinfo doinfo;//文件信息（与服务器定义一样）
int startask(int n);开始传文件n
long m_index;
BOOL good[BLACK];
int  filerange[100];
CString fname;
CString fnametwo;
UINT threadfunc(long index);//下载进程
int sendrequest(int n);//发文件信息
cdownload(int thno1);
virtual ~cdownload();
};</pre>
下面先介绍sendrequest(int n)，在开始前，向服务器发获得文件消息命令，以便让客户端知道有哪些文件可传
<pre>int cdownload::sendrequest(int n)
{
//建套接字
sockaddr_in local;
SOCKET m_socket;
int rc=0;
//初使化服务器地址
local.sin_family=AF_INET;
local.sin_port=htons(1028);
local.sin_addr.S_un.S_addr=inet_addr(ip);
m_socket=socket(AF_INET,SOCK_STREAM,0);
int ret;
//联接服务器
ret=connect(m_socket,(LPSOCKADDR)&amp;local,sizeof(local));
//有错的话
if(ret&lt;0){
AfxMessageBox("联接错误");
closesocket(m_socket);
return -1;
}
//初使化命令
fileinfo fileinfo1;
fileinfo1.len=n;
fileinfo1.seek=50;
fileinfo1.type=1;
//发送命令
int aa=sendn(m_socket,(char*)&amp;fileinfo1,100);
if(aa&lt;0){
closesocket(m_socket);
return -1;
}
//接收服务器传来的信息
aa=readn(m_socket,(char*)&amp;fileinfo1,100);
if(aa&lt;0){
closesocket(m_socket);
return -1;
}
//关闭
shutdown(m_socket,2);
closesocket(m_socket);
return 1;
}</pre>
有了文件消息后我们就可以下载文件了。在主函数中，用法如下：
<pre>//下载第clno个文件，并为它建一个新cdownload类
down[clno]=new cdownload(clno);
//开始下载，并初使化
type=down[clno]-&gt;startask(clno);
//建立各线程
createthread(clno);</pre>
下面介绍<strong>开始方法</strong>:
<pre>//开始方法
int cdownload::startask(int n)
{
//读入文件长度
doinfo.filelen=zmfile[n].length;
//读入名字
fname=zmfile[n].name;
CString tmep;
//初使化文件名
tmep.Format("\\temp\\%s",fname);
//给主函数发消息
CString aaa;
aaa="正在读取 "+fname+" 信息，马上开始下载。。。\n";
AfxGetMainWnd()-&gt;SendMessageToDescendants(WM_AGE1,(LPARAM)aaa.GetBuffer(0),1);
aaa.ReleaseBuffer();
//如果文件长度小于0就返回
if(doinfo.filelen&lt;=0) return -1;
//建一个以.down结尾的文件记录文件信息
CString m_temp;
m_temp=fname+".down";
doinfo.name=m_temp;
FILE* fp=NULL;
CFile myfile;
//如果是第一次下载文件，初使化各记录文件
if((fp=fopen(m_temp,"r"))==NULL){
filerange[0]=0;
//文件分块
for(int i=0;i&lt;BLACK;i++)
{
if(i&gt;0)
filerange[i*2]=i*(doinfo.filelen/BLACK+1);
filerange[i*2+1]=doinfo.filelen/BLACK+1;
}
filerange[BLACK*2-1]=doinfo.filelen-filerange[BLACK*2-2];
myfile.Open(m_temp,CFile::modeCreate|CFile::modeWrite | CFile::typeBinary);
//写入文件长度
myfile.Write(&amp;doinfo.filelen,sizeof(int));
myfile.Close();
　
CString temp;
for(int ii=0;ii&lt;BLACK;ii++){
//初使化各进程记录文件信息（以.downN结尾）
temp.Format(".down%d",ii);
m_temp=fname+temp;
myfile.Open(m_temp,CFile::modeCreate|CFile::modeWrite | CFile::typeBinary);
//写入各进程文件信息
myfile.Write(&amp;filerange[ii*2],sizeof(int));
myfile.Write(&amp;filerange[ii*2+1],sizeof(int));
myfile.Close();
}
((CMainFrame*)::AfxGetMainWnd())-&gt;m_work.m_ListCtrl-&gt;AddItemtwo(n,2,0,0,0,doinfo.threadno);
}
else{
//如果文件已存在，说明是续传，读上次信息
CString temp;
　
m_temp=fname+".down0";
if((fp=fopen(m_temp,"r"))==NULL)
return 1;
else fclose(fp);
int bb;
bb=0;
//读各进程记录的信息
for(int ii=0;ii&lt;BLACK;ii++)
{
temp.Format(".down%d",ii);
m_temp=fname+temp;
　
myfile.Open(m_temp,CFile::modeRead | CFile::typeBinary);
myfile.Read(&amp;filerange[ii*2],sizeof(int));
myfile.Read(&amp;filerange[ii*2+1],sizeof(int));
myfile.Close();
bb = bb+filerange[ii*2+1];
CString temp;
}
if(bb==0) return 1;
doinfo.totle=doinfo.filelen-bb;
　
((CMainFrame*)::AfxGetMainWnd())-&gt;m_work.m_ListCtrl-&gt;AddItemtwo(n,2,doinfo.totle,1,0,doinfo.threadno);
}
　	//建立下载结束进程timethread，以管现各进程结束时间。
DWORD dwthread;
::CreateThread(NULL,0,timethread,(LPVOID)this,0,&amp;dwthread);
return 0;
}</pre>
下面介绍<strong>建立各进程</strong>函数，很简单：
<pre>void CMainFrame::createthread(int threadno)
{
DWORD dwthread;
//建立BLACK个进程
for(int i=0;i&lt;BLACK;i++)
{
m_thread[threadno][i]=	::CreateThread(NULL,0,downthread,(LPVOID)down[threadno],0,&amp;dwthread);
}
}</pre>
<strong>downthread进程函数</strong>
<pre>DWORD WINAPI downthread(LPVOID lpparam)
{
cdownload* pthis=(cdownload*)lpparam;
//进程引索＋1
InterlockedIncrement(&amp;pthis-&gt;m_index);
//执行下载进程
pthis-&gt;threadfunc(pthis-&gt;m_index-1);
return 1;
}
</pre>
下面介绍<strong>下载进程函数</strong>,最最核心的东西了
<pre>UINT cdownload::threadfunc(long index)
{
//初使化联接
sockaddr_in local;
SOCKET m_socket;
int rc=0;
　
local.sin_family=AF_INET;
local.sin_port=htons(1028);
local.sin_addr.S_un.S_addr=inet_addr(ip);
m_socket=socket(AF_INET,SOCK_STREAM,0);
int ret;
//读入缓存
char* m_buf=new char[SIZE];
int re,len2;
fileinfo fileinfo1;
//联接
ret=connect(m_socket,(LPSOCKADDR)&amp;local,sizeof(local));
//读入各进程的下载信息
fileinfo1.len=filerange[index*2+1];
fileinfo1.seek=filerange[index*2];
fileinfo1.type=2;
fileinfo1.fileno=doinfo.threadno;
　
re=fileinfo1.len;
　
//打开文件　
CFile destFile;
FILE* fp=NULL;
//是第一次传的话
if((fp=fopen(fname,"r"))==NULL)
destFile.Open(fname, CFile::modeCreate|CFile::modeWrite | CFile::typeBinary|CFile::shareDenyNone);
else
//如果文件存在，是续传
destFile.Open(fname,CFile::modeWrite | CFile::typeBinary|CFile::shareDenyNone);
//文件指针移到指定位置
destFile.Seek(filerange[index*2],CFile::begin);
//发消息给服务器，可以传文件了
sendn(m_socket,(char*)&amp;fileinfo1,100);
CFile myfile;
CString temp;
temp.Format(".down%d",index);
m_temp=fname+temp;
　	//当各段长度还不为0时
while(re&gt;0){
len2=re&gt;SIZE?SIZE:re;
　
//读各段内容
int len1=readn(m_socket,m_buf,len2);
//有错的话
if(len1&lt;0){
closesocket(m_socket);
break;
}
　
//写入文件
destFile.Write(m_buf, len1);
//更改记录进度信息
filerange[index*2+1]-=len1;
filerange[index*2]+=len1;
//移动记录文件指针到头
myfile.Seek(0,CFile::begin);
//写入记录进度
myfile.Write(&amp;filerange[index*2],sizeof(int));
myfile.Write(&amp;filerange[index*2+1],sizeof(int));
//减去这次读的长度
re=re-len1;
//加文件长度
doinfo.totle=doinfo.totle+len1;
};
//这块下载完成，收尾
　
myfile.Close();
destFile.Close();
delete [] m_buf;
shutdown(m_socket,2);
　
　
if(re&lt;=0) good[index]=TRUE;
return 1;
}</pre>
<p>到这客户端的主要模块和机制已基本介绍完。希望好好体会一下这种多线程断点续传的方法。 </p>
<img src ="http://www.cppblog.com/yishanhante/aggbug/22374.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-04-20 15:18 <a href="http://www.cppblog.com/yishanhante/articles/22374.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用完成端口开发大响应规模的Winsock应用程序[转]</title><link>http://www.cppblog.com/yishanhante/articles/22302.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Thu, 19 Apr 2007 06:05:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/22302.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/22302.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/22302.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/22302.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/22302.html</trackback:ping><description><![CDATA[<p>原文出处：<a href="http://msdn.microsoft.com/msdnmag/issues/1000/Winsock/" target=_blank><u><font color=#0000ff>http://msdn.microsoft.com/msdnmag/issues/1000/Winsock/</font></u></a><br><br>通常要开发网络应用程序并不是一件轻松的事情，不过，实际上只要掌握几个关键的原则也就可以了——创建和连接一个套接字，尝试进行连接，然后收发数据。真正难的是要写出一个可以接纳少则一个，多则数千个连接的网络应用程序。本文将讨论如何通过Winsock2在Windows NT 和 Windows 2000上开发高扩展能力的Winsock应用程序。文章主要的焦点在客户机/服务器模型的服务器这一方，当然，其中的许多要点对模型的双方都适用。 </p>
<p><font color=#009999><strong>API与响应规模</strong></font></p>
<p>通过Win32的重叠I/O机制，应用程序可以提请一项I/O操作，重叠的操作请求在后台完成，而同一时间提请操作的线程去做其他的事情。等重叠操作完成后线程收到有关的通知。这种机制对那些耗时的操作而言特别有用。不过，像Windows 3.1上的WSAAsyncSelect()及Unix下的select()那样的函数虽然易于使用，但是它们不能满足响应规模的需要。而完成端口机制是针对操作系统内部进行了优化，在Windows NT 和 Windows 2000上，使用了完成端口的重叠I/O机制才能够真正扩大系统的响应规模。</p>
<p><font color=#009999><strong>完成端口</strong></font></p>
<p>一个完成端口其实就是一个通知队列，由操作系统把已经完成的重叠I/O请求的通知放入其中。当某项I/O操作一旦完成，某个可以对该操作结果进行处理的工作者线程就会收到一则通知。而套接字在被创建后，可以在任何时候与某个完成端口进行关联。</p>
<p>通常情况下，我们会在应用程序中创建一定数量的工作者线程来处理这些通知。线程数量取决于应用程序的特定需要。理想的情况是，线程数量等于处理器的数量，不过这也要求任何线程都不应该执行诸如同步读写、等待事件通知等阻塞型的操作，以免线程阻塞。每个线程都将分到一定的CPU时间，在此期间该线程可以运行，然后另一个线程将分到一个时间片并开始执行。如果某个线程执行了阻塞型的操作，操作系统将剥夺其未使用的剩余时间片并让其它线程开始执行。也就是说，前一个线程没有充分使用其时间片，当发生这样的情况时，应用程序应该准备其它线程来充分利用这些时间片。</p>
<p>完成端口的使用分为两步。首先创建完成端口，如以下代码所示：<br></p>
<pre>HANDLE    hIocp;
hIocp = CreateIoCompletionPort(
INVALID_HANDLE_VALUE,
NULL,
(ULONG_PTR)0,
0);
if (hIocp == NULL) {
// Error
}
</pre>
完成端口创建后，要把将使用该完成端口的套接字与之关联起来。方法是再次调用CreateIoCompletionPort ()函数，第一个参数FileHandle设为套接字的句柄，第二个参数ExistingCompletionPort 设为刚刚创建的那个完成端口的句柄。<br>以下代码创建了一个套接字，并把它和前面创建的完成端口关联起来：
<pre>SOCKET    s;
s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET) {
// Error
if (CreateIoCompletionPort((HANDLE)s,
hIocp,
(ULONG_PTR)0,
0) == NULL)
{
// Error
}
...
}
</pre>
<br>这时就完成了套接字与完成端口的关联操作。在这个套接字上进行的任何重叠操作都将通过完成端口发出完成通知。注意，CreateIoCompletionPort()函数中的第三个参数用来设置一个与该套接字相关的&#8220;完成键(completion key)&#8221;(译者注：完成键可以是任何数据类型)。每当完成通知到来时，应用程序可以读取相应的完成键，因此，完成键可用来给套接字传递一些背景信息。
<p>在创建了完成端口、将一个或多个套接字与之相关联之后，我们就要创建若干个线程来处理完成通知。这些线程不断循环调用GetQueuedCompletionStatus ()函数并返回完成通知。</p>
<p>下面，我们先来看看应用程序如何跟踪这些重叠操作。当应用程序调用一个重叠操作函数时，要把指向一个overlapped结构的指针包括在其参数中。当操作完成后，我们可以通过GetQueuedCompletionStatus()函数中拿回这个指针。不过，单是根据这个指针所指向的overlapped结构，应用程序并不能分辨究竟完成的是哪个操作。要实现对操作的跟踪，你可以自己定义一个OVERLAPPED结构，在其中加入所需的跟踪信息。</p>
<p>无论何时调用重叠操作函数时，总是会通过其lpOverlapped参数传递一个OVERLAPPEDPLUS结构(例如WSASend、 WSARecv等函数)。这就允许你为每一个重叠调用操作设置某些操作状态信息，当操作结束后，你可以通过GetQueuedCompletionStatus()函数获得你自定义结构的指针。注意OVERLAPPED字段不要求一定是这个扩展后的结构的第一个字段。当得到了指向OVERLAPPED结构的指针以后，可以用CONTAINING_RECORD宏取出其中指向扩展结构的指针。</p>
<p>OVERLAPPED 结构的定义如下：</p>
<pre>typedef struct _OVERLAPPEDPLUS {
OVERLAPPED        ol;
SOCKET            s, sclient;
int               OpCode;
WSABUF            wbuf;
DWORD             dwBytes, dwFlags;
// 其它有用的信息
} OVERLAPPEDPLUS;
#define OP_READ     0
#define OP_WRITE    1
#define OP_ACCEPT   2
</pre>
下面让我们来看看工作者线程的情况。<br><br>工作线程WorkerThread代码：
<pre>DWORD WINAPI WorkerThread(LPVOID lpParam)
{
ULONG_PTR       *PerHandleKey;
OVERLAPPED      *Overlap;
OVERLAPPEDPLUS  *OverlapPlus,
*newolp;
DWORD           dwBytesXfered;
while (1)
{
ret = GetQueuedCompletionStatus(
hIocp,
&amp;dwBytesXfered,
(PULONG_PTR)&amp;PerHandleKey,
&amp;Overlap,
INFINITE);
if (ret == 0)
{
// Operation failed
continue;
}
OverlapPlus = CONTAINING_RECORD(Overlap, OVERLAPPEDPLUS, ol);
switch (OverlapPlus-&gt;OpCode)
{
case OP_ACCEPT:
// Client socket is contained in OverlapPlus.sclient
// Add client to completion port
CreateIoCompletionPort(
(HANDLE)OverlapPlus-&gt;sclient,
hIocp,
(ULONG_PTR)0,
0);
//  Need a new OVERLAPPEDPLUS structure
//  for the newly accepted socket. Perhaps
//  keep a look aside list of free structures.
newolp = AllocateOverlappedPlus();
if (!newolp)
{
// Error
}
newolp-&gt;s = OverlapPlus-&gt;sclient;
newolp-&gt;OpCode = OP_READ;
// This function prepares the data to be sent
PrepareSendBuffer(&amp;newolp-&gt;wbuf);
ret = WSASend(
newolp-&gt;s,
&amp;newolp-&gt;wbuf,
1,
&amp;newolp-&gt;dwBytes,
0,
&amp;newolp.ol,
NULL);
if (ret == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
// Error
}
}
// Put structure in look aside list for later use
FreeOverlappedPlus(OverlapPlus);
// Signal accept thread to issue another AcceptEx
SetEvent(hAcceptThread);
break;
case OP_READ:
// Process the data read
// ...
// Repost the read if necessary, reusing the same
// receive buffer as before
memset(&amp;OverlapPlus-&gt;ol, 0, sizeof(OVERLAPPED));
ret = WSARecv(
OverlapPlus-&gt;s,
&amp;OverlapPlus-&gt;wbuf,
1,
&amp;OverlapPlus-&gt;dwBytes,
&amp;OverlapPlus-&gt;dwFlags,
&amp;OverlapPlus-&gt;ol,
NULL);
if (ret == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
// Error
}
}
break;
case OP_WRITE:
// Process the data sent, etc.
break;
} // switch
} // while
}  // WorkerThread
</pre>
其中每句柄键(PerHandleKey)变量的内容，是在把完成端口与套接字进行关联时所设置的完成键参数；Overlap参数返回的是一个指向发出重叠操作时所使用的那个OVERLAPPEDPLUS结构的指针。
<p>要记住，如果重叠操作调用失败时(也就是说，返回值是SOCKET_ERROR，并且错误原因不是WSA_IO_PENDING)，那么完成端口将不会收到任何完成通知。如果重叠操作调用成功，或者发生原因是WSA_IO_PENDING的错误时，完成端口将总是能够收到完成通知。<br><br><strong><font color=#009999>Windows NT和Windows 2000的套接字架构</font></strong><br><br>对于开发大响应规模的Winsock应用程序而言，对Windows NT和Windows 2000的套接字架构有基本的了解是很有帮助的。下图是Windows 2000中的Winsock架构：<br><img height=231 src="http://www.vckbase.net/document/journal/vckbase50/images/winsockfig03.gif" width=200></p>
<p>与其它类型操作系统不同，Windows NT和Windows 2000的传输协议没有一种风格像套接字那样的、可以和应用程序直接交谈的界面，而是采用了一种更为底层的API，叫做传输驱动程序界面(Transport Driver Interface,TDI)。Winsock的核心模式驱动程序负责连接和缓冲区管理，以便向应用程序提供套接字仿真(在AFD.SYS文件中实现)，同时负责与底层传输驱动程序对话。</p>
<p><strong><font color=#009999>谁来负责管理缓冲区？</font></strong><br><br>正如上面所说的，应用程序通过Winsock来和传输协议驱动程序交谈，而AFD.SYS负责为应用程序进行缓冲区管理。也就是说，当应用程序调用send()或WSASend()函数来发送数据时，AFD.SYS将把数据拷贝进它自己的内部缓冲区(取决于SO_SNDBUF设定值)，然后send()或WSASend()函数立即返回。也可以这么说，AFD.SYS在后台负责把数据发送出去。不过，如果应用程序要求发出的数据超过了SO_SNDBUF设定的缓冲区大小，那么WSASend()函数会阻塞，直至所有数据发送完毕。</p>
<p>从远程客户端接收数据的情况也类似。只要不用从应用程序那里接收大量的数据，而且没有超出SO_RCVBUF设定的值，AFD.SYS将把数据先拷贝到其内部缓冲区中。当应用程序调用recv()或WSARecv()函数时，数据将从内部缓冲拷贝到应用程序提供的缓冲区。</p>
<p>多数情况下，这样的架构运行良好，特别在是应用程序采用传统的套接字下非重叠的send()和receive()模式编写的时候。不过程序员要小心的是，尽管可以通过setsockopt()这个API来把SO_SNDBUF和SO_RCVBUF选项值设成0(关闭内部缓冲区)，但是程序员必须十分清楚把AFD.SYS的内部缓冲区关掉会造成什么后果，避免收发数据时有关的缓冲区拷贝可能引起的系统崩溃。</p>
<p>举例来说，一个应用程序通过设定SO_SNDBUF为0把缓冲区关闭，然后发出一个阻塞send()调用。在这样的情况下，系统内核会把应用程序的缓冲区锁定，直到接收方确认收到了整个缓冲区后send()调用才返回。似乎这是一种判定你的数据是否已经为对方全部收到的简洁的方法，实际上却并非如此。想想看，即使远端TCP通知数据已经收到，其实也根本不代表数据已经成功送给客户端应用程序，比如对方可能发生资源不足的情况，导致AFD.SYS不能把数据拷贝给应用程序。另一个更要紧的问题是，在每个线程中每次只能进行一次发送调用，效率极其低下。</p>
<p>把SO_RCVBUF设为0，关闭AFD.SYS的接收缓冲区也不能让性能得到提升，这只会迫使接收到的数据在比Winsock更低的层次进行缓冲，当你发出receive调用时，同样要进行缓冲区拷贝，因此你本来想避免缓冲区拷贝的阴谋不会得逞。</p>
<p>现在我们应该清楚了，关闭缓冲区对于多数应用程序而言并不是什么好主意。只要要应用程序注意随时在某个连接上保持几个WSARecvs重叠调用，那么通常没有必要关闭接收缓冲区。如果AFD.SYS总是有由应用程序提供的缓冲区可用，那么它将没有必要使用内部缓冲区。</p>
<p>高性能的服务器应用程序可以关闭发送缓冲区，同时不会损失性能。不过，这样的应用程序必须十分小心，保证它总是发出多个重叠发送调用，而不是等待某个重叠发送结束了才发出下一个。如果应用程序是按一个发完再发下一个的顺序来操作，那浪费掉两次发送中间的空档时间，总之是要保证传输驱动程序在发送完一个缓冲区后，立刻可以转向另一个缓冲区。</p>
<p><font color=#009999><strong>资源的限制条件</strong></font></p>
<p>在设计任何服务器应用程序时，其强健性是主要的目标。也就是说，</p>
<p>你的应用程序要能够应对任何突发的问题，例如并发客户请求数达到峰值、可用内存临时出现不足、以及其它短时间的现象。这就要求程序的设计者注意Windows NT和2000系统下的资源限制条件的问题，从容地处理突发性事件。</p>
<p>你可以直接控制的、最基本的资源就是网络带宽。通常，使用用户数据报协议(UDP)的应用程序都可能会比较注意带宽方面的限制，以最大限度地减少包的丢失。然而，在使用TCP连接时，服务器必须十分小心地控制好，防止网络带宽过载超过一定的时间，否则将需要重发大量的包或造成大量连接中断。关于带宽管理的方法应根据不同的应用程序而定，这超出了本文讨论的范围。</p>
<p>虚拟内存的使用也必须很小心地管理。通过谨慎地申请和释放内存，或者应用lookaside lists(一种高速缓存)技术来重新使用已分配的内存，将有助于控制服务器应用程序的内存开销(原文为&#8220;让服务器应用程序留下的脚印小一点&#8221;)，避免操作系统频繁地将应用程序申请的物理内存交换到虚拟内存中(原文为&#8220;让操作系统能够总是把更多的应用程序地址空间更多地保留在内存中&#8221;)。你也可以通过SetWorkingSetSize()这个Win32 API让操作系统分配给你的应用程序更多的物理内存。</p>
<p>在使用Winsock时还可能碰到另外两个非直接的资源不足情况。一个是被锁定的内存页面的极限。如果你把AFD.SYS的缓冲关闭，当应用程序收发数据时，应用程序缓冲区的所有页面将被锁定到物理内存中。这是因为内核驱动程序需要访问这些内存，在此期间这些页面不能交换出去。如果操作系统需要给其它应用程序分配一些可分页的物理内存，而又没有足够的内存时就会发生问题。我们的目标是要防止写出一个病态的、锁定所有物理内存、让系统崩溃的程序。也就是说，你的程序锁定内存时，不要超出系统规定的内存分页极限。</p>
<p>在Windows NT和2000系统上，所有应用程序总共可以锁定的内存大约是物理内存的1/8(不过这只是一个大概的估计，不是你计算内存的依据)。如果你的应用程序不注意这一点，当你的发出太多的重叠收发调用，而且I/O没来得及完成时，就可能偶尔发生ERROR_INSUFFICIENT_RESOURCES的错误。在这种情况下你要避免过度锁定内存。同时要注意，系统会锁定包含你的缓冲区所在的整个内存页面，因此缓冲区靠近页边界时是有代价的(译者理解，缓冲区如果正好超过页面边界，那怕是1个字节，超出的这个字节所在的页面也会被锁定)。</p>
<p>另外一个限制是你的程序可能会遇到系统未分页池资源不足的情况。所谓未分页池是一块永远不被交换出去的内存区域，这块内存用来存储一些供各种内核组件访问的数据，其中有的内核组件是不能访问那些被交换出去的页面空间的。Windows NT和2000的驱动程序能够从这个特定的未分页池分配内存。</p>
<p>当应用程序创建一个套接字(或者是类似的打开某个文件)时，内核会从未分页池中分配一定数量的内存，而且在绑定、连接套接字时，内核又会从未分页池中再分配一些内存。当你注意观察这种行为时你将发现，如果你发出某些I/O请求时(例如收发数据)，你会从未分页池里再分配多一些内存(比如要追踪某个待决的I/O操作，你可能需要给这个操作添加一个自定义结构，如前文所提及的)。最后这就可能会造成一定的问题，操作系统会限制未分页内存的用量。</p>
<p>在Windows NT和2000这两种操作系统上，给每个连接分配的未分页内存的具体数量是不同的，未来版本的Windows很可能也不同。为了使应用程序的生命期更长，你就不应该计算对未分页池内存的具体需求量。</p>
<p>你的程序必须防止消耗到未分页池的极限。当系统中未分页池剩余空间太小时，某些与你的应用程序毫无关系的内核驱动就会发疯，甚至造成系统崩溃，特别是当系统中有第三方设备或驱动程序时，更容易发生这样的惨剧(而且无法预测)。同时你还要记住，同一台电脑上还可能运行有其它同样消耗未分页池的其它应用程序，因此在设计你的应用程序时，对资源量的预估要特别保守和谨慎。</p>
<p>处理资源不足的问题是十分复杂的，因为发生上述情况时你不会收到特别的错误代码，通常你只能收到一般性的WSAENOBUFS或者ERROR_INSUFFICIENT_RESOURCES 错误。要处理这些错误，首先，把你的应用程序工作配置调整到合理的最大值(译者注：所谓工作配置，是指应用程序各部分运行中所需的内存用量，请参考 <a href="http://msdn.microsoft.com/msdnmag/issues/1000/Bugslayer/Bugslayer1000.asp" target=_blank><u><font color=#0000ff>http://msdn.microsoft.com/msdnmag/issues/1000/Bugslayer/Bugslayer1000.asp</font></u></a> ，关于内存优化，译者另有译文)，如果错误继续出现，那么注意检查是否是网络带宽不足的问题。之后，请确认你没有同时发出太多的收发调用。最后，如果还是收到资源不足的错误，那就很可能是遇到了未分页内存池不足的问题了。要释放未分页内存池空间，请关闭应用程序中相当部分的连接，等待系统自行渡过和修正这个瞬时的错误。<br><br><strong><font color=#009999>接受连接请求</font></strong><br><br>服务器要做的最普通的事情之一就是接受来自客户端的连接请求。在套接字上使用重叠I/O接受连接的惟一API就是AcceptEx()函数。有趣的是，通常的同步接受函数accept()的返回值是一个新的套接字，而AcceptEx()函数则需要另外一个套接字作为它的参数之一。这是因为AcceptEx()是一个重叠操作，所以你需要事先创建一个套接字(但不要绑定或连接它)，并把这个套接字通过参数传给AcceptEx()。以下是一小段典型的使用AcceptEx()的伪代码： </p>
<pre>do {
-等待上一个 AcceptEx 完成
-创建一个新套接字并与完成端口进行关联
-设置背景结构等等
-发出一个 AcceptEx 请求
}while(TRUE);</pre>
作为一个高响应能力的服务器，它必须发出足够的AcceptEx调用，守候着，一旦出现客户端连接请求就立刻响应。至于发出多少个AcceptEx才够，就取决于你的服务器程序所期待的通信交通类型。比如，如果进入连接率高的情况(因为连接持续时间较短，或者出现交通高峰)，那么所需要守候的AcceptEx当然要比那些偶尔进入的客户端连接的情况要多。聪明的做法是，由应用程序来分析交通状况，并调整AcceptEx守候的数量，而不是固定在某个数量上。
<p>对于Windows2000，Winsock提供了一些机制，帮助你判定AcceptEx的数量是否足够。这就是，在创建监听套接字时创建一个事件，通过WSAEventSelect()这个API并注册FD_ACCEPT事件通知来把套接字和这个事件关联起来。一旦系统收到一个连接请求，如果系统中没有AcceptEx()正在等待接受连接，那么上面的事件将收到一个信号。通过这个事件，你就可以判断你有没有发出足够的AcceptEx()，或者检测出一个非正常的客户请求(下文述)。这种机制对Windows NT 4.0不适用。</p>
<p>使用AcceptEx()的一大好处是，你可以通过一次调用就完成接受客户端连接请求和接受数据(通过传送lpOutputBuffer参数)两件事情。也就是说，如果客户端在发出连接的同时传输数据，你的AcceptEx()调用在连接创建并接收了客户端数据后就可以立刻返回。这样可能是很有用的，但是也可能会引发问题，因为AcceptEx()必须等全部客户端数据都收到了才返回。具体来说，如果你在发出AcceptEx()调用的同时传递了lpOutputBuffer参数，那么AcceptEx()不再是一项原子型的操作，而是分成了两步：接受客户连接，等待接收数据。当缺少一种机制来通知你的应用程序所发生的这种情况：&#8220;连接已经建立了，正在等待客户端数据&#8221;，这将意味着有可能出现客户端只发出连接请求，但是不发送数据。如果你的服务器收到太多这种类型的连接时，它将拒绝连接更多的合法客户端请求。这就是黑客进行&#8220;拒绝服务&#8221;攻击的常见手法。</p>
<p>要预防此类攻击，接受连接的线程应该不时地通过调用getsockopt()函数(选项参数为SO_CONNECT_TIME)来检查AcceptEx()里守候的套接字。getsockopt()函数的选项值将被设置为套接字被连接的时间，或者设置为-1(代表套接字尚未建立连接)。这时，WSAEventSelect()的特性就可以很好地利用来做这种检查。如果发现连接已经建立，但是很久都没有收到数据的情况，那么就应该终止连接，方法就是关闭作为参数提供给AcceptEx()的那个套接字。注意，在多数非紧急情况下，如果套接字已经传递给AcceptEx()并开始守候，但还未建立连接，那么你的应用程序不应该关闭它们。这是因为即使关闭了这些套接字，出于提高系统性能的考虑，在连接进入之前，或者监听套接字自身被关闭之前，相应的内核模式的数据结构也不会被干净地清除。</p>
<p>发出AcceptEx()调用的线程，似乎与那个进行完成端口关联操作、处理其它I/O完成通知的线程是同一个，但是，别忘记线程里应该尽力避免执行阻塞型的操作。Winsock2分层结构的一个副作用是调用socket()或WSASocket() API的上层架构可能很重要(译者不太明白原文意思，抱歉)。每个AcceptEx()调用都需要创建一个新套接字，所以最好有一个独立的线程专门调用AcceptEx()，而不参与其它I/O处理。你也可以利用这个线程来执行其它任务，比如事件记录。</p>
<p>有关AcceptEx()的最后一个注意事项：要实现这些API，并不需要其它提供商提供的Winsock2实现。这一点对微软特有的其它API也同样适用，比如TransmitFile()和GetAcceptExSockAddrs()，以及其它可能会被加入到新版Windows的API. 在Windows NT和2000上，这些API是在微软的底层提供者DLL(mswsock.dll)中实现的，可通过与mswsock.lib编译连接进行调用，或者通过WSAIoctl() (选项参数为SIO_GET_EXTENSION_FUNCTION_POINTER)动态获得函数的指针。</p>
<p>如果在没有事先获得函数指针的情况下直接调用函数(也就是说，编译时静态连接mswsock.lib，在程序中直接调用函数)，那么性能将很受影响。因为AcceptEx()被置于Winsock2架构之外，每次调用时它都被迫通过WSAIoctl()取得函数指针。要避免这种性能损失，需要使用这些API的应用程序应该通过调用WSAIoctl()直接从底层的提供者那里取得函数的指针。<br><br>参见下图套接字架构： <br><img height=231 src="http://www.vckbase.net/document/journal/vckbase50/images/winsockfig03.gif" width=200> <br><br><br><strong><font color=#009999>TransmitFile 和 TransmitPackets </font></strong><br><br>Winsock 提供两个专门为文件和内存数据传输进行了优化的函数。其中TransmitFile()这个API函数在Windows NT 4.0 和 Windows 2000上都可以使用，而TransmitPackets()则将在未来版本的Windows中实现。</p>
<p>TransmitFile()用来把文件内容通过Winsock进行传输。通常发送文件的做法是，先调用CreateFile()打开一个文件，然后不断循环调用ReadFile() 和WSASend ()直至数据发送完毕。但是这种方法很没有效率，因为每次调用ReadFile() 和 WSASend ()都会涉及一次从用户模式到内核模式的转换。如果换成TransmitFile()，那么只需要给它一个已打开文件的句柄和要发送的字节数，而所涉及的模式转换操作将只在调用CreateFile()打开文件时发生一次，然后TransmitFile()时再发生一次。这样效率就高多了。</p>
<p>TransmitPackets()比TransmitFile()更进一步，它允许用户只调用一次就可以发送指定的多个文件和内存缓冲区。函数原型如下：<br></p>
<pre>BOOL TransmitPackets(
SOCKET hSocket,
LPTRANSMIT_PACKET_ELEMENT lpPacketArray,
DWORD nElementCount,
DWORD nSendSize,
LPOVERLAPPED lpOverlapped,
DWORD dwFlags
); </pre>
其中，lpPacketArray是一个结构的数组，其中的每个元素既可以是一个文件句柄或者内存缓冲区，该结构定义如下：<br>
<pre>typedef struct _TRANSMIT_PACKETS_ELEMENT {
DWORD dwElFlags;
DWORD cLength;
union {
struct {
LARGE_INTEGER     nFileOffset;
HANDLE            hFile;
};
PVOID             pBuffer;
};
} TRANSMIT_FILE_BUFFERS;
</pre>
其中各字段是自描述型的(self explanatory)。<br>dwElFlags字段：指定当前元素是一个文件句柄还是内存缓冲区(分别通过常量TF_ELEMENT_FILE 和TF_ELEMENT_MEMORY指定)；<br>cLength字段：指定将从数据源发送的字节数(如果是文件，这个字段值为0表示发送整个文件)；<br>结构中的无名联合体：包含文件句柄的内存缓冲区(以及可能的偏移量)。
<p>使用这两个API的另一个好处，是可以通过指定TF_REUSE_SOCKET和TF_DISCONNECT标志来重用套接字句柄。每当API完成数据的传输工作后，就会在传输层级别断开连接，这样这个套接字就又可以重新提供给AcceptEx()使用。采用这种优化的方法编程，将减轻那个专门做接受操作的线程创建套接字的压力(前文述及)。</p>
<p>这两个API也都有一个共同的弱点：Windows NT Workstation 或 Windows 2000 专业版中，函数每次只能处理两个调用请求，只有在Windows NT、Windows 2000服务器版、Windows 2000高级服务器版或 Windows 2000 Data Center中才获得完全支持。</p>
<p><font color=#009999><strong>放在一起看看</strong></font></p>
<p>以上各节中，我们讨论了开发高性能的、大响应规模的应用程序所需的函数、方法和可能遇到的资源瓶颈问题。这些对你意味着什么呢？其实，这取决于你如何构造你的服务器和客户端。当你能够在服务器和客户端设计上进行更好地控制时，那么你越能够避开瓶颈问题。</p>
<p>来看一个示范的环境。我们要设计一个服务器来响应客户端的连接、发送请求、接收数据以及断开连接。那么，服务器将需要创建一个监听套接字，把它与某个完成端口进行关联，为每颗CPU创建一个工作线程。再创建一个线程专门用来发出AcceptEx()。我们知道客户端会在发出连接请求后立刻传送数据，所以如果我们准备好接收缓冲区会使事情变得更为容易。当然，不要忘记不时地轮询AcceptEx()调用中使用的套接字(使用SO_CONNECT_TIME选项参数)来确保没有恶意超时的连接。</p>
<p>该设计中有一个重要的问题要考虑，我们应该允许多少个AcceptEx()进行守候。这是因为，每发出一个AcceptEx()时我们都同时需要为它提供一个接收缓冲区，那么内存中将会出现很多被锁定的页面(前文说过了，每个重叠操作都会消耗一小部分未分页内存池，同时还会锁定所有涉及的缓冲区)。这个问题很难回答，没有一个确切的答案。最好的方法是把这个值做成可以调整的，通过反复做性能测试，你就可以得出在典型应用环境中最佳的值。</p>
<p>好了，当你测算清楚后，下面就是发送数据的问题了，考虑的重点是你希望服务器同时处理多少个并发的连接。通常情况下，服务器应该限制并发连接的数量以及等候处理的发送调用。因为并发连接数量越多，所消耗的未分页内存池也越多；等候处理的发送调用越多，被锁定的内存页面也越多(小心别超过了极限)。这同样也需要反复测试才知道答案。</p>
<p>对于上述环境，通常不需要关闭单个套接字的缓冲区，因为只在AcceptEx()中有一次接收数据的操作，而要保证给每个到来的连接提供接收缓冲区并不是太难的事情。但是，如果客户机与服务器交互的方式变一变，客户机在发送了一次数据之后，还需要发送更多的数据，在这种情况下关闭接收缓冲就不太妙了，除非你想办法保证在每个连接上都发出了重叠接收调用来接收更多的数据。</p>
<p><font color=#009999><strong>结论</strong></font></p>
<p>开发大响应规模的Winsock服务器并不是很可怕，其实也就是设置一个监听套接字、接受连接请求和进行重叠收发调用。通过设置合理的进行守候的重叠调用的数量，防止出现未分页内存池被耗尽，这才是最主要的挑战。按照我们前面讨论的一些原则，你就可以开发出大响应规模的服务器应用程序。<br></p>
<img src ="http://www.cppblog.com/yishanhante/aggbug/22302.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-04-19 14:05 <a href="http://www.cppblog.com/yishanhante/articles/22302.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Overlapped I/O</title><link>http://www.cppblog.com/yishanhante/articles/21606.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Tue, 10 Apr 2007 10:06:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/21606.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/21606.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/21606.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/21606.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/21606.html</trackback:ping><description><![CDATA[&nbsp;<font size=2>重叠I/O也是一种异步I/O,同样也支持Win32的其它对象,当然在Winsock中可以发挥很大的作用。使用Overlapped开发支持一定数量的Socket的应用，效率是相当很高的。但就我个人的观点，在Win32下做网络应用的开发，如果要支持100个以上的Socket的话，还是考虑Completion Port I/O。要求支持Socket最好是100个以下，我是基于这样考虑的：Overlapped是通过多线程支持多Socket的，如果开辟的线程太多的话，势必影响了系统的性能；Completion Port I/O可以更好支持大量的客户端。这两种在Windows下具有高效率的I/O都不支持Windows CE及其它平台。我在这里说一下在开发网络应用时什么时候用Overlapped I/O 模型：准备在Win98和以上版本或WinNT3.1和以上版本做开发，且要求支持Socket最好在100个以下。另外在串口开发中，考虑效率问题，有很多地方用到了Overlapped I/O。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Overlapped I/O主要涉及一个数据结构Overlapped(Winsock中是WSAOverlapped)和一个函数WSAGetOverlappedResult(..)。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Overlapped I/O执行步骤很清晰，只要下面三步：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1) 应用先通过WSASend或WSARecv(不知道有没有其它的请求，我只用过这两个函数)，注意要向两者转入WSAOverlapped参数，<br>表示，执行的是Overlapped操作；<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2)在一个循环中，调用GetOverlappedResult(..)等待操作完成，GetOverlappedResult返回时，进行相应的处理，如处理数据；<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3) 最后，还在(2)循环中，发送另外一个请求(WSASend或WSARecv),重复处理(2)、(3)两步。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 第一步中执行WSASend或WSARecv时，函数立即返回，得到SOCKET_ERROR信息且此时WSAGetErrorLast返回WSA_IO_PENDING，说明调用已成功，Winsock正在处理WSASend或WSARecv的请求。个人认为Winsock在内部开辟了新的线程处理，应用程序不用管理多线程，达到异步的目的，有利于性能的提高。WSASend或WSARecv也可能返回"0"，表示立即成功，这时，应用还是可以在WSAGetOverlappedResult()处等待，处理过程与上面是一样的；也就是说我们不须要马上在WSASend或WSARecv进行相关的处理。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;WSAGetOverlappedResult返回FASLE且WSAGetLastError返回WSA_IO_INCOMPLETE，表示处理正在进行中。&nbsp;&nbsp;&nbsp;</font>
<p><font size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 下面我给出支持单个Socket及支持多个Socket的Console程序代码。先来看看支持单个Socket的程序,考虑到代码简洁性，只给一个框架，同时不进行出错处理。<br>int main()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSAOVERLAPPED&nbsp;&nbsp;&nbsp;&nbsp; Overlapped;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 启动Winsock及建立监听套接字<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; listen(hListenSocket,&nbsp; 5);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;hClientSocket&nbsp;&nbsp; = &nbsp;&nbsp;accept(hListenSocket,&nbsp; &nbsp;NULL,&nbsp;&nbsp; NULL);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;ZeroMemory(&amp;Overlapped,&nbsp;&nbsp; sizeof(WSAOVERLAPPED));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;nResult&nbsp; &nbsp;=&nbsp; &nbsp;WSARecv(...);&nbsp;&nbsp;&nbsp; // 发出请求<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for (; ;)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;bResult &nbsp; =&nbsp; &nbsp;WSAGetOverlappedResult(...);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 函数返回后进行相应的处理<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nResult&nbsp; &nbsp;=&nbsp;&nbsp; WSARecv(...);&nbsp; // 发出另外一个请求<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br></font><font size=2>}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 上面的程序只是想说明一下过程，程序没有实现什么功能。这样做的目的是节约字数，用来说明我下面支持多个Socket的Console应用。请继续看。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;先看一个自定义的结构体，单句柄数据结构，通过该结构,主线程与某个特定的子线程中的套接字相互联系。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; typedef&nbsp; &nbsp;struct&nbsp;&nbsp; &nbsp;_PER_HANDLE_DATA<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;SOCKET&nbsp;&nbsp; hSocket;&nbsp;&nbsp;&nbsp;&nbsp; // 主键:通信套接字<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char&nbsp;&nbsp; &nbsp;szClientIP[16];// 自定义字段:客户端地址<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int nOperateType;&nbsp;&nbsp; // 自定义字段:操作类型<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;}PER_HANDLE_DATA,&nbsp;&nbsp; FAR* &nbsp;LPPER_HANDLE_DATA;<br>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 在上面的结构中还可以加入自己需要的字段。在我下面的例子程序中，szClientIP是用来保存连接上来的客户端的IP的，这样在主线程将这个结构体传给子线程后，在子线程中根据客户端IP就知道目前处理的是哪个客户端了。下面是程序的大部分，同样除去一些简单的出错输出。<br>#define&nbsp;&nbsp;&nbsp;&nbsp; LISTEN_PORT 5000<br>#define&nbsp;&nbsp;&nbsp;&nbsp; DATA_BUFSIZE 8192<br>#define&nbsp;&nbsp;&nbsp;&nbsp; POST_RECV 0X01&nbsp;&nbsp;&nbsp;<br>#define&nbsp;&nbsp;&nbsp;&nbsp; POST_SEND 0X02</font></p>
<p><font size=2>int&nbsp;&nbsp; main(&nbsp; )<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;LPPER_HANDLE_DATA&nbsp;&nbsp;&nbsp; lpPerHandleData;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hListenSocket;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hClientSocket;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SOCKADDR_IN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ClientAddr;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nAddrLen;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;HANDLE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hThread;&nbsp;</font></p>
<p><font size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Start WinSock and create a listen socket.</font></p>
<p><font size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;listen(hListenSocket, &nbsp;5);&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (; ;)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nAddrLen&nbsp; = &nbsp;sizeof(SOCKADDR);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hClientSocket &nbsp;= &nbsp;accept(hListenSocket, &nbsp;(LPSOCKADDR)&amp;ClientAddr, &nbsp;&amp;nAddrLen);</font></p>
<p><font size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerHandleData = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;lpPerHandleData-&gt;hSocket&nbsp; = &nbsp;hClientSocket;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;// 注意这里将连接的客户端的IP地址，保存到了lpPerHandleData字段中了<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;strcpy(lpPerHandleData-&gt;szClientIP,&nbsp; &nbsp;inet_ntoa(ClientAddr.sin_addr));</font></p>
<p><font size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;// 为本次客户请求产生子线程<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hThread = CreateThread(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;NULL,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OverlappedThread,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;lpPerHandleData,&nbsp;&nbsp; // 将lpPerHandleData传给子线程<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;CloseHandle(hThread);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; <br>&nbsp;return 0;<br>}</font></p>
<p><font size=2>DWORD&nbsp; &nbsp;WINAPI&nbsp;&nbsp; OverlappedThread(LPVOID&nbsp;&nbsp;&nbsp; lpParam)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;LPPER_HANDLE_DATA&nbsp;&nbsp;&nbsp;&nbsp; lpPerHandleData&nbsp; &nbsp;=&nbsp; &nbsp;(LPPER_HANDLE_DATA)lpParam;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;WSAOVERLAPPED Overlapped;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;WSABUF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wsaBuf;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Buffer[DATA_BUFSIZE];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BOOL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bResult;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nResult;<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;ZeroMemory(&amp;Overlapped, sizeof(WSAOVERLAPPED));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;wsaBuf.buf = Buffer;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wsaBuf.len = sizeof(Buffer);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerHandleData-&gt;nOperateType = POST_RECV;&nbsp;&nbsp;&nbsp;&nbsp; // 记录本次操作是Recv(..)</font></p>
<p><font size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;dwFlags = 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;nResult = WSARecv(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerHandleData-&gt;hSocket,&nbsp;&nbsp; // Receive socket<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;wsaBuf,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 指向WSABUF结构的指针<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // WSABUF数组的个数<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&amp;dwNumOfBytesRecved,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 存放当WSARecv完成后所接收到的字节数<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;dwFlags,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // A pointer to flags<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&amp;Overlapped,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // A pointer to a WSAOVERLAPPED structure <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // A pointer to the completion routine,this is NULL <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp; &nbsp;( nResult&nbsp;&nbsp; ==&nbsp; &nbsp;SOCKET_ERROR&nbsp;&nbsp;&nbsp; &nbsp;&amp;&amp;&nbsp;&nbsp; GetLastError() !=&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSA_IO_PENDING)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("WSARecv(..) failed.\n");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; free(lpPerHandleData);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;while (TRUE)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bResult&nbsp; = &nbsp;WSAGetOverlappedResult(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerHandleData-&gt;hSocket,&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;Overlapped,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;dwBytesTransferred,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 当一个同步I/O完成后,接收到的字节数<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;TRUE,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 等待I/O操作的完成 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&amp;dwFlags&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if&nbsp; &nbsp;(bResult &nbsp;== &nbsp;FALSE &nbsp;&amp;&amp; &nbsp;WSAGetLastError() &nbsp;!= &nbsp;WSA_IO_INCOMPLETE)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;printf("WSAGetOverlappdResult(..) failed.\n");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; free(lpPerHandleData);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 1;&nbsp;&nbsp; // 错误退出<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp; (dwBytesTransferred == 0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("客户端已退出,将断开与之的连接!\n");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; closesocket(lpPerHandleData-&gt;hSocket);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; free(lpPerHandleData);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 在这里将接收到的数据进行处理<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("Received from IP: %s.\ndata: %s\n", lpPerHandleData-&gt;szClientIP, wsaBuf.buf);&nbsp;&nbsp;&nbsp;&nbsp; </font></p>
<p><font size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 发送另外一个请求操作<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ZeroMemory(&amp;Overlapped, sizeof(WSAOVERLAPPED));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerHandleData-&gt;nOperateType = POST_RECV;</font></p>
<p><font size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;dwFlags = 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;nResult = WSARecv(...);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (...)</font><font size=2>{}</font></p>
<p><font size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;}</font></p>
<p><font size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br>}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;程序结构比较清晰，lpPerHandleData是主线程与子线程联系的纽带，子线程是通过这个结构获得所处理客户端的情况的。在不同的应用中可以将这个结构定义成不同的形式，以符合所实现应用的需要。注意结构体的nOperateType在GetOverlappedResult返回时用到,可以根据这个字段确定我们下一步的操作。请朋友们多提意见了。</font><font size=2><br></font></p>
<img src ="http://www.cppblog.com/yishanhante/aggbug/21606.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-04-10 18:06 <a href="http://www.cppblog.com/yishanhante/articles/21606.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Winsock 完成端口模型简介</title><link>http://www.cppblog.com/yishanhante/articles/19544.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Sat, 10 Mar 2007 09:51:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19544.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19544.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19544.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19544.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19544.html</trackback:ping><description><![CDATA[
		<p>摘自《Networking Programming for Microsoft Windows》第八章</p>
		<p>“完成端口”模型是迄今为止最为复杂的一种I/O模型。然而，假若一个应用程序同时需要管理为数众多的套接字，那么采用这种模型，往往可以达到最佳的系统性能！</p>
		<p>从本质上说，完成端口模型要求我们创建一个Win32完成端口对象，通过指定数量的线程，对重叠I/O请求进行管理，以便为已经完成的重叠I/O请求提供服务。</p>
		<p>使用这种模型之前，首先要创建一个I/O完成端口对象，用它面向任意数量的套接字句柄，管理多个I/O请求。要做到这一点，需要调用CreateCompletionPort函数。<br />该函数定义如下：</p>
		<p>
				<br />HANDLE CreateIoCompletionPort(<br />    HANDLE FileHandle,<br />    HANDLE ExistingCompletionPort,<br />    ULONG_PTR CompletionKey,<br />    DWORD NumberOfConcurrentThreads<br />);</p>
		<p>在我们深入探讨其中的各个参数之前，首先要注意该函数实际用于两个明显有别的目的：<br />1. 用于创建一个完成端口对象。<br />2. 将一个句柄同完成端口关联到一起。 </p>
		<p>最开始创建一个完成端口时，唯一感兴趣的参数便是NumberOfConcurrentThreads（并发线程的数量）；前面三个参数都会被忽略。NumberOfConcurrentThreads参数的特殊之处在于，它定义了在一个完成端口上，同时允许执行的线程数量。理想情况下，我们希望每个处理器各自负责一个线程的运行，为完成端口提供服务，避免过于频繁的线程“场景”切换。若将该参数设为0，表明系统内安装了多少个处理器，便允许同时运行多少个线程！可用下述代码创建一个I/O完成端口：</p>
		<p>
				<br />hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);</p>
		<p>该语句的作用是返回一个句柄，在为完成端口分配了一个套接字句柄后，用来对那个端口进行标定（引用）。 </p>
		<p>一、工作者线程与完成端口<br />成功创建一个完成端口后，便可开始将套接字句柄与对象关联到一起。但在关联套接字之前，首先必须创建一个或多个“工作者线程”，以便在I/O请求投递给完成端口对象后，为完成端口提供服务。在这个时候，大家或许会觉得奇怪，到底应创建多少个线程，以便为完成端口提供服务呢？这实际正是完成端口模型显得颇为“复杂”的一个方面，因为服务I/O请求所需的数量取决于应用程序的总体设计情况。在此要记住的一个重点在于，在我们调用CreateIoCompletionPort时指定的并发线程数量，与打算创建的工作者线程数量相比，它们代表的并非同一件事情。早些时候，我们曾建议大家用CreateIoCompletionPort函数为每个处理器<br />都指定一个线程（处理器的数量有多少，便指定多少线程）以避免由于频繁的线程“场景”交换活动，从而影响系统的整体性能。CreateIoCompletionPort函数的NumberOfConcurrentThreads参数明确指示系统：在一个完成端口上，一次只允许n个工作者线程运行。假如在完成端口上创建的工作者线程数量超出n个，那么在同一时刻，最多只允许n个线程运行。但实际上，在一段较短的时间内，系统有可能超过这个值，但很快便会把它减少至事先在CreateIoCompletionPort函数中设定的值。那么，为何实际创建的工作者线程数量有时要比CreateIoCompletionPort函数设定的多一些呢？这样做有必要吗？如先前所述，这主要取决于<br />应用程序的总体设计情况。假定我们的某个工作者线程调用了一个函数，比如Sleep或WaitForSingleObject，但却进入了暂停（锁定或挂起）状态，那么允许另一个线程代替它的位置。换言之，我们希望随时都能执行尽可能多的线程；当然，最大的线程数量是事先在CreateIoCompletionPort调用里设定好的。这样一来，假如事先预计到自己的线程有可能暂时处于停顿状态，那么最好能够创建比CreateIoCompletionPort的NumberOfConcurrentThreads参数的值多的线程，以便到时候充分发挥系统的潜力。一旦在完成端口上拥有足够多的工作者线程来为I/O请求提供服务，便可着手将套接字句柄同完成端口关联到一起。这要求我们在一个现有的完成端口上，调用CreateIoCompletionPort函数，同时为前三个参数——FileHandle，ExistingCompletionPort和CompletionKey——提供套接字的信息。其中， FileHandle参数指定一个要同完成端口关联在一起的套接字句柄。ExistingCompletionPort参数指定的是一个现有的完成端口。CompletionKey（完成键）参数则指定要与某个特定套接字句柄关联在一起的“单句柄数据”；在这个参数中，应用程序可保存与一个套接字对应的任意类型的信息。之所以把它叫作“单句柄数据”，是由于它只对<br />应着与那个套接字句柄关联在一起的数据。可将其作为指向一个数据结构的指针，来保存套接字句柄；在那个结构中，同时包含了套接字的句柄，以及与那个套接字有关的其他信息。</p>
		<p>根据我们到目前为止学到的东西，首先来构建一个基本的应用程序框架。下面阐述了如何使用完成端口模型，来开发一个ECHO服务器应用。在这个程序中，我们基本上按下述步骤行事：</p>
		<p>1) 创建一个完成端口。第四个参数保持为0，指定在完成端口上，每个处理器一次只允许执行一个工作者线程。<br />2) 判断系统内到底安装了多少个处理器。<br />3) 创建工作者线程，根据步骤2)得到的处理器信息，在完成端口上，为已完成的I/O请求提供服务。<br />4) 准备好一个监听套接字，在端口5150上监听进入的连接请求。<br />5) 使用accept函数，接受进入的连接请求。<br />6) 创建一个数据结构，用于容纳“单句柄数据”，同时在结构中存入接受的套接字句柄。<br />7) 调用CreateIoCompletionPort，将自accept返回的新套接字句柄同完成端口关联到一起。通过完成键（CompletionKey）参数，将单句柄数据结构传递给CreateIoCompletionPort。<br />8) 开始在已接受的连接上进行I/O操作。在此，我们希望通过重叠I/O机制，在新建的套接字上投递一个或多个异步WSARecv或WSASend请求。这些I/O请求完成后，一个工作者线程会为I/O请求提供服务，同时继续处理未来的I/O请求，稍后便会在步骤3 )指定的工作者例程中，体验到这一点。<br />9) 重复步骤5 ) ~ 8 )，直至服务器中止。</p>
		<p>二、完成端口和重叠I/O<br />将套接字句柄与一个完成端口关联在一起后，便可以套接字句柄为基础，投递发送与接收请求，开始对I/O请求的处理。接下来，可开始依赖完成端口，来接收有关I/O操作完成情况的通知。从本质上说，完成端口模型利用了Win32重叠I/O机制。在这种机制中，象WSASend和WSARecv这样的Winsock API调用会立即返回。此时，需要由我们的应用程序负责在以后的某个时间，通过一个OVERLAPPED结构，来接收调用的结果。在完成端口模型中，要想做到这一点，需要使用GetQueuedCompletionStatus（获取排队完成状态）函数，让一个或多个工作者线程在完成端口上等待。该函数的定义如下：</p>
		<p>
				<br />BOOL GetQueuedCompletionStatus(<br />    HANDLE CompletionPort,<br />    LPDWORD lpNumberOfBytes,<br />    PULONG_PTR lpCompletionKey,<br />    LPOVERLAPPED* lpOverlapped,<br />    DWORD dwMilliseconds<br />);</p>
		<p>其中，CompletionPort参数对应于要在上面等待的完成端口。lpNumberOfBytes参数负责在完成了一次I/O操作后（如WSASend或WSARecv），接收实际传输的字节数。lpCompletionKey参数为原先传递进入CreateIoCompletionPort函数的套接字返回“单句柄数据”。如我们早先所述，大家最好将套接字句柄保存在这个“键”（Key）中。lpOverlapped参数用于接收完成的I/O操作的重叠结果。这实际是一个相当重要的参数，因为可用它获取每个I/O操作的数据。而最后一个参数，dwMilliseconds，用于指定调用者希望等待一个完成数据包在完成端口上出现的时间。假如将其设为INFINITE，调用会无休止地等待下去。 </p>
		<p>三、单句柄数据和单I/O操作数据<br />一个工作者线程从GetQueuedCompletionStatus这个API调用接收到I/O完成通知后，在lpCompletionKey和lpOverlapped参数中，会包含一些必要的套接字信息。利用这些信息，可通过完成端口，继续在一个套接字上的I/O处理。通过这些参数，可获得两方面重要的套接字数据：单句柄数据，以及单I/O操作数据。其中，lpCompletionKey参数包含了“单句柄数据”，因为在一个套接字首次与完成端口关联到一起的时候，那些数据便与一个特定的套接字句柄对应起来了。这些数据正是我们在进行CreateIoCompletionPort API调用的时候，通过CompletionKey参数传递的。如早先所述，应用程序可通过该参数传递任意类型的数据。通常情况下，应用程序会将与I/O请求有关的套接字句柄保存在这里。lpOverlapped参数则包含了一个OVERLAPPED结构，在它后面跟随“单I/O操作数据”。我们的工作者线程处理一个完成数据包时（将数据原封不动打转回去，接受连接，投递另一个线程，等等），这些信息是它必须要知道的。单I/O操作数据可以是追加到一个OVERLAPPED结构末尾的、任意数量的字节。假如一个函数要求用到一个OVERLAPPED结构，我们便必须将这样的一个结构传递进去，以满足它的要求。要想做到这一点，一个简单的方法是定义一个结构，然后将OVERLAPPED结构作为新结构的第一个元素使用。举个例子来说，可定义下述数据结构，实现对单I/O操作数据的管理：</p>
		<p>
				<br />typedef struct<br />{<br />    OVERLAPPED Overlapped;<br />    WSABUF     DataBuf;<br />    CHAR       Buffer[DATA_BUFSIZE];<br />    BOOL       OperationType;<br />}PER_IO_OPERATION_DATA</p>
		<p>该结构演示了通常要与I/O操作关联在一起的某些重要数据元素，比如刚才完成的那个I/O操作的类型（发送或接收请求）。在这个结构中，我们认为用于已完成I/O操作的数据缓冲区是非常有用的。要想调用一个Winsock API函数，同时为其分配一个OVERLAPPED结构，既可将自己的结构“造型”为一个OVERLAPPED指针，亦可简单地撤消对结构中的OVERLAPPED元素的引用。如下例所示：</p>
		<p>PER_IO_OPERATION_DATA PerIoData;<br />// 可像下面这样调用一个函数<br />  WSARecv(socket, ..., (OVERLAPPED *)&amp;PerIoData);<br />// 或像这样<br />  WSARecv(socket, ..., &amp;(PerIoData.Overlapped));</p>
		<p>在工作线程的后面部分，等GetQueuedCompletionStatus函数返回了一个重叠结构（和完成键）后，便可通过撤消对OperationType成员的引用，调查到底是哪个操作投递到了这个句柄之上（只需将返回的重叠结构造型为自己的PER_IO_OPERATION_DATA结构）。对单I/O操作数据来说，它最大的一个优点便是允许我们在同一个句柄上，同时管理多个I/O操作（读/写，多个读，多个写，等等）。大家此时或许会产生这样的疑问：在同一个套接字上，真的有必要同时投递多个I/O操作吗？答案在于系统的“伸缩性”，或者说“扩展能力”。例如，假定我们的机器安装了多个中央处理器，每个处理器都在运行一个工作者线程，那么在同一个时<br />候，完全可能有几个不同的处理器在同一个套接字上，进行数据的收发操作。 </p>
		<p>最后要注意的一处细节是如何正确地关闭I/O完成端口—特别是同时运行了一个或多个线程，在几个不同的套接字上执行I/O操作的时候。要避免的一个重要问题是在进行重叠I/O操作的同时，强行释放一个OVERLAPPED结构。要想避免出现这种情况，最好的办法是针对每个套接字句柄，调用closesocket函数，任何尚未进行的重叠I/O操作都会完成。一旦所有套接字句柄都已关闭，便需在完成端口上，终止所有工作者线程的运行。要想做到这一点， 需要使用PostQueuedCompletionStatus函数，向每个工作者线程都发送一个特殊的完成数据包。该函数会指示每个线程都“立即结束并退出”。下面是PostQueuedCompletionStatus函数的定义：</p>
		<p>
				<br />BOOL PostQueuedCompletionStatus(<br />    HANDLE CompletionPort,<br />    DWORD dwNumberOfBytesTransferred,<br />    ULONG_PTR dwCompletionKey,<br />    LPOVERLAPPED lpOverlapped<br />);</p>
		<p>其中，CompletionPort参数指定想向其发送一个完成数据包的完成端口对象。而就dwNumberOfBytesTransferred、dwCompletionKey和lpOverlapped这三个参数来说，每一个都允许我们指定一个值，直接传递给GetQueuedCompletionStatus函数中对应的参数。这样一来，一个工作者线程收到传递过来的三个GetQueuedCompletionStatus函数参数后，便可根据由这三个参数的某一个设置的特殊值，决定何时应该退出。例如，可用dwCompletionPort参数传递0值，而一个工作者线程会将其解释成中止指令。一旦所有工作者线程都已关闭，便可使用CloseHandle函数，关闭完成端口，最终安全退出程序。</p>
		<p>注：CreateIoCompletionPort ，PostQueuedCompletionStatus ，GetQueuedCompletionStatus 等函数的用法说明。</p>
		<p>Platform SDK: Storage </p>
		<p> </p>
		<p> </p>
		<p>I/O Completion Ports</p>
		<p>I/O completion ports are the mechanism by which an application uses a pool of threads that was created when the application was started to process asynchronous I/O requests. These threads are created for the sole purpose of processing I/O requests. Applications that process many concurrent asynchronous I/O requests can do so more quickly and efficiently by using I/O completion ports than by using creating threads at the time of the I/O request. </p>
		<p> </p>
		<p> </p>
		<p>  </p>
		<p> </p>
		<p> </p>
		<p>I/O完成端口(s)是一种机制，通过这个机制，应用程序在启动时会首先创建一个线程池，然后该应用程序使用线程池处理异步I/O请求。这些线程被创建的唯一目的就是用于处理I/O请求。对于处理大量并发异步I/O请求的应用程序来说，相比于在I/O请求发生时创建线程来说，使用完成端口(s)它就可以做的更快且更有效率。 </p>
		<p> </p>
		<p> </p>
		<p>  </p>
		<p> </p>
		<p> </p>
		<p>The CreateIoCompletionPort function associates an I/O completion port with one or more file handles. When an asynchronous I/O operation started on a file handle associated with a completion port is completed, an I/O completion packet is queued to the port. This can be used to combine the synchronization point for multiple file handles into a single object. </p>
		<p> </p>
		<p> </p>
		<p>  </p>
		<p> </p>
		<p> </p>
		<p>CreateIoCompletionPort函数会使一个I/O完成端口与一个或多个文件句柄发生关联。当与一个完成端口相关的文件句柄上启动的异步I/O操作完成时，一个I/O完成包就会进入到该完成端口的队列中。对于多个文件句柄来说，就可以把这些多个文件句柄合并成一个单独的对象，这个可以被用来结合同步点？ </p>
		<p> </p>
		<p> </p>
		<p>  </p>
		<p> </p>
		<p> </p>
		<p>A thread uses the GetQueuedCompletionStatus function to wait for a completion packet to be queued to the completion port, rather than waiting directly for the asynchronous I/O to complete. Threads that block their execution on a completion port are released in last-in-first-out (LIFO) order. This means that when a completion packet is queued to the completion port, the system releases the last thread to block its execution on the port. </p>
		<p> </p>
		<p> </p>
		<p>调用GetQueuedCompletionStatus函数，某个线程就会等待一个完成包进入到完成端口的队列中，而不是直接等待异步I/O请求完成。线程（们）就会阻塞于它们的运行在完成端口（按照后进先出队列顺序的被释放）。这就意味着当一个完成包进入到完成端口的队列中时，系统会释放最近被阻塞在该完成端口的线程。 </p>
		<p> </p>
		<p> </p>
		<p>  </p>
		<p> </p>
		<p> </p>
		<p>When a thread calls GetQueuedCompletionStatus, it is associated with the specified completion port until it exits, specifies a different completion port, or frees the completion port. A thread can be associated with at most one completion port. </p>
		<p> </p>
		<p> </p>
		<p>调用GetQueuedCompletionStatus，线程就会将会与某个指定的完成端口建立联系，一直延续其该线程的存在周期，或被指定了不同的完成端口，或者释放了与完成端口的联系。一个线程只能与最多不超过一个的完成端口发生联系。 </p>
		<p> </p>
		<p> </p>
		<p>  </p>
		<p> </p>
		<p> </p>
		<p>The most important property of a completion port is the concurrency value. The concurrency value of a completion port is specified when the completion port is created. This value limits the number of runnable threads associated with the completion port. When the total number of runnable threads associated with the completion port reaches the concurrency value, the system blocks the execution of any subsequent threads that specify the completion port until the number of runnable threads associated with the completion port drops below the concurrency value. The most efficient scenario occurs when there are completion packets waiting in the queue, but no waits can be satisfied because the port has reached its concurrency limit. In this case, when a running thread calls GetQueuedCompletionStatus, it will immediately pick up the queued completion packet. No context switches will occur, because the running thread is continually picking up completion packets and the other threads are unable to run. </p>
		<p> </p>
		<p> </p>
		<p>完成端口最重要的特性就是并发量。完成端口的并发量可以在创建该完成端口时指定。该并发量限制了与该完成端口相关联的可运行线程的数目。当与该完成端口相关联的可运行线程的总数目达到了该并发量，系统就会阻塞任何与该完成端口相关联的后续线程的执行，直到与该完成端口相关联的可运行线程数目下降到小于该并发量为止。最有效的假想是发生在有完成包在队列中等待，而没有等待被满足，因为此时完成端口达到了其并发量的极限。此时，一个正在运行中的线程调用GetQueuedCompletionStatus时，它就会立刻从队列中取走该完成包。这样就不存在着环境的切换，因为该处于运行中的线程就会连续不断地从队列中取走完成包，而其他的线程就不能运行了。 </p>
		<p> </p>
		<p> </p>
		<p>  </p>
		<p> </p>
		<p> </p>
		<p>The best value to pick for the concurrency value is the number of CPUs on the machine. If your transaction required a lengthy computation, a larger concurrency value will allow more threads to run. Each transaction will take longer to complete, but more transactions will be processed at the same time. It is easy to experiment with the concurrency value to achieve the best effect for your application. </p>
		<p> </p>
		<p> </p>
		<p>对于并发量最好的挑选值就是您计算机中cpu的数目。如果您的事务处理需要一个漫长的计算时间，一个比较大的并发量可以允许更多线程来运行。虽然完成每个事务处理需要花费更长的时间，但更多的事务可以同时被处理。对于应用程序来说，很容易通过测试并发量来获得最好的效果。 </p>
		<p> </p>
		<p> </p>
		<p>  </p>
		<p> </p>
		<p> </p>
		<p>The PostQueuedCompletionStatus function allows an application to queue its own special-purpose I/O completion packets to the completion port without starting an asynchronous I/O operation. This is useful for notifying worker threads of external events. </p>
		<p> </p>
		<p> </p>
		<p>PostQueuedCompletionStatus函数允许应用程序可以针对自定义的专用I/O完成包进行排队，而无需启动一个异步I/O操作。这点对于通知外部事件的工作者线程来说很有用。 </p>
		<p> </p>
		<p> </p>
		<p>  </p>
		<p> </p>
		<p> </p>
		<p>The completion port is freed when there are no more references to it. The completion port handle and every file handle associated with the completion port reference the completion port. All the handles must be closed to free the completion port. To close the port handle, call the CloseHandle function. </p>
		<p> </p>
		<p> </p>
		<p>在没有更多的引用针对某个完成端口时，需要释放该完成端口。该完成端口句柄以及与该完成端口相关联的所有文件句柄都需要被释放。调用CloseHandle可以释放完成端口的句柄。 <br /></p>
<img src ="http://www.cppblog.com/yishanhante/aggbug/19544.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-10 17:51 <a href="http://www.cppblog.com/yishanhante/articles/19544.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>