1
\$\begingroup\$

I implemented a tcp socket server using the Microsoft IO Completion Port Model, which is basically an asynchronous model where WSAXXX function return immediately and you use a worker thread to handle actual completion of the operation and handle data received or sent.

There is quite a bit of code in tcpserver but not mammoth. I used the Microsoft debug library in crtdbg.h here to check for memory leaks and in my testing there were no leaks, even on shutdown of the server.

I have splattered printf's all over the place for testing. This would be converted to proper logging in a real program but for review printf is probably easier to follow.

tcpserver.h:

/*
Description: Asynchronous socket server interface
Author: Angus Comber
*/
#ifndef TCPSERVER_HPP_
#define TCPSERVER_HPP_

#ifdef WIN32

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <winsock2.h>  // Windows sockets v2
#include <ws2tcpip.h>  // getaddrinfo
#pragma comment(lib,"ws2_32.lib")  //winsock2 lib

#include <chrono>
#include <thread>
#include <list>
#include <set>
#include <mutex>

namespace itl {

/** main server class.  Uses two inner classes: operation_context for IO handling and clients to represent tcp socket clients. */
class tcpserver {
public:
    //! construct with port to listen on and optional data buffersize
    tcpserver(const unsigned short port, const int buffersize = 4096);
    virtual ~tcpserver();
    //! start_accepting blocks (until you call stop)
    void start_accepting();
    void stop();
    //!  returns requestid which is later seen in data_sent function when async send operation completed
    unsigned send(const SOCKET clientsocket, const unsigned char* data, const unsigned length);
    //! client lifecycle notification
    virtual void client_connected(const SOCKET clientsocket);
    virtual void client_closed(const SOCKET clientsocket);
    //! IO notification
    virtual void data_received(const SOCKET clientsocket, const unsigned char* data, const unsigned length);
    virtual void data_sent(const SOCKET clientsocket, const unsigned requestid);

private:

    enum operation_type { OP_UNKNOWN, OP_READ, OP_WRITE };

    /** this class inherits from the OVERLAPPED struct used by io completion ports model for async io.
        overlapped part is about setting up buffers for IO.  operation_context adds contextual information
        to inform worker thread about the type of operation, eg whether a read or a write */
    class operation_context : public OVERLAPPED {
    public:
        operation_context(tcpserver* parent);
        ~operation_context();

        WSABUF            wsabuf;   //! OS specific data buffer
        SOCKET            socket;   //! accepted socket - the client
        operation_type    optype_;  //! type of operation - read or write
        unsigned          remaining_to_send_;  //! indicates remaining data to send if length requested > buffersize
        char*             buffer_;  //! actual buffer used
        unsigned          requestid_;  //! used for sending only, to notify asynchronous completion 

    private:
        tcpserver*        parent_;  //! provides access to outer class member data
        operation_context(const operation_context&);
        operation_context& operator=(const operation_context&);
    };


    class client {
    public:
        client(const SOCKET socket);
        ~client();
        const SOCKET get_socket() const;
        bool shutdown();
        bool close();
        bool operator<(const client& other) const;
    private:
        const SOCKET socket_;
    };

    //! sending data in blocks <= buffersize_
    unsigned chunk_send(const SOCKET clientsocket, const unsigned char* data, const unsigned length);
    //! associate client socket with io completion port mechanism
    void associate_socket(const SOCKET clientsocket);
    //! worker thread to handle queued io
    void async_io_handler();
    //! server listening port
    const unsigned short port_;
    //! buffer size used for send/receive data used in overlapped struct
    const int buffersize_;
    // io completion port specific implementation detail
    HANDLE ioport_;
    //! server listening socket
    SOCKET  listensocket_;
    //! send requests return immediately with a request id which is reported asynchronously in data_sent function
    unsigned requestid_; 
    // flag to indicate that server is closing down - do not accept any more clients
    bool stopping_;

    int worker_thread_count_;

    std::list<std::thread*> workerthreads_;

    std::set<client*> client_list_;

    void recv_data(const SOCKET clientsocket);

    void handle_operation(operation_context* context, DWORD bytes_transferred);
    //! low level handling of client connected/disconnected
    void handle_client_connected(const SOCKET clientsocket);
    void handle_client_closed(const SOCKET clientsocket);

    unsigned get_next_requestid();

    std::mutex client_list_mutex;
    std::mutex requestid_mutex;
};


} // itl namespace

#elif
#error Only Windows platform supported
#endif

#endif // TCPSERVER_HPP_

tcpserver.cpp:

/*
Description: Implementation of asynchronous socket server
using winsock io completion port model
Author: Angus Comber
*/

#include "detect_leaks.hpp"

#include "tcpserver.hpp"

#include <stdio.h>
#include <cassert>
#include <iostream>
#include <string>
#include <algorithm>

#ifdef WIN32

namespace itl {
//! print winsock error as text description
static void print_winsock_error(const int error);


tcpserver::operation_context::operation_context(tcpserver* parent)
    : parent_(parent), socket(INVALID_SOCKET), optype_(OP_UNKNOWN), remaining_to_send_(0), requestid_(0) {

    // Must zero overlapped struct members otherwise get invalid handle error
    Internal = InternalHigh = 0;
    Offset = OffsetHigh = 0;
    hEvent = 0;

    buffer_ = new char[parent_->buffersize_]();

    wsabuf.len = parent_->buffersize_;
    wsabuf.buf = buffer_;
}

tcpserver::operation_context::~operation_context() {
    delete[] buffer_;
}


//! tcp clients
tcpserver::client::client(const SOCKET socket) : socket_(socket) {
    printf("client socket %u constructed\n", socket_);
}

tcpserver::client::~client() {
    printf("client socket %u destructed\n", socket_);
}

const SOCKET tcpserver::client::get_socket() const {
    return socket_; 
}

bool tcpserver::client::operator<(const tcpserver::client& other) const {
    return get_socket() < other.get_socket(); 
}

bool tcpserver::client::shutdown() {
    printf("shutting down client socket %u\n", socket_);
    // SD_SEND notifies remote client we want to close and it in turn responds
    // with zero byte close message - this allows us to delete context and not leak memory
    return ::shutdown(socket_, SD_SEND) == 0;
}

bool tcpserver::client::close() {
    printf("Closing client socket %u\n", socket_);
    return closesocket(socket_) == 0;
}


tcpserver::tcpserver(const unsigned short port, const int buffersize)
        : port_(port), 
          buffersize_(buffersize), 
          ioport_(0),
          worker_thread_count_(0), 
          listensocket_(INVALID_SOCKET), 
          requestid_(0),
          stopping_(false) {

        start_detecting();

        printf("tcpserver()\n");

    WSADATA w = { 0 };
    int error = WSAStartup(0x0202, &w);

    if (error || w.wVersion != 0x0202)
    { // there was an error
        throw "Could not initialise Winsock2\n";
    }

    SYSTEM_INFO info = { 0 };
    ::GetSystemInfo(&info);
    const DWORD cpus = info.dwNumberOfProcessors;

    // rule of thumb, we create cpus * 2 worker threads.  This is so that if a
    // worker thread gets blocked for any reason eg busy processing, there will
    // be an available 'spare' thread to step in and do some work
    worker_thread_count_ = cpus * 2;
}

tcpserver::~tcpserver() {

    printf("~tcpserver()\n");
    CloseHandle(ioport_);
    WSACleanup();
}

void tcpserver::stop() {

    stopping_ = true;

    // trigger clients to close

    // do client list locking in two parts

    {
        std::lock_guard<std::mutex> lock(client_list_mutex);

        for (auto& thisclient : client_list_) {
            thisclient->shutdown();
        }
    }

    // PostQueuedCompletionStatus can send a custom msg which we use to quit
    for (int i = 0; i < worker_thread_count_; ++i) {
        // post zero completion key to indicate to worker thread to close - we have to do this for no. threads created by server
        if (!::PostQueuedCompletionStatus(ioport_,
            0, // bytesCopied
            0, // completionKey
            0)) // overlapped
        {
            printf("Error posting stop message to worker threads (PostQueuedCompletionStatus error)\n");
            print_winsock_error(WSAGetLastError());
        }
        printf("stop, PostQueuedCompletionStatus called on thread idx %u\n", i);
        // delay to give worker threads time to close
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    // cleanup thread memory
    auto it = workerthreads_.begin();
    while (it != workerthreads_.end()) {
        std::cout << "deleting thread id " << (*it)->get_id() << std::endl;

        if ((*it)->joinable())
            (*it)->join();

        delete *it;
        it = workerthreads_.erase(it);

    }

    // delay to give accept function time to close
    std::this_thread::sleep_for(std::chrono::seconds(1));


    {
        std::lock_guard<std::mutex> lock(client_list_mutex);

        // close client sockets
        while (!client_list_.empty()) {
            auto& it = client_list_.begin();
            (*it)->close();
            delete *it;
            client_list_.erase(it);
        }
    }

    printf("stop -> closing listening socket %u\n", listensocket_);
    closesocket(listensocket_);
}


void tcpserver::async_io_handler() {

    printf("tcpserver::async_io_handler() called, worker thread id: %u\n", GetCurrentThreadId());

    DWORD bytes_transferred = 0;
    ULONG_PTR completion_key = 0;
    BOOL success = FALSE;
    operation_context* context = 0;

    // GetQueuedCompletionStatus waits for an io completion packet to be queued 
    // on an io completion port.  when that happens, function dequeues.
    while (success = ::GetQueuedCompletionStatus(ioport_,
        &bytes_transferred,
        &completion_key,
        (LPOVERLAPPED *)&context,
        INFINITE) == TRUE)
    {  
        if (context) {
            printf("GetQueuedCompletionStatus just returned optype=%u, overlapped internal=%u, overlapped InternalHigh=%u, wsabuf len=%u, socket=%u, xferbytes=%u\n",
                context->optype_, context->Internal, context->InternalHigh, context->wsabuf.len, context->socket, bytes_transferred);
        }
        else {
            printf("context null after GetQueuedCompletionStatus\n");
        }


        // if we called stop it passes 0 completion_key
        if (0 == completion_key)
        {
            printf("bytesCopied==0 and competionKey=0 - exiting worker loop for thread %u\n", GetCurrentThreadId());
            // if ending - context will be null - do not try to delete
            break;
        }
        else if (0 == bytes_transferred) {
            // means that client has disconnected.
            printf("GetQueuedCompletionStatus with zero completion key, socket=%u, error=%u\n", (SOCKET)completion_key, context->Internal);

            if (context->Internal != 0) 
                print_winsock_error(context->Internal); 

            handle_client_closed((SOCKET)completion_key);

            if (context) {
                printf("delete context on client close ptr=%p\n", context);
                delete context;
            }
            else {
                printf("NOT deleting context on client close\n");
            }
        }
        else
        {
            // completion packet
            printf("Process completion packet here, bytesCopied=%u, completion key=%u\n", bytes_transferred, completion_key);

            handle_operation(context, bytes_transferred);
        }
    }

    // When GetQueuedCompletionStatus returns FALSE indicating an error
    // for interest sake lets print error - could be normal termination
    //int error = WSAGetLastError();
    if (!success)
        print_winsock_error(WSAGetLastError());


    std::cout << "out of worker thread id " << std::this_thread::get_id() << std::endl;
}

void tcpserver::client_connected(const SOCKET clientsocket) {
    printf("client socket %u created\n", clientsocket);
}

void tcpserver::client_closed(const SOCKET clientsocket) {
    printf("client socket %u closed\n", clientsocket);
}

void tcpserver::handle_client_connected(const SOCKET clientsocket) {

    std::lock_guard<std::mutex> lock(client_list_mutex);

    client_list_.insert(new client(clientsocket));
    client_connected(clientsocket);
}

void tcpserver::handle_client_closed(const SOCKET clientsocket) {

    std::lock_guard<std::mutex> lock(client_list_mutex);

    auto& it = std::find_if(client_list_.begin(), client_list_.end(), [clientsocket](const client* cl) { return cl->get_socket() == clientsocket; });
    if (it != client_list_.end()) {
        delete *it;
        client_list_.erase(it);
    }
    client_closed(clientsocket);
}

void tcpserver::data_received(const SOCKET clientsocket, const unsigned char* data, const unsigned length) {
    printf("%u bytes of data received from client socket %u\n", length, clientsocket);
    printf("Data=%.*s\n", length, reinterpret_cast<const char*>(data));
}

void tcpserver::data_sent(const SOCKET clientsocket, const unsigned requestid) {
    printf("data_sent to client socket %u, requestid %u\n", clientsocket, requestid);
}

void tcpserver::handle_operation(operation_context* context, DWORD bytes_transferred) {

    // what operation to perform
    switch (context->optype_) {
    case OP_READ:
    {
        printf("handle_operation called - OP_READ\n");
        // print out bytes
        data_received(context->socket, reinterpret_cast<const unsigned char*>(context->buffer_), bytes_transferred);

        // continue with next read
        recv_data(context->socket);
    }
    break;
    case OP_WRITE:
    {
        printf("handle_operation called - OP_WRITE\n");

        assert(context->remaining_to_send_ >= bytes_transferred);

        context->remaining_to_send_ -= bytes_transferred;

        if (context->remaining_to_send_ == 0) {
            data_sent(context->socket, context->requestid_);
        }

        printf("WSASend completed, %u bytes transferred, %u bytes remaining.  All bytes sent = %s\n", bytes_transferred, context->remaining_to_send_, context->remaining_to_send_ == 0 ? "true" : "false");
    }
    break;
    default:
        //printf("handle_operation called - default case - not read or write\n");
        printf("unhandled operation!\nbytes_transferred=%u\n", bytes_transferred);
    }

    if (context) {
        printf("handle_operation - delete context ptr=%p\n", context);
        delete context;
    }
}

void tcpserver::start_accepting() {

    ioport_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE,
        0, // no existing port
        0, // ignored
        worker_thread_count_ / 2);  // precalculated in constructor

    if (ioport_ == 0)
    {
        // Call GetLastError for more information.
        printf("!!! SEVERE ERROR !!! start_accepting CreateIoCompletionPort error, GetLastError() = %u", GetLastError());
    }

    // A completion port is a kernel object that you can associate with a number of file handles
    // create the worker threads
    for (int i = 0; i < worker_thread_count_; ++i)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        std::thread* thisthread = new std::thread(&tcpserver::async_io_handler, this);

        // cache the threads in a container so we can cleanup if server has to be re-started
        workerthreads_.push_back(thisthread);
    }

    // Create a SOCKET for the server to listen for client connections

    //! create listening socket to use overlapped IO
    listensocket_ = ::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (listensocket_ == INVALID_SOCKET) {
        int err = WSAGetLastError();
        printf("!!! SEVERE ERROR !!! WSASocket error %d\n", err);
        print_winsock_error(err);
        return;
    }
    else {
        printf("listen socket %u created\n", listensocket_);
    }

    sockaddr_in server_address = { 0 };
    server_address.sin_addr.s_addr = INADDR_ANY;  // bind to all server  interfaces
    server_address.sin_family = PF_INET;
    server_address.sin_port = htons(port_);

    // bind an address to the server listening socket
    int ret = bind(listensocket_, (const sockaddr*)&server_address, sizeof(server_address));
    ret = bind(listensocket_, result->ai_addr, (int)result->ai_addrlen);
    if (ret == SOCKET_ERROR) {
        int err = WSAGetLastError();
        printf("!!! SEVERE ERROR !!! bind failed on listening socket %u, with error: %d\n", listensocket_, err);
        print_winsock_error(err);
        closesocket(listensocket_);
        return;
    }
    else {
        printf("listen socket %u bound to address\n", listensocket_);
    }

    // listen - causes a bound TCP socket to enter listening state.
    if (listen(listensocket_, SOMAXCONN) == SOCKET_ERROR) {
        int err = WSAGetLastError();
        printf("!!! SEVERE ERROR !!! Listen failed  on listening socket %u with error: %ld\n", listensocket_, err);
        print_winsock_error(err);
        closesocket(listensocket_);
        return;
    }
    else {
        printf("Listening socket %u now listening\n", listensocket_);
    }


    SOCKET clientsocket = INVALID_SOCKET;
    sockaddr_in client_address;
    int address_size = sizeof(client_address);

    while (!stopping_) {

        // As clients are accepted, associate client socket with completion port
        // so that we are notified on io activity (completion).

        // will block on WSAAccept waiting for clients to connect
        clientsocket = ::WSAAccept(
            listensocket_,
            reinterpret_cast<sockaddr*>(&client_address),
            &address_size, 0, 0);

        printf("WSAAccept returned, clientsocket=%u\n", clientsocket);

        if (stopping_) {
            break;
        }

        if (clientsocket == INVALID_SOCKET) {
            int ret = WSAGetLastError();
            printf("WSAAccept failed, closing listening socket %u: error: %d\n", listensocket_, ret);

            print_winsock_error(ret);

            closesocket(listensocket_);
            break;
        }
        else {
            handle_client_connected(clientsocket);

            associate_socket(clientsocket);

            // Kick off the first read
            recv_data(clientsocket);
        }
    }

    printf("out of accepting loop\n");
}

unsigned tcpserver::chunk_send(const SOCKET clientsocket, const unsigned char* data, const unsigned length) {

    unsigned requestid = get_next_requestid();

    unsigned num_chunks = length % buffersize_ != 0 ? length / buffersize_ + 1 : length / buffersize_;
    printf("length = %u, sending in %u chunks\n", length, num_chunks);

    for (unsigned i = 0; i < num_chunks; ++i) {
        unsigned start_index = i * buffersize_;

        // An operation_context is per io operation and goes with the WSAXXXX function.
        // you are basically assigning a buffer for the read or write operation
        operation_context* context = new operation_context(this);
        context->socket = clientsocket;
        context->optype_ = OP_WRITE;
        context->requestid_ = requestid;

        context->remaining_to_send_ = length - start_index;
        printf("Remainder to send = %u for clientsocket %u\n", context->remaining_to_send_, clientsocket);

        // copy data to send buf
        unsigned chunk_length = start_index + buffersize_ > length
            ? length - start_index : buffersize_;

        printf("context buffer sending bytes from message start: %u, message length: %u\n", start_index, chunk_length);

        memcpy(context->buffer_, &data[start_index], chunk_length);

        // for sending we need to adjust wsabuf.len
        context->wsabuf.len = chunk_length;


        DWORD flags = 0;
        DWORD bytes_sent = 0;

        printf("send operation_context* context = new operation_context for client socket %u, ptr=%p, thread=%u\n", clientsocket, context, GetCurrentThreadId());
        int retval = WSASend(clientsocket,
            &context->wsabuf, 1, &bytes_sent, flags, (OVERLAPPED*)context, NULL);

        // could get a synchronous completion
        if (retval != SOCKET_ERROR) {
            printf("WSASend returned immediately in send from client %u, bytes sent=%u\n", context->socket, bytes_sent);

            // do nothing here - even though data already received, to simplify 
            // code, we handle processing in completion stage in worker thread
        }
        else {
            int err = WSAGetLastError();
            printf("WSASend returned SOCKET_ERROR, WSAGetLastError()=%s\n", err == WSA_IO_PENDING ? "WSA_IO_PENDING" : "non pending error");
            if (err != WSA_IO_PENDING) {
                printf("Error in WSASend %d\n", err);

                print_winsock_error(err);
                requestid = 0;  // to indicate failure
            }
            else {
                printf("pending data on WSASend call for client %u\n", context->socket);
            }
        }
    }
    return requestid;
}


unsigned tcpserver::send(const SOCKET clientsocket, const unsigned char* data, const unsigned length) {

    return chunk_send(clientsocket, data, length);
}


// perform asynchronous read on client socket
void tcpserver::recv_data(const SOCKET clientsocket) {

    // An operation_context is per io operation and goes with the WSAXXXX function.
    // you are basically assigning buffers for the read or write operation
    operation_context* context = new operation_context(this);
    context->socket = clientsocket;
    context->optype_ = OP_READ;
    DWORD flags = 0;
    DWORD bytes_received = 0;

    printf("recv_data operation_context* context = new operation_context for client socket %u, ptr=%p, thread=%u\n", 
        clientsocket, context, GetCurrentThreadId());

    int retval = WSARecv(clientsocket,
        &context->wsabuf, 1, &bytes_received, &flags, (OVERLAPPED*)context, NULL);

    // could get a synchronous completion
    if (retval != SOCKET_ERROR) {
        printf("WSARecv returned immediately in data_recv from client %u, bytes=%u\n", context->socket, bytes_received);

        // do nothing here - even though data already received, to simplify 
        // code, we handle processing in completion stage in worker thread
    }
    else {
        int err = WSAGetLastError();
        printf("WSARecv returned SOCKET_ERROR, WSAGetLastError()=%s\n", err == WSA_IO_PENDING ? "WSA_IO_PENDING" : "non pending error");
        if (err != WSA_IO_PENDING) {
            printf("recv_data WSARecv error %d\n", err);

            print_winsock_error(err);
        }
        else {
            printf("pending data on WSARecv call for client %u\n", context->socket);
        }
    }
}


// after accepting socket, it must be associated with our io completion port handle so that
// iocp will monitor io operations on this socket
void tcpserver::associate_socket(const SOCKET clientsocket) {
    HANDLE check = ::CreateIoCompletionPort((HANDLE)clientsocket, ioport_, (DWORD)clientsocket, 0);
    if (check == NULL)
    {
        // Never seen this occur
        printf("Error occurred executing CreateIoCompletionPort()\n");
    }
}

unsigned tcpserver::get_next_requestid() {

    std::lock_guard<std::mutex> lock(requestid_mutex);

    // generate next requestid
    requestid_++;
    if (requestid_ == 0)
        requestid_ = 1;

    return requestid_;
}

static void print_winsock_error(const int error) {

    // could check whether an error first before bothering with below code

    HMODULE lib = ::LoadLibraryA("WSock32.dll");

    char* s = NULL;
    FormatMessageA(FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, 
        (LPCVOID)lib, error,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPSTR)&s, 0, NULL);

    if (s) {
        printf("%s\n", s);
        LocalFree(s);
    }

    if (lib)
        ::FreeLibrary(lib);
}

} // namespace itl

#elif
#error Only Windows platform supported
#endif

echoserver.hpp (test using echo all data back to client):

/*
Description: Echo server using tcpserver io completion port server class.  
Listens on tcp port 7 for requests and echos back whatever received.
Author: Angus Comber
*/
#ifndef ECHOSERVER_HPP_
#define ECHOSERVER_HPP_

#include "tcpserver.hpp"

namespace itl {

    class echoserver : public tcpserver {
    public:
        echoserver(const unsigned short port, const int buffersize = 4096);
        void start();

        virtual void client_connected(const SOCKET clientsocket);
        virtual void client_closed(const SOCKET clientsocket);
        virtual void data_received(const SOCKET clientsocket, const unsigned char* data, const unsigned length);
        virtual void data_sent(const SOCKET clientsocket, const unsigned requestid);
    };

} // namespace itl

#endif // ECHOSERVER_HPP_

echoserver.cpp:

#include "echoserver.hpp"

namespace itl {

echoserver::echoserver(const unsigned short port, const int buffersize)
    : tcpserver(port, buffersize)
{
    printf("echoserver()\n");
}

void echoserver::start() {
    start_accepting();
}

void echoserver::client_connected(const SOCKET clientsocket) {
    printf("echoserver::client_connected, client=%u\n", clientsocket);
}

void echoserver::client_closed(const SOCKET clientsocket) {
    printf("echoserver::client_closed, client=%u\n", clientsocket);
}

void echoserver::data_received(const SOCKET clientsocket, const unsigned char* data, const unsigned length) {
    printf("echoserver::data_received, client=%u\n", clientsocket);
    printf("%u bytes of data received from client socket %u\n", length, clientsocket);
    printf("Data=%.*s\n", length, reinterpret_cast<const char*>(data));

    // We now echo the data back to client
    printf("echoing data back to client\n");
    // if we had a protocol that had an end of message marker then we could wait until got complete message
    // and then send back in one go.  but we just send back as we receive here
    printf("send returned requestid %u\n", send(clientsocket, data, length));
}

void echoserver::data_sent(const SOCKET clientsocket, const unsigned requestid) {
    printf("echoserver::data_sent to client socket %u, requestid %u\n", clientsocket, requestid);
}

} // namespace itl

detect_leaks.hpp (to detect memory leaks):

#ifndef DETECT_LEAKS_HPP__
#define DETECT_LEAKS_HPP__

#define _CRTDBG_MAP_ALLOC

#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <crtdbg.h>

#define new new(_CLIENT_BLOCK,__FILE__, __LINE__)

// The following macros set and clear, respectively, given bits
// of the C runtime library debug flag, as specified by a bitmask.
#ifdef   _DEBUG
#define  SET_CRT_DEBUG_FIELD(a) \
    _CrtSetDbgFlag((a) | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG))
#define  CLEAR_CRT_DEBUG_FIELD(a) \
    _CrtSetDbgFlag(~(a) & _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG))
#else
#define  SET_CRT_DEBUG_FIELD(a)   ((void) 0)
#define  CLEAR_CRT_DEBUG_FIELD(a) ((void) 0)
#endif

void start_detecting();

#endif // DETECT_LEAKS_HPP__

detect_leaks.cpp:

#include "detect_leaks.hpp"

void start_detecting() {

    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    // Send all reports to STDOUT
    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT);
    _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDOUT);
    _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDOUT);

    // Set the debug-heap flag so that freed blocks are kept on the
    // linked list, to catch any inadvertent use of freed memory
    SET_CRT_DEBUG_FIELD(_CRTDBG_DELAY_FREE_MEM_DF);

    // Set the debug-heap flag so that memory leaks are reported when the process terminates.
    SET_CRT_DEBUG_FIELD(_CRTDBG_LEAK_CHECK_DF);
}

main.cpp:

#include "echoserver.hpp"

// for testing processing of shutdown
void shutdowner(itl::echoserver* server) {

    std::this_thread::sleep_for(std::chrono::seconds(60));
    server->stop();
}

int main() {

    {
        itl::echoserver server(7, 100);
        // testing - kick off a thread to stop server in 1 minute
        std::thread t2(shutdowner, &server);
        server.start();

        if (t2.joinable())
            t2.join();
    }

}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.