cppzmq icon indicating copy to clipboard operation
cppzmq copied to clipboard

Zero-Copy or fewer Copies on send and receive

Open klahaag opened this issue 5 years ago • 3 comments

Is there somewhere documentation on how to use zero copy or a minimal amount of allocs and copies when using cppzmq?

I wrote a minimal example that does not work as I expected: Basically, I want to provide to the send and the recv method a buffer that is managed by myself. However, after my recv operation zmq seems to change the content of my buffer concurrently after message_t type is released. Imo it should not do anything with it.

#define NOMINMAX
#include "zmq.hpp"

#include <iostream>

static std::pair<char*, size_t> recv(zmq::socket_t& socket, char* recvBuffer, size_t size)
{
  zmq::message_t message(recvBuffer, size);
  socket.recv(message);
  return { static_cast<char*>(message.data()), message.size() };
}

void send(zmq::socket_t& socket, char* buffer, size_t size)
{
  zmq::message_t message(buffer, size);
  auto rc = socket.send(message, zmq::send_flags::none);
}

int main(int argc, char *argv[])
{
  std::string serverAddr("tcp://localhost:5559");
  zmq::context_t context(1);
  zmq::socket_t socket(context, ZMQ_REQ);
  socket.connect(serverAddr.c_str());

  char sendBuffer[]{ 'h', 'e', 'l', 'l', 'o' };
  size_t const sendBufferSize = sizeof(sendBuffer);

  size_t const recvBufferSize = 300;
  char recvBuffer[recvBufferSize];

  for(int request = 0; request < 10; request++)
  {
    send(socket, sendBuffer, sendBufferSize);
    auto res = recv(socket, recvBuffer, recvBufferSize);

    std::cout << "comment this line to produce correct output" << std::endl;
    std::cout << "Received reply " << request << " [" << std::string(res.first, res.second) << "]" << std::endl;
  }
}

Output:

Received reply 0 [ 6(O¢
▀  ]
comment to produce correct output
Received reply 1 [ 6(O¢
▀  ]
comment to produce correct output
Received reply 2 [ 6(O¢
▀  ]
comment to produce correct output
Received reply 3 [ 6(O¢
▀  ]
comment to produce correct output
Received reply 4 [ 6(O¢
▀  ]
comment to produce correct output
Received reply 5 [ 6(O¢
▀  ]
comment to produce correct output
Received reply 6 [ 6(O¢
▀  ]
comment to produce correct output
Received reply 7 [ 6(O¢
▀  ]
comment to produce correct output
Received reply 8 [ 6(O¢
▀  ]
comment to produce correct output
Received reply 9 [ 6(O¢
▀  ]

if i comment the line std::cout << "comment this line to produce correct output" << std::endl; I get the correct output.

using python as server:

import numpy as np
import zmq

from struct import unpack

port = "5559"
    
context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind("tcp://*:%s" % port)

while True:
    message = socket.recv(0)
    
    print("Received request: %s" %message)
    socket.send_string("World from %s" %port)

klahaag avatar Aug 08 '19 13:08 klahaag

Looks a use after free error, returning a pointer to function local data (contents of message) in the recv function.

gummif avatar Aug 14 '19 14:08 gummif

You are right. But how do I pass my own buffer to a recv so that it's written to my buffer and not somewhere else?

klahaag avatar Aug 16 '19 11:08 klahaag

For fixed sized buffers you can do something like this, where data will be written to buf

std::array<std::byte, 1024> buf;
auto ret = socket.recv(zmq::buffer(buf));
// check that ret is valid and not truncated

gummif avatar Aug 16 '19 15:08 gummif