1
votes

Overview

I am writing a simple test game server in C++. Currently, I have a bound socket listening to anyone, and when a certain packet type arrives, I want to save the sockaddr struct into a object which will be passed to a separate thread. That separate thread will be looping through all sockets and attempting to write data to them.

For testing purposes, I just want to do the following:

  1. Listen for the packet and determine its type (done)
  2. Pass the source address information to a separate function (done)
  3. Create a new socket, and bind it on a new port (done, but maybe an issue here?)
  4. Send data through that newly bound/made socket using the sockaddr_in that was passed in earlier (done?)

Setup

Receiving

Currently, this is how the global listener is configured:

void lobbyactions_thread() {
    struct sockaddr_in any;
    memset(&any, 0, sizeof(any));
    any.sin_family = AF_INET;
    any.sin_port = htons(LOBBYACTIONS_PORT);
    any.sin_addr.s_addr = htonl(INADDR_ANY);
    unsigned int any_sz = sizeof(any);
    packets::LOBBYACTION_PACKET jpacket;

    int socketfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    bind(socketfd, (struct sockaddr*) &any, sizeof(any))

    while (server_manager::STAY_ALIVE) {
        struct sockaddr_in* new_con = new struct sockaddr_in;
        memset(new_con, 0, sizeof(*new_con));
        unsigned int new_con_size = sizeof(*new_con);

        recvfrom(socketfd, &jpacket, packets::LOBBYACTION_PACKET_SIZE, 0, (sockaddr*)new_con, &new_con_size);
        bool save = false;
        if (jpacket.type == JOIN) { save = true; server_manager::join(jpacket.id, new_con); }

        // ...

        jpacket.okay = true;
        if (!save) { 
            sendto(socketfd, &jpacket, packets::LOBBYACTION_PACKET_SIZE, 0, (sockaddr*)&any, any_sz);
            free(any); 
        }
    }
}

Saving and temporarily Sending

When a join is handled, I want to save the recipient into an internal queue of other recipients / users / connections. For testing purposes, I have this join method which will simply create the new socket, bind it, and send some data just as a proof of concept.

struct Slot {
    int playerSocket;
    int playerAssignedPort;
    struct sockaddr_in* sourceAddress;
    unsigned int sourceAddressSize;
};

void join(int id, struct sockaddr_in* raddr) {
    Slot* ps = new Slot();
    ps->playerAssignedPort = 8810 // temporary
    ps->playerSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(ps->playerAssignedPort);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    ps->sourceAddressSize = sizeof(addr);

    bind(ps->playerSocket, (struct sockaddr*)&addr, ps->sourceAddressSize);

    ps->sourceAddress = raddr;
    ps->sourceAddress->sin_port = htons(ps->playerAssignedPort);

    // ... 

    sendto(ps->playerSocket, &updatePacket, packets::GAMESTATE_PACKET_SIZE, 0, (sockaddr*)(ps->sourceAddress), ps->sourceAddressSize);

    // ...

}

Question

Why is it that I can not bind to this socket? Am I passing the wrong section of any around? Or is this something that is not really possible? Any help or pointers (terrible pun maybe intended) would be great.

Why is it that when I save the struct sockaddr_in that was populated by recvfrom I can for some reason no longer send messages, albeit through another port and another socket? Strange enough, I've come to find that when the client and server have to go through a local router, this does not work, but if the client and server do not have to go through a local router, this infact works! Could it be that there is additional packet/header information that I need to gather? Or is the router doing something (or lack there of) that will restrict the messages? Could it be that there is some weird port forwarding issue, and not infact a software issue at play here?

Edit

It may be important to note that the everything works currently in the lobbyactions_thread() method, i.e. the receive, process, and send. Its the join() method that is the issue.

Updated code, in part from own discovery and User @selbie

1
You use the words "datagram" and "connection" near each other. That doesn't make sense. The whole point of UDP is that it's connection-less - there is no concept of connection. In light of this, it's not clear what you are trying to do.Igor Tandetnik
@IgorTandetnik, indeed you are correct, I in my mind think of this as maintaining a "connection" even though in actuality UDP does not act in this way. However my question still stands, and perhaps I'll try to expand a bit here: How can I take the sockaddr (gets updated by recvfrom) and pass that elsewhere to send data onto a different socket that is bound to a different port. ie, I get data from 1.2.3.4:5 in my lobbyactions, and want to create a new socket and send that same address data at a different port, ie I want to send something to 1.2.3.4:6 later on.dovedevic
I'm not sure I understand the difficulty. sockaddr_in is just a (usually) 16-byte structure - there's no black magic in it. You "send" it somewhere the same way you send any other piece of data - e.g. by passing its address as a parameter to some function. Take the filled-in structure, keep the address part, update the port number, and pass it along to sendtoIgor Tandetnik
Right, and that's what I'm trying to do however it seems the socket won't bind for some reason...dovedevic
Please do not use struct sockaddr_in directly. It makes your code IPv4-only. Instead, use the address family independent functions like getaddrinfo() and getnameinfo() to convert from hostnames/ports to struct sockaddr and back. The getaddrinfo() function can also be used to give you a list of struct sockaddrs that you can bind() to.G. Sliepen

1 Answers

1
votes

Your bind call is failing for two reasons. One, you are trying to bind multiple sockets to the same port. IF the first bind succeeds on port 8810, the second socket bind attempt on the same port will fail. Further, you are trying to bind to a remote address, not a local address - that will always fail.

I think you may be confusing this intent of what the bind function does. For a UDP socket, the bind call specifies what port (and which local IP address) to send and receive datagram messages on. Standard behavior is to bind to INADDR_ANY for IP (all adapters with IP address). Just as you have your lobby code.

When a player joins via a UDP message, you do not want to bind to the remote address for the new socket, because that will indeed fail. Rather you want to bind to INADDR_ANY as IP and port 0 (to get an available port automatically assigned).

So change this:

bind(ps->playerSocket, (struct sockaddr*)&addr, ps->sourceAddressSize); // <- Wont 

bind!

To this:

sockaddr_in addrTemp = {}; // zero-init the local bind address (INADDR_ANY and port 0)
addrTemp.sin_family = AF_INET;
int result = bind(ps->playerSocket, (sockaddr*)&addrTemp, sizeof(addrTemp));

You can call getsockname later to get the port number that was assigned to your socket as a result of using port 0 in the bind call.

Also, please consider a design that would use a single socket for all players on a dedicated port number. That will likely work better for NAT traversal and you won't have to worry about running out of ports. Can vary greatly if you are building a client-server game vs. Peer-to-peer game.