《一个简单的完成端口 服务端 客户端 类.doc》由会员分享,可在线阅读,更多相关《一个简单的完成端口 服务端 客户端 类.doc(13页珍藏版)》请在三一文库上搜索。
1、一个简单的完成端口 服务端 客户端 类一个简单的完成端口(服务端/客户端)类2010-06-19 10:01一个简单的完成端口(服务端/客户端)类作者:spinoza翻译:麦子芽儿,POWERCPP(后面部分内容)下载源代码原文网址:源码使用了高级的完成端口(IOCP)技术,该技术可以有效地服务于多客户端。本文提出了一些IOCP编程中出现的实际问题的解决方法,并提供了一个简单的echo版本的可以传输文件的客户端/服务器程序。程序截图:1.1环境要求本文读者需要熟悉C+、TCP/IP、Socket编程、MFC,和多线程。源码使用Winsock 2.0和IOCP技术,要求:Windows NT/2
2、000或以上:要求Windows NT3.5或以后版本Windows 95/98/ME:不支持Visual C+.NET,或完整更新过的Visual C+6.0 1.2摘要当你开发不同类型的软件,你迟早必须处理C/S的开发。对一个程序员来说,写一个通用的C/S编码是一项困难的工作。本文档提供了一份简单但是功能强大的C/S源码,可以扩展到任何类型的C/S应用程序中。这份源码使用了高级的IOCP技术,该技术可以高效的服务于多客户端。IOCP提供了解决每个客户端占用一个线程的瓶颈问题的办法,只使用几个处理线程,异步输入/输出来发送/接收。IOCP技术被广泛应用在各种类型的高效服务端,例如Apache
3、等。这份源码也提供了一系列的在处理通信和C/S软件中经常使用的功能,如文件接收/传送功能和逻辑线程池管理。本文重点在于出现在IOCP程序API中实用的解决方案,以及关于源码的全面的文档。另外,一份简单的echo版的可处理多连接和文件传输的C/S程序也在这里提供。2.1引言本文提出了一个类,可以用在客户端和服务端。这个类使用IOCP(Input Output Completion Ports)和异步(非阻塞)机制。通过这些简单的源码,你可以:服务或连接多客户端和服务端异步发送或接收文件创建并管理一个逻辑工作者线程池,用以处理繁重的客户端/服务器请求或计算找到一份全面但简单的解决客户端/服务器通信
4、的源码是件困难的事情。在网络上找到的源码要么太复杂(超过20个类),要命没有提供足够的效率。本源码的设计尽可能简单,并提供了充足的文档。在这篇文章中,我们简洁的呈现出了Winsock API 2.0支持的IOCP技术,说明了在编写过程中出现的棘手问题,并提出了每一个问题的解决方案。2.2异步完成端口介绍如果一个服务器应用程序不能同时支持多个客户端,那是毫无意义的,为此,通常使用异步I/O请求和多线程。根据定义,一个异步I/O请求会立即返回,而留下I/O请求处于等待状态。有时,I/O异步请求的结果必须与主线程同步。这可以通过几种不同方式解决。同步可以通过下面的方式实现:使用事件当异步请求结束时会
5、马上触发一个信号。这种方式的缺点是线程必须检查并等待事件被触发使用GetOverlappedResult函数这种方式与上一种方式有相同的缺点。使用Asynchronous Procedure Calls(或APC)这种方式有几个缺点。首先,APC总是在请求线程的上下文中被请求;第二,为了执行APC,请求线程必须在可变等候状态下挂起。使用IOCP这种方式的缺点是必须解决很多实际的棘手的编程问题。编写IOCP可能有点麻烦。2.2.1为什么使用IOCP?通过使用IOCP,我们可以解决每个客户端占用一个线程的问题。通常普遍认为如果软件不能运行在真正的多处理器机器上,执行能力会严重降低。线程是系统资源,
6、而这些资源既不是无限的,也不是低价的。IOCP提供了一种方式来使用几个线程公平的处理多客户端的输入/输出。线程被挂起,不占用CPU周期直到有事可做。2.3什么是IOCP?我们已经看到IOCP只是一个线程同步对象,类似于信号灯,因此IOCP并不是一个复杂的概念。一个IOCP对象与几个支持待定异步I/O请求的I/O对象绑定。一个可以访问IOCP的线程可以被挂起,直到一个待定的异步I/O请求结束。3 IOCP是怎样工作的?要使用IOCP,你必须处理三件事情,绑定一个socket到完成端口,创建异步I/O请求,并与线程同步。为从异步I/O请求获得结果,如那个客户端发出的请求,你必须传递两个参数:Com
7、pletionKey参数和OVERLAPPED结构。3.1关键参数第一个参数:CompletionKey,是一个DWORD类型的变量。你可以传递任何你想传递的唯一值,这个值将总是同该对象绑定。正常情况下会传递一个指向结构或类的指针,该结构或类包含了一些客户端的指定对象。在源码中,传递的是一个指向ClientContext的指针。3.2 OVERLAPPED参数这个参数通常用来传递异步I/O请求使用的内存缓冲。很重要的一点是:该数据将会被锁定并不允许从物理内存中换出页面(page out)。3.3绑定一个socket到完成端口一旦创建完成一个完成端口,可以通过调用CreateIoCompleti
8、onPort函数来绑定socket到完成端口。形式如下:BOOL IOCPS:AssociateSocketWithCompletionPort(SOCKET socket,HANDLE hCompletionPort,DWORD dwCompletionKey)HANDLE h=CreateIoCompletionPort(HANDLE)socket,hCompletionPort,dwCompletionKey,m_nIOWorkers);return h=hCompletionPort;3.4响应异步I/O请求响应具体的异步请求,调用函数WSASend和WSARecv。他们也需要一个参数
9、:WSABUF,这个参数包含了一个指向缓冲的指针。一个重要的规则是:通常当服务器/客户端响应一个I/O操作,不是直接响应,而是提交给完成端口,由I/O工作者线程来执行。这么做的原因是:我们希望公平的分割CPU周期。通过发送状态给完成端口来发出I/O请求,如下:BOOL bSuccess=PostQueuedCompletionStatus(m_hCompletionPort,pOverlapBuff-GetUsed(),(DWORD)pContext,&pOverlapBuff-m_ol);3.5与线程同步与I/O工作者线程同步是通过调用GetQueuedCompletionStatus函数来
10、实现的(如下)。这个函数也提供了CompletionKey参数和OVERLAPPED参数,如下:BOOL GetQueuedCompletionStatus(HANDLE CompletionPort,/handle to completion port LPDWORD lpNumberOfBytes,/bytes transferred PULONG_PTR lpCompletionKey,/file completion key LPOVERLAPPED*lpOverlapped,/buffer DWORD dwMilliseconds/optional timeout value);3.
11、6四个棘手的IOCP编码问题和解决方法使用IOCP时会出现一些问题,其中有一些不是很直观的。在使用IOCP的多线程编程中,一个线程函数的控制流程不是笔直的,因为在线程和通讯直接没有关系。在这一章节中,我们将描述四个不同的问题,可能在使用IOCP开发客户端/服务器应用程序时会出现,分别是:The WSAENOBUFS error problem.(WSAENOBUFS错误问题)The package reordering problem.(包重构问题)The access violation problem.(访问非法问题)3.6.1 WSAENOBUFS问题这个问题通常很难靠直觉发现,因为当你
12、第一次看见的时候你或许认为是一个内存泄露错误。假定已经开发完成了你的完成端口服务器并且运行的一切良好,但是当你对其进行压力测试的时候突然发现服务器被中止而不处理任何请求了,如果你运气好的话你会很快发现是因为WSAENOBUFS错误而影响了这一切。每当我们重叠提交一个send或receive操作的时候,其中指定的发送或接收缓冲区就被锁定了。当内存缓冲区被锁定后,将不能从物理内存进行分页。操作系统有一个锁定最大数的限制,一旦超过这个锁定的限制,那么就会产生WSAENOBUFS错误了。如果一个服务器提交了非常多的重叠的receive在每一个连接上,那么限制会随着连接数的增长而变化。如果一个服务器能够
13、预先估计可能会产生的最大并发连接数,服务器可以投递一个使用零缓冲区的receive在每一个连接上。因为当你提交操作没有缓冲区时,那么也不会存在内存被锁定了。使用这种办法后,当你的receive操作事件完成返回时,该socket底层缓冲区的数据会原封不动的还在其中而没有被读取到receive操作的缓冲区来。此时,服务器可以简单的调用非阻塞式的recv将存在socket缓冲区中的数据全部读出来,一直到recv返回WSAEWOULDBLOCK为止。这种设计非常适合那些可以牺牲数据吞吐量而换取巨大并发连接数的服务器。当然,你也需要意识到如何让客户端的行为尽量避免对服务器造成影响。在上一个例子中,当一个
14、零缓冲区的receive操作被返回后使用一个非阻塞的recv去读取socket缓冲区中的数据,如果服务器此时可预计到将会有爆发的数据流,那么可以考虑此时投递一个或者多个receive来取代非阻塞的recv来进行数据接收。(这比你使用1个缺省的8K缓冲区来接收要好的多。)源码中提供了一个简单实用的解决WSAENOBUF错误的办法。我们执行了一个零字节缓冲的异步WSARead(.)(参见OnZeroByteRead(.)。当这个请求完成,我们知道在TCP/IP栈中有数据,然后我们通过执行几个有MAXIMUMPACKAGESIZE缓冲的异步WSARead(.)去读,解决了WSAENOBUFS问题。但
15、是这种解决方法降低了服务器的吞吐量。总结:解决方法一:投递使用空缓冲区的receive操作,当操作返回后,使用非阻塞的recv来进行真实数据的读取。因此在完成端口的每一个连接中需要使用一个循环的操作来不断的来提交空缓冲区的receive操作。解决方法二:在投递几个普通含有缓冲区的receive操作后,进接着开始循环投递一个空缓冲区的receive操作。这样保证它们按照投递顺序依次返回,这样我们就总能对被锁定的内存进行解锁。3.6.2包重构问题.尽管使用IO完成端口的待发操作将总是按照他们发送的顺序来完成,线程调度安排可能使绑定到完成端口的实际工作不按指定的顺序来处理。例如,如果你有两个I/O工
16、作者线程,你可能接收到字节块2,字节块1,字节块3。这就意味着:当你通过向I/O完成端口提交请求数据发送数据时,数据实际上用重新排序过的顺序发送了。这可以通过只使用一个工作者线程来解决,并只提交一个I/O请求,等待它完成。但是如果这么做,我们就失去了IOCP的长处。解决这个问题的一个简单实用办法是给我们的缓冲类添加一个顺序数字,如果缓冲顺序数字是正确的,则处理缓冲中的数据。这意味着:有不正确的数字的缓冲将被存下来以后再用,并且因为执行原因,我们保存缓存到一个HASH MAP对象中(如m_SendBufferMap和m_ReadBufferMap)。获取这种解决方法的更多信息,请查阅源码,仔细查
17、看IOCPS类中如下的函数:GetNextSendBuffer(.)and GetNextReadBuffer(.),to get the ordered send or receive buffer.IncreaseReadSequenceNumber(.)and IncreaseSendSequenceNumber(.),to increase the sequence numbers.3.6.3异步等待读和字节块包处理问题最通用的服务端协议是一个基于协议的包,首先X个字节代表包头,包头包含了详细的完整的包的长度。服务端可以读包头,计算出需要多少数据,继续读取直到读完一个完整的包。当服务端
18、同时只处理一个异步请求时工作的很好。但是,如果我们想发挥IOCP服务端的全部潜能,我们应该启用几个等待的异步读事件,等待数据到达。这意味着几个异步读操作是不按顺序完成的,通过等待的读事件返回的字节块流将不会按顺序处理。而且,一个字节块流可以包含一个或几个包,也可能包含部分包,如下图所示:这个图形显示了部分包(绿色)和完整包(黄色)是怎样在不同字节块流中异步到达的。这意味着我们必须处理字节流来成功的读取一个完整的包。而且,我们必须处理部分包(图表中绿色的部分)。这就使得字节流的处理更加困难。这个问题的完整解决方法在IOCPS类的ProcessPackage()函数中。3.6.4访问非法问题这是一
19、个较小的问题,代码设计导致的问题更胜于IOCP的特定问题。假设一个客户端连接已经关闭并且一个I/O请求返回一个错误标志,然后我们知道客户端已经关闭。在参数CompletionKey中,我们传递了一个指向结构ClientContext的指针,该结构中包含了客户端的特定数据。如果我们释放这个ClientContext结构占用的内存,并且同一个客户端处理的一些其它I/O请求返回了错误代码,我们通过转换参数CompletionKey为一个指向ClientContext结构的指针并试图访问或删除它,会发生什么呢?一个非法访问出现了!这个问题的解决方法是添加一个数字到结构中,包含等待的I/O请求的数量(m
20、_nNumberOfPendingIO),然后当我们知道没有等待的I/O请求时删除这个结构。这个功能通过函数EnterIoLoop()和ReleaseClientContext()来实现。3.7源码略读源码的目标是提供一系列简单的类来处理所有IOCP编码中的问题。源码也提供了一系列通信和C/S软件中经常使用的函数,如文件接收/传送函数,逻辑线程池处理,等等。上图功能性的图解说明了IOCP类源码。我们有几个IO工作者线程通过完成端口来处理异步IO请求,这些工作者线程调用一些虚函数,这些虚函数可以把需要大量计算的请求放到一个工作队列中。逻辑工作者通过类中提供的这些函数从队列中取出任务、处理并发回结
21、果。GUI经常与主类通信,通过Windows消息(因为MFC不是线程安全的)、通过调用函数或通过使用共享的变量。图三上图显示了类结构纵览。图3中的类说明如下:CIOCPBuffer:管理异步请求的缓存的类。IOCPS:处理所有通信的主类。JobItem:保存逻辑工作者线程要处理的任务的结构。ClientContex:保存客户端特定信息的结构(如状态、数据,等等)。3.7.1缓冲设计-CIOCPBuffer类使用异步I/O调用时,我们必须提供私有的缓冲区供I/O操作使用。当我们将帐号信息放入分配的缓冲供使用时有许多情况需要考虑:.分配和释放内存代价高,因此我们应重复使用以及分配的缓冲(内存),因
22、此我们将缓冲保存在列表结构中,如下所示:/Free Buffer List.CCriticalSection m_FreeBufferListLock;CPtrList m_FreeBufferList;/OccupiedBuffer List.(Buffers that is currently used)CCriticalSection m_BufferListLock;CPtrList m_BufferList;/Now we use the function AllocateBuffer(.)/to allocate memory or reuse abuffer.有时,当异步I/O调
23、用完成后,缓冲里可能不是完整的包,因此我们需要分割缓冲去取得完整的信息。在CIOCPS类中提供了SplitBuffer函数。同样,有时候我们需要在缓冲间拷贝信息,IOCPS类提供了AddAndFlush函数。.众所周知,我们也需要添加序号和状态(IOType变量,IOZeroReadCompleted,等等)到我们的缓冲中。.我们也需要有将数据转换到字节流或将字节流转换到数据的方法,CIOCPBuffer也提供了这些函数。以上所有问题都在CIOCPBuffer中解决。3.8如何使用源代码从IOCP继承你自己的类(如图3),实现IOCPS类中的虚函数(例如,threadpool),在任何类型的服
24、务端或客户端中实现使用少量的线程有效地管理大量的连接。3.8.1启动和关闭服务端/客户端调用下面的函数启动服务端BOOL Start(int nPort=999,int iMaxNumConnections=1201,int iMaxIOWorkers=1,int nOfWorkers=1,int iMaxNumberOfFreeBuffer=0,int iMaxNumberOfFreeContext=0,BOOL bOrderedSend=TRUE,BOOL bOrderedRead=TRUE,int iNumberOfPendlingReads=4);nPort服务端侦听的端口.(-1客户
25、端模式.)iMaxNumConnections允许最大的连接数.(使用较大的数.)iMaxIOWorkers I/O工作线程数nOfWorkers逻辑工作者数量Number of logical workers.(可以在运行时改变.)iMaxNumberOfFreeBuffer重复使用的缓冲最大数.(-1不使用,0=不限)iMaxNumberOfFreeContext重复使用的客户端信息对象数(-1 for不使用,0=不限)bOrderedRead顺序读取.(我们已经在3.6.2.处讨论过)bOrderedSend顺序写入.(我们已经在3.6.2.处讨论过)iNumberOfPendlingR
26、eads等待读取数据时未决的异步读取循环数连接到远程服务器(客户端模式nPort=-1),调用函数:CodeConnect(const CString&strIPAddr,int nPort).strIPAddr远程服务器的IP地址.nPort端口调用ShutDown()关闭连接例如:if(!m_iocp.Start(-1,1210,2,1,0,0)AfxMessageBox(Error could not start the Client);.m_iocp.ShutDown();4.1源代码描述更多关于源代码的信息请参考代码里的注释。4.1.1虚函数NotifyNewConnection新的
27、连接已接受NotifyNewClientContext空的ClientContext结构被分配NotifyDisconnectedClient客户端连接断开ProcessJob逻辑工作者需要处理一个工作NotifyReceivedPackage新的包到达NotifyFileCompleted文件传送完成。4.1.2重要变量所有变量共享使用时必须加锁避免存取违例,所有需要加锁的变量,名称为XXX则锁变量名称为XXXLock。m_ContextMapLock;保存所有客户端数据(socket,客户端数据,等等)ContextMap m_ContextMap;m_NumberOfActiveConn
28、ections保存已连接的连接数4.1.3重要函数GetNumberOfConnections()返回连接数CString GetHostAdress(ClientContext*p)提供客户端上下文,返回主机地址BOOL ASendToAll(CIOCPBuffer*pBuff);发送缓冲上下文到所有连接的客户端DisconnectClient(CString sID)根据客户端唯一编号,断开指定的客户端CString GetHostIP()返回本地IP JobItem*GetJob()将JobItem从队列中移出,如果没有job,返回NULL BOOL AddJob(JobItem*pJo
29、b)添加Job到队列BOOL SetWorkers(int nThreads)设置可以任何时候调用的逻辑工作者数量DisconnectAll();断开所有客户端ARead()异步读取ASend()异步发送,发送数据到客户端ClientContext*FindClient(CString strClient)根据字符串ID寻找客户(非线程安全)DisconnectClient(ClientContext*pContext,BOOL bGraceful=FALSE);端口客户DisconnectAll()端口所有客户StartSendFile(ClientContext*pContext)根据Cl
30、ientContext结构发送文件(使用经优化的transmitfile(.)函数)PrepareReceiveFile(.)接收文件准备。调用该函数时,所有进入的字节流已被写入到文件。PrepareSendFile(.)打开文件并发送包含文件信息的数据包。函数禁用ASend(.)函数,直到文件传送关闭或中断。DisableSendFile(.)禁止发送文件模式DisableRecevideFile(.)禁止文件接收模式5.1文件传输文件传输使用Winsock 2.0中的TransmitFile函数。TransmitFile函数通过连接的socket句柄传送文件数据。函数使用操作系统的高速缓冲
31、管理器(cache manager)接收文件数据,通过sockets提供高性能的文件数据传输。异步文件传输要点:在TransmitFile函数返回前,所有其他发送或写入到该socket的操作都将无法执行,因为这将使文件数据混乱。因此,在PrepareSendFile()函数调用之后,所有ASend都被禁止。因为操作系统连续读取文件数据,你可以使用FILE_FLAG_SEQUENTIAL_SCAN参数来优化高速缓冲性能。发送文件时我们使用了内核异步操作(TF_USE_KERNEL_APC)。TF_USE_KERNEL_APC的使用可以更好地提升性能。有可能,无论如何,TransmitFile在线
32、程中的大量使用,这种情形可能会阻止APCs的调用.文件传输按如下顺序执行:服务器调用PrepareSendFile(.)函数初始化文件传输。客户端接收文件信息时,调用PrepareReceiveFile(.)作接收前的准备,并发送一个包到服务器告知开始文件传送。当包到达服务器端,服务器端调用StartSendFile(.)采用高性能的TransmitFile函数发送指定文件。6源代码示例提供的源代码演示代码是一个echo客户端/服务器端程序,并提供了对文件传输的支持(图4)。在代码中,MyIOCP类从IOCP继承,处理客户端/服务器端的通讯,所涉及的虚函数可以参见4.1.1处。客户端或服务器端
33、最重要的部分是虚函数NotifyReceivedPackage,定义如下:void MyIOCP:NotifyReceivedPackage(CIOCPBuffer*pOverlapBuff,int nSize,ClientContext*pContext)BYTE PackageType=pOverlapBuff-GetPackageType();switch(PackageType)case Job_SendText2Client:Packagetext(pOverlapBuff,nSize,pContext);break;case Job_SendFileInfo:PackageFile
34、Transfer(pOverlapBuff,nSize,pContext);break;case Job_StartFileTransfer:PackageStartFileTransfer(pOverlapBuff,nSize,pContext);break;case Job_AbortFileTransfer:DisableSendFile(pContext);break;函数接收进入的信息并执行远程连接发送的请求。这种情况,只是简单的echo或文件传输的情形。服务器端和客户端源代码分成两个工程,IOCP和IOCPClient。6.1编译问题使用VC+6.0或VC.NET编译,你可能在编译
35、CFile时得到一些奇怪的错误,如:if(pContext-m_File.m_hFile!=INVALID_HANDLE_VALUE)-error C2446:!=:no conversionfromvoid*tounsigned int这个问题可以通过更新头文件(*.h)或VC+6.0的版本或改变类型转换错误来解决,在修正了错误后,服务器端/客户端源代码可以在不需MFC的情况下使用。7特别的考虑和经验总结当你在其他类型的程序中使用本代码,有一些可以避免的编程陷阱和多线程问题。非确定错误指的是那些随机出现的错误,很难通过执行相同顺序的任务来重现这些错误。这是最坏的错误类型,通常,错误出现在内部
36、源代码的设计中。当服务器端有多个IO工作线程在运行,为客户端提供连接,如果程序员没有考虑多线程环境,可能会发生存取违例等不确定错误。经验#1:在未对上下文加锁时,不要读写客户上下文(例如:ClientContext)。通知函数(例如:Notify*(ClientContext*pContext)已经是线程安全,处理成员变量ClientContext可以不需要解锁、解锁。/不要这样使用/If(pContext-m_bSomeData)pContext-m_iSomeData=0;/应该这样使用/.pContext-m_ContextLock.Lock();If(pContext-m_bSomeD
37、ata)pContext-m_iSomeData=0;pContext-m_ContextLock.Unlock();/大家都知道的,当你锁定上下文,其他线程或GUI都将等待它。经验#2:避免使用复杂的和其他类型的上下文锁,应为容易造成死锁(例如:A在等待B,B在等待C,C在等待A,A死锁)pContext-m_ContextLock.Lock();/code code.pContext2-m_ContextLock.Lock();/code code.pContext2-m_ContextLock.Unlock();/code code.pContext-m_ContextLock.Unlo
38、ck();以上代码可能导致死锁经验#3:不要在通知函数(例如:Notify*(ClientContext*pContext)以外处理客户上下文,如果你需要这样做,你要放入m_ContextMapLock.Lock();m_ContextMapLock.Unlock();参考如下代码:ClientContext*pContext=NULL;m_ContextMapLock.Lock();pContext=FindClient(ClientID);/safe to access pContext,if it is not NULL/and are Locked(Rule of thumbs#1:)
39、/code.code.m_ContextMapLock.Unlock();/Here pContext can suddenly disappear because of disconnect./do not access pContext members here.8将来的工作将来,代码将提供以下功能:添加支持AcceptEx(.)接受新连接,处理短连接和DOS攻击。源代码兼容Win32,STL,WTL等环境。为什么我觉得这个类有问题呢?首先关于接收数据时,投递长度为0的缓冲区。在这个程序中,它并不是说先发送一个缓冲区长度为0的去接收数据,然后接收到完成通知时再调用缓冲区长度不为0的去接收。
40、而是在一个循环中,不断地提交缓冲长度为0的,中间也穿插的缓冲区长度不为0的,等待接收数据,这样难道不会产生内存被锁定吗?其次在OnReadCompleted方法中,如果接收数据按顺序进行接收,比如我投递的顺序是1、2、3、4、5,但是完成通知的时候,刚好反过来顺序是5、4、3、2、1,那么if(m_bReadInOrder)pOverlapBuff=GetNextReadBuffer(pContext,pOverlapBuff);while(pOverlapBuff)/TRACE(R%irn,pOverlapBuff-GetSequenceNumber();/Mark that we are
41、Using the buffer.pOverlapBuff-Use(dwIoSize);#ifdef TRANSFERFILEFUNCTIONALITY if(!pContext-m_bFileReceivedMode)#endif ProcessPackage(pContext,dwIoSize,pOverlapBuff);#ifdef TRANSFERFILEFUNCTIONALITY else AddToFile(pContext,dwIoSize,pOverlapBuff);#endif IncreaseReadSequenceNumber(pContext);pOverlapBuff
42、=NULL;if(m_bReadInOrder)pOverlapBuff=GetNextReadBuffer(pContext);ARead(pContext);/pOverlapBuff为NULL由于序号是5、4、3、2势必又调用这个方法4次。(xuezhimeng发表于2010-3-20 15:31:00)调试了一下,发现客户端发送不了数据。在服务端UINT nRetVal=WSARecv(pContext-m_Socket,pOverlapBuff-GetWSABuffer(),1,&dwIoSize,&ulFlags,&pOverlapBuff-m_ol,NULL);后查看pOverl
43、apBuff-GetWSABuffer()-buf,发现都是空的。而且调试过程中老有程序挂掉。(bsnail发表于2010-3-11 11:20:00)强顶,虽然还没下载,先顶了再说。(nanbo发表于2009-4-3 16:11:00)这个代码用来学习是不错的,有很多思路值得学习比如内存管理,安全通讯,但还是有很多的问题.可用性不强!(song_0962发表于2009-2-24 20:49:00)同上,我也觉得是这样,这个IOCP的例子前几年就看过了,并没有什么太大的意义,其实做服务器主要是做内存的管理,大量的工作和优化都在recv数据之后,不管是IOCP还是epoll等都是系统提供的一个调用方法。至今还没看到网上有谁真正把服务器集群-客户端整体性的给它抽象出一个比较完整的原型来,可能这就是现在程序员还有的那么一丁点利用价值吧。(hhatss007发表于2009-2-24 15:46:00)我封装过一个比这简单的IOCP类,包含服务端和客户端(lwch发表于2009-2-24 15:39:00)代码比较乱,一个简单的东西写复杂了。(m_point发表于2009-2-24 12:57:00)特别声明:1:资料来源于互联网,版权归属原作者2:资料内容属于网络意见,与本账号立场无关3:如有侵权,请告知,立即删除。124413124857
链接地址:https://www.31doc.com/p-2784469.html