Click here to Skip to main content
Click here to Skip to main content

A simple IOCP Server/Client Class

By , 11 Dec 2008
 

1.1 Requirements

  • The article expects the reader to be familiar with C++, TCP/IP, socket programming, MFC, and multithreading.
  • The source code uses Winsock 2.0 and the IOCP technology, and requires:
    • Windows NT/2000 or later: Requires Windows NT 3.5 or later.
    • Windows 95/98/ME: Not supported.
    • Visual C++ .NET, or a fully updated Visual C++ 6.0.

1.2 Abstract

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 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. 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. This article focuses on the practical solutions that arise with the IOCP programming API, and also presents an overall documentation of the source code. Furthermore, a simple echo client/server which can handle multiple connections and file transfer is also presented here.

2.1 Introduction

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. The source code is based on many other source codes and articles: [1, 2, and 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. 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.

2.2 Introduction to asynchronous Input/Output Completion Ports (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. By definition, an asynchronous I/O call returns immediately, leaving the I/O call pending. At some point of time, the result of the I/O asynchronous call must be synchronized with the main thread. 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.
  • 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. Coding IOCP can be a bit of a hassle.

2.2.1 Why using IOCP?

By using IOCP, we can overcome the "one-thread-per-client" problem. 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". The threads are suspended, and don't use the CPU cycles until there is something to do.

2.3 What is 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. An IOCP object is associated with several I/O objects that support pending asynchronous I/O calls. A thread that has access to an IOCP can be suspended until a pending asynchronous I/O call is finished.

3 How does IOCP work?

To get more information on this part, I referred to other articles. [1, 2, 3, see References.]

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. 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.

3.1 The completion key parameter

The first parameter, the CompletionKey, is just a variable of type 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.

3.2 The OVERLAPPED parameter

This parameter is commonly used to pass the memory buffer that is used by the asynchronous I/O call. 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

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:

BOOL IOCPS::AssociateSocketWithCompletionPort(SOCKET socket, 
               HANDLE hCompletionPort, DWORD dwCompletionKey)
   {
       HANDLE h = CreateIoCompletionPort((HANDLE) socket, 
             hCompletionPort, dwCompletionKey, m_nIOWorkers);
       return h == hCompletionPort;
   }

3.4 Making the asynchronous I/O call

To make the actual asynchronous call, the functions WSASend, WSARecv are called. They also need to have a parameter WSABUF, that contains a pointer to a buffer that is going to be used. 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. The reason for this is, we want the CPU cycles to be partitioned fairly. The I/O calls are made by posting a status to the completion port, see below:

BOOL bSuccess = PostQueuedCompletionStatus(m_hCompletionPort, 
                       pOverlapBuff->GetUsed(), 
                       (DWORD) pContext, &pOverlapBuff->m_ol);

3.5 Synchronization with the thread

Synchronization with the I/O worker threads is done by calling the GetQueuedCompletionStatus function (see below). The function also provides the CompletionKey parameter and the OVERLAPPED parameter (see below):

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.6 Four thorny IOCP coding hassles and their solutions

There are some problems that arise while using IOCP, some of them are not intuitive. 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. In this section, we will represent four different problems that can occur while developing client/server applications using IOCPs. They are:

  • The WSAENOBUFS error problem.
  • The package reordering problem.
  • The access violation problem.

3.6.1 The WSAENOBUFS error problem

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.

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.

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. 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).

A simple practical solution to the WSAENOBUFS error problem is in the source code provided. We perform an asynchronous WSARead(..) (see OnZeroByteRead(..)) with a zero byte buffer. 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. This solution locks physical memory only when data arrives, and solves the WSAENOBUFS problem. But this solution decreases the throughput of the server (see Q6 and A6 in section 9 F.A.Q).

3.6.2 The package reordering problem

This problem is also being discussed by [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. 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". 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.

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.

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 (e.g., m_SendBufferMap and m_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:

  • GetNextSendBuffer (..) and GetNextReadBuffer(..), to get the ordered send or receive buffer.
  • IncreaseReadSequenceNumber(..) and IncreaseSendSequenceNumber(..), to increase the sequence numbers.

3.6.3 Asynchronous pending reads and byte chunk package processing problem

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. 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. 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. Furthermore, a byte chunk stream can contain one or several packages and also partial packages, as shown in figure 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).

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). 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.

3.6.4 The access violation problem

This is a minor problem, and is a result of the design of the code, rather than an IOCP specific problem. 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. In the parameter CompletionKey, we pass a pointer to a structure ClientContext that contains client specific data. 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? 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. This is done by the EnterIoLoop(..) function and ReleaseClientContext(..).

3.7 The overview of the source code

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. 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.

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. 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.

Figure 3. The figure above shows the class overview.

The classes that can be observed in figure 3 are:

  • CIOCPBuffer: A class used to manage the buffers used by the asynchronous I/O calls.
  • IOCPS: The main class that handles all the communication.
  • JobItem: A structure which contains the job to be performed by the logical worker threads.
  • ClientContext: A structure that holds client specific information (status, data, etc.).

3.7.1 The buffer design – The CIOCPBuffer class

When using asynchronous I/O calls, we have to provide a private buffer to be used with the I/O operation. 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;
       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 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. This is done by the SplitBuffer function in the CIOCPS class. Also, sometimes we need to copy information between the buffer, and this is done by the AddAndFlush(..) function in the IOCPS class.
  • As we know, we also need to add a sequence number and a state (IOType variable, IOZeroReadCompleted, etc.) to our buffer.
  • 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.

All the solutions to the problems we have discussed above exist in the CIOCPBuffer class.

3.8 How to use the source code?

By inheriting your own class from IOCP (shown in figure 3) and using the virtual functions and the functionality provided by the IOCPS class (e.g., 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.

3.8.1 Starting and closing the server/client

To start the server, call the function:

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);
  • nPortt

    Is the port number that the server will listen on. (Let it be -1 for client mode.)

  • 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)

  • iMaxNumberOfFreeContext

    Maximum number of client information objects that are saved for reuse. (-1 for none, 0= Infinite number)

  • bOrderedRead

    Make sequential reads. (We have discussed this before in section 3.6.2.)

  • bOrderedSend

    Make sequential writes. (We have discussed this before in section 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:

Connect(const CString &strIPAddr, int nPort)
  • strIPAddr

    The IP address of the remote server.

  • nPort

    The port.

To close, make the server call the function: ShutDown().

For example:

MyIOCP m_iocp;
if(!m_iocp.Start(-1,1210,2,1,0,0))
AfxMessageBox("Error could not start the Client");
….
m_iocp.ShutDown();

4.1 Source code description

For more details about the source code, please check the comments in the source code.

4.1.1 Virtual functions

  • NotifyNewConnection

    Called when a new connection has been established.

  • NotifyNewClientContext

    Called when an empty ClientContext structure is allocated.

  • 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

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.

  • m_ContextMapLock;

    Holds all the client data (socket, client data, etc.).

  • ContextMap m_ContextMap;
  • m_NumberOfActiveConnections

    Holds the number of connected connections.

4.1.3 Important functions

  • 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.

  • JobItem* GetJob()

    Removes a JobItem from the queue, returns NULL if there are no Jobs.

  • 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.

  • 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.

  • DisableSendFile(..)

    Disables send file mode.

  • DisableRecevideFile(..)

    Disables receive file mode.

5.1 File transfer

File transfer is done by using the Winsock 2.0 TransmitFile function. The TransmitFile function transmits file data over a connected socket handle. 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. Therefore, all the calls to ASend will be disabled after the PrepareSendFile(..) function.
  • Since the operating system reads the file data sequentially, you can improve caching performance by opening the file handle with FILE_FLAG_SEQUENTIAL_SCAN.
  • We are using the kernel asynchronous procedure calls while sending the file (TF_USE_KERNEL_APC). Use of TF_USE_KERNEL_APC can deliver significant performance benefits. 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.

The file transfer is made in this order: the sever initializes the file transfer by calling the PrepareSendFile(..) function. 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. 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.

6 The source code example

The provided source code example is an echo client/server that also supports file transmission (figure 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.

The most important part of the client or server code is the virtual function NotifyReceivedPackage, as described below:

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 :
             PackageFileTransfer(pOverlapBuff,nSize,pContext);
             break; 
         case Job_StartFileTransfer: 
             PackageStartFileTransfer(pOverlapBuff,nSize,pContext);
             break;
         case Job_AbortFileTransfer:
             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.

6.1 Compiler issues

When compiling with VC++ 6.0 or .NET, you may get some strange errors dealing with the CFile class, as:

“if (pContext->m_File.m_hFile != 
INVALID_HANDLE_VALUE) <-error C2446: '!=' : no conversion "
"from 'void *' to 'unsigned int'”

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. After some modifications, the server/client source code can be used without MFC.

7 Special considerations & rule of thumbs

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.

Rule of thumb #1:

Never read/write to the client context (e.g., ClientContext) with out locking it using the context lock as in the example below. The notification function (e.g., Notify*(ClientContext *pContext)) is already “thread safe”, and you can access the members of ClientContext without locking and unlocking the context.

//Do not do it in this way

//
If(pContext->m_bSomeData)
pContext->m_iSomeData=0;
//
// Do it in this way. 

//….

pContext->m_ContextLock.Lock(); 
If(pContext->m_bSomeData) 
pContext->m_iSomeData=0; 
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:

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” (e.g., A waiting for B that is waiting for C that is waiting for A => deadlock).

pContext-> m_ContextLock.Lock();
//… code code .. 

pContext2-> m_ContextLock.Lock(); 
// code code.. 

pContext2-> m_ContextLock.Unlock(); 
// code code.. 

pContext-> m_ContextLock.Unlock();

The code above may cause a deadlock.

Rule of thumb #3:

Never access a client context outside the notification functions (e.g., Notify*(ClientContext *pContext)). If you do, you have to enclose it with m_ContextMapLock.Lock();m_ContextMapLock.Unlock();. See the source code below.

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:) 

//code .. code.. 

m_ContextMapLock.Unlock(); 
// Here pContext can suddenly disappear because of disconnect. 

// do not access pContext members here.

8 Future work

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.
  2. The source code will be portable to other platforms as Win32, STL, and WTL.

9 F.A.Q

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?

A1: The code tries to reuse the allocated buffers instead of releasing and reallocating it. You can change this by altering the parameters, iMaxNumberOfFreeBuffer and iMaxNumberOfFreeContext. Please review section 3.8.1.

Q2: I get compilation errors under .NET: "error C2446: '!=' : no conversion from 'unsigned int' to 'HANDLE'" etc.. What is the problem?

A2: This is because of the different header versions of the SDK. Just change the conversion to HANDLE so the compiler gets happy. You can also just remove the line #define TRANSFERFILEFUNCTIONALITY and try to compile.

Q3: Can the source code be used without MFC? Pure Win32 and in a service?

A3: The code was developed to be used with a GUI for a short time (not days or years). I developed this client/server solution for use with GUIs in an MFC environment. 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. I don’t like MFC either, so send me a copy when you change the code. Thanks.

Q4: Excellent work! Thank you for this. When will you implement AcceptEx(..) instead of the connection listener thread?

A4: As soon as the code is stable. 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 enjoy that you like my code. Please vote!

Q5: Why start several I/O workers? Is this necessary if you don’t have a true multiprocessor computer?

A5: No, it is not necessary to have several I/O workers. Just one thread can handle all the connections. On common home computers, one I/O worker gives the best performance. You do not need to worry about possible access violation threats either. But as computers are getting more powerful each day (e.g., hyperthreading, dual-core, etc.), why not have the possibility to have several threads? :=)

Q6: Why use several pending reads? 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”. 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). 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. This may lead to a WSAENOBUFFS error when the physical memory is full (many concurrent connections). 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. 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. 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. (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?

A7: When you allocate memory with new, the memory is allocated in the virtual memory or the physical memory. 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). 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. 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). 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. I have found that the performance gain is too small relative when compared to the complexity of the source code.

10 References

11 Revision History

  • Version 1.0 - 2005-05-10
    • Initial public release.
  • Version 1.1 - 2005-06-13
    • Fixed some memory leakage (e.g., ~CIOCPBuffer()).
    • TransmitFile is now optional in the source code (by using #define TRANSFERFILEFUNCTIONALITY).
    • Some extra functions are added (by using #define SIMPLESECURITY).
  • Version 1.11 - 2005-06-18
    • Changes in IOCPS::ProcessPackage(…) to avoid access violation.
    • Error in CIOCPBuffer::Flush(..) fixed.
    • Changes in IOCPS::Connect(..) to release socket when an error occurs.
  • Version 1.12 - 2005-11-29
    • Changes in IOCPS::OnWrite(….) to avoiding entering an infinite loop.
    • Changes in OnRead(…) and OnZeroByteRead (…) to avoid access violation if memory is full and AllocateBuffer fails.
    • Changes in OnReadCompleted(…) to avoid access violation.
    • Changes in AcceptIncomingClient(..) to better handle a new connection when the maximum number of connections is reached.
  • Version 1.13 - 2005-12-29
    • ReleaseBuffer(…) added to ARead(..), ASend(..), AZeroByteRead(..) to avoid memory leakage.
    • Changes in DisconnectClient(…) and ReleaseClientContext(…) to avoid “duplicate key” error when clients rapidly connect and disconnect.
    • Changes in IOWorkerThreadProc(…), OnWrite(ClientContext *pContext,…), etc. to avoid buffer leakage.
    • Changes in DisconnectClient( unsigned int iID) to avoid access violation.
    • Added EnterIOLoop(..)/ExitIPLoop(..) to StartSendFile(..) and OnTransmitFileCompleted(..) to avoid access violation.
    • Some unessential error messages removed from the release mode, and additional debug information (e.g., TRACE(..) ) added to the source code in debug mode.
    • The function AcceptIncomingClients(..) changed and replaced with AssociateIncomingClientWithContext(..).
    • The Connect(..) function now uses AssociateIncomingClientWithContext(..).
    • Transfer file functions are now completely optional by making #define TRANSFERFILEFUNCTIONALITY.
    • Changes in DisableSendFile(..)and other file transfer functions, to avoid access violation.
    • 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. 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
    • Changes in IOWorkerThreadProc(LPVOID pParam)to avoid memory leakage (e.g., new ClientContext) on shutdown ("bug" detected by Maxim Y. Mluhov).
    • Small changes in OnReadCompleted(..).
    • Small change in IOCPS::DisconnectIfIPExist(..), to gain performance (fix by spring).
    • Small change in CIOCPBuffer::Flush(...) (fix by spring).
    • When using multiple pending reads (e.g., m_iNumberOfPendlingReads>1) with multiple I/O workers (e.g., m_iMaxIOWorkers>1), the order of the packages is broken. Temporary fix added to IOCPS::startup() (e.g., if(m_iMaxIOWorkers>1) m_iNumberOfPendlingReads=1;).
    • Updated section 8 and 6.3.2 in the article.
  • Version 1.15 - 2006-06-19
    • Changes in the CIOCPBuffer class and 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).
    • Changes in IOCPS::OnInitialize(..) to avoid WSAENOBUFS, exchanged the order of the ARead(..), AZeroByteRead(..).
    • Multiple pending read removed when multiple I/O workers are used. (Temporary fix is now permanent fix, read A6 and Q6.)
    • The #define SIMPLESECURITY functions are used inside the ConnectAcceptCondition(..) with the SO_CONDITIONAL_ACCEPT parameter using WSAAccept(..), increasing security. 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.
  • Version 1.16 - 2008-12-08
    • FIX: Changes in IOCPS::GetNextReadBuffer(ClientContext *pContext, CIOCPBuffer *pBuff) to avoid memory leak with CMap.
    • FIX: AllocateBuffer() can return NULL pointer in the following code in method IOCPS::ARead()
    • FIX: Socket leakage in IOCPS::AssociateIncomingClientWithContext(SOCKET clientSocket) on shutdown.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

spinoza
Program Manager
Sweden Sweden
Member
Amin Gholiha.
Education:
- 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. For business proposal email Gholiha@rocketmail.com, all other emails will be ignored.
Current Work:
Project Manager
www.easysoft.nu (the best free e-signature tool)

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberTao Leee26 Apr '13 - 16:46 
GREAT
QuestionDetected memory leaks when CIOCPApp closedmemberTao Leee25 Apr '13 - 23:02 
The thread 0x15C4 has exited with code 2 (0x2).
Detected memory leaks!
Dumping objects ->
thrdcore.cpp(166) : {75} client block at 0x00481170, subtype 0, 112 bytes long.
a CWinThread object at $00481170, 112 bytes long
Object dump complete.
The thread 0x140C has exited with code 2 (0x2).
GeneralMy vote of 5memberysgepl24 Jan '13 - 6:15 
good
Questionst::map instead of CMap in IOCPmemberChandrashekhar Machineni20 Nov '12 - 20:17 
Hi Anybody used std::map intead of CMap in IOCP. I used in my implementation but if throws "out of memory" error when "Disconnectall" clients. I want to make it as DLL so iam trying to remove MFC stuff from the project, Hence facing this irritating problem.
 
You suggession?
Question提交一个遗留了好几年的BUG。。。。memberrabbithnhhzy15 Nov '12 - 6:48 
在IOCPS.cpp文件中的PrepareSendFile方法
(int IOCP.cpp file,to search 'PrepareSendFile' function)
找到if (!pContext->m_File.Open(lpszFilename, CFile::modeRead | CFile::typeBinary)这一行
(to search 'if (!pContext->m_File.Open(lpszFilename, CFile::modeRead | CFile::typeBinary)' this line)
修改为if (!pContext->m_File.Open(lpszFilename, CFile::modeRead | CFile::typeBinary | CFile::shareDenyNone ))
(to modify it 'if (!pContext->m_File.Open(lpszFilename, CFile::modeRead | CFile::typeBinary | CFile::shareDenyNone ))')
 
这样,就可以同时发送EXE文件了,也不会crc32问题了。。。这问题整死我了,我在发送winrar打包的EXE的时候,这问题疑惑了我几年。
AnswerRe: 提交一个遗留了好几年的BUG。。。。memberbillowqiu4 Dec '12 - 1:59 
这中文整的……
Beyond myself!

AnswerRe: 提交一个遗留了好几年的BUG。。。。memberzjlufo28 Feb '13 - 2:59 
怒飙中文!
Questionmaybe a bug in IOCPS::OnWrite?memberfenix1244 Nov '12 - 23:38 
Previously I saw a paragraph which implied that WSASend() may not send the whole buffer in WSABUFFER sometimes.
Will it cause some problems when sending a lot of buffers at the same time?
QuestionAny body implemented IOCP in a DLL?memberChandrashekhar Machineni10 Oct '12 - 18:19 
Hi Guys,
 
Iam new to the socket programming and IOCP. Anybody created DLL using this IOCP class?I want to implement this functionality in my project. Iam looking for DLL to use in my project.
 
Thanks alot!
 
It would be great help if someone send me DLL source code.
 
Thanks & Regards,
Chandrashekhar
SuggestionA better way to sove the problem--The WSAENOBUFS error problem.memberchenfeibiao14 Jul '12 - 20:09 
Hi,spinoza:
It is a good way to send 0 byte to protect the system from loking memory buffers.However,we get the goal by sacrificing the throughput.If only we begin this mode when it is really necessary.But how?
We can do as follows:
Firstly:We can get the key number that the system can accept socket connectes from Windows Registry.Generally,the number is 5000.Of cource,we can achieve the number from the stress test.
Secondly:Now we have got the key number(we set the number 4500 for intance).When the count of the client connectes arrives,we change the Server's model to block_model to accept more client connects.
What do you think this approch?
 
Regards!
GeneralHello [modified]memberpapagal7 Jul '12 - 23:43 
Congratulations for this nice article. I developed an LSP(Layer Service Provider) and I have problems with some applications that use IOCP in that the connection for the applications locks at a time. What I discovered is that GetQueuedCompletionStatus return different errors but the last error which blocks applications connections is 1236(ERROR_CONNECTION_ABORTED). Can you give me please a tip what it's happenning?
 
Thank you!

modified 8 Jul '12 - 6:30.

SuggestionA little spell errormemberoliver25826 Apr '12 - 22:08 
Hi,
I think you miss-spelled 'alterable wait state' in the '2.2 Introduction to asynchronous Input/Output Completion Ports (IOCP)' part,it should be 'alertable wait state'
QuestionTransmitFile fails for a file larger than 2GBmemberRui Frazao3 Apr '12 - 7:02 
The maximum number of bytes that can be transmitted using a single call to the TransmitFile function is 2,147,483,646, the maximum value for a 32-bit integer minus 1. The maximum number of bytes to send in a single call includes any data sent before or after the file data pointed to by the lpTransmitBuffers parameter plus the value specified in the nNumberOfBytesToWrite parameter for the length of file data to send. If an application needs to transmit a file larger than 2,147,483,646 bytes, then multiple calls to the TransmitFile function can be used with each call transferring no more than 2,147,483,646 bytes. Setting the nNumberOfBytesToWrite parameter to zero for a file larger than 2,147,483,646 bytes will also fail since in this case the TransmitFile function will use the size of the file as the value for the number of bytes to transmit."
See article http://msdn.microsoft.com/en-us/library/windows/desktop/ms740565(v=vs.85).aspx[^]
 
Someone changed the code to solve this?
Thanks & Regards,
Rui
SuggestionRe: TransmitFile fails for a file larger than 2GBmemberAizz25 Apr '12 - 15:56 
Simple way: Split the file(each < 2G) then call TransmitFile in turns Smile | :)
 
sss
QuestionThank you for your codemember700208921 Mar '12 - 6:27 
Thank you for your code
GeneralMy vote of 5memberMember 432084414 Mar '12 - 12:41 
thanks for sharing.
Questionwhy the ProcessPackage not use lockermembersusubuhui23 Feb '12 - 19:27 
i find that it is not use locker in the function ProcessPackage(not file transfer model) of OnReadCompleted(....)??
 
while(pOverlapBuff!=NULL)
		{
			//TRACE("R> %i\r\n",pOverlapBuff->GetSequenceNumber());

			// Mark that we are Using the buffer..
			pOverlapBuff->Use(dwIoSize);
#ifdef TRANSFERFILEFUNCTIONALITY
			if(!pContext->m_bFileReceivedMode)
#endif
				ProcessPackage(pContext,dwIoSize,pOverlapBuff);
#ifdef TRANSFERFILEFUNCTIONALITY

QuestionIOCP Experiencememberhector santos13 Nov '11 - 14:16 
Hi, I just wish to share my "late entry" into IOCP experience. I came across your code in my researching to see how others resolve one of the problems you cited (3.6.4 access violations). Time will tell as I begin the process of fine tuning the robusting of our IOCP implementation, if we need to use some of the solutions you implemented, but one of the things I already see overall that you made a point of and it is completely unacceptable is the sacrifice of throughput.
 
I'm writing because we have a high througth virtual comm framework in our modem/tcp hosting product lines. It serveed the low to mid high connections needs and I didn't think it was feasibility to go to a very complex IOCP, specially when the event products comes with highly integrated overhead.
 
In short, again, time will tell now that we are finally revamping to a IOCP framework (for some of the protocols), what you didn't point out (at least I didn't see it) is the one major factor that propagates the complex IOCP synchronization design requirements - bandwidth difference in I/O direction, and the higher the difference, the worst it gets.
 
Case in points. In the early days, the bandwidths (bauds) were the same in each direction, or at least with the early internet, the difference was small, that the ACK/NAK issues were not problematic. This changed when DSL came along with 1MB/256KB download/upload and all of the sudden major WEB servers, IIS, APACHE, our own Wildcat! WEB Server and others began to cause problems with web browsers in particular the infamous IE "Page not found" problem. The solution here was for the server to use a HALF-CLOSE socket shutdown. Servers were closing the socket for a HTTP response before the client can ACK it and thus caused a RSET.
 
I am not sure there is much to do about this, but you did point out that if a servers knows that a client will send in one burst (packet), then you can better control it. The reality is that you can't design with that presumption and vice versa, the client can not presume it will get server responses in one packet. At the end of the day, the physical MTU size dictates the # of packet frames. With a TCP over PPP framework and socket buffers of 8K, thats atleast 8 packet frames.
 
The solutions have been well long defined and ideally, you have two independent overlapping I/O threads during all the buffering for you. However, it doesn't scale well before a certain limit with having at least 3 threads per connection under high throughput needs.
 
In my revamping our code, rather than a complete new fresh start, I have been trying to learn and use IOCP to reduce the # of threads, but not to the extent that one thread has to do everything and hence you get into an extremely complex synchronization design.
 
Of course, this doesn't still address the integration issues. Any example code will work, until you put "guts" into it and there are non-async interfaces that are completely out of your control.
 
My overall approach to software engineering has always been a black box mentality - you can not depend on the idiosynchroncies of the underlining OS run time stack. You need to understand it, but randomness needs to be factored out. My #1 rule of thumb in SYNC 101 is not to depend on "time" to synchronize data flow, yet, time functionalities need to factored in as well in a controlled manner, i.e. a Connection Session/Management TIMEOUT monitoring. Well done in a single thread model and also also for non-persistent connection models like HTTP v1.0 where we use a global idle/timeout monitor. I'm testing both methods now with IOCP - using the global monitor vs using the GQCS() timeout parameter. The latter opens the idea of awakening up threads for non-iocp events thus more context switching.
 
Anyway, hopefully we can learn from your code to not repeat some things you found with your IOCP work.
 
Thanks
AnswerRe: IOCP Experiencememberspinoza5 Dec '11 - 1:18 
Thanks for the feedback!
 
You are absolutely correct about:
 
"is the one major factor that propagates the complex IOCP synchronization design requirements - bandwidth difference in I/O direction, and the higher the difference, the worst it gets."
 
I have not the time to update the article and the source code for a while. But it is on my todo list.
 
I have implemented a different version (non MFC) for a company in the past. The strategy we used for that was the following:
 
1) Multiple IOCP ports. (one IO-Worker per IOCP port)
2) Workload balancing on new connection.
3) Let the IO-Workers collect statistics about bandwidth(I/O.
4) Reassign sockets from one IOCP port to other using info in 3 to make a fare load balansing..(Collect all the sockets with high I/O to one IOCP port, and collect all the socets with low I/O to another).
 
nr 1 removes many problem with synchronization (if you set IOWORKER to 1 the current source code switch off some of the operations). 2-4 gives you better control over the load balancing and utilization of your Hardware.
 
I cannot share that source code (I do not own it) but I can say that this is the best strategy.
//Spinoza

GeneralMy vote of 5memberleomaye21 Sep '11 - 18:11 
Explain an easy way of a IOCP implementation
GeneralMy vote of 5member1488giorgio15 Sep '11 - 21:55 
Very good for learnig
GeneralMy vote of 5memberAshish Tyagi 4029 Aug '11 - 1:25 
Best IOCP artical I ever read.
Thanks a lot Mr. Amin Gholiha
QuestionPacket Fragmentation & Sequencingmemberchoochy200321 Jul '11 - 22:23 
Hi spinoza
 
Great article!!! It's helped me implement a Winsock IOCP in Delphi and its working great. The only thing I don’t really understand fully is dealing with the packet fragmentation and sequencing.
I know you have touched on it in your article and I have been staring blankly at your code, but could you please elaborate on the implementation that deals with the sequencing and fragmentation. Maybe even add this to your article.
 
Thanks a million!
 
Chris
Question讨论,有不太明白的地方;有人能帮着翻译下吗?memberlalauo19 Jul '11 - 0:59 
感觉不太明白:
在代码中,不管是读还是写,都是先投递一个IO消息,然后,再等线程来处理;
为什么要进行两次投递操作,真的不是很明白;
AnswerRe: 讨论,有不太明白的地方;有人能帮着翻译下吗?memberxiale1 Sep '11 - 15:59 
没仔细看他的代码.不过据我所知,收数据是必须先要有recv才能收到数据,线程并不是做收发数据之用,而是处理IO完成端口接收完成的消息,这与异步选择模型是不同的,异步选择模型是收到消息是告诉你缓冲区已经接收到了数据,此时要进行recv操作将数据从缓存COPY到用户数据区,此中是阻塞的。而IO完成端口是完全后台的,只返回一个消息告诉你已经接收完了,线程判断是哪个完成的事件,如果是recv当然要补上一个recv异步接收函数,不然它怎么接收下一次的数据呢? 建议你去看看 网络编程 这本书!
这仅是我的理解,如有错误,敬请指教!
AnswerRe: 讨论,有不太明白的地方;有人能帮着翻译下吗?memberyjvujj15 Jan '12 - 2:21 
大哥。这货不用翻译了。中文网站全是这个。。。你直接搜索 IOCP .注意看图。
 
基本上全是。
GeneralRe: 讨论,有不太明白的地方;有人能帮着翻译下吗?memberlalauo25 Aug '12 - 9:15 
时隔一年,再看这个代码,还是没有明白;
1 为什么加一个READ->READ_COMPLET 的转换;这个或许并不需要,直接WSARecv就可以发起一个READ接收请求;类同的,WRITE->WRITE_COMPLET 这个转换也可以不需要;同样的道理; 
2 即然是先进先出的IO队列操作,为什么还要人为的加上队列代码;
3 Worker 也可以使用完成端口;手工调度线程有什么好处?
GeneralSendFile timeoutmemberkamplus23 Feb '11 - 21:51 
after send Job_SendFileInfo msg,may be wait for timeout!
if client don't respone ,the server will do nothing
Generalbad messagesize,can you hlep me? [modified]memberMember 386382525 Nov '10 - 16:57 
I do not understand why you want to specify the data format in the data area, such as messagesize | message [messagesize | message]
If I change your code,nSize + sizeof(UINT) is always equal to nUsedBuffer What will it? Frown | :(

modified on Friday, November 26, 2010 12:50 AM

Questionamazing article! (but i need a way to fix/clean broken connections)membercternoey20 Oct '10 - 23:57 
dear spinoza,
 
Several years ago i implemented a client/server design using raknet for my communication protocol. In order to make the system more reliable/robust, i eventually implemented an option to connect via the iocp logic you provided in your iocp server/client class.
 
Over time it became clear, that this iocp class was outperforming raknet in in terms of reliability, speed, firewall-compatibility etc. i am really, really impressed! (My apologies to raknet if they have improvements or fixes that i never found out about.)
 
Naturally, this iocp logic is now used by default and the raknet library is not really used much at all anymore.
 
Unfortunately, i do have one headache from using your wonderful code.
 
It seems that once or twice a month, a client pc has trouble connecting. And to my surprise, it is not sufficient even to completely reboot that client pc. However, the issue is easily resolved by restarting the server application. naturally, a restart of the server application in the middle of a business day (causing ALL client applications to get disconnected) is not very fun. And it seems strange to need such a drastic step in order to fix a connection problem with a single client.
 
So, do you have any comment about this? Can you imagine what would cause such a scenario? Is there a way to clear the bad data or bad state without disconnecting all of my clients?
 
thanks for any comments you might have about this.
 
and thanks again for your beautiful article!
 
best regards,
-chris
GeneralMy vote of 5membergouyabin8 Oct '10 - 22:02 
It helps me very much to my work.Thanks very much.
GeneralGreat article. A critical question regarding case study 3.6.3 erroneous case [modified]membergstefano11 Sep '10 - 7:25 
Thanks a lot for the great job and the great description article.
 
I have a small but i thing the most critical question.
In case that we are using 2*proc_units+2 working threads (recommendation in all IOCP speqs)
the most serious problem is the one that you describe in section 3.6.3 in your article.
But it can be even worst in case that the byte chunk phenomenon deals with missordering case (combination of 3.6.2 & 3.6.3)
Do you have any idea of how to deal with this situation. Have you ever faced it?
Supposing that we have reception packet order 2,1,3 and not 1,2,3 which is a simple case to reassembly the packets,
 
in case that packet 2 arrives (and under your sequence number model you suggest)
basically we have no SN information participating in the byte_chunk of the first part of the packet.
Am I correct? That makes the reassembly mechanism incapable of reassembling the specific packet!!
 
The combination of 3.6.2 and 3.6.3 has to deal with more than one working threads in parallel on both serving parts for WSASend and WSARecv and
in both terminals (server and clients)
Specifically as far as I can understand the chunk production case has to deal with a second WSASend before the IOWriteCompleted completion packet arrival. So both information are travelling on the same TCP packet. Can this sideeffect produce the corruption of your predefined header?
 
Sorry for the in depth questions of mine? I am trying to theoritically cover the issue in order to figure out if the overall performance of IOCP mechanism worths the programming pain as a part of any feature client server project of mine!
 
Waiting for your reply and Thanks in advance!!
george

modified on Sunday, September 12, 2010 4:47 AM

GeneralMy vote of 5memberCr4zyC0de14 Aug '10 - 22:08 
Very good job
GeneralI have changed the code to c++ core without mfc.memberhuzhangyou200228 Jun '10 - 18:04 
dear spinoza:

Thanks for your perfect code in codeproject. And in your article, you say if anyone have change the code without MFC,send a copy to you.Now I send the code to you.I have changed it with several place.And most important is as follows:

1:If any client cuts off its line,the former server can't detect it. And in my code, I add RegisterWaitForSingleObject and TimeOutCallback function in the class.If the timeout is come,the client will be deleted.
2:in WSASend function, the WSASend bytes may be less then the buffer we want to send.So,in OnWriteCompleted function,if dwIoSize < pOverlapBuff->GetUsed() && dwIoSize > 0,I used following code
if(pOverlapBuff->Flush(dwIoSize) == TRUE)
{
//pOverlapBuff->
pOverlapBuff->SetOperation(IOWrite);
ASend(pContext,pOverlapBuff);
}

3:In function OnInitialize,I think we just need post AZeroByteRead not ARead.if AZeroByteRead Complete means they are really some data need to recv,then we post ARead,if AReadComplete,we then post AZeroByteRead.

that's all,thans for your code.
 
My Blog: http://doserver.net
hello,world!

GeneralRe: I have changed the code to c++ core without mfc.memberhanlng30 Jun '10 - 16:41 
HI
I think another block should be modified,please see the ShutDownIOWorkers procedure in class IOCPS.
while (pos != NULL)
{
pThread = (CWinThread* )m_IOWorkerList.GetNext (pos);
if(pThread)
{
if(::GetExitCodeThread (pThread->m_hThread, &dwExitCode)&&dwExitCode == STILL_ACTIVE)
bIOWorkersRunning=TRUE;
else
bIOWorkersRunning=FALSE;
}
}
This while(pos != NULL) loop will check all of the IO worker thread and the other while(bIOWorkersRunning) outside it will take the value of bIOWorkersRunning to indicate whether check again.I think if the tail thread in m_IOWorkerList has been shut down, the value of bIOWorkersRunning became FALSE,and the outside while(bIOWorkersRunning) loop will exit,but perhaps some other IO worker threads still running.
 
So I think it should be modified like this:
 
BOOL bIOWorkersRunning=TRUE;
CWinThread* pThread=NULL;
while(TRUE)
{
bIOWorkersRunning = FALSE; // Clear the tag before new check loop
 
// Send Empty Message into CompletionPort so that the threads die.
PostQueuedCompletionStatus(m_hCompletionPort, 0, (DWORD) NULL, NULL);
POSITION pos = m_IOWorkerList.GetHeadPosition();
while(pos != NULL)
{
pThread = (CWinThread* )m_IOWorkerList.GetNext (pos);
if(pThread)
{
if(::GetExitCodeThread (pThread->m_hThread, &dwExitCode)&&dwExitCode == STILL_ACTIVE)
{
// if there is a running thread, exit check loop and go on posting the exit IO MSG bIOWorkersRunning = TRUE;
break;
}
}
}
if(bIOWorkersRunning == FALSE) break; // if there is no thread is running,exit the while loop
}
 
And I think the best way to wait the IO worker thread to finish is that we use WaitForSingleObject(..., 16) to determine whether the thread is alread finished with WAIT_OBJECT_0 or WAIT_TIMEOUT returned, if WAIT_OBJECT_0 returned there is no need to go on invoke ::GetExitCodeThread, otherwise the BoundsChecker will alert an invalid handle value when you invoke ::GetExitCodeThread(...). The same situation when we wait the workers(job) thread to finish.
 
Do you think so?
By the way, your blog is nice!
GeneralRe: I have changed the code to c++ core without mfc.memberhuzhangyou200230 Jun '10 - 18:28 
Hi,gays,thans for your "by the way" first.
 
hmmmm,I think your code do the same thing as the original author after I had checked your code carefully.
and your advise may be great,using WaitForSingleObject maybe more suitable.
 
And may be the author have not checked his mail long time. I havn't received his reply for days.
hello,world!

GeneralRe: I have changed the code to c++ core without mfc.memberhpking1 Aug '10 - 0:02 
我想看看你怎么去掉的mfc了,我也不喜欢MFC,有没有手机电话的留个给我,现在急用!
huh,good!

GeneralRe: I have changed the code to c++ core without mfc.memberhpking1 Aug '10 - 18:20 
where is the code,huh?
huh,good!

QuestionIs there a fatal mistake in this sample?memberMaccocoa14 Jun '10 - 20:26 
Thank the author for his excellent work of the code!
 
But I think there is a fatal mistake in it.
Please take a look at 3.6.2 about the packet reorder.
"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."
That's TRUE.
 
"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 (e.g., m_SendBufferMap and m_ReadBufferMap)."
It's also an excellent solution to resolve this problem.
 
But in the code, let's see the solution:
void CIOCPS::MakeOrderdRead(ClientContext *pContext, CIOCPBuffer *pBuff)
{
if(pContext != NULL && pBuff != NULL)
{
//pContext->m_ContextLock.Lock();
 
pBuff->SetSequenceNumber(pContext>m_ReadSequenceNumber); // this line
//TRACE("MakeOrderdRead> %i\r\n", pBuff->GetSequenceNumber());
DWORD dwIoSize = 0;
ULONG ulFlags = MSG_PARTIAL;
UINT nRetVal = WSARecv(pContext->m_Socket,
pBuff->GetWSABuffer(),
1,
&dwIoSize,
&ulFlags,
&pBuff->m_ol,
NULL);
if(nRetVal == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING)
{
if(WSAGetLastError() != WSAENOTSOCK)
{
CString msg;
msg.Format("Disconnect in Onread Possible Socket Error: %s", WSAErrCode2Text(WSAGetLastError()));
SaveLog(msg);
}
//pContext->m_ContextLock.Unlock();
ReleaseBuffer(pBuff);
TRACE(">MakeOrderdRead(%x)\r\n", pContext);
DisconnectClient(pContext);
ReleaseClientContext(pContext);
}
else
{
pContext->m_ReadSequenceNumber = (pContext->m_ReadSequenceNumber + 1) % MAXIMUMSEQUENSENUMBER;
//pContext->m_ContextLock.Unlock();
}
}
}
 
Take a look at the bold line text.
pBuff->SetSequenceNumber(pContext>m_ReadSequenceNumber);
If current IOWorkerThread()(ThreadA)this line execute finish and another IOWorkerThread() get the CPU,and it also run at this line and it WSARecv() immediately, and then ThreadA continue running, and call WSARecv().As you see, the Recv MSG post in incorrect order, so IOCP awake IOWorkerThread in incorrect order.
 
So, the code should still insert zhe line "//pContext-&gt;m_ContextLock.Lock();"
and I dont know why the author cut this from the function.
 
The same mistake also in
BOOL CIOCPBase::ASend(ClientContext *pContext, CIOCPBuffer *pOverlapBuff)
function.
Always we can't guarantee that the IOCPS->SetSendSequenceNumber() function and WSASend() function execute toghter atomic.
Or can't guarantee IOCPBuffer->SetSequenceNumber() function and WSARecv() function execute toghter atomic.
Am I think in wrong way? Is there someone can discuss the question with me?
AnswerRe: Is there a fatal mistake in this sample?memberMaccocoa14 Jun '10 - 20:33 
I think because the probability of the scene that I had said occurs is very tiny, so perhaps nobody found it until now.
Any one can give the answer or send me email(hanlng@163.com) to discuss the question? Wink | ;)
GeneralRe: Is there a fatal mistake in this sample?memberspinoza21 Jun '10 - 7:48 
Hello
 
Good analysis and call.
 
It was a while ago I wrote this code. I see that I have removed the Context lock(). The reason for this as I recall is the following:
 
"For each context (or socket) you make one call to WSARecv(..) at the time, independent on number of working Threads. IOCP is thread safe (basically a queue of finished async calls) and since you make only one WSARecv(..) per client at the time you will not have this. This would not work if you have several pending reads for the same Socket.

(see Multiple pending read removed when multiple I/O workers are used. (Temporary fix is now permanent fix, read A6 and Q6.) in the article). " I removed that since the benefit of having several pending reads or buffers in kernel (TCP/IP) waiting for data since you have to shuffle them around or implement locked sequential buffer shuffling that is not good.
 
The reason of sequencing is because of CPU Load sharing (that the code do itself) see the function ASend(..) to understand. ( I make a post into the Queue so that the CPU is shared fairly among the client independent on the their internet connection.) "

However my memory can make tricks on me (or windows API) so I ask you:
 
Did you experimentally see this issue or is it just a code analysis?
//Spinoza

GeneralRe: Is there a fatal mistake in this sample?memberhanlng29 Jun '10 - 22:46 
Hi Spinoza:
 
I'm very surprised with your reply because I've conceived that you hadn't concerned about this code anymore.So I greatly appreciate it!
 
It is so useful for me and I think I had ignored the point of the thread-safe attribute of IOCP.
I have had read & study all the code & article and almost all the discussion and reply, perhaps I haven't noticed that. But now after your answer, it is clear to me. Thank you very much!
 
Actually I get the issue only by code analysis, so perhaps we should trust your memory and your analysis.
 
Best wishes for you and thanks again!
QuestionShutDownIOWorkers() can be die you , i cant understand it. Do you know why ?memberlovesxw30 May '10 - 22:01 
here is the funtion:
 
void IOCPS::ShutDownIOWorkers()
{
DWORD dwExitCode;
m_bShutDown=TRUE;
// Should wait for All IOWorkers to Shutdown..
BOOL bIOWorkersRunning=TRUE;
CWinThread* pThread=NULL;
while(bIOWorkersRunning)
{
// Send Empty Message into CompletionPort so that the threads die.
if(bIOWorkersRunning)
PostQueuedCompletionStatus(m_hCompletionPort, 0, (DWORD) NULL, NULL);
//Sleep(60);
// Check if the IOWorkers are terminated..
POSITION pos = m_IOWorkerList.GetHeadPosition ();
 
while (pos != NULL)
{
pThread = (CWinThread* )m_IOWorkerList.GetNext (pos);
if(pThread)
{
if (::GetExitCodeThread (pThread->m_hThread, &dwExitCode)&&dwExitCode == STILL_ACTIVE)
{
bIOWorkersRunning=TRUE;
}
else
bIOWorkersRunning=FALSE;
}

 
}
 
}
m_IOWorkerList.RemoveAll();
 
}
 
//code end
 
the problem is that:
i use it but it into dead cycle.
dwExitCode=STILL_ACTIVE
This sentence , not equal to STILL_ACTIVE ,olny equal 259.
 
Do you know Why ? Help
 
PS:中文:为什么退出的时候ShutDownIOWorkers()这个函数 进入死循环 ,GetExitCodeThread 得到的dwExitCode永远等于STILL_ACTIVE?
AnswerDo not send any message to main thread while it is waiting for you to die.memberhanlng30 Jun '10 - 16:45 
Poke tongue | ;-P

modified on Wednesday, June 30, 2010 10:52 PM

Questionhow to transfer several file in one time?memberyyaisq30 Apr '10 - 22:20 
thanks for you excellent code.
but i have a problem,
would you tell me how to transfer several file in one time?
thank you very much?
AnswerRe: how to transfer several file in one time?memberkamplus23 Feb '11 - 22:00 
use threads if you can
General使用中发现的BUGmemberxzm26 Mar '10 - 17:00 
这个程序问题蛮多的。
1、如果工作者线程个数为0的话,程序退出时会使操作系统发生错误,因为在此过程中会一直发送完成通知,而又得不到响应。
2、IOCPS::DisconnectClient在处理上有问题,
pContext->m_ContextLock.Lock();
BOOL bDisconnect = pContext->m_Socket != INVALID_SOCKET;
pContext->m_ContextLock.Unlock();
这回导致同一个ClientContext会被NotifyDisconnectedClient调用两次。实际测试这种几率太高了。
3、IOCPS::OnInitialize方法里
for(int i=0; i<m_iNumberOfPendlingReads; i++)
{
EnterIOLoop(pContext); // One for each Read Loop
ARead(pContext);
}
这种写法并不正确,因为在EnterIOLoop(pContext);时,它没有去判断pContext->m_nNumberOfPendlingIO是不是为0了,如果为0的话,你再调用ARead(pContext);就会使m_FreeContextList存在两个一摸一样的pContext。因为代码在ReleaseClientContext并没有判断pContext是否在m_FreeContextList是否已经存在了,然后在m_FreeContextList.AddHead((void*)pContext),这种情况发生了,后果就比较严重了。首先是ReleaseClientContext方法里的断言ASSERT(nNumberOfPendlingIO>=0);产生一个错误对话框,接着如果运气不好的话,应用程序0x80000003错误。只不过这种几率比较小,测试了十几次偶尔发生那么一两次。
其他的BUG改着改着就忘了。
GeneralRe: 使用中发现的BUGmemberchaoqun31 May '10 - 7:14 
能否说说这几个问题是如何改的?
GeneralRe: 使用中发现的BUGmemberspinoza21 Jun '10 - 7:59 
Hello
 
1) fully normal
2) Correct it can happen, but there is no problem. The important thing is that you know someone has disconnected. Who cares if you get the notification two times.
3) for(int i=0; i<m_iNumberOfPendlingReads; i++). (m_iNumberOfPendlingReads > 1 only if number of Ioworkers are equal 1). if (Ioworkers > 1 &&iNumberOfPendlingReads > 1) the code break down. See Article notice under the 1.16 release.
 
Best
//Spinoza

GeneralRe: 使用中发现的BUGmemberxzm21 Jun '10 - 21:51 
很抱歉,我英文很差劲。【I am sorry. My English is so poor.】
 
很抱歉是我没有说清楚【I am sorry. I did not made it clear.】:
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
第1个问题发生在IOCPS.cpp文件的void IOCPS::ShutDownIOWorkers()方法。【First Bug is on the IOCPS.cpp file, void IOCPS::ShutDownIOWorkers() function.】
void IOCPS::ShutDownIOWorkers()
{
DWORD dwExitCode;
m_bShutDown=TRUE;
// Should wait for All IOWorkers to Shutdown..
BOOL bIOWorkersRunning=TRUE;
CWinThread* pThread=NULL;
while(bIOWorkersRunning)
{
// Send Empty Message into CompletionPort so that the threads die.
if(bIOWorkersRunning) //如果bIOWorkersRunning始终为TRUE,那么将导致一直发送完成消息,直到我们系统响应不了,悲剧就会发生(这时只能重启电脑,要不什么东西都不能用了)。【if bIOWorkersRunning from beginning to end is TRUE, then It will call PostQueuedCompletionStatus function all the time until the system can not respone.】
PostQueuedCompletionStatus(m_hCompletionPort, 0, (DWORD) NULL, NULL);
// Sleep(60);
// Check if the IOWorkers are terminated..
POSITION pos = m_IOWorkerList.GetHeadPosition ();
//这里就是问题发生的所在,因为如果工作者线程数量为0,pos将始终为NULL,bIOWorkersRunning的值也不会被改变,所以觉得有必要要先判断当前的工作者线程的数量。【That is the question. If IO worker threads count is 0, the 'pos' is NULL, 'bIOWorkersRunning' is TRUE all along. So I think it is necessary Judging the IO worker threads count before.】
//PS:测试方法 调用IOCPS::Start方法时将iMaxIOWorkers参数设定为0。(又或者某种错误发生使工作者线程数减少到0呢?)【Test: You can call the IOCPS::Start function set 'iMaxIOWorkers' param to 0】
while (pos != NULL)
{
pThread = (CWinThread* )m_IOWorkerList.GetNext (pos);
if(pThread)
{
if (::GetExitCodeThread (pThread-&gt;m_hThread, &amp;dwExitCode)&amp;&amp;dwExitCode == STILL_ACTIVE)
bIOWorkersRunning=TRUE;
else
bIOWorkersRunning=FALSE;
}
 
}
 
}
m_IOWorkerList.RemoveAll();
 
}
 

 
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
第2个问题:发生在IOCPS.cpp的void IOCPS::DisconnectClient(ClientContext *pContext, BOOL bGraceful)方法上,【The second BUG】
void IOCPS::DisconnectClient(ClientContext *pContext, BOOL bGraceful)
{
if(pContext!=NULL)
{
pContext-&gt;m_ContextLock.Lock(); //这个地方加锁了
BOOL bDisconnect=pContext-&gt;m_Socket!=INVALID_SOCKET;
pContext-&gt;m_ContextLock.Unlock(); //但是这里解锁了,这意味着如果有多个工作者线程的话,当同时处理到这里的话,就可能使bDisconnect都为TRUE,【Unlock the pContext, if IO worker threads count large than 1, may be 2 threads get the 'bDisconnect' value is TRUE】
//也就是说,对于同一个ClientContext,下面的if模块可能会被执行多次。【In other words, the if module may be called twice or more.】
// If we have an active socket close it.
if(bDisconnect)
{
 
//
// Remove it From m_ContextMap.
//
m_ContextMapLock.Lock();
BOOL bRet=FALSE;
//Remove it from the m_ContextMapLock,,
if(m_ContextMap[pContext-&gt;m_Socket]!=NULL)
{
bRet=m_ContextMap.RemoveKey(pContext-&gt;m_Socket);
if(bRet)
m_NumberOfActiveConnections--;
}
m_ContextMapLock.Unlock();
 

TRACE("Client %i, Disconnected %x.\r\n",pContext-&gt;m_Socket,pContext);
 
pContext-&gt;m_ContextLock.Lock();
// Notify that we are going to Disconnect A client.
NotifyDisconnectedClient(pContext); //那么对于同一个pContext断开连接的通知也可能多于一次,这肯定不是我们想要的。这就是我所说的缺陷。【NotifyDisconnectedClient may be called twice or more.】
pContext-&gt;m_ContextLock.Unlock();
 
#ifdef SIMPLESECURITY
if(m_bOneIPPerConnection)
{
 
//
// Remove the IP address from list..
//
 
sockaddr_in sockAddr;
memset(&amp;sockAddr, 0, sizeof(sockAddr));
int nSockAddrLen = sizeof(sockAddr);
int iResult = getpeername(pContext-&gt;m_Socket,(SOCKADDR*)&amp;sockAddr, &amp;nSockAddrLen);
 

if (iResult != INVALID_SOCKET)
{
void* pVoid=(void*)sockAddr.sin_addr.S_un.S_addr;
m_OneIPPerConnectionLock.Lock();
POSITION pos=m_OneIPPerConnectionList.Find(pVoid);
if ( pos!=NULL )
{
m_OneIPPerConnectionList.RemoveAt(pos);
}
m_OneIPPerConnectionLock.Unlock();
}
 
}
#endif
 
//
// If we're supposed to abort the connection, set the linger value
// on the socket to 0.
//
if ( !bGraceful )
{
 
LINGER lingerStruct;
lingerStruct.l_onoff = 1;
lingerStruct.l_linger = 0;
setsockopt( pContext-&gt;m_Socket, SOL_SOCKET, SO_LINGER,
(char *)&amp;lingerStruct, sizeof(lingerStruct) );
}
//
// Now close the socket handle. This will do an abortive or graceful close, as requested.
CancelIo((HANDLE) pContext-&gt;m_Socket);
closesocket( pContext-&gt;m_Socket );
pContext-&gt;m_Socket = INVALID_SOCKET;
 
}
#ifdef _DEBUG
TRACE("Context %x is is disconnected but not removed from Context map \r\n",pContext);
#endif
}
}
 

 
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
第3个问题:这个缺陷在也在IOCPS.cpp文件的void IOCPS::OnInitialize(ClientContext *pContext, DWORD dwIoSize,CIOCPBuffer *pOverlapBuff)方法。
提高这个问题发生的条件是:在测试的时候,创建多个套接字(比如2000个socket)然后去连接服务器,连接上以后,客户端马上调用closesocket关闭。【You can coding a client test program create 2 thousand socket, then connect to server. If client connceted the server, then client call closesocket function at once.】
void IOCPS::OnInitialize(ClientContext *pContext, DWORD dwIoSize,CIOCPBuffer *pOverlapBuff)
{
// Do some init here..
 
// Notify new connection.
pContext-&gt;m_ContextLock.Lock();
NotifyNewConnection(pContext);
pContext-&gt;m_ContextLock.Unlock();
/*
Operations using the IO completion port will always complete in the order that they were submitted.
Therefore we start A number of pendling read loops (R) and at last a Zero byte read to avoid the WSAENOBUFS problem.
The number of m_iNumberOfPendlingReads should not be so big that we get the WSAENOBUFS problem.
*/
// A ZeroByteLoop. EnterIOLoop is not needed here. Already done in previus call.
AZeroByteRead(pContext,pOverlapBuff); //因为是客户端连接上以后,马上调用closesocket关闭连接,AZeroByteRead调用会投递一个IOZeroByteRead完成消息,【If client connceted the server, then client call closesocket function at once. And call AZeroByteRead function will post a 'IOZeroByteRead' message.】
//那么在OnZeroByteRead方法里WSARecv的调用就可能失败,然后调用ReleaseClientContext回收pContext(因为此时pContext-&gt;m_nNumberOfPendlingIO为0)。那么这种情况下,到这里就该结束了,不能再往下执行了。【So the OnZeroByteRead function called WSARecv may be fail, then OnZeroByteRead function call ReleaseClientContext function recycle the pContext】

// m_iNumberOfPendlingReads=1 by default.
for(int i=0;i&lt;m_iNumberOfPendlingReads;i++)
{
//所以在调用EnterIOLoop之前,也应该先判断下pContext-&gt;m_nNumberOfPendlingIO的值是不是0,如果是EnterIOLoop方法和ARead方法就不能再调用。
//如果这里pContext-&gt;m_nNumberOfPendlingIO已经为0了(也就是说pContext已经被回收了),ARead方法调用PostQueuedCompletionStatus投递IORead完成通知。那么OnRead调用肯定失败。然后也调用ReleaseClientContext回收pContext。这就是所说的m_FreeContextList存在两个一摸一样的pContext的原因。
EnterIOLoop(pContext); // One for each Read Loop
ARead(pContext);
}
 
}
 

inline BOOL IOCPS::ReleaseClientContext(ClientContext *pContext)
{
BOOL bRet=FALSE;
if(pContext!=NULL)
{
 
//
// We are removing this pContext from the penling IO port.
//
int nNumberOfPendlingIO=ExitIOLoop(pContext);
 
// We Should not get an EnterIOLoopHere Because the client are disconnected.
 
#ifdef _DEBUG
if(nNumberOfPendlingIO&lt;0) //请看IOCPS::OnInitialize的描述,所以nMumberOfPendling&lt;0就可能成立
{
TRACE("FATAL ERROR AccessViolation possible\r\n");
TRACE("Releasing %i(%x) nNumberOfPendlingIO=%i.\r\n",pContext-&gt;m_Socket,pContext,nNumberOfPendlingIO);
}
ASSERT(nNumberOfPendlingIO&gt;=0);
#endif
// If no one else is using this pContext and we are the only owner. Delete it.
if(nNumberOfPendlingIO==0)
{
//
// Remove it From m_ContextMap.
//
 
pContext-&gt;m_ContextLock.Lock();
NotifyContextRelease(pContext);
ReleaseBuffer(pContext-&gt;m_pBuffOverlappedPackage);
#ifdef TRANSFERFILEFUNCTIONALITY
if (pContext-&gt;m_File.m_hFile != (unsigned int)INVALID_HANDLE_VALUE)
{
 
pContext-&gt;m_File.Close();
}
pContext-&gt;m_bFileSendMode=FALSE;
pContext-&gt;m_bFileReceivedMode=FALSE;
pContext-&gt;m_iMaxFileBytes=-1;
#endif
 
ReleaseBufferMap(&amp;pContext-&gt;m_ReadBufferMap);
ReleaseBufferMap(&amp;pContext-&gt;m_SendBufferMap);
// Added.
pContext-&gt;m_CurrentReadSequenceNumber=0;
pContext-&gt;m_ReadSequenceNumber=0;
pContext-&gt;m_SendSequenceNumber=0;
pContext-&gt;m_CurrentSendSequenceNumber=0;
pContext-&gt;m_ContextLock.Unlock();
 
// Move the Context to the free context list (if Possible).
m_FreeContextListLock.Lock();
if(m_FreeContextList.GetCount()&lt;m_iMaxNumberOfFreeContext||m_iMaxNumberOfFreeContext==0)
{
bRet=m_FreeContextList.AddHead((void*)pContext)!=NULL; //如果pContext已经在m_FreeContextList里了呢?
TRACE("Putting (%x) in Freecontext list nNumberOfPendlingIO=%i.\r\n",pContext,nNumberOfPendlingIO);
 
}else // Or just delete it.
{
if(pContext)
{
TRACE("Context (%x) RIP\r\n",pContext);
pContext-&gt;m_Socket = INVALID_SOCKET;
delete pContext;
pContext=NULL;
}
}
m_FreeContextListLock.Unlock();
return TRUE;
}
}
return FALSE;
}
 
抱歉,不会翻译,看了自己的翻译都恶心,我的悲哀。就说明这个意思,我想大家都是敲代码的,不用写太清楚,也能看明白。

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 11 Dec 2008
Article Copyright 2005 by spinoza
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid