I need some direction for client server

Started by
3 comments, last by Drew_Benton 12 years, 11 months ago
Here is my situation. I am making a tiny MMO... (so maybe that would just make it a MO) game, now before anyone totally dismisses this post due to the previous statement, let me say, I have no illusions about taking on a project like this.

What I am having trouble with is my server client design. I will have a server running the game, and players will connect in through a client. Ther server will of course need to handle multiple connections, and the client of course only one.

Im using TCP for the connection, and I am wanting to do this in c# (I know its probably best for the server to be in something like c++ , but I really dont care since this is a hobby project only)

I have been online all over the web looking up tutorials on networking with c# , client server models, however most of those only talk about chat programs, and they dont really give much explaination...
Also it seems every tutorial is different.

So... Is there a standard way of doing this? Or does it not really matter so much, as long as it works?
I have done somthing like this in c++ back in the day, so im more used to a continuous running game loop, where the server processes any input from clients. However, I see so many tutorials having an event fire when data comes into the server.
Is this the best way to go? I would think the old continuous loop would be better.

Anyway... if anyone has a good network model they could share with me, or if they know of a tutorial that is doing all the right stuff, please let me know, Thanks.
Advertisement

So... Is there a standard way of doing this? Or does it not really matter so much, as long as it works?


If it's C#, you probably want to use System.Net.Socket, and either BeginReceive(), or ReceiveAsync(), to receive data. ReceiveAsync() generates less garbage when used correctly, but BeginReceive() is easier to use IMO. Both these mechanisms will use I/O completion ports and a worker thread pool on Windows, and thus be very efficient. For accepting sockets, you want BeginAccept()/AcceptAsync(), and writing similarly. I don't know how efficient the implementation is on mono (Linux/MacOS), but the API is such that the implementation *could* be very efficient there too.

Btw: I find that efficient I/O is actually easier in C#/.NET than almost any other environment, because of this design. C++ comes close with boost::asio. Java, Python, Erlang, or anything else can't touch that.
enum Bool { True, False, FileNotFound };
I'm loving C# and .Net 4.0 more and more nowadays for the reasons hplus mentioned. There are quite a lot of different approaches you can take using the .Net libraries. You can look into those methods he already mentioned as they are your basic efficient methods most people end up using.

For your little project, and for the sake of getting something up and running to get your head wrapped around, I'd like to direct you to two cool little APIs.

TcpClient / TCPListener - These are wrapper APIs that sacrifice a lot of the customization and flexibility you have with Socket, but as you can see in the example code, there's not much code to deal with (compare that to something like this for example.). This is most useful if you want to only service a few connections or so, and you'd use the thread per client model. While that method does not scale once you need to support more players, it really sounds like you just need to get something started to get the ball rolling. In a language like C#, adhere to the Managed Thread Pool rules and make your own threads to handle each TCPClient that connects rather than post to the worker thread queue.

TCPChannel - This API is basically a RPC wrapper. Rather than worry about the specific network protocol, you simply send and receive objects in a layer above Microsoft's TCP stream protocol. The MSDN reference does not really show how easy it is to use it, so take a look at this example to get started. NOTE: There are flaws and short-comings in that example, but it should give you the gist of how to get started so you can go back to MSDN and implement it better. Using this approach would be highly experimental and only practical really if your game client was also written in C# or used a C# DLL to handle the networking. This article goes over the basics of it, but that article is for older versions, so you would have to consult MSDN to implement it the correct way for 4.0. This method might not be practical for many uses, but it's only something for you to consider to get something up and running faster.

Both of these classes won't really solve any of your real problems though. They just allow for easier and faster prototyping of networked systems. You will eventually have to learn how to do it the most traditional way, since most people usually hit a feature or performance wall with the aforementioned APIs. If you can make them work for what you need, then great! But if not, they are still good to understand because they are great for making tools to test your networked code with. Ultimately with .Net, you want to be able to say: I [ can / cannot ] use [ class A / class B / .. ] for [ Task a / Task b / ... ] because [ Reason 1 / Reason 2 / ... ]. So it only helps improve your knowledge of what tools you have available if you give them a try. You never know when you might be able to make use of it!

The biggest challenge you will run into will be multi-thread programming in C#. Since there are worker thread pools and a lot more threads in your traditional C/C++ program, you have to be aware of which data might be used from different threads concurrently and which data is dependent on other data that might need locking. For example, if you make a global List of players and lock the list for any Add/Remove/Enumerate operations, that protects the list, but not the objects inside. As a result, if you were to lock each Player before using it, which you typically would, you can introduce a deadlock or race condition with other logic that makes use of the Player object at the same time, which all too often can happen since there are so many threads in the system.

If you are rusty on threaded programming or would not consider yourself well versed in all the aspects of thread programming in C#, it would be wise to simply make a simple Select based server and not using the thread pool for anything, and just handle all your logic in the main thread. This would be your "continuous old loop" as you so described. For a project like yours, the tradeoffs are well justified since you will be able to make things work first then worry about converting to a more efficient, scalable solution.

Truth be told, the actual method you use won't matter for a while. You will still have the same "networked game programming" problems to solve with all of them. Just because your network code can scale to tens of thousands of connections doesn't mean your game design and implementation can. ;)

That is why I'd suggest just something up and running to get familiar with the problems you will face so you can work on those rather than worry about how efficient or scalable your core is. You will be rewriting code no matter what. This advice is only being given as-is because you are using C#. The time spent and experiences learned doing things the 'less than optimal way' are significantly more beneficial here than if you were to do the same in something like C++. I.e. a DIY thread per client is never really recommended generally, but the implementation logic required is the exact same as using BeingXX functions. The only difference is the system manages the threads and you do not. If you understand how to make a thread per client work yourself, i.e. the multi-threading part and lock, you should be able to easily adopt it to the async system. All of that is assuming you have shared state in the system that requires multi-threaded management. If you don't, then the statement won't hold true.

For everything else relating to server design of the topic you mentioned, literally just read the entire forum. Take a few days or weeks if you have the patience and read through all the threads with keywords relating to your game genre. Specifically, read through all of hplus's posts; there's in invaluable trove of knowledge just there for the taking! A lot of content seems not to be indexed from the old site, so check those forums too. The more time you are willing to put upfront for research now, the better off you will be in the future in regards designing your system in a way that has a very high likelihood of being successful. Good luck!
Thanks so much for the detailed information.
@hplus, yes, I guess I forgot to mention that I am working on a windows platform. I have been reading through some of your posts and gleaning insight from them.
@Drew I think your correct in your 'get something up and running' suggestion. Your post has given me lots of direction to look into things.

My threading experience is not so good, the main problem I have with it is the whole locking data part.
I try to use something in the data, a flag or something stating if the data is 'busy', although I can see where sometimes might fail... I always feel there might be the chance that between the 'check if data is busy' and 'set data to busy' some other thread might grab it first.

I have heard bad things about the each client on a thread idea. Something about not a good idea to create threads at runtime, and a limit on the ammt of threads. So Im not really sure I want to follow that line of logic... or have I been mislead?

Thanks again, I will be looking into, and trying to implement the ideas you both have given.

I have heard bad things about the each client on a thread idea. Something about not a good idea to create threads at runtime, and a limit on the ammt of threads. So Im not really sure I want to follow that line of logic... or have I been mislead?


I kind of mentioned that in my post, but I'll restate it. Normally, in most languages (where 'threads' are operating system threads), it's not a good idea if you are aiming to have a lot of concurrent connections and there is a lot of shared state. The cost of always creating more threads and then the system resources required to maintain so many threads (context switches, lock contention) eventually overwhelms the system. Actually getting to that point is just dependent on how much processing is going on in each thread and how many synchronization objects there are. A high end machine can certainly handle a lot before getting to that point, but it is not really a viable solution in the long run.

The thing to understand with C# is (more like .Net), without manually setting the ThreadPool size, you can actually degrade your async system into a "thread per client" design if you do not take care. For example, let's say you do all your packet processing in the callback (not recommended). The callback is invoked from the ThreadPool, so you tie up a worker thread. As you get more concurrent requests from more connections, more threads are needed in the thread pool, so the system creates more threads for you based on its logic for growing the ThreadPool. This is why it is not recommended to use the ThreadPool for such long blocking tasks; it is designed to accommodate more frequent short lived tasks (that run in the background with a set priority). See the previously linked MSDN article for example.

Ideally, you want to minimize the time spent in the callback so this does not happen. Unfortunately, actually doing so can result in a more complicated design, depending on what trade-offs you are willing to accept. As a result, and this is only because the way C# works, implementing a thread per client (say like for the TCPClient network IO), turns out to be the worst case you have to already account for when using the async methods with shared state.

The only difference is (and I'm so over simplifying this), with the async functions, the actual network IO takes place in a completion thread (and operate more efficiently), whereas your own thread per client design takes place in a user thread (which would be akin to a worker thread). Because of this, if you start out with something simple and understand how it works and where the flaws are when it comes to scaling, you can immediately apply that knowledge to the next best system, some form of async, and not fall victim to the problems you otherwise would have. I've seen a lot of people have such problems because they've yet to learn the implications of what you do in the callback function has on the rest of the system.

So my point is at least, and others might disagree, rather than starting with the most complex, efficient method, which has a lot of problems to solve that you will not yet know of, start with something simple and well known where most problems are obvious (I don't like the word obvious, but computer science common sense, such as basics of threading and synchronization). That way, you can directly address the known problems so the unknown problems can be encountered and handled appropriately more easily. Once you take care of that and move to a more advanced system, you already know a lot more to be equipped to handle the problems that come with that system as well, so anything you might not have encountered before, is only part of a smaller subset of problems than you would of previously had if you had not.

The concept of "a thread per client" is bad is only because of the nature of operating system threads and synchronization overhead. If threads were free and synchronization was over-headless, then there would be nothing wrong with the model, theoretically. So as you go to languages that handle these aspects differently, you have to keep that in mind.

As I said before, my advice comes just from the way C# works. I'd never suggest this for something like C/C++, because in practice, you would just be wasting your time. With C#, you are not. When using any programming language, you have to understand traditional programming problems in context of the language. In C#, there's a lot of riding on the way the language works in regards to internals, such as memory management, GC, threading, and so on than in a language like C/C++. In C/C++, it's more operating system / hardware constraint driven. In C#, you still have to worry about such things, but the perspective is different. You have to worry how .Net handles them and code accordingly to that.

When you use a productivity language like C# and the .Net framework, I feel you should make use of it's productivity features for fast prototyping if you are just getting started (and are not developing software in the traditional sense). Worry about performance and optimizations afterwards, once you get a good idea of what you are going to do and how you can utilize different classes in the .Net framework to accomplish your task. All of this is said in my opinion. :)

This topic is closed to new replies.

Advertisement