Andariel
Andariel

Reputation: 9

Sending Cap'n Proto messages over TCP in C++

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("[email protected]");
  // 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("[email protected]");
  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!

Upvotes: 0

Views: 2283

Answers (1)

Kenton Varda
Kenton Varda

Reputation: 45246

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);

Upvotes: 1

Related Questions