I'm designing a game server (using asio) which is suppose to handle a large number of small messages (game updates).
The server's role is simply to pass messages from 1 player to all other.
Whenever a new message is received I copy it into a new buffer and pass a shared pointer to other connections (to their queue), so it gets destroyed when they all finish writing.
Since the life span of those small messages is extremely short I would like to avoid allocating buffers all the time.
But how should I deal with this?
Should each connection copy the message contents to their buffer?
Or perhaps each player should have a buffer for his messages and the write callbacks of other connections should notify him when they finished writing, so he can reuse the buffer space.
Since the player's read buffer will already contain the message I'd like to figure out a way to avoid allocating additional buffers completely.
I'd simply pass pointer+length for that sub array.
But in that case I'd have to stop receiving data from that client or rely on the empty part of the buffer, which can likely not be enough.
Although that shouldn't be a problem since writes are usually fast.
The only big problem with this would be if the client can not receive fast enough, either due to a bug or a malicious user.
In tcp this occurs because the client tells the server that he can not read any more. Idk if this can be ignored.
Should I simply disconnect a client if async_write takes to long?
How do i determine a good timeout?
Do you know any alternatives?
My idea of wrapping zero copy buffers behind some kind of standard interface would provide fast allocations for messages (possibly using a fixed block allocator) and minimal changes to the use of these buffers.
As for timeouts, you don't want to disconnect a player just because their stream sufferes some kind of hiccup. You could drop updates. That way you wouldn't need to queue messages for slow clients, and that might be better if they're running on slow kit. But I don't think you should disconnect them.
First time I've read your post it wasn't clear to me how you would implement something like that, but I think I understand now.
The server will have a list of buffers of various sizes and when the connection sees that the next message is an update it would use server's strand to reserve one of the buffers, read the message into it and send to others.
I can pass them a shared_ptr so they would notify me when the buffer can be reused.
The only downside of it being that my read stream can't receive all of the available data from the tcp stream, since I can't let it read the update, meaning there will be more io_service post requests, which isn't that big of a bottle neck.
My alternative idea was worse. It was to read the update into the read buffer of the connection and write that to other connections. Implying that this connection can not read anything until everyone's got the message.
This wouldn't be that big of a problem in general, since the operation is so quick, but one connection could end up blocking everyone if it couldn't or purposely wouldn't send fast enough.
This was the reason why I wanted to impose a time limit for send operations.
The idea behind a custom allocator is, for certain special cases, you can do better than the generic allocator, malloc. For example, if your blocks are all the same size, it's quite easy to maintain a list of free and used blocks and do allocations quickly with little fragmentation.
An alternative, is to use a pool as someone suggested above. However, if you use a custom allocator, it's easier to integrate your memory pool with STL, as all the containers allow you to specify an allocator.
A shared pointer is a good alternative to copying data between buffers in a clean way. And it's an easy solution to your copy behavior.
I'm not clear on why you can't fully read the input stream, but I haven't used Boost ASIO for a while. I kinda gave up on it some time ago. I think it's best to write whatever you've received immediately, and not wait for all the data to arrive. Waiting, in general, is to be avoided when there's stuff to do. That's kinda the point of asynchronous I/O.
I'm not clear on why you can't fully read the input stream
My streams look like this: lenght, messageData, lenght, messageData...
When I said I can't read the entire available input at once I meant that I have to read the length first and based on that allocate a buffer for it and then I can read the rest of the input.
This is nothing specific to asio.
About allocating buffers.
I can not know the required buffer size in advance, but in one room players will probably have updates of the same size.
So my plan is to pre-allocate several pools (of buffers) for different buffer sizes. And on request I would take the smallest available buffer.
The only problem I have here is that I will have to lock every time I access those pools. And I hate locking.
But better that than calling new uint8_t[] every time I get an update.