0

I'm totally new to networking and just started to use Cap'n Proto too.

This is some sample program from here:

void writeAddressBook(int fd) {
  ::capnp::MallocMessageBuilder message;

  AddressBook::Builder addressBook = message.initRoot<AddressBook>();
  ::capnp::List<Person>::Builder people = addressBook.initPeople(2);

  Person::Builder alice = people[0];
  alice.setId(123);
  alice.setName("Alice");
  alice.setEmail("alice@example.com");
  // Type shown for explanation purposes; normally you'd use auto.
  ::capnp::List<Person::PhoneNumber>::Builder alicePhones =
      alice.initPhones(1);
  alicePhones[0].setNumber("555-1212");
  alicePhones[0].setType(Person::PhoneNumber::Type::MOBILE);
  alice.getEmployment().setSchool("MIT");

  Person::Builder bob = people[1];
  bob.setId(456);
  bob.setName("Bob");
  bob.setEmail("bob@example.com");
  auto bobPhones = bob.initPhones(2);
  bobPhones[0].setNumber("555-4567");
  bobPhones[0].setType(Person::PhoneNumber::Type::HOME);
  bobPhones[1].setNumber("555-7654");
  bobPhones[1].setType(Person::PhoneNumber::Type::WORK);
  bob.getEmployment().setUnemployed();

  writePackedMessageToFd(fd, message);
}

The last line uses writePackedMessageToFd() which takes fd as a file descriptor and message created by MallocMessageBuilder.

I work on Windows with Visual Studio 2017.

I would like to send message to a remote server which will answer with a similar Cap'nP object.

The question is how can I send it and receive the answer?

I tried to initialize and create a socket in the following way:

    //Initialize WinSock
    WSAData data;
    WORD ver = MAKEWORD(2, 2);
    int wsResult = WSAStartup(ver, &data);
    if (wsResult != 0) {
        cerr << "Can't start WinSock, Error #" << wsResult << endl;
        return;
    }
    else {
        cout << "Socket initialized!" << endl;
    }


    //Create socket
    SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET) {
        cerr << "Can't create socket, Error #" << WSAGetLastError() << endl;
        WSACleanup();
        return;
    }
    else {
        cout << "Socket created!" << endl;
    }

If everything went fine socket() return a file descriptor. So I just used writePackedMessageToFd(sock, message), but didn't work. By the way I don't understand this concept since the socket doesn't "know" which IP and port I want to use. I should specify them when I use the connect() function.

I tried to skip the writePackedMessageToFd() function. Connected to the server with connect() and just used Windows' send() function to send the message. Something like this:

    string ipAddress = "127.0.0.1";     //IP Address of server 
    int port = 58661;           //Listening port of server

    //Initialize WinSock
    WSAData data;
    WORD ver = MAKEWORD(2, 2);
    int wsResult = WSAStartup(ver, &data);
    if (wsResult != 0) {
        cerr << "Can't start WinSock, Error #" << wsResult << endl;
        return;
    }
    else {
        cout << "Socket initialized!" << endl;
    }


    //Create socket
    SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET) {
        cerr << "Can't create socket, Error #" << WSAGetLastError() << endl;
        WSACleanup();
        return;
    }
    else {
        cout << "Socket created!" << endl;
    }

    //Fill in a hint structure
    sockaddr_in hint;
    hint.sin_family = AF_INET;
    hint.sin_port = htons(port);
    inet_pton(AF_INET, ipAddress.c_str(), &hint.sin_addr);

    //Connect to server
    int connResult = connect(sock, (sockaddr*)&hint, sizeof(hint));
    if (connResult == SOCKET_ERROR) {
        cerr << "Can't connect to server, Error #" << WSAGetLastError() << endl;
        closesocket(sock);
        WSACleanup();
        return;
    }
    else {
        cout << "Connected to " << ipAddress << " on port " << port << endl;
    }

    //Send and receive data
    char buf[4096];
    //string mess = "Hello";

    //Send message
    //int sendResult = send(sock, mess.c_str(), mess.size() + 1, 0);
    int sendResult = send(sock, (const char*)&message, sizeof(message) + 1, 0);


    //Wait for response
    ZeroMemory(buf, 4096);
    int bytesReceived = recv(sock, buf, 4096, 0);
    if (bytesReceived > 0) {
        cout << "SERVER>" << string(buf, 0, bytesReceived) << endl;
    }


    //Close down everything
    closesocket(sock);
    WSACleanup();
    cout << "Connection closed!" << endl;

This one sended something but it was definitely wrong because the server didn't respond.

In brief: I would like to send and receive Cap'n Proto packed messages over TCP connection to a specified IP:port.

How could I accomplish this? I really need some help.

I would really really appreciate Your help! Thanks in advance!

Andariel
  • 9
  • 1
  • 3
  • Did you try using the RPC logic that is already built in to Cap'n Proto? Don't write your own from scratch. The example you have shown comes from Cap'n Proto's [Serialization](https://capnproto.org/cxx.html) documentation. Did you look at its [RPC Protocol](https://capnproto.org/rpc.html) and [C++ RPC](https://capnproto.org/cxxrpc.html) documentations at all? "*The Cap’n Proto C++ RPC layer sits on top of the [serialization layer](https://capnproto.org/cxx.html) and implements the [RPC protocol](https://capnproto.org/rpc.html)*" – Remy Lebeau Nov 01 '17 at 00:10
  • If you really want to implement the socket I/O manually, have a look at the "Specification" section at the bottom of the [RPC Protocol](https://capnproto.org/rpc.html) documentation. – Remy Lebeau Nov 01 '17 at 00:16
  • @RemyLebeau There are plenty of good reasons to want to use simple Cap'n Proto serialization on a raw socket, rather than use the whole RPC and async framework. It all depends on the use case. Sometimes the RPC system is overkill. – Kenton Varda Nov 01 '17 at 04:13

1 Answers1

1

On Windows, socket() does not return a file descriptor. It returns a Windows SOCKET, which is actually a HANDLE cast to an integer. On Windows, "file descriptors" are implemented as a compatibility layer in the C runtime library; they are not directly supported by the OS.

You can use kj::HandleInputStream and kj::HandleOutputStream to perform I/O on sockets on Windows.

kj::HandleOutputStream out((HANDLE)sock);
capnp::writeMessage(out, message);

And:

kj::HandleInputStream in((HANDLE)sock);
capnp::InputStreamMessageReader message(in);
Kenton Varda
  • 41,353
  • 8
  • 121
  • 105
  • Thank you very much!! I'm not sure how can tell to the `sock` socket that which IP:port I would like to use? Should I bind sock to them? (Sorry I'm really new to this stuff :( ) If use my socket initialized and created in the code above then I get an unhandled exception `exception: kj::ExceptionImpl ` in `exception.c++` in line 679. Am I missing something? Thank You!! – Andariel Nov 01 '17 at 08:21
  • please, could you clarify my question? Thank you! – Andariel Nov 02 '17 at 08:41
  • @Andariel On the server you need to use bind(), listen(), and accept(). On the client you need to use connect(). Sorry, but how to use these is basic networking, not Cap'n Proto specific, and I'm only here to answer Cap'n Proto questions. I suggest posting a new question about this, but keep Cap'n Proto out of it, so that people who don't know Cap'n Proto feel comfortable answering. Alternatively, there are a *lot* of guides about this on the internet. – Kenton Varda Nov 04 '17 at 03:13