0

I currently have two processes running on the same computer, a 'producer' and a Qt application tasked with displaying the data. These two need to exchange binary data (byte arrays, serialized objects). The data 'parcels' may range in size from a handful of bytes to tens of MBs.

What's a/the simple and elegant way to do this?

I thought about using boost::asio and Google Protocol Buffers, shared memory regions or just low-level sockets to achieve this, but I'm interested in learning about other solutions that I may have overlooked.

Performance is not absolutely critical but decent latency (<1 sec) and bandwidth (let's say, >5MB/sec) are necessary.

Thanks in advance!

F. P.
  • 5,018
  • 10
  • 55
  • 80
  • 2
    'boost::asio and Google Protocol Buffers' sonds very promising to me. Depending on your use cases instead of boost::asio, [Zero-MQ](http://zeromq.org/) could be handy. Though your question might be OT as is, since answers might be opinion based (that's why I posted this as comment). – πάντα ῥεῖ May 04 '14 at 17:17
  • what os or do you want to be agnostic? – Duck May 04 '14 at 17:17
  • @Duck proposing boost::asio sounds like ... – πάντα ῥεῖ May 04 '14 at 17:18
  • It's sounds like someone familiar with c++ solutions who may or may not care about cross-platform issues? – Duck May 04 '14 at 17:19
  • @πάνταῥεῖ Zero-MQ looks great. Thanks. I'll have a look at it. – F. P. May 04 '14 at 17:28
  • @Duck Preferably agnostic, may be Linux specific if need be. – F. P. May 04 '14 at 17:29
  • You have a good start with your own and the posted suggestion then. QT has classes that wrap common IPC too, no? – Duck May 04 '14 at 17:35
  • You mean in terms of serialization? The producer application does not depend on Qt and I would like to keep it that way, so I might just use GPBuffers for that. – F. P. May 04 '14 at 17:39

1 Answers1

1

If you don't like extra libraries then you can try the following:

#if defined _WIN32 || defined _WIN64
    #include <windows.h>
#else
    #include <sys/types.h>
    #include <sys/mman.h>
    #include <dlfcn.h>
    #include <fcntl.h>
    #include <unistd.h>
#endif

#include <cstdint>


typedef struct
{
    #if defined _WIN32 || defined _WIN64
    void* hFileMap;
    #else
    int hFileMap;
    #endif
    void* pData;
    size_t size;
} MemoryMap;

void* CreateMemoryMap(MemoryMap* info, const char* MapName, unsigned int size)
{
    #if defined _WIN32 || defined _WIN64
    info->hFileMap = NULL;
    info->pData = NULL;
    info->size = 0;

    if ((info->hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, MapName)) == NULL)
    {
        return NULL;
    }

    if ((info->pData = MapViewOfFile(info->hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, size)) == NULL)
    {
        CloseHandle(info->hFileMap);
        return NULL;
    }

    #else

    info->hFileMap = NULL;
    info->pData = NULL;
    info->size = 0;

    if ((info->hFileMap = open(MapName, O_RDWR | O_CREAT, 438)) == -1)
    {
        return NULL;
    }

    if ((info->pData = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, hFileMap, 0)) == MAP_FAILED)
    {
        close(hFileMap);
        return NULL;
    }
    #endif

    info->size = size;
    return info->pData;
}

void* OpenMemoryMap(MemoryMap* info, const char* MapName, unsigned int size)
{
    #if defined _WIN32 || defined _WIN64
    info->hFileMap = NULL;
    info->pData = NULL;
    info->size = 0;

    if ((info->hFileMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, MapName)) == NULL)
    {
        return NULL;
    }

    if ((info->pData = MapViewOfFile(info->hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, size)) == NULL)
    {
        CloseHandle(info->hFileMap);
        return NULL;
    }

    #else

    info->hFileMap = NULL;
    info->pData = NULL;
    info->size = 0;

    if ((info->hFileMap = open(MapName, O_RDWR | O_CREAT, 438)) == -1)
    {
        return NULL;
    }

    if ((info->pData = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, hFileMap, 0)) == MAP_FAILED)
    {
        close(info->hFileMap);
        return NULL;
    }
    #endif

    info->size = size;
    return info->pData;
}

void CloseMap(MemoryMap* data)
{
    #if defined _WIN32 || defined _WIN64
    UnmapViewOfFile(data->pData);
    CloseHandle(data->hFileMap);
    #else
    munmap(data->pData, data->size);
    close(data->hFileMap);
    #endif
}



template<typename T>
class CAllocator
{
    private:
        size_t size;
        void* data = nullptr;

    public:
        typedef T* pointer;
        typedef const T* const_pointer;

        typedef T& reference;
        typedef const T& const_reference;

        typedef size_t size_type;
        typedef ptrdiff_t difference_type;

        typedef T value_type;


        CAllocator() {}
        CAllocator(void* data_ptr, size_type max_size) noexcept : size(max_size), data(data_ptr) {};

        template<typename U>
        CAllocator(const CAllocator<U>& other) noexcept {};

        CAllocator(const CAllocator &other) : size(other.size), data(other.data) {}

        template<typename U>
        struct rebind {typedef CAllocator<U> other;};

        pointer allocate(size_type n, const void* hint = 0) {return static_cast<pointer>(data);}
        void deallocate(void* ptr, size_type n) {}
        size_type max_size() const {return size / sizeof(T);}
};

template <typename T, typename U>
inline bool operator == (const CAllocator<T>&, const CAllocator<U>&) {return true;}

template <typename T, typename U>
inline bool operator != (const CAllocator<T>& a, const CAllocator<U>& b) {return !(a == b);}



/** Test case **/
#include <vector>
#include <iostream>
#include <stdexcept>

int main()
{
    /** Sender **/
    MemoryMap data = {0};
    if (!CreateMemoryMap(&data, "MapName", 1024))
    {
        if (!OpenMemoryMap(&data, "MapName", 1024))
        {
            throw std::runtime_error("Cannot map memory.");
        }
    }

    std::vector<int, CAllocator<int>> shared_sender_vector(CAllocator<int>(data.pData, data.size));
    shared_sender_vector.push_back(10);

    for (int i = 0; i < 10; ++i)
    {
        shared_sender_vector.push_back(i + 1);
    }



    /** Receiver **/


    MemoryMap data2 = {0};
    if (!CreateMemoryMap(&data2, "MapName", 1024))
    {
        if (!OpenMemoryMap(&data2, "MapName", 1024))
        {
            throw std::runtime_error("Cannot map memory.");
        }
    }


    int* offset = static_cast<int*>(data2.pData);
    std::vector<int, CAllocator<int>> shared_receiver_vector(CAllocator<int>(++offset, data2.size));
    shared_receiver_vector.reserve(*(--offset));

    for (int i = 0; i < 10; ++i)
    {
        std::cout<<shared_receiver_vector[i]<<" ";
    }

    CloseMap(&data);
    CloseMap(&data2);
}

It prints:

1 2 3 4 5 6 7 8 9 10

Can be used for strings and all sorts of containers that accepts allocators. They store their data directly in the shared-memory map. The allocator is the same as the one I wrote for:

Boost Pool experience requested. Is it useful as allocator with preallocation? so it can be reused for whatever you want..

Community
  • 1
  • 1
Brandon
  • 22,723
  • 11
  • 93
  • 186
  • Hmmm, may be too weak solution for what the OP asked for :-/ ... Also the OP didn't mention not wanting extra libraries. – πάντα ῥεῖ May 04 '14 at 17:54
  • I am aware of what OP mentioned. I just thought I'd offer a custom solution. I'm not sure if it's weak or not. I was under the impression that other alternatives use the above code internally. Maybe not the allocator but the memory mapping for sure. I can't see why this would be any slower than the alternatives. Then again, I could be wrong; but how wrong? – Brandon May 04 '14 at 18:00
  • The major problem may not lie in performance for sharing the serialized data, but to solve the various communication routing patterns as it's done e.g. with Zero-MQ. I didn't state your answer _'is wrong'_ anyway ... – πάντα ῥεῖ May 04 '14 at 18:22