Advertisement

C#: Waiting for socket data and messages from other threads at the same time, and other questions.

Started by December 31, 2018 04:30 PM
4 comments, last by Ian Reed 5 years, 10 months ago

I am working in C#.
I am creating a multiplayer game/chat server.
The game server can run multiple games at a time, with no more than 10 participants in each game.
I intend to only use 1 "game thread" per CPU core, which means a single game thread will run the tick logic for multiple games, then go to sleep until the next tick.
In addition to these game threads, I would like to create a single "network thread" within this multiplayer server that can listen for new TCP connections, incoming TCP data, incoming UDP data, and also receive messages from the game threads which are running tick logic periodically.
The network thread will pass incoming data to the game thread that is dealing with that player endpoint.
It needs to receive messages from the game threads so they can tell it to close a socket for a disconnected player, possibly to send outgoing data through the network thread, and probably other communication I haven't thought of yet.
MSDN says the C# Socket class is thread safe, so perhaps the game threads can send out directly on the sockets, rather than passing their outgoing messages to the network thread to be sent out.
Should I have the game threads pass their outgoing messages to the network thread, or just send them out directly on the sockets?

Game communication is handled over TCP.
I need the incoming UDP data because the game server will also support voice chat between friends (a chat party), linked to a specific game, or linked to a specific team within a game.
I've already got mic capture, encoding to Opus, transmitting via UDP, receiving via UDP, decoding from Opus with its packet loss concealment and forward error correction, and voice sample playback working in a separate test application.
I could send UDP voice data directly between peers, but I don't want to deal with NAT punch through between peers yet, plus it seems like while sending directly between peers could give lower latency, it also has a trade off of sending more upstream data from each client, over the client's possibly podunk network.

The way I usually handle cross thread communication is to use a BlockingCollection which external threads add their messages to, and the receiving thread calls either Take or TryTake, depending on whether it wants to specify a timeout.
For instance, a game thread would call TryTake so it can receive messages from the network thread, but also specify a timeout so it wakes up for the next tick.
For communication where the sender actually wants to block waiting for a result, I group the message with a ManualResetEvent so the sending thread can wait on it until the receiving thread has set the result and triggered it.

I want the network thread to be able to call Socket.Select passing in all its sockets, so it can respond as soon as a new TCP client has connected, or TCP or UDP data is available for reading.
But I also want it to be able to handle messages coming from game threads immediately.
Is there a way to wait on both incoming data from sockets, and incoming messages from other threads?

I currently plan on calling Socket.Select passing in the bound TCP listening socket, the bound UDP socket, and all TCP sockets created by new connections.
For reference, its prototype is:
public static void Select(IList checkRead, IList checkWrite, IList checkError, int microSeconds);

I will pass them in the checkRead parameter, and pass an infinite timeout. (Unless I end up needing to wake up in order to check for messages from other threads.)
It seems like I should also pass them in the checkError parameter, so I can handle connection errors immediately.
Will Socket.Select return when a new UDP packet comes in if I have bound that UDP socket to an EndPoint?
If not, do you have a recommendation for how to wait on both TCP and UDP data at the same time?

Thanks.

 

For waiting on multiple things at once and waking up a thread when any of them are ready, you can use either WaitHandle.WaitAny or Task.WhenAny, depending on whether you want to use WaitHandles (ManualResetEvent, AutoResetEvent, etc) or Tasks.

For the UDP socket receive, you can use BeginReceiveFrom and use the returned IAsyncResult.WaitHandle.  You can also use TaskFactory.FromAsync if you want to convert any of the begin/end function pairs to a Task instead.

Advertisement

Thanks, but I am wanting to both wait on incoming socket data and messages from other threads.
WaitHandle.WaitAny and Task.WhenAny work just as well as my BlockingCollection.TryTake solution for receiving messages from other threads, but none of them help me wait on incoming socket data at the same time.
Is there a way to get a WaitHandle that will trigger when a socket has data to be read?

 

Sorry about that; I edited my previous message.  Typically you will call BeginReceiveFrom in order to start an async operation to read data which might not even be present yet, then wait for that operation to complete instead of calling Select.  The various 'Begin' methods return an IAsyncResult which contain a WaitHandle.

For the BlockingCollection, I don't see any WaitHandles in the API so you probably need to use something else.  Quick googling:  https://stackoverflow.com/questions/21225361/is-there-anything-like-asynchronous-blockingcollectiont

Or you could just write your own collection.  All you really need to do is provide a ManualResetEvent which is controlled by the collection's Add/Remove equivalents.

Ah, I see. Yes, that makes sense.
The WaitHandle on IAsyncResult does seem pretty helpful.
I'll play with this for a bit and see if I hit any other issues.
Thank you very much.

 

This topic is closed to new replies.

Advertisement