Which is best network for MMORPG?

Started by
3 comments, last by hplus0603 4 years ago

hi, we are developing a mmorpg with a small team. There are 2 structures we can create on the server side for managing clients and processing packages. I wonder what structure is better. I would be very happy if there is a good structure that you can recommend.

  • First Structure
IdleSessionQueue and ActiveSessionMap
TaskQueue and SendQueue

In this structure, idle sessions (IdleSessionQueue) are initially created and there is a thread (NetworkListenerThread) that listens to the network and accepts users. This thread takes a session from idle sessions when a new user arrives and adds the session to the active sessions table (ActiveSessionMap) using the values ​​of SessionId and Session. then creates a thread (ClientListenerThread) to listen to the session added to the table. a ClientListenerThread occurs for each user. When the client has disconnect, it is added back to IdleSessions again.

When the client sends a request (Packet), that request is added to TaskQueue. A certain number of workersThreads are created in advance. As long as there is a package in TaskQueue, WorkerThreads receives the package and processes it and adds the new package to SendQueue. SenderThreads receives and sends each packet in SendQueue<SendSession,Packet> to client.

  • Second Structure
IdleSessionQueue and ActiveSessionMap

In this structure, idle sessions (IdleSessionQueue) are initially created and there is a thread (NetworkListenerThread) that listens to the network and accepts users. This thread takes a session from idle sessions when a new user arrives and adds this session to the active sessions table (ActiveSessionMap) using the values of SessionId and Session. then creates a thread (ClientListenerThread) to listen to the session added to the table. a ClientListenerThread occurs for each user. When the client has disconnect, it is added to IdleSessions again. When the client sends a request (Packet), that request is processed by ClientListenerThread and the result is sent to one or more clients.

Which one is better solution or what is the best solution for this problem?

Advertisement

How about not having a thread per client? Seriously, having a thread per client is a bad idea.

@a light breeze Games like world of warcraft and knight online use thread per client.

Thread per client is a bad idea, for a number of reasons, although how bad does depend on how many clients you have, how beefy your servers can be, how much goes on on your servers, and so on. 50,000 players in a single server process will require quite some care in everything you do. A lambda function invocation per action done by a player might be a perfectly fine architecture in a space trading game.

My main question is: What are you trying to optimize? You're talking about “re-using Idle Sessions,” which sounds to me like an object pool allocator. While object pool allocators are swell for objects that you allocate by the tens of thousands (think things you do when you render, or for each individual object in a large fully-simulated world) they totally don't matter for things that happen every few seconds. Just delete it when you're done, allocate a new one when a user connects. Running the risk of re-using some old state/data in the old session isn't worth it.

There's also a big question about what your simulation looks like, and what your player count is per “server process." If your game is largely like a MUD, with some chat, some spells/weapons, and some line tracing to make sure nobody moves through a wall, then your solution will look very different from if your game looks (on the inside) like a modern first-person shooter with real-time interactive physics and 120 Hz simulation.

It's very hard to build optimal code for a goal that you haven't formulated!

My guess is you're going to want to:

  • reasonably use multi-core servers
  • not spend all your time locking the same big lock for protecting a shared world data structure
  • do interest management for each player (e g, not all players see all objects)
  • resolve rules for game actions and movement, but not simulate things like rolling barrels on the server

In this case, you will want to define clear subsystems in your game, where each subsystem is a single-directional pipe. Ideally, each subsystem has sub-areas, which can each be served without touching/sharing mutable state with other sub-areas within the same subsystem. This may be different “rooms” or “areas” in a world, or different “player connections" in a view management system.

There should be a pipeline of updates/commands/information going into each subsystem. Writing this queue should ideally be lock-free, or at least not use a lock that's frequently contended. (A reader/writer lock could be used.)

Each subsystem should process data as:

  • take all the data out of the queue in one swell foop – this is often implemented as a swap between two queue object instances
  • partition all the data into the sub-areas to be processed
  • run all the sub-areas in parallel, which reads the shared world state, but don't write it
  • sub-area work generates “update tasks” for updating the shared world state, and also “outgoing updates” to the next step in the pipeline
  • all the world state updates are applied when all the sub-processors are complete, and the next subsystem can empty its input queue (this system's output queue) in parallel

These subsystems can of course, in turn, run in parallel, in an overlapping pipeline. At some point, the simple act of “partition the entire queue onto the sub-areas” may end up dominating (see Amdahl's law) and at that point, the output of one stage would do the partitioning ahead of time, to one small input queue per subsystem area.

Note that the exact details of object management doesn't really matter much, compared to getting locking, queuing, and dependencies right. That's what makes a server able to scale to modern multi-core hardware. Once you have the structure right, you can start improving resource usage by using smarter I/O (uring, etc) and smarter primitives (lock-free queues instead of locked, say) and so on. But those concerns come much later, once you have an overall architecture that is robust and well behaved.

enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement