Sign in to follow this  
BradDaBug

Waiting on messages

Recommended Posts

I'm trying to think of a nice way to handle this, but I can't, really. Let's say I'm using UDP*, so I have no guarantee packets are getting where I send them. So the client sends a message to the server asking for a piece of information (like what map the server is currently playing, etc). The client should wait 200 ms or so and if it doesn't get a response it sends the request again. But the client can't just sit there and wait for a response, it has to keep working, so the code looks something like this:
RequestMap()
{
    SendRequest("what map is playing?");
}

Process() // called once per frame
{
    if (needMap)
    {
        map = GotMapYet();
        if (map)
            needMap = false; // ok, we can continue on
        else
        {
            if (timeSinceLastRequest > 200) // in milliseconds
            {
                RequestMap();
                timeSinceLastRequest = 0;
            }
        }
    }
}
What this bit of code means is that I'm going to have to have a finite state machine where I check for every possible state and handle it explicitly. That just seems really ugly, and that Process() function could quickly get huge. I'm sure this is a really common issue, so are there any good ways to approach it? Is there a name for this kind of problem? * Suggesting using TCP instead of UDP doesn't really have anything to do with this, since I'd still have cases where I have to wait on either the client or the server for a specific response even with TCP. This is just an example.

Share this post


Link to post
Share on other sites
That's a bad way to do things.
The client shouldn't request a map from the server, the server should send it when needed (log in and entering another map). The client should just wait until it gets the map, and then display it.
Similarly for getting other stuff, such as what the players nearby do (do they move, do they log out, fight, etc.)
And you should use TCP, unless you REALLY need UDP (for FPS maybe).
From your qustion it seems you are not very familiar with client/server communication in a MMO (or even multiplayer game) so using UDP will just make things worse.
Just use TCP until you get more experience, and then, if you really need to, switch to UDP.
I have years of MMO programming experience, and I still woldn't dare to switch to UDP. It can be a pain.

P.S. the process() function should do anything but dispatch all the incoming packets to the functions that handle them, based on the packet type. this way, your process() function won't become too big, and you can also organize your code better.

Share this post


Link to post
Share on other sites
In general, you don't want to code you communications protocol state machine inside the actual application code. Instead, you want to define the protocol in a layer of your own, and re-use that layer for each transaction that goes across that protocol.

In the example you're suggesting (which might flood the server under certain conditions, as your re-transmit interval isn't adaptive), you'd probably have a layer that supports asynchronous requests:


class Response {
public:
Response() { done_ = false; data_ = 0; size_ = 0; }
~Response() { free( data_ ); }
bool done_;
void * data_;
size_t size_;

void * request_;
size_t requestSize_;
Time transmitTimer_;
};

int serial;
std::map< int, Response * > requests;

Response * MakeReqeust( int code, void * params, size_t size ) {
++serial;
Response * r = new Response;
requests[serial] = r;
// make packet containing code, serial, and params/size data
// put request into r->requestData_ etc
r->transmitTimer_ = 0;
return r;
}

void ConnectionPoll() {
// for each element in requests
Time t = Now();
if( r->transmitTimer_ < t ) {
r->transmitTimer_ = t + RETRANSMIT_TIME;
sendPacket( r->requestData_, r->requestSize_ );
}
}

void ReceivePacket( ... ) {
// if packet is of type "response to request"
aSerial = extract_serial_from_packet();
aSize = extract_data_size_from_packet();
requests[aSerial]->data_ = malloc( aSize );
requests[aSerial]->size_ = aSize;
memcpy( requests[aSerial]->data_, extract_data_from_packet(), aSize );
requests[aSerial]->done_ = true;
requests.erase( requests.find( aSerial ) );
}


void YourFunction() {
if( !map ) {
if( !mapRequest ) {
mapRequest = MakeRequest( GET_MAP, 0, 0 );
}
if( mapRequest->done_ ) {
map = makeMapFromData( mapRequest->data_, mapRequest->size_ );
delete mapRequest;
mapRequest = 0;
}
}
}



Your main loop would call ConnectionPoll() to service outgoing requests, and ReceivePacket() to decode incoming packets.

You can make this much nicer in many ways. For example: refining the protocol header (request codes, serial, etc) to support something "real" (timing, re-transmits, etc); supporting cancellation of requests; configuring requests with a callback that's called when the request completes; using templates and pointer-to-member-function to cut out typing; using co-routines to provide the illusion of synchronous request execution; etc.

Share this post


Link to post
Share on other sites
I have no idea what hplus is talking about (sorry!), but I ran into a similar problem and solved it pretty simply just by making a thread whose job was to block and wait for incoming UDP packets, get the information, and stick it on a queue (or you might want to use a stack or something else). Then my actual application code just has to poll the queue every now and then to see if there's anything in it.

I later learned this was called a producer-consumer pattern. Fancy that.

Share this post


Link to post
Share on other sites
You may want to look at this article from Code Project:

http://www.codeproject.com/internet/jbsocketserver1.asp

It should give you a good start as to understanding what HPlus means...

Keep in mind that this was originally created for a Server application, but considering your program is "message-pushed" (what is the correct term I'm looking for??? correct me please), this would work for you as well.

I would then continue through the whole set of articles in this series (seven) to get a full grasp as to what you want to do.

ONE thing that HPlus mentioned that is not included in this series (which I dunno why not) is the use of TTL (time to live) on the packets... I would incorporate that somewhere.

- Eric

Share this post


Link to post
Share on other sites
Icefox:

The problem that OP had was not "how do I receive data in my program" but instead "how do I manage state when I have asynchronous operations, possibly lossy, outstanding, and may need to re-send data if it gets lost."

My suggestion was to encapsulate the behaviors of "having an asynchronous request outstanding" and "wanting to send a message to the server" into a single object that can be re-used everywhere you need this behavior. You can also factor these behaviors into two separate, smaller, behaviors, and compose them.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this