现在的位置: 首页 > 综合 > 正文


2014年09月05日 ⁄ 综合 ⁄ 共 47641字 ⁄ 字号 评论关闭



  • The article expects the reader to be familiar with C++, TCP/IP, socket programming, MFC, and multithreading.文章预计,读者熟悉C + +中, TCP / IP协议,套接字编程, MFC的,和多线程。
  • The source code uses Winsock 2.0 and the IOCP technology, and requires:源代码使用Winsock的2.0和IOCP技术,并要求:
    • Windows NT/2000 or later: Requires Windows NT 3.5 or later.视窗NT/2000或更高版本:需要Windows NT 3.5或更高版本。
    • Windows 95/98/ME: Not supported. Windows 95/98/Me中:不支持。
    • Visual C++ .NET, or a fully updated Visual C++ 6.0. Visual C + +中的。 NET ,或完全更新的Visual C + + 6.0 。

1.2 Abstract 1.2摘要

When you develop different types of software, sooner or later, you will have to deal with client/server development.当你制定不同类型的软件,迟早,你将不得不应付客户机/服务器的发展。 To write a comprehensive client/server code is a difficult task for a programmer.写一个全面客户机/服务器代码是一项艰巨的任务,为程序员。 This documentation presents a simple but powerful client/server source code that can be extended to any type of client/server application.这一文件提出了一个简单而有力的客户机/服务器的源代码,可以扩大到任何类型的客户机/服务器应用程序。 This source code uses the advanced IOCP technology which can efficiently serve multiple clients.这源代码使用了先进的IOCP技术,它可以有效地服务多个客户。 IOCP presents an efficient solution to the "one-thread-per-client" bottleneck problem (among others), using only a few processing threads and asynchronous input/output send/receive. IOCP提供了一个有效的解决办法“一个线程每客户”的瓶颈问题(等等) ,只用少数的处理线程和异步输入/输出传送/接收。 The IOCP technology is widely used for different types of high performance servers as Apache etc. The source code also provides a set of functions that are frequently used while dealing with communication and client/server software as file receiving/transferring function and logical thread pool handling.在IOCP技术被广泛用于不同类型的高性能服务器的Apache等的源代码还提供了一系列功能,经常使用的处理,同时通讯和客户机/服务器软件作为档案接收/传递函数和逻辑线程池处理。 This article focuses on the practical solutions that arise with the IOCP programming API, and also presents an overall documentation of the source code.本文的重点是切合实际的解决办法产生的IOCP编程的API ,同时也提出了一个总体文件的源代码。 Furthermore, a simple echo client/server which can handle multiple connections and file transfer is also presented here.此外,一个简单的回声客户机/服务器可以处理多个连接和文件传输也提出了这里。

2.1 Introduction 2.1导言

This article presents a class which can be used in both the client and server code.本文介绍了一类可用于在客户端和服务器代码。 The class uses IOCP (Input Output Completion Ports) and asynchronous (non-blocking) function calls which are explained later.类使用IOCP (输入输出完成端口)和异步(无阻塞)函数调用予以解释后。 The source code is based on many other source codes and articles: [1, 2, and 3].源代码是基于许多其他源代码和文章: [ 1 , 2 , 3 ] 。

With this simple source code, you can:有了这个简单的源代码,您可以:

  • Service or connect to multiple clients and servers.服务或连接到多个客户机和服务器。
  • Send or receive files asynchronously.发送或接收文件异步。
  • Create and manage a logical worker thread pool to process heavier client/server requests or computations.创建和管理一个合乎逻辑的工人线程池来处理较重的客户机/服务器请求或计算。

It is difficult to find a comprehensive but simple source code to handle client/server communications.这是很难找到一个全面的,但简单的源代码来处理客户机/服务器通信。 The source codes that are found on the net are either too complex (20+ classes), or don’t provide sufficient efficiency.源代码,发现净要么过于复杂( 20 +类) ,或没有提供足够的效率。 This source code is designed to be as simple and well documented as possible.这源代码的设计是简单,有案可稽的。 In this article, we will briefly present the IOCP technology provided by Winsock API 2.0, and also explain the thorny problems that arise while coding and the solution to each one of them.在本文中,我们将简要地介绍IOCP技术所提供的API 2.0 Winsock的,也解释了棘手问题而产生的编码和解决每其中之一。

2.2 Introduction to asynchronous Input/Output Completion Ports (IOCP) 2.2介绍异步输入/输出完成端口( IOCP )

A server application is fairly meaningless if it cannot service multiple clients at the same time, usually asynchronous I/O calls and multithreading is used for this purpose.服务器应用程序是相当无意义的,如果它不能服务多个客户端在同一时间,通常是异步I / O要求和多线程是用于这一目的。 By definition, an asynchronous I/O call returns immediately, leaving the I/O call pending.根据定义,一个异步I / O要求立即返回,留下的I / O呼叫等待。 At some point of time, the result of the I/O asynchronous call must be synchronized with the main thread.在某一点的时间,结果的I / O异步调用必须是同步进行的主线。 This can be done in different ways.这可以采取不同的方式。 The synchronization can be performed by:同步可以执行的:

  • Using events - A signal is set as soon as the asynchronous call is finished.利用事件-信号是尽快将异步调用完成。 The disadvantage of this approach is that the thread has to check or wait for the event to be set.这种办法的缺点是,线程已经检查或等待事件来定。
  • Using the GetOverlappedResult function - This approach has the same disadvantage as the approach above.使用GetOverlappedResult功能-这种方法具有相同的劣势,因为上面的方法。
  • Using Asynchronous Procedure Calls (or APC) - There are several disadvantages associated with this approach.使用异步过程调用(或装甲运兵车) -有几个缺点与此相关的办法。 First, the APC is always called in the context of the calling thread, and second, in order to be able to execute the APCs, the calling thread has to be suspended in the so called alterable wait state.首先,装甲运兵车始终是所谓的背景下调用线程,第二,为了能够执行装甲运兵车,电话线已暂停在所谓的可变等待状态。
  • Using IOCP - The disadvantage of this approach is that there are many practical thorny programming problems that must be solved.使用IOCP -这种办法的缺点是,有许多实际棘手的规划问题,必须加以解决。 Coding IOCP can be a bit of a hassle.编码IOCP可有点麻烦。

2.2.1 Why using IOCP? 2.2.1使用IOCP为什么?

By using IOCP, we can overcome the "one-thread-per-client" problem.通过使用IOCP ,我们能够克服“一个线程每客户”的问题。 It is commonly known that the performance decreases heavily if the software does not run on a true multiprocessor machine.众所周知,业绩下降严重,如果没有软件运行于一个真正的多处理器机器。 Threads are system resources that are neither unlimited nor cheap.线程是系统资源既不是无限的,也不便宜。

IOCP provides a way to have a few (I/O worker) threads handle multiple clients' input/output "fairly". IOCP提供了一种有几个(的I / O工人)线程处理多个客户的输入/输出“相当” 。 The threads are suspended, and don't use the CPU cycles until there is something to do.该线程暂停,并且不使用的CPU周期,直到有些事情做。

2.3 What is IOCP? 2.3什么是IOCP ?

We have already stated that IOCP is nothing but a thread synchronization object, similar to a semaphore, therefore IOCP is not a sophisticated concept.我们已经说过, IOCP只不过是线程同步对象,类似的信号,因此IOCP不是一个复杂的概念。 An IOCP object is associated with several I/O objects that support pending asynchronous I/O calls.一个IOCP对象是与一些相关的I / O对象,支持等待异步I / O要求。 A thread that has access to an IOCP can be suspended until a pending asynchronous I/O call is finished.线程已进入一个IOCP可以暂停直至等待异步I / O要求完成。

3 How does IOCP work? 3如何不IOCP工作?

To get more information on this part, I referred to other articles.为了获得更多有关这一部分,我提到的其他条款。 [1, 2, 3, see References.] [ 1 , 2 , 3 ,见参考。 ]

While working with IOCP, you have to deal with three things, associating a socket to the completion port, making the asynchronous I/O call, and synchronization with the thread.虽然工作IOCP ,你必须处理的三件事,联系插座的完成端口,使异步I / O要求,并同步线程。 To get the result from the asynchronous I/O call and to know, for example, which client has made the call, you have to pass two parameters: the CompletionKey parameter, and the OVERLAPPED structure.要获得源于异步I / O要求,并了解,例如,该客户端已发出这一呼吁的,你必须通过两个参数: CompletionKey参数,以及OVERLAPPED

3.1 The completion key parameter 3.1完成关键参数

The first parameter, the CompletionKey , is just a variable of type DWORD .第一个参数, CompletionKey ,仅仅是一个变量的类型DWORD You can pass whatever unique value you want to, that will always be associated with the object.您可以通过独特的价值无论你想,这将永远是联系对象。 Normally, a pointer to a structure or a class that can contain some client specific objects is passed with this parameter.通常情况下,一个指针的结构或一类,可以包含一些特定对象的客户通过这一参数。 In the source code, a pointer to a structure ClientContext is passed as the CompletionKey parameter.在源代码,指针结构ClientContext通过的CompletionKey参数。

3.2 The OVERLAPPED parameter 3.2重叠参数

This parameter is commonly used to pass the memory buffer that is used by the asynchronous I/O call.此参数是常用的通过内存缓冲区所使用的异步I / O要求。 It is important to note that this data will be locked and is not paged out of the physical memory.重要的是要注意到,这一数据将被锁住,而不是页面的物理内存。 We will discuss this later.我们将讨论这点。

3.3 Associating a socket with the completion port 3.3缔套接字与完成端口

Once a completion port is created, the association of a socket with the completion port can be done by calling the function CreateIoCompletionPort in the following way:一旦完成端口创建的,该协会的插座与完成端口可以通过电话的功能CreateIoCompletionPort的方式如下:

 BOOL IOCPS::AssociateSocketWithCompletionPort(SOCKET socket,布尔IOCPS : : AssociateSocketWithCompletionPort (插座插座, 
HANDLE hCompletionPort, DWORD dwCompletionKey)处理hCompletionPort ,双字节dwCompletionKey )
HANDLE h = CreateIoCompletionPort((HANDLE) socket,处理h = CreateIoCompletionPort ( (处理)插座,
hCompletionPort, dwCompletionKey, m_nIOWorkers); hCompletionPort , dwCompletionKey , m_nIOWorkers ) ;
return h == hCompletionPort; 返回 h == hCompletionPort ;

3.4 Making the asynchronous I/O call 3.4制作的异步I / O要求

To make the actual asynchronous call, the functions WSASend , WSARecv are called.为了使实际的异步调用,其职能WSASendWSARecv要求。 They also need to have a parameter WSABUF , that contains a pointer to a buffer that is going to be used.他们还需要有一个参数WSABUF ,包含一个指针的缓冲区将被使用。 A rule of thumb is that normally when the server/client wants to call an I/O operation, they are not made directly, but is posted into the completion port, and is performed by the I/O worker threads.一条经验法则是,通常在服务器/客户端要请一个I / O操作,他们不直接,但张贴到完成端口,并进行了I / O工作线程。 The reason for this is, we want the CPU cycles to be partitioned fairly.这样做的理由是,我们希望CPU周期被划分相当。 The I/O calls are made by posting a status to the completion port, see below:该I / O要求是由张贴地位,完成端口,见下文:

 BOOL bSuccess = PostQueuedCompletionStatus(m_hCompletionPort,布尔bSuccess = PostQueuedCompletionStatus ( m_hCompletionPort , 
pOverlapBuff- > GetUsed(), pOverlapBuff , GetUsed ( ) ,
(DWORD) pContext, &pOverlapBuff- > m_ol); (双字节) pContext , & pOverlapBuff , m_ol ) ;

3.5 Synchronization with the thread 3.5同步与线程

Synchronization with the I/O worker threads is done by calling the GetQueuedCompletionStatus function (see below).同步的I / O工作线程是通过调用GetQueuedCompletionStatus函数(见下文) 。 The function also provides the CompletionKey parameter and the OVERLAPPED parameter (see below):该功能还提供了CompletionKey参数和OVERLAPPED参数(见下文) :

 BOOL GetQueuedCompletionStatus(布尔GetQueuedCompletionStatus ( 
HANDLE CompletionPort, // handle to completion port处理CompletionPort / / 处理完成港口
LPDWORD lpNumberOfBytes, // bytes transferred LPDWORD lpNumberOfBytes / / 字节转移
PULONG_PTR lpCompletionKey, // file completion key PULONG_PTR lpCompletionKey / / 文件完成关键
LPOVERLAPPED *lpOverlapped, // buffer LPOVERLAPPED * lpOverlapped / / 缓冲区
DWORD dwMilliseconds // optional timeout value的DWORD dwMilliseconds / /可选的超时值
); ) ;

3.6 Four thorny IOCP coding hassles and their solutions 3.6四IOCP编码棘手的麻烦及其解决办法

There are some problems that arise while using IOCP, some of them are not intuitive.也有一些问题,出现在使用IOCP ,其中有些人是不直观。 In a multithreaded scenario using IOCPs, the control flow of a thread function is not straightforward, because there is no relationship between threads and communications.在一个多线程的情况下使用IOCPs ,控制流动的一个线程函数不是简单的,因为没有任何关系,线程和通信。 In this section, we will represent four different problems that can occur while developing client/server applications using IOCPs.在这一节中,我们将代表四个不同的问题,就可能发生,而发展中国家客户机/服务器应用IOCPs 。 They are:它们是:

  • The WSAENOBUFS error problem. WSAENOBUFS错误的问题。
  • The package reordering problem.包重新排序的问题。
  • The access violation problem.访问冲突问题。

3.6.1 The WSAENOBUFS error problem 3.6.1错误的问题WSAENOBUFS

This problem is non intuitive and difficult to detect, because at first sight, it seems to be a normal deadlock or a memory leakage "bug".这个问题是不直观,很难察觉,因为乍一看,这似乎是一种正常的僵局或内存泄漏“千年虫” 。 Assume that you have developed your server and everything runs fine.假设您开发您的服务器,一切运行良好。 When you stress test the server, it suddenly hangs.当您应力测试服务器,它突然挂起。 If you are lucky, you can find out that it has something to do with the WSAENOBUFS error.如果您是幸运的,你可以找到了,它已经跟了WSAENOBUFS错误。

With every overlapped send or receive operation, it is possible that the data buffer submitted will be locked.每一次重复发送或接收操作,可能是缓冲区的数据提交将被锁定。 When memory is locked, it cannot be paged out of physical memory.当记忆被锁定,不能分页的物理内存。 The operating system imposes a limit on the amount of memory that can be locked.作业系统施加限制的内存量,可以锁定。 When this limit is reached, the overlapped operations will fail with the WSAENOBUFS error.当达到此限制,重叠的操作都将无效的WSAENOBUFS错误。

If a server posts many overlapped receives on each connection, this limit will be reached when the number of connections grow.如果服务器许多重叠的职位上获得的每一个方面,这一限额将达到时,连接数量的增长。 If a server anticipates handling a very high number of concurrent clients, the server can post a single zero byte receive on each connection.如果服务器预计处理非常高的一些并行的客户,服务器可以张贴一个零字节就得到每个连接。 Because there is no buffer associated with the receive operation, no memory needs to be locked.由于没有缓冲与接收操作,没有记忆需要锁定。 With this approach, the per-socket receive buffer should be left intact because once the zero-byte receive operation is completed, the server can simply perform a non-blocking receive to retrieve all the data buffered in the socket's receive buffer.这一做法,每接收缓冲区插座应原封不动,因为一旦零字节的接收操作完成,服务器可以简单地执行无阻塞收到检索所有的数据缓冲在插座的接收缓冲区。 There is no more data pending when the non-blocking receive fails with WSAEWOULDBLOCK .没有更多的资料,以等候时无阻塞接受失败, WSAEWOULDBLOCK This design would be for the one that requires the maximum possible concurrent connections while sacrificing the data throughput on each connection.这种设计将是一个需要尽可能并行连接同时牺牲的数据吞吐量的每个方面。 Of course, the more you know about how the clients interact with the server, the better.当然,更多的你知道如何与客户互动的服务器,就越好。 In the previous example, a non-blocking receive was performed once the zero-byte receive completes retrieving the buffered data.在前面的示例中,一个非阻塞接收进行一次零字节接收完成检索缓冲数据。 If the server knows that clients send data in bursts, then once the zero-byte receive is completed, it may post one or more overlapped receives in case the client sends a substantial amount of data (greater than the per-socket receive buffer that is 8 KB by default).如果服务器知道客户端发送数据的扫射,然后再次零字节接收完成后,它可以张贴一个或多个重叠的情况下收到的客户端发送了大量的数据(大于每插座接收缓冲区是8 KB的默认) 。

A simple practical solution to the WSAENOBUFS error problem is in the source code provided.一种简单实用的解决办法WSAENOBUFS错误的问题是在提供源代码。 We perform an asynchronous WSARead(..) (see OnZeroByteRead(..) ) with a zero byte buffer.我们执行异步WSARead(..) (见OnZeroByteRead(..) ) ,以零字节的缓冲区。 When this call completes, we know that there is data in the TCP/IP stack, and we read it by performing several asynchronous WSARead(..) with a buffer of MAXIMUMPACKAGESIZE .当完成这一呼吁,我们知道,有数据, TCP / IP协议栈,以及我们读到它的表演几个异步WSARead(..)一个缓冲的MAXIMUMPACKAGESIZE This solution locks physical memory only when data arrives, and solves the WSAENOBUFS problem.该解决方案锁定物理内存只有当数据到达,并解决了WSAENOBUFS问题。 But this solution decreases the throughput of the server (see Q6 and A6 in section 9 FAQ).但是,这一解决方案降低了吞吐量的服务器(见问题6和A6在第9条常见问题解答) 。

3.6.2 The package reordering problem 3.6.2包重新排序问题

This problem is also being discussed by [3].这个问题还正在讨论[ 3 ] 。 Although committed operations using the IO completion port will always be completed in the order they were submitted, thread scheduling issues may mean that the actual work associated with the completion is processed in an undefined order.虽然致力于业务使用的IO完成端口将永远是完成的顺序,他们已提交,线程调度问题,可能意味着相关的实际工作的完成是处理了一个未定义的秩序。 For example, if you have two I/O worker threads and you should receive "byte chunk 1, byte chunk 2, byte chunk 3", you may process the byte chunks in the wrong order, namely, "byte chunk 2, byte chunk 1, byte chunk 3".例如,如果您有两个的I / O工作线程,你应该会收到“字节块1字节块2 ,字节块3 ”时,可能会进程的字节块中的错误的顺序,即“字节块2 ,字节块1 ,字节块3 “ 。 This also means that when you are sending the data by posting a send request on the I/O completion port, the data can actually be sent in a reordered way.这也意味着,当你发送数据的公布发送请求的I / O完成端口,数据实际上可以被重新排序的方式。

This can be solved by only using one worker thread, and committing only one I/O call and waiting for it to finish, but if we do this, we lose all the benefits of IOCP.这可以解决只用一个工人线程,并承诺只有一个的I / O要求,并等待它完成,但如果我们做到这一点,我们失去了所有的好处IOCP 。

A simple practical solution to this problem is to add a sequence number to our buffer class, and process the data in the buffer if the buffer sequence number is in order.一个简单的实际解决这一问题的办法是增加一个序列编号,以我们的缓冲级,并过程中的数据缓冲区,如果缓冲区的序列号是在命令。 This means that the buffers that have incorrect numbers have to be saved for later use, and because of performance reasons, we will save the buffers in a hash map object (eg, m_SendBufferMap and m_ReadBufferMap ).这意味着缓冲区有不正确的人数保存供以后使用,而且由于性能原因,我们将保存在一个缓冲区哈希地图对象(例如, m_SendBufferMapm_ReadBufferMap ) 。

To get more information about this solution, please go through the source code, and take a look at the following functions in the IOCPS class:要获得更多信息这一解决方案,请通过源代码,并看看下面的职能IOCPS类别:

  • GetNextSendBuffer (..) and GetNextReadBuffer(..) , to get the ordered send or receive buffer. GetNextSendBuffer (..)GetNextReadBuffer(..) ,得到命令发送或接收缓冲区。
  • IncreaseReadSequenceNumber(..) and IncreaseSendSequenceNumber(..) , to increase the sequence numbers. IncreaseReadSequenceNumber(..)IncreaseSendSequenceNumber(..) ,以增加序列号。

3.6.3 Asynchronous pending reads and byte chunk package processing problem 3.6.3异步等待读取和字节块包处理问题

The most common server protocol is a packet based protocol where the first X bytes represent a header and the header contains details of the length of the complete packet.最常见的服务器协议是一个基于包的协议的第一个X字节代表了标题和标题包含细节的篇幅完整的数据包。 The server can read the header, work out how much more data is required, and keep reading until it has a complete packet.该服务器可以读取头,工作了多少更多的数据需要,并随时阅读,直到它有一个完整的数据包。 This works fine when the server is making one asynchronous read call at a time.此工程罚款当服务器正在读一个异步调用的时间。 But if we want to use the IOCP server's full potential, we should have several pending asynchronous reads waiting for data to arrive.但是,如果我们想要使用IOCP服务器的全部潜力,我们应该有一些悬而未决的异步读取等待数据的到来。 This means that several asynchronous reads complete out of order (as discussed before in section 3.6.2), and byte chunk streams returned by the pending reads will not be processed in order.这意味着,一些异步读取完整的命令(如前讨论3.6.2节) ,和字节块流回到之前的内容将不会被处理的命令。 Furthermore, a byte chunk stream can contain one or several packages and also partial packages, as shown in figure 1.此外,一个字节块流可以包含一个或几个包和还部分封装,如图1所示。

Figure 1. The figure shows how partial packages (green) and complete packages (yellow) can arrive asynchronously in different byte chunk streams (marked 1, 2, 3). 图1 。这个数字表明,局部包(绿色)和完整的软件包(黄色)可以到达不同的异步字节块流(标1 , 2 , 3 ) 。

This means that we have to process the byte stream chunks in order to successfully read a complete package.这就意味着我们必须处理的字节流块,以便成功地宣读了一份完整的软件包。 Furthermore, we have to handle partial packages (marked with green in figure 1).此外,我们必须处理好局部包(绿色标示在图1 ) 。 This makes the byte chunk package processing more difficult.这使得字节块包处理更加困难。 The full solution to this problem can be found in the ProcessPackage(..) function in the IOCPS class.全面解决这一问题可以在ProcessPackage(..)功能的IOCPS类。

3.6.4 The access violation problem 3.6.4存取违规问题

This is a minor problem, and is a result of the design of the code, rather than an IOCP specific problem.这是一个小问题,是由于设计的代码,而不是一个IOCP的具体问题。 Suppose that a client connection is lost and an I/O call returns with an error flag, then we know that the client is gone.假设客户端连接丢失和一个I / O调用返回一个错误的国旗,然后我们知道,客户现在都不见了。 In the parameter CompletionKey , we pass a pointer to a structure ClientContext that contains client specific data.在参数CompletionKey ,我们通过一个指针结构ClientContext包含客户的具体数据。 What happens if we free the memory occupied by this ClientContext structure, and some other I/O call performed by the same client returns with an error code, and we transform the parameter CompletionKey variable of DWORD to a pointer to ClientContext , and try to access or delete it?会发生什么事,如果我们免费的记忆被这ClientContext结构,和一些其他的I / O要求由同一客户端返回一个错误代码,我们把参数CompletionKey变量DWORD的指针ClientContext ,并尝试访问或删除它? An access violation occurs!发生存取违规!

The solution to this problem is to add a number to the structures that contain the number of pending I/O calls ( m_nNumberOfPendlingIO ), and we delete the structure when we know that there are no more pending I/O calls.解决这个问题的办法是增加了一些在结构中包含一些未决的I / O要求( m_nNumberOfPendlingIO ) ,我们删除的结构时,我们知道,没有更多等待的I / O要求。 This is done by the EnterIoLoop(..) function and ReleaseClientContext(..) .这样做是由EnterIoLoop(..)功能和ReleaseClientContext(..)

3.7 The overview of the source code 3.7总的源代码

The goal of the source code is to provide a set of simple classes that handle all the hassled code that has to do with IOCP.这一目标的源代码是提供一套简单的类,处理所有hassled代码,是同IOCP 。 The source code also provides a set of functions which are frequently used while dealing with communication and client/server software as file receiving/transferring functions, logical thread pool handling, etc..源代码还提供了一套功能是经常使用的处理,同时通讯和客户机/服务器软件作为档案接收/传输功能,逻辑线程池处理,等等。

Figure 2. The figure above illustrates an overview of the IOCP class source code functionality. 图2 。上面的数字说明概述了IOCP类的源代码的功能。

We have several IO worker threads that handle asynchronous I/O calls through the completion port (IOCP), and these workers call some virtual functions which can put requests that need a large amount of computation in a work queue.我们有一些辅助线程的IO处理异步I / O要求通过完成端口( IOCP ) ,并要求这些工人一些虚拟的职能可以提出要求,需要大量的计算工作队列。 The logical workers take the job from the queue, and process it and send back the result by using some of the functions provided by the class.从逻辑上考虑工人的工作从队列和进程,并传回的结果用一些职能提供了一流的。 The Graphical User Interface (GUI) usually communicates with the main class using Windows messages (because MFC is not thread safe) and by calling functions or by using the shared variables.该图形用户界面( GUI )通常与通信类的主要使用Windows邮件(因为MFC的不是线程安全)和通话功能或使用共享变量。

Figure 3. The figure above shows the class overview. 图3 。上面的数字显示,一流的概况。

The classes that can be observed in figure 3 are:该班,可以看到如图3是:

  • CIOCPBuffer : A class used to manage the buffers used by the asynchronous I/O calls. CIOCPBuffer :一类用于管理缓冲区所使用的异步I / O要求。
  • IOCPS : The main class that handles all the communication. IOCPS :主要类,处理所有的通信。
  • JobItem : A structure which contains the job to be performed by the logical worker threads. JobItem :一种结构其中包含工作必须由合乎逻辑的工作线程。
  • ClientContext : A structure that holds client specific information (status, data, etc.). ClientContext :一种结构,拥有客户的具体信息(状态,数据等) 。

3.7.1 The buffer design – The CIOCPBuffer class 3.7.1缓冲区设计-一流的C IOCPBuffer

When using asynchronous I/O calls, we have to provide a private buffer to be used with the I/O operation.当使用异步I / O要求,我们必须提供一个私人的缓冲区可用于I / O操作。 There are some considerations that are to be taken into account when we allocate buffers to use:也有一些因素是必须考虑到当我们分配缓冲器使用:

  • To allocate and free memory is expensive, therefore we should reuse buffers (memory) which have been allocated.分配和释放内存非常昂贵,因此我们应该重用缓冲区(内存) ,其中已分配。 Therefore, we save buffers in the linked list structures given below:因此,我们保存在缓冲区链表结构如下:

     // Free Buffer List.. / /释放缓冲区列表.. 
    CCriticalSection m_FreeBufferListLock; CCriticalSection m_FreeBufferListLock ;
    CPtrList m_FreeBufferList; CPtrList m_FreeBufferList ;
    // OccupiedBuffer List.. / / OccupiedBuffer名单.. (Buffers that is currently used) (缓冲器是目前使用)
    CCriticalSection m_BufferListLock; CCriticalSection m_BufferListLock ;
    CPtrList m_BufferList; CPtrList m_BufferList ;
    // Now we use the function AllocateBuffer(..) / /现在我们使用的功能AllocateBuffer ( .. )
    // to allocate memory or reuse a buffer. / /分配内存或再使用一个缓冲区。
  • Sometimes, when an asynchronous I/O call is completed, we may have partial packages in the buffer, therefore the need to split the buffer to get a complete message.有时,当一个异步I / O通话完毕后,我们可能会部分包在缓冲区,因此需要分割的缓冲区,以获得一个完整的讯息。 This is done by the SplitBuffer function in the CIOCPS class.这样做是由SplitBuffer功能CIOCPS类。 Also, sometimes we need to copy information between the buffer, and this is done by the AddAndFlush(..) function in the IOCPS class.此外,有时我们需要复制的信息之间的缓冲区,这是所做的AddAndFlush(..)功能的IOCPS类。
  • As we know, we also need to add a sequence number and a state ( IOType variable, IOZeroReadCompleted , etc.) to our buffer.正如我们所知,我们还需要添加一个序列号和一个国家( IOType变量, IOZeroReadCompleted等) ,以我们的缓冲区。
  • We also need methods to convert data to byte stream and byte stream to data, some of these functions are also provided in the CIOCPBuffer class.我们还需要方法来转换数据,以字节流和字节流数据,其中的一些职能也提供了CIOCPBuffer类。

All the solutions to the problems we have discussed above exist in the CIOCPBuffer class.所有的办法来解决我们上面讨论中存在CIOCPBuffer类。

3.8 How to use the source code? 3.8如何使用的源代码?

By inheriting your own class from IOCP (shown in figure 3) and using the virtual functions and the functionality provided by the IOCPS class (eg, threadpool), it is possible to implement any type of server or client that can efficiently manage a huge number of connections by using only a few number of threads.继承自己的阶级从IOCP (如图3所示) ,并利用虚拟的职能和功能所提供的IOCPS类(例如,线程池) ,有可能实施的任何类型的服务器或客户端,可以有效地管理一个庞大的数字连接使用只有少数的线程数。

3.8.1 Starting and closing the server/client 3.8.1启动和关闭服务器/客户端

To start the server, call the function:要启动服务器,呼叫的功能:

 BOOL Start( int nPort=999, int iMaxNumConnections=1201,布尔开始( 整数 nPort = 999 整数 iMaxNumConnections = 1201 
int iMaxIOWorkers=1, int nOfWorkers=1, 整数 iMaxIOWorkers = 1 , 整数 nOfWorkers = 1 ,
int iMaxNumberOfFreeBuffer=0, 整数 iMaxNumberOfFreeBuffer = 0 ,
int iMaxNumberOfFreeContext=0, 整数 iMaxNumberOfFreeContext = 0 ,
BOOL bOrderedSend=TRUE,布尔bOrderedSend = TRUE时,
BOOL bOrderedRead=TRUE,布尔bOrderedRead = TRUE时,
int iNumberOfPendlingReads=4); 整数 iNumberOfPendlingReads = 4 ) ;
  • nPortt

    Is the port number that the server will listen on.是的端口号,服务器将侦听。 (Let it be -1 for client mode.) (让我们为客户-1模式。 )

  • iMaxNumConnections

    Maximum number of connections allowed.最大连接数允许的。 (Use a big prime number.) (使用一个大素数。 )

  • iMaxIOWorkers

    Number of Input/Output worker threads.一些输入/输出工作线程。

  • nOfWorkers

    Number of logical workers.一些合乎逻辑的工人。 (Can be changed at runtime.) (可以改变在运行时) 。

  • iMaxNumberOfFreeBuffer

    Maximum number of buffers that we save for reuse.最大数量的缓冲区,我们除了重用。 (-1 for none, 0= Infinite number) ( -1为0票反对, 0 =无限数目)

  • iMaxNumberOfFreeContext

    Maximum number of client information objects that are saved for reuse.最大数量的客户端信息对象保存为重用。 (-1 for none, 0= Infinite number) ( -1为0票反对, 0 =无限数目)

  • bOrderedRead

    Make sequential reads.使连续读取。 (We have discussed this before in section 3.6.2.) (我们已经讨论过这个节之前3.6.2 。 )

  • bOrderedSend

    Make sequential writes.使连续写入。 (We have discussed this before in section 3.6.2.) (我们已经讨论过这个节之前3.6.2 。 )

  • iNumberOfPendlingReads

    Number of pending asynchronous read loops that are waiting for data.一些未决异步读的循环正在等待数据。

To connect to a remote connection (Client mode nPort = -1), call the function:要连接到远程连接(客户端模式nPort = -1 ) ,呼叫的功能:

 Connect( const CString &strIPAddr, int nPort)连接( 构造 CString & strIPAddr , 整数 nPort ) 
  • strIPAddr

    The IP address of the remote server.的IP地址远程服务器。

  • nPort

    The port.该港口。

To close, make the server call the function: ShutDown() .要关闭,使服务器的呼叫功能: ShutDown()

For example:例如:

 MyIOCP m_iocp; MyIOCP m_iocp ; 
if (!m_iocp.Start(- 1 , 1210 , 2 , 1 , 0 , 0 )) 如果 ( ! m_iocp.Start ( -11 210, 2 , 1 , 0 , 0 ) )
AfxMessageBox( " Error could not start the Client" ); AfxMessageBox ( “错误无法启动客户端” ) ;
…. ... 。
m_iocp.ShutDown(); m_iocp.ShutDown ( ) ;

4.1 Source code description 4.1源代码说明

For more details about the source code, please check the comments in the source code.如需更详细的源代码,请检查意见的源代码。

4.1.1 Virtual functions 4.1.1虚拟职能

  • NotifyNewConnection

    Called when a new connection has been established.所谓当一个新的连接已经建立。

  • NotifyNewClientContext

    Called when an empty ClientContext structure is allocated.所谓当空ClientContext分配结构。

  • NotifyDisconnectedClient

    Called when a client disconnects.所谓当一个客户端断开。

  • ProcessJob

    Called when logical workers want to process a job.所谓的合乎逻辑的工人时,要处理工作。

  • NotifyReceivedPackage

    Notifies that a new package has arrived.通知说,新的一揽子已经到来。

  • NotifyFileCompleted

    Notifies that a file transfer has finished.通知说,文件传输已经完成。

4.1.2 Important variables 4.1.2重要的变数

Notice that all the variables have to be exclusively locked by the function that uses the shared variables, this is important to avoid access violations and overlapping writes.请注意,所有变量都必须完全锁定的功能,使用共享变量,这是很重要的访问,以避免侵犯和重叠写到。 All the variables with name XXX, that are needed to be locked, have a XXXLock variable.所有的变数名称三十,这是需要加以锁定,有XXXLock变数。

  • m_ContextMapLock ; m_ContextMapLock ;

    Holds all the client data (socket, client data, etc.).拥有所有客户的数据(插座,客户数据,等等) 。

  • ContextMap m_ContextMap ; ContextMap m_ContextMap ;
  • m_NumberOfActiveConnections

    Holds the number of connected connections.持有的数量连接连接。

4.1.3 Important functions 4.1.3重要职能

  • GetNumberOfConnections()

    Returns the number of connections.返回连接数。

  • CString GetHostAdress(ClientContext* p)

    Returns the host address, given a client context.返回主机地址,因为客户方面。

  • BOOL ASendToAll(CIOCPBuffer *pBuff);

    Sends the content of the buffer to all the connected clients.发送的内容缓冲区的所有连接的客户。

  • DisconnectClient(CString sID)

    Disconnects a client, given the unique identification number.断开一个客户,由于独特的识别号码。

  • CString GetHostIP()

    Returns the local IP number.返回本地IP号码。

  • JobItem* GetJob()

    Removes a JobItem from the queue, returns NULL if there are no Jobs.删除JobItem从队列,返回NULL ,如果没有乔布斯。

  • BOOL AddJob(JobItem *pJob)

    Adds a Job to the queue.增加了就业的队列中。

  • BOOL SetWorkers( int nThreads)

    Sets the number of logical workers that can be called anytime.设置一些合乎逻辑的工人,可随时要求。

  • DisconnectAll();

    Disconnect all the clients.断开所有的客户。

  • ARead(…)

    Makes an asynchronous read.使异步读取。

  • ASend(…)

    Makes an asynchronous send.使异步传送。 Sends data to a client.发送数据到客户端。

  • ClientContext* FindClient(CString strClient)

    Finds a client given a string ID.认定一个客户提供一个字符串编号。 OBS!地震! Not thread safe!不是线程安全!

  • DisconnectClient(ClientContext* pContext, BOOL bGraceful=FALSE);

    Disconnects a client.断开一个客户。

  • DisconnectAll()

    Disconnects all the connected clients.断开所有连接的客户。

  • StartSendFile(ClientContext *pContext)

    Sends a file specified in the ClientContext structure, using the optimized transmitfile(..) function.发送一个文件中指定的ClientContext结构,采用优化transmitfile(..)的功能。

  • PrepareReceiveFile(..)

    Prepares the connection for receiving a file.准备接收方面的文件。 When you call this function, all incoming byte streams are written to a file.当您调用此功能,所有收到的字节流写入一个文件。

  • PrepareSendFile(..)

    Opens a file and sends a package containing information about the file to the remote connection.打开一个文件,并发出了一个包含有关文件的远程连接。 The function also disables the ASend(..) function until the file is transmitted or aborted.功能也禁用ASend(..)功能,直至该文件转发或流产。

  • DisableSendFile(..)

    Disables send file mode.禁用发送文件模式。

  • DisableRecevideFile(..)

    Disables receive file mode.禁用接收档案模式。

5.1 File transfer 5.1文件传输

File transfer is done by using the Winsock 2.0 TransmitFile function.文件传输是通过使用Winsock的2.0 TransmitFile功能。 The TransmitFile function transmits file data over a connected socket handle.TransmitFile功能传送文件的数据连接的接口处理。 This function uses the operating system's cache manager to retrieve file data, and provides high-performance file data transfer over sockets.此功能使用操作系统的缓存经理检索档案资料,并提供高性能的文件数据传输插座。 These are some important aspects of asynchronous file transferring:这些都是一些重要方面的异步文件传输:

  • Unless the TransmitFile function is returned, no other sends or writes to the socket should be performed because this will corrupt the file.除非TransmitFile职能是回来了,没有其他发送或写入插座应进行,因为这会损坏文件。 Therefore, all the calls to ASend will be disabled after the PrepareSendFile(..) function.因此,所有的电话ASend将被禁用后, PrepareSendFile(..)的功能。
  • Since the operating system reads the file data sequentially, you can improve caching performance by opening the file handle with FILE_FLAG_SEQUENTIAL_SCAN .由于作业系统读取文件数据顺序,你可以提高性能的缓存打开文件处理与FILE_FLAG_SEQUENTIAL_SCAN
  • We are using the kernel asynchronous procedure calls while sending the file ( TF_USE_KERNEL_APC ).我们正在使用的内核的异步程序呼叫的同时发送文件( TF_USE_KERNEL_APC ) 。 Use of TF_USE_KERNEL_APC can deliver significant performance benefits.使用TF_USE_KERNEL_APC可以提供显着的性能好处。 It is possible (though unlikely), however, that the thread in which the context TransmitFile is initiated is being used for heavy computations; this situation may prevent APCs from launching.这是可能的(虽然不大可能) ,但是,线程在该方面TransmitFile启动正在使用的重型计算;这种情况可能会阻止装甲运兵车发射。

The file transfer is made in this order: the sever initializes the file transfer by calling the PrepareSendFile(..) function.文件传输是在这项命令:在服务器初始化文件传输致电PrepareSendFile(..)的功能。 When the client receives the information about the file, it prepares for it by calling the PrepareReceiveFile(..) , and sends a package to the sever to start the file transfer.当客户端收到有关文件,准备为它通过调用PrepareReceiveFile(..) ,并发出了一个一揽子的服务器来启动文件传输。 When the package arrives at the server side, the server calls the StartSendFile(..) function that uses the high performance TransmitFile function to transmit the specified file.当包到达服务器端,服务器调用StartSendFile(..)功能,使用高性能TransmitFile职能转交指定的文件。

6 The source code example 6源代码的例子

The provided source code example is an echo client/server that also supports file transmission (figure 4).所提供的源代码的例子是回声客户机/服务器,也支持文件传输(图4 ) 。 In the source code, a class MyIOCP inherited from IOCP handles the communication between the client and the server, by using the virtual functions mentioned in section 4.1.1.在源代码,一类MyIOCP继承IOCP处理之间的沟通客户端和服务器,通过使用虚拟职能节中提到4.1.1 。

The most important part of the client or server code is the virtual function NotifyReceivedPackage , as described below:最重要的一部分,客户端或服务器代码是虚函数NotifyReceivedPackage ,分述如下:

 void MyIOCP::NotifyReceivedPackage(CIOCPBuffer *pOverlapBuff, 无效 MyIOCP : : NotifyReceivedPackage ( CIOCPBuffer * pOverlapBuff , 
int nSize,ClientContext *pContext) 整数 nSize , ClientContext * pContext )
BYTE PackageType=pOverlapBuff- > GetPackageType(); 字节 PackageType = pOverlapBuff , GetPackageType ( ) ;
switch (PackageType) 开关( PackageType )
case Job_SendText2Client : 案件 Job_SendText2Client :
Packagetext(pOverlapBuff,nSize,pContext); Packagetext ( pOverlapBuff , nSize , pContext ) ;
break ; 打破 ;
case Job_SendFileInfo : 案件 Job_SendFileInfo :
PackageFileTransfer(pOverlapBuff,nSize,pContext); PackageFileTransfer ( pOverlapBuff , nSize , pContext ) ;
break ; 打破 ;
case Job_StartFileTransfer: 案件 Job_StartFileTransfer :
PackageStartFileTransfer(pOverlapBuff,nSize,pContext); PackageStartFileTransfer ( pOverlapBuff , nSize , pContext ) ;
break ; 打破 ;
case Job_AbortFileTransfer: 案件 Job_AbortFileTransfer :
DisableSendFile(pContext); DisableSendFile ( pContext ) ;
break ;}; 打破 ; ) ;

The function handles an incoming message and performs the request sent by the remote connection.功能处理传入讯息,并执行的请求派出的远程连接。 In this case, it is only a matter of a simple echo or file transfer.在这种情况下,这只是一个问题,一个简单的重复或文件传输。 The source code is divided into two projects, IOCP and IOCPClient, which are the server and the client side of the connection.源代码分为两个项目, IOCP和IOCPClient ,这是服务器和客户端的连接。

6.1 Compiler issues 6.1编译器的问题

When compiling with VC++ 6.0 or .NET, you may get some strange errors dealing with the CFile class, as:当编译用VC + + 6.0或。 NET中,您可能会得到一些奇怪的错误处理CFile类,如:

 <span><span style="text-align: left; direction: ltr;">“if (pContext-&gt;m_File.m_hFile !=</span> “如果( pContext , ” m_File.m_hFile ! =</span> <br><span><span style="text-align: left; direction: ltr;">INVALID_HANDLE_VALUE) &lt;-error C2446: '!=' : no conversion "</span> INVALID_HANDLE_VALUE ) “错误C2446 : ' ! = ' :没有转换”</span> <br><span><span style="text-align: left; direction: ltr;">"from 'void *' to 'unsigned int'”</span> “从'无效* '到'无符号整数”</span>

This problems can be solved if you update the header files ( *.h ) or your VC++ 6.0 version, or just change the type conversion error.这个问题可以得到解决,如果您更新头文件 *. h )或您用VC + + 6.0版本,或者只是改变类型转换错误。 After some modifications, the server/client source code can be used without MFC.经过一些修改,服务器/客户端的源代码可以使用MFC的。

7 Special considerations & rule of thumbs 7日特别考虑与法治的大拇指

When you are using this code in other types of applications, there are some programming traps related to this source code and "multithreaded programming" that can be avoided.当您使用此代码在其他类型的应用,也有一些相关的编程陷阱这个源代码和“多线程程序设计” ,可避免。 Nondeterministic errors are errors that occur stochastically “Randomly”, and it is hard to reproduce these nondeterministic errors by performing the same sequence of tasks that created the error.随意性错误发生的错误随机“随机” ,很难复制这种随意性错误,履行相同序列的任务,创造了错误。 These types of errors are the worst types of errors that exist, and usually, they occur because of errors in the core design implementation of the source code.这些类型的错误是最严重的错误类型的存在,通常,他们出现的错误,因为在核心设计实施的源代码。 When the server is running multiple IO working threads, serving clients that are connected, nondeterministic errors as access violations can occur if the programmer has not thought about the source code multithread environment.当服务器运行的是多线程的IO工作,服务客户端连接,随意性错误的机会,就可能发生的侵犯,如果程序员没有想到的源代码多线程环境。

Rule of thumb #1:经验法则# 1 :

Never read/write to the client context (eg, ClientContext ) with out locking it using the context lock as in the example below.从来没有读/写到用户端的范围内(例如, ClientContext )与锁定了使用范围锁定在下面的例子。 The notification function (eg, Notify*(ClientContext *pContext) ) is already “thread safe”, and you can access the members of ClientContext without locking and unlocking the context.通知功能(例如, Notify*(ClientContext *pContext)已经是“线程安全” ,并可以访问的成员ClientContext没有锁定和解锁的背景。

 // Do not do it in this way / /不要它以这种方式 
// … / / ...
If(pContext- > m_bSomeData)如果( pContext , m_bSomeData )
pContext- > m_iSomeData=0; pContext , m_iSomeData = 0 ;
// … / / ...

 // Do it in this way. / /不要它以这种方式。 
// …. / / ... 。
pContext- > m_ContextLock.Lock(); pContext , m_ContextLock.Lock ( ) ;
If(pContext- > m_bSomeData)如果( pContext , m_bSomeData )
pContext- > m_iSomeData=0; pContext , m_iSomeData = 0 ;
pContext- > m_ContextLock.Unlock(); pContext , m_ContextLock.Unlock ( ) ;
// … / / ...

Also, be aware that when you are locking a Context, other threads or GUI would be waiting for it.此外,应认识到,当你锁定的情况下,其他线程或图形用户界面将等待着它。

Rule of thumb #2:经验法则# 2 :

Avoid or "use with special care" code that has complicated "context locks" or other types of locks inside a “context lock”, because this may lead to a “deadlock” (eg, A waiting for B that is waiting for C that is waiting for A => deadlock).避免或“使用特殊照顾”的代码已复杂的“背景锁”或其他类型的锁在一个“范围锁定” ,因为这可能会导致“僵局” (例如,等待的B这是等待的C正在等待= “僵局) 。

 pContext- > m_ContextLock.Lock(); pContext ,  m_ContextLock.Lock ( ) ; 
// … code code .. / / ...代码代码..
pContext2- > m_ContextLock.Lock(); pContext2 , m_ContextLock.Lock ( ) ;
// code code.. / /代码代码..
pContext2- > m_ContextLock.Unlock(); pContext2 , m_ContextLock.Unlock ( ) ;
// code code.. / /代码代码..
pContext- > m_ContextLock.Unlock(); pContext , m_ContextLock.Unlock ( ) ;

The code above may cause a deadlock.上述的程式码可能会导致僵局。

Rule of thumb #3:经验法则# 3 :

Never access a client context outside the notification functions (eg, Notify*(ClientContext *pContext) ).从来没有进入客户的范围之外的通知功能(例如, Notify*(ClientContext *pContext) If you do, you have to enclose it with m_ContextMapLock.Lock();m_ContextMapLock.Unlock(); .如果你这样做,你必须附上与m_ContextMapLock.Lock(); ... m_ContextMapLock.Unlock(); See the source code below.见的源代码如下。

 ClientContext* pContext=NULL ; ClientContext * pContext =空; 
m_ContextMapLock.Lock(); m_ContextMapLock.Lock ( ) ;
pContext = FindClient(ClientID); pContext = FindClient ( ClientID ) ;
// safe to access pContext, if it is not NULL / /安全访问pContext ,如果它不是空
// and are Locked (Rule of thumbs#1:) / /并锁定(大拇指规则# 1 : )
// code .. / /代码.. code.. 代码..
m_ContextMapLock.Unlock(); m_ContextMapLock.Unlock ( ) ;
// Here pContext can suddenly disappear because of disconnect. / /这里pContext可以突然消失,因为断开。
// do not access pContext members here. / /不pContext成员进入这里。

8 Future work 8日未来的工作

In future, the source code will be updated to have the following features in chronological order:在未来,源代码将更新为具有以下特点按时间顺序为:

  1. The implementation of AcceptEx(..) function to accept new connections will be added to the source code, to handle short lived connection bursts and DOS attacks.实施AcceptEx(..)函数接受新的连接将被加入到源代码,以处理短命的连接扫射和DOS攻击。
  2. The source code will be portable to other platforms as Win32, STL, and WTL.源代码将携带到其他平台的Win32 ,的STL和WTL 。

9 FAQ 9日的常见问题

Q1 : The amount of Memory used (server program is rising steadily on increase in client connections, as seen using the 'Windows Task Manager'. Even if clients disconnect, the amount of memory used does not decrease. What's the problem? 1 :的内存量使用(服务器程序是稳步上升的增加,客户端连接,如使用' Windows任务管理器' 。即使客户端断开,数额使用的内存不减少。什么问题?

A1 : The code tries to reuse the allocated buffers instead of releasing and reallocating it. 格A1 :代码尝试重用分配的缓冲区而不是重新分配和释放它。 You can change this by altering the parameters, iMaxNumberOfFreeBuffer and iMaxNumberOfFreeContext .你可以改变这种通过改变参数, iMaxNumberOfFreeBufferiMaxNumberOfFreeContext Please review section 3.8.1.请仔细阅读节3.8.1 。

Q2 : I get compilation errors under .NET: "error C2446: '!=' : no conversion from 'unsigned int' to 'HANDLE'" etc.. 问题2 :我要下编错误的。 NET : “错误C2446 : ' ! = ' :无转换从'无符号整数'至'处理' ”等。 What is the problem?问题是什么呢?

A2 : This is because of the different header versions of the SDK. 答2 :这是因为不同的头版本的SDK的。 Just change the conversion to HANDLE so the compiler gets happy.只要改变转换HANDLE ,使编译器得到幸福。 You can also just remove the line #define TRANSFERFILEFUNCTIONALITY and try to compile.您也可以删除#define TRANSFERFILEFUNCTIONALITY线#define TRANSFERFILEFUNCTIONALITY并尝试编译。

Q3 : Can the source code be used without MFC? 问题3 :可以的源代码是使用MFC的? Pure Win32 and in a service?纯Win32和在服务吗?

A3 : The code was developed to be used with a GUI for a short time (not days or years). 答3 :代码是为了使用一个GUI很短的时间(而不是几天或数年) 。 I developed this client/server solution for use with GUIs in an MFC environment.本人开发此客户机/服务器解决方案,用于GUIs在MFC的环境。 Of course, you can use it for normal server solutions.当然,你可以用它进行正常的服务器解决方案。 Many people have.许多人。 Just remove the MFC specific stuff as CString , CPtrList etc.., and replace them with Win32 classes.只要删除MFC的具体内容为CStringCPtrList等。 ,并取代它们的Win32类。 I don’t like MFC either, so send me a copy when you change the code.我不喜欢的MFC要么,所以寄给我一份当您更改代码。 Thanks.谢谢。

Q4 : Excellent work! 4 :出色的工作! Thank you for this.谢谢你这一点。 When will you implement AcceptEx(..) instead of the connection listener thread?当你执行AcceptEx(..)听众的连接线?

A4 : As soon as the code is stable. 答4 :当代码是稳定的。 It is quite stable right now, but I know that the combination of several I/O workers and several pending reads may cause some problems.这是相当稳定的权利,但我知道的几个组合的I / O工人和一些悬而未决的内容可能会导致一些问题。 I enjoy that you like my code.我喜欢你喜欢我的代码。 Please vote!请投票!

Q5 : Why start several I/O workers? 问5 :为什么一些启动的I / O工人? Is this necessary if you don’t have a true multiprocessor computer?这是必要的,如果你没有一个真正的多处理器计算机?

A5 : No, it is not necessary to have several I/O workers. 答5 :没有,没有必要有若干的I / O工人。 Just one thread can handle all the connections.只需一个线程可以处理所有的连接。 On common home computers, one I/O worker gives the best performance.关于共同家用电脑,一个的I / O工作者提供了最佳的性能。 You do not need to worry about possible access violation threats either.您不必担心可能的存取违规或者威胁。 But as computers are getting more powerful each day (eg, hyperthreading, dual-core, etc.), why not have the possibility to have several threads?但是,随着电脑越来越强大的每一天(例如,超线程,双核心等等) ,为什么不能有可能有几个线程? :=) : = )

Q6 : Why use several pending reads? 问6 :为什么要使用若干悬而未决的内容? What is it good for?它是什么好处?

A6 : That depends on the server development strategy that is adapted by the developer, namely “many concurrent connections” vs. “ high throughput server”. 答6 :这取决于服务器发展战略,是适应的开发,即“许多并行连接”与“高吞吐量的服务器” 。 Having multiple pending reads increases the throughput of the server because the TCP/IP packages will be written directly into the passed buffer instead of to the TCP/IP stack (no double-buffering).多待了内容增加了吞吐量,因为服务器的TCP / IP包将被写入直接通过缓冲区,而不是TCP / IP堆栈(没有双缓冲) 。 If the server knows that clients send data in bursts, pending reads increase the performance (high throughput).如果服务器知道客户端发送数据的扫射,等待读取性能提高(高通量) 。 However, every pending receive operation (with WSARecv() ) that occurs forces the kernel to lock the receive buffers into the non-paged pool.然而,每一个接受手术之前(与WSARecv()发生的核心力量锁定接收缓冲区的非分页池。 This may lead to a WSAENOBUFFS error when the physical memory is full (many concurrent connections).这可能导致错误WSAENOBUFFS当物理内存已满(许多并发连接) 。 The use of pending reads/writes have to be done carefully, and aspects such as “page size on the architecture” and “the amount of non-paged pool (1/4 of the physical memory)” have to be taken into consideration.使用之前,读/写有许多工作要做仔细,和各个方面,如“页面大小的架构”和“数额非分页池( 1 / 4的物理内存) ”必须加以考虑。 Furthermore, if you have more than one IO worker, the order of packages is broken (because of the IOCP structure), and the extra work to maintain the order makes it unnecessary to have multiple pending reads.此外,如果您有一个以上的IO工人,该命令的包被打破(因为IOCP结构) ,以及额外的工作以维持秩序使不必要的有多个悬而未决的内容。 In this design, multiple pending reads is turned off when the number of I/O workers is greater than one because the implementation can not handle the reordering.在此设计,行文多待关闭时的数量的I / O工人大于1由于执行不能处理的调整。 (The sequence number must exist in the payload instead.) (序列号码必须存在有效载荷代替。 )

Q7 : In the previous article, you stated that we have to implement memory management using the VirtualAlloc function instead of new , why have you not implemented it? 问7 :在前面的文章,你说,我们必须执行记忆体管理使用VirtualAlloc函数而不是new ,为什么你不执行呢?

A7 : When you allocate memory with new , the memory is allocated in the virtual memory or the physical memory. 答7 :当您分配内存new的,内存是分配在虚拟内存或物理内存。 Where the memory is allocated is unknown, the memory can be allocated between two pages.如果记忆体分配是未知的,内存可以分配两页。 This means that we load too much memory into the physical memory when we access a certain data (if we use new ).这意味着,我们负荷太多记忆的物理内存时,我们获得了一定的数据(如果我们new新的) 。 Furthermore, you do not know if the allocated memory is in physical memory or in virtual, and also you can not tell the system when "writing back" to hard disk is unnecessary (if we don’t care of the data in memory anymore).此外,你不知道,如果分配的内存是在物理内存或虚拟,也可以不告诉系统时, “书面回”到硬盘上是不必要的(如果我们不关心的数据在内存中了) 。 But be aware!!但要知道! Any new allocation using VirtualAlloc* will always be rounded up to 64 KB (page file size) boundary so that if you allocate a new VAS region bound to the physical memory, the OS will consume an amount of physical memory rounded up to the page size, and will consume the VAS of the process rounded up to 64 KB boundary.任何新的分配使用VirtualAlloc*将永远是四舍五入为64 KB (页面文件的大小)的边界,以便,如果您分配一个新的增值业务区域必然的物理内存,操作系统将消费金额的物理内存四舍五入至页面大小,并会消耗增值的过程四舍五入至64 KB的边界。 Using VirtualAlloc can be difficult: new and malloc use virualAlloc internally, but every time you allocate memory with new / delete , a lot of other computation is done, and you do not have the control to put your data (data related to each other) nicely inside the same page (without overlapping two pages).使用VirtualAlloc可能很难: new malloc使用virualAlloc国内,但每次分配内存new delete删除,还有很多其他的计算做到这一点,和你没有控制,把你的数据(相关数据互相)不错内的同一页上(不重叠的两页) 。 However, heaps are best for managing large numbers of small objects, and I shall change the source code so it only uses new / delete because of code cleanness.然而,成堆的最佳管理大量的小物体,我将更改源代码,以便它只能使用new delete删除,因为代码清洁度。 I have found that the performance gain is too small relative when compared to the complexity of the source code.我认为,性能提升太小,相对比较复杂的源代码。

10 References 10个参考

11 Revision History 11日修订历史

  • Version 1.0 - 2005-05-10版本1.0 -2 005年5月1 0号

    • Initial public release.首次公开发行。
  • Version 1.1 - 2005-06-13版本1.1 -0 5年6月1 3日
    • Fixed some memory leakage (eg, ~CIOCPBuffer() ).固定一些内存泄漏(例如, ~CIOCPBuffer()
    • TransmitFile is now optional in the source code (by using #define TRANSFERFILEFUNCTIONALITY ). TransmitFile现在是可选的源代码(使用#define TRANSFERFILEFUNCTIONALITY ) 。
    • Some extra functions are added (by using #define SIMPLESECURITY ).一些额外添加功能(使用#define SIMPLESECURITY ) 。
  • Version 1.11 - 2005-06-18版本1.11 -0 5年6月1 8号
    • Changes in IOCPS::ProcessPackage(…) to avoid access violation.变化IOCPS::ProcessPackage(…) ,以避免存取违规。
    • Error in CIOCPBuffer::Flush(..) fixed.误差CIOCPBuffer::Flush(..)固定的。
    • Changes in IOCPS::Connect(..) to release socket when an error occurs.变化IOCPS::Connect(..)释放插座时发生错误。
  • Version 1.12 - 2005-11-29版本1.12 -2 005-11-29
    • Changes in IOCPS::OnWrite(….) to avoiding entering an infinite loop.变化IOCPS::OnWrite(….)以避免进入一个无限循环。
    • Changes in OnRead(…) and OnZeroByteRead (…) to avoid access violation if memory is full and AllocateBuffer fails.变化OnRead(…)OnZeroByteRead (…) ,以避免违规存取记忆体,如果充分和AllocateBuffer失败。
    • Changes in OnReadCompleted(…) to avoid access violation.变化OnReadCompleted(…) ,以避免存取违规。
    • Changes in AcceptIncomingClient(..) to better handle a new connection when the maximum number of connections is reached.变化AcceptIncomingClient(..) ,以便更好地处理一个新的连接时,最大连接数达到。
  • Version 1.13 - 2005-12-29版本1.13 -2 005年1 2月2 9号
    • ReleaseBuffer(…) added to ARead(..) , ASend(..) , AZeroByteRead(..) to avoid memory leakage. ReleaseBuffer(…)加入ARead(..)ASend(..)AZeroByteRead(..) ,以避免记忆体泄漏。
    • Changes in DisconnectClient(…) and ReleaseClientContext(…) to avoid “duplicate key” error when clients rapidly connect and disconnect.变化DisconnectClient(…)ReleaseClientContext(…)为避免“重复的关键”的错误时,客户的快速连接和断开。
    • Changes in IOWorkerThreadProc(…) , OnWrite(ClientContext *pContext,…) , etc. to avoid buffer leakage.变化IOWorkerThreadProc(…)OnWrite(ClientContext *pContext,…)等,以避免缓冲区泄漏。
    • Changes in DisconnectClient( unsigned int iID) to avoid access violation.变化DisconnectClient( unsigned int iID)存取违规。
    • Added EnterIOLoop(..)/ExitIPLoop(..) to StartSendFile(..) and OnTransmitFileCompleted(..) to avoid access violation.新增EnterIOLoop(..)/ExitIPLoop(..)StartSendFile(..)OnTransmitFileCompleted(..) ,以避免存取违规。
    • Some unessential error messages removed from the release mode, and additional debug information (eg, TRACE(..) ) added to the source code in debug mode.一些非本质的错误讯息从释放模式,和额外的调试信息(例如, TRACE(..) )添加到源代码调试模式。
    • The function AcceptIncomingClients(..) changed and replaced with AssociateIncomingClientWithContext(..) .功能AcceptIncomingClients(..)改变,代之以AssociateIncomingClientWithContext(..)
    • The Connect(..) function now uses AssociateIncomingClientWithContext(..) . Connect(..)函数现在使用AssociateIncomingClientWithContext(..)
    • Transfer file functions are now completely optional by making #define TRANSFERFILEFUNCTIONALITY .传输文件功能已完全可选的决策#define TRANSFERFILEFUNCTIONALITY
    • Changes in DisableSendFile(..) and other file transfer functions, to avoid access violation.变化DisableSendFile(..)和其他文件传输功能,以避免存取违规。
    • Some unnecessary functions and comments removed from the source code.一些不必要的职能和评论从源代码。 Appropriate functions are now made private, protected, or public.适当的职能是现在私营,保护,或公开。
    • Several functions are now “inlined” to avoid the overhead of calling a function and for gaining performance.一些职能现在“内插” ,以避免间接调用一个函数,并取得业绩。
    • Removed and replaced EnterIOLoop(..) and other code in OnWrite(ClientContext *pContext,…) to avoid access violations.移除和替换EnterIOLoop(..)和其他代码OnWrite(ClientContext *pContext,…) ,以避免存取违规。 Information is in the source code.信息是源代码。
    • Added "random disconnect" to demo server, and "auto reconnect" to demo client, plus additional cleanup in the demo project, and I now follow my own advices.由“随机断开”演示服务器, “自动重新”演示客户端,加上清理的示范项目,并按照我现在我自己的建议。 :=) : = )
  • Version 1.14 - 2006-02-18版本1.14 -0 6年2月1 8日
    • Changes in IOWorkerThreadProc(LPVOID pParam) to avoid memory leakage (eg, new ClientContext ) on shutdown ("bug" detected by Maxim Y. Mluhov).变化IOWorkerThreadProc(LPVOID pParam)记忆体泄漏(例如, new ClientContext )在关机( “千年虫”马克西姆检测Mluhov元) 。
    • Small changes in OnReadCompleted(..) .小的变化OnReadCompleted(..)
    • Small change in IOCPS::DisconnectIfIPExist(..) , to gain performance (fix by spring).小的变化IOCPS::DisconnectIfIPExist(..) ,取得业绩(修正的春天) 。
    • Small change in CIOCPBuffer::Flush(...) (fix by spring).小的变化CIOCPBuffer::Flush(...) (修正的春天) 。
    • When using multiple pending reads (eg, m_iNumberOfPendlingReads > 1 ) with multiple I/O workers (eg, m_iMaxIOWorkers > 1 ), the order of the packages is broken.当使用多个悬而未决的内容(例如, m_iNumberOfPendlingReads > 1与多个I / O工人(例如, m_iMaxIOWorkers > 1该命令的包被打破了。 Temporary fix added to IOCPS::startup() (eg, if (m_iMaxIOWorkers > 1 ) m_iNumberOfPendlingReads=1; ).临时的解决办法添加到IOCPS::startup() (例如if (m_iMaxIOWorkers > 1 ) m_iNumberOfPendlingReads=1;如果if (m_iMaxIOWorkers > 1 ) m_iNumberOfPendlingReads=1; ) 。
    • Updated section 8 and 6.3.2 in the article.最新的第8条和6.3.2的规定。
  • Version 1.15 - 2006-06-19版本1.15 -0 6年6月1 9日
    • Changes in the CIOCPBuffer class and AllocateBuffer(..) .变化CIOCPBuffer阶级和AllocateBuffer(..) Now, all the memory allocation/de –allocation is made on the heap using new / delete and VirtualAlloc(..) is not used (read question 7 for more information).现在,所有的内存分配/取消了分配上的堆newdelete删除和VirtualAlloc(..)不使用(读第7个问题的更多信息) 。
    • Changes in IOCPS::OnInitialize(..) to avoid WSAENOBUFS , exchanged the order of the ARead(..) , AZeroByteRead(..) .变化IOCPS::OnInitialize(..) ,以避免WSAENOBUFS ,交换秩序的ARead(..)AZeroByteRead(..)
    • Multiple pending read removed when multiple I/O workers are used.待多读时删除多个I / O工人使用。 (Temporary fix is now permanent fix, read A6 and Q6.) (临时修复现在永久修复,阅读A6和问题6 。 )
    • The #define SIMPLESECURITY functions are used inside the ConnectAcceptCondition(..) with the SO_CONDITIONAL_ACCEPT parameter using WSAAccept(..) , increasing security.#define SIMPLESECURITY职能是用于内部ConnectAcceptCondition(..)SO_CONDITIONAL_ACCEPT参数使用WSAAccept(..) ,增加安全性。 We can refuse connections in a lower level in the kernel (not sending ACK => the attacker thinks that the server is down).我们可以拒绝连接在一个较低的水平在内核(不发送应答= “攻击者认为,服务器已关闭) 。
    • IsAlreadyConnected(..) and IsInBannedList(..) replacing DisconnectIfIPExist(..) and DisconnectIfBanned(..) because of optimization, the IP compare is using sockaddr_in instead of string compare. IsAlreadyConnected(..)IsInBannedList(..)取代DisconnectIfIPExist(..)DisconnectIfBanned(..)由于优化,知识产权比较使用sockaddr_in而不是字符串比较。

12 Other IOCP implementations using this source code 12其他IOCP实现利用这个源代码


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)本文连同任何相关的源代码和文件,根据许可项目的代码开放许可证( CPOL )

About the Author作者简介

spinoza 斯宾诺莎

Amin Gholiha.阿明Gholiha 。
- Master of Science in Information Technology. -科学硕士在信息技术。
- Degree of Master of Education. -硕士学位教育。
Knowledge/interest: programming (.NET,Visual, C#/C++), neural network, mathematical modeling, signal processing, sequence analysis, pattern recognition,robot technology, system design, security and business management systems.知识/兴趣:编程( 。 NET中,视觉, C #中/ C + +中) ,神经网络,数学建模,信号处理,序列分析,模式识别,机器人技术,系统设计,安全和业务管理系统。 For business proposal email Gholiha@rocketmail.com, all other emails will be ignored.对于企业提出的电子邮件Gholiha@rocketmail.com ,所有其他的电子邮件将被忽略。
Current Work:当前的工作:
Project Manager / Developer项目经理/开发
www.iba-worldwide.com www.iba - worldwide.com
Occupation:职业: Program Manager 项目经理
Company:公司: Iba 伊巴
Location:地点: 瑞典 Sweden 瑞典