3
\$\begingroup\$

Getting close to a release of generic server.

Nisse Server: Part 1 Helper Functions
Nisse Server: Part 2 Socket Layer
Nisse Server: Part 3 Stream Layer
Nisse Server: Part 4 Server
Nisse Server: Part 5 Handler

As a demonstration protocol (also for testing). Here is a class that pulls it all together. With this we can have a function server that implements the "Simple Protocol".

The "Simple Protocol" (is something I invented for testing) is simply this.

Request  : RequBody
RequBody : <8 bytes Size object><Message of Correct Size>

Response : RespBody
RespBody : <8 bytes Size object><The request Body with " -> OK <-" appeneded>

Note: This is not HTTP or anything based on it.

Here we are using boost/coroutine that allows us to return control to the main function (trying to avoid using the term thread).

The CoRoutine object worker does all the work. When you call worker() control is transferred to the CoRoutine. When the code in worker invokes yield() the call to worker() returns. The difference is that the next time worker() is called control returns to the point where it previously yielded from, so to the lambda it looks like the call to yield() just returned.

This allows us to wrap the yield() into a lambda and pass it to the [IO]SocketStream object so that when a read/write would block we actually yield back to the main routine.

This is designed to test that on large requests that a read will not block and return to the main event loop and potentially get data from another socket while it waits.

ProtocolSimpleStreamNisse.h

#ifndef THORSANVIL_NISSE_PROTOCOL_SIMPLE_STREAM_NISSE_H
#define THORSANVIL_NISSE_PROTOCOL_SIMPLE_STREAM_NISSE_H

#include "NisseHandler.h"
#include "SocketStream.h"
#include <boost/coroutine/asymmetric_coroutine.hpp>

namespace ThorsAnvil
{
    namespace Nisse
    {
        namespace ProtocolSimple
        {

using CoRoutine = boost::coroutines::asymmetric_coroutine<void>::pull_type;
using Yield     = boost::coroutines::asymmetric_coroutine<void>::push_type;

class Message
{
    public:
        std::size_t     size;
        std::string     message;
    public:
        Message() = default;
        Message(Message&& move)
            : size(std::move(move.size))
            , message(std::move(move.message))
        {}
        friend std::istream& operator>>(std::istream& stream, Message& info)
        {
            if (stream.read(reinterpret_cast<char*>(&info.size), sizeof(info.size)))
            {
                info.message.resize(info.size);
                stream.read(reinterpret_cast<char*>(&info.message[0]), info.size);
            }
            return stream;
        }
        friend std::ostream& operator<<(std::ostream& stream, Message& info)
        {
            info.size = info.message.size();
            if (stream.write(reinterpret_cast<char*>(&info.size), sizeof(info.size)))
            {
                stream.write(&info.message[0], info.size);
            }
            return stream;
        }
};

class ReadMessageStreamHandler: public NisseHandler
{
    private:
        CoRoutine       worker;
    public:
        ReadMessageStreamHandler(NisseService& parent, LibEventBase* base, ThorsAnvil::Socket::DataSocket&& socket);
        virtual void eventActivate(LibSocketId sockId, short eventType) override;
};

class WriteMessageStreamHandler: public NisseHandler
{
    private:
        CoRoutine       worker;
    public:
        WriteMessageStreamHandler(NisseService& parent, LibEventBase* base, ThorsAnvil::Socket::DataSocket&& socket, Message&& message);
        virtual void eventActivate(LibSocketId sockId, short eventType) override;
};

        }
    }
}

#endif

ProtocolSimpleStreamNisse.cpp

#include "ProtocolSimpleStreamNisse.h"
#include "NisseService.h"

using namespace ThorsAnvil::Nisse::ProtocolSimple;

ReadMessageStreamHandler::ReadMessageStreamHandler(NisseService& parent, LibEventBase* base, ThorsAnvil::Socket::DataSocket&& so)
    : NisseHandler(parent, base, so.getSocketId(), EV_READ)
    , worker([&parent = *this, socket = std::move(so)](Yield& yield) mutable
                {
                    Socket::ISocketStream   stream(socket, [&yield](){yield();}, [](){});
                    Message                 message;
                    yield();

                    stream >> message;
                    parent.moveHandler<WriteMessageStreamHandler>(std::move(socket), std::move(message));
                }
            )
{}

void ReadMessageStreamHandler::eventActivate(LibSocketId /*sockId*/, short /*eventType*/)
{
    worker();
}

WriteMessageStreamHandler::WriteMessageStreamHandler(NisseService& parent, LibEventBase* base, ThorsAnvil::Socket::DataSocket&& so, Message&& ms)
    : NisseHandler(parent, base, so.getSocketId(), EV_WRITE)
    , worker([&parent = *this, socket = std::move(so), message = std::move(ms)](Yield& yield) mutable
                {
                    Socket::OSocketStream   stream(socket, [&yield](){yield();}, [](){});
                    yield();

                    message.message += " -> OK <-";
                    stream << message;
                    parent.dropHandler();
                }
            )
{}

void WriteMessageStreamHandler::eventActivate(LibSocketId /*sockId*/, short /*eventType*/)
{
    worker();
}

Nisse.pp

#include "NisseService.h"
#include "ProtocolSimpleStreamNisse.h"
#include "NisseHandler.h"

#include <iostream>

int main()
{
    try
    {
        using ThorsAnvil::Nisse::NisseService;

        NisseService    service;

        using ThorsAnvil::Nisse::ProtocolSimple::ReadMessageStreamHandler;
        service.listenOn<ReadMessageStreamHandler>(40718);
        service.listenOn<ReadMessageStreamHandler>(40719);
        service.listenOn<ReadMessageStreamHandler>(40720);
        service.listenOn<ReadMessageStreamHandler>(40721);

        service.start();
    }
    catch (std::exception const& e)
    {
        //Log exception
        throw;
    }
    catch (...)
    {
        //Log exception
        throw;
    }
}

Building:

If you put together all the source in the last 6 articles and compile together with -levent it should build an executable.

External requirements. libevent and Boost Co-Routines

I have a github project with all this code that I will make public when I have finished up a couple of more things.

But until then I have provided everything you need to build an executable I think (let me know if anything goes wrong).

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