Developing an extendable chatserver

Started by
8 comments, last by Toolmaker 19 years, 11 months ago
I am trying to develop a chatserver with options to have it evolve into a MUD later. However, first previous attempt ended up in a terrible mess which was hard to maintain. Now that I am learning C# I want to give it another shot, and start out simple by just building a chat server with username/password login. Taking the OOP path I came up with a few classes: Server, ServerUI(Windows frontend), User and Protocol. I think the classes speak for themself, however, I have no idea how I would handle the users. I listen for incoming connections inside the server class, and once a new connection is found, I create a new User object and pass the connection to the socket inside the class. But how do I handle incoming data and the state of the user? Should I use an enum for it''s connection state? Connected, validated username, validated password, online. What other options are available? And how should I have the user class handle it''s data? Verifying username/password can be done inside the User class itself, but sending messages or handling commands aren''t. How do I pass these back to the server? Delegates? And then have the server foreach over all the users and call User.Send()? And then for commands, how do I handle those? In my previous MUD I use 1 class CCommand and stuffed all commands in there. It was a rather ugly and hard to maintain solution. What alternatives do I have here? Toolmaker My site /* -Earth is 98% full. Please delete anybody you can.*/

Advertisement
You might want to seperate all network functionality and user functionality.
So, a user does not contain a socket, but an ID.

If you want to send information to a certain user you can do two things:
- You have the user send the message
- Or you have the "server object" send the message

The advantage of the first method is that the server does not require to know the socket ID the user is connected to. This is part of the client itself.
On the other hand, you might want to think about whether sending or messaging is really a task of a user.
Even better... what IS a user? Is it a user interacting with the world? Or is a user just a connected remote client?
In the second definition a client can be a user interacting with the world, but it can also be a monitor, monitoring the status of the server.
That''s why I object to users having sockets. A client is linked to a user (not the other way around), and a client is connected to a socket on a specific interface.

So, if we put this in a layered model:

Layer 3: Users linked to by clients
Layer 2: Clients linked to one socket manager and one socket
Layer 1: Socket manager containing Sockets

If next week you decide to make you MUD, and you think the whole user thingy you built is useless, than you can still recycle the socket manager and clients (provided that a "User" was derived from a base-class, perhapes even derived from a "Client"... but I''ll leave that up to you). Because you uncoupled the users and sockets you can recycle your code.


As for the commands...
I assume you have a parser parsing user input.
I believe that a Command is a class of it''s own. A Command contains a trigger word, and parameters. Parameters are also a class of their own. They can be parsed into an integer or a textual representation by the Parameter class.
Furthermore a command has an action. Which can be a function pointer for example.
Commands are part of a CommandManager. If the parser sees that something is a command (ie: prefixed by a ''/'') then it calls the command manager to find and call a command with trigger "command" and x parameters.
When you init your game you can do the following:
Command* c = commandManager.addCommand("trigger");
c->addParameter(c, "name1", type);
c->addParameter(c, "name2", type);
c->setCommandAction(FUNCTIONPTR, parameter1, parameter2);

Command* c = commandManager.addCommand("trigger2");
c->addParameter(c, "name3", type);
c->addParameter(c, "name4", type);
c->setCommandAction(FUNCTIONPTR2, parameter1, parameter2);


etc etc...
I think you have quite a generic and expandable piece of work there then.


OK... that was a lot of drivel... I hope it made some sense. I often have the habit of just writing down my thoughts and completely lose the topic.

Good luck!
STOP THE PLANET!! I WANT TO GET OFF!!
Thanks for the constructive reply .

It''s a rather good plan to develop each command into his own class that derives from either an interface or an abstract Command class. I don''t really know how C# does that, but I will do a little research on that later.

I still have a few questions concerning the sockets. The layout you gave me is rather excellent. I have a SocketManager class, which holds the listen socket(s) and all the sockets + their ID. When the listener detects a new connection is creates a new socket and gives it an ID. After this I trigger a NewConnection() event in the Server class(that''s one of the pros about C#) and passes the ID.

But you stated about a client and a user. How do I envision this? Is a client an abstract interface? Or is it just another class with an instance of User?

Also, I assume the server class is responsible for handling the messages. Once the socket has received a new line of data it triggers Server.DataArrived() with the ID and the text arrived. Using a switch for the state of the user (Client.State) it will call the different functions which will update the state of the connection(So, if State == ClientState.Username the server will call Client.Username(ReceiveMessage); which checks if the name exists in the userbase, etc.). If the user is logged in, it should call Server.ProcessMessage() in order to call the commands or send the text to all users.

As a last question, how do I link back a client/user to a socket? For instance, when a user has typed 3 times the wrong password I want to close the connection. The only way I think of is to call Server.DisconnectUser(ID) from within the Client.Password() function.

I am not all that new to programming, but I never practiced much OOP design so I want to get this done right this time .

Toolmaker


My site
/* -Earth is 98% full. Please delete anybody you can.*/

quote:Original post by Toolmaker
Thanks for the constructive reply .

It's a rather good plan to develop each command into his own class that derives from either an interface or an abstract Command class. I don't really know how C# does that, but I will do a little research on that later.

I still have a few questions concerning the sockets. The layout you gave me is rather excellent. I have a SocketManager class, which holds the listen socket(s) and all the sockets + their ID. When the listener detects a new connection is creates a new socket and gives it an ID. After this I trigger a NewConnection() event in the Server class(that's one of the pros about C#) and passes the ID.

But you stated about a client and a user. How do I envision this? Is a client an abstract interface? Or is it just another class with an instance of User?


About the client and user...

A user is always a client...
But a client is not always a user...
Think of a remote monitor for your game. The connection the remote monitor is connected to is not a "user".

So yes, you could say that a client is the base class for the user and monitor.

You might want to look into the observer-observable design pattern for this. The observer "subscribes" to the observable, and when something happens (ie: data arrived) the observable "notifies" the observer(s).
The great advantage is that you only have a dependency towards the Observable. The Observable does not need to know details of the Observer.
I'm a C++ person, so bear with me.
class Observer{  virtual void notify(/*params, probably the socket ID and corresponding data*/);};class Observable{  vector<Observer*> observers;  void notifyAll(); // loop through all observers and call their notify()  void subscribe(Observer* observer); // add observer to the vector};class Server : public Observer{  Server();  // socketManager->subscribe(this);  void notify();  // handle user message};class SocketManager : public Observable{  // stuff happens, notify the observers}


quote:
Also, I assume the server class is responsible for handling the messages. Once the socket has received a new line of data it triggers Server.DataArrived() with the ID and the text arrived. Using a switch for the state of the user (Client.State) it will call the different functions which will update the state of the connection(So, if State == ClientState.Username the server will call Client.Username(ReceiveMessage); which checks if the name exists in the userbase, etc.). If the user is logged in, it should call Server.ProcessMessage() in order to call the commands or send the text to all users.

Yes, you could do that. But what is the function of a "user" in such a case. Is is a storage class for storing information about the user? (name, score, location, etc etc) Or will it also do some parsing? If you decide for the user class to process the login you might also want to think about the user class handling the commands. But then again, you don't want the user class to be calling network functions or game related functions. So what I'd do is put the login validation in the server object (or another object that deals with logins).

quote:
As a last question, how do I link back a client/user to a socket? For instance, when a user has typed 3 times the wrong password I want to close the connection. The only way I think of is to call Server.DisconnectUser(ID) from within the Client.Password() function.

Same thing, move the password validation out of the user object to the server object. Personally I think it's more of a task of the server rather that the user. But that's just my opinion.

quote:
I am not all that new to programming, but I never practiced much OOP design so I want to get this done right this time

I'm relatively unexperienced too, but what I learned throughout time is that when designing a system it is VERY important to get the responsibilities and functionality of the objects right. So often I've run into problems because I put something in the wrong class or didn't think about the structure well enough.

Also very important is the "what if"-question. As a seasoned programmer you must have heard of this before. That was the first thing that came when I read you were putting sockets inside the users. "what if you want to re-use your network code, YOu will have to recode your user too".

[edited by - Structural on May 18, 2004 8:03:11 AM]
STOP THE PLANET!! I WANT TO GET OFF!!
I am literally sitting on top of this thread . As for being a C++ person, I am a C++ person aswell. C# is the .NET descedant of C++ and is mainly C++ with a few exceptions and additions. It has delegates, events and got rid of the memory management. My reason is I switched to C# is that I probably need it in my next job, and thus having some experience is excellent.

Well, you just explained what I wanted to know. I decided to move authentication into an UserManager class. This class will be responsible for authentication and loading/saving the userdata.

Also, I think the client idea is a good plan. I will create a base class Client and derive user from that. However, how does that differ a user from a monitor? Ofcourse, the monitor retrieves data, etc. but when I moved out all the work from the user class except holding data, what''s the point in having a base class? A monitors tasks are different then a user class task. This is ofcourse different when the User class is going to perform tasks.

It seems like I am a complete n00b when it comes to design. Good thing there is teh intarweb!

quote:
Also, I think the client idea is a good plan. I will create a base class Client and derive user from that. However, how does that differ a user from a monitor? Ofcourse, the monitor retrieves data, etc. but when I moved out all the work from the user class except holding data, what''s the point in having a base class? A monitors tasks are different then a user class task. This is ofcourse different when the User class is going to perform tasks.


Exactly, so the Client object becomes redundant. Or not?
[thinking-out-loud-mode]
You need to link the socket ID to a user at some point. This can be done in the user class itself.
BUT...
A monitor class also has a socket ID. So, this is shared functionality and you might want to move that to a base class.

And... what if... you want to support multiple socket-managers. How do you determine to which manager a client belongs? That would be a pointer or an ID to a socketmanager.

And, can a socket be in a certain state? Can it be disabled? Or closed, but with the client still existing?

And what about dependencies. If you wouldn''t derive the user from a base class then the server object will need to know the exact implementation of the user. What if you want to add a new type of user with special attributes. You will need to include a new header and change some code in your server.
[/thinking-out-loud-mode]

I''m not sure... You could add the socketID to every user and monitor. It won''t be much work. But perhaps you could keep it more generic and better manageable.
STOP THE PLANET!! I WANT TO GET OFF!!
You won and convinced me.

It''s not about winning. I hope this structure is expandable enough and does what you want.

Ask yourself "what if I want to use the network code in a FPS game". That should be possible. You uncoupled the server from the network code with the observer-observable pattern. It should be as easy as network.subscribe(this) and network.sendTo(client,msg). Messages should come back to the observer as you subscribed.

"What if I want to use this code in a MUD"
Can you access the user object from your game? You probably need to to update scores and items and I don''t know what. Are you going to pass a pointer to the game telling it what user performed the action? Or can the game itself access the list in the server object and iterate to find the right user?

There''s still a lot to think about.
STOP THE PLANET!! I WANT TO GET OFF!!
If you want to use C# and .NET, maybe you can check the features of .NET remoting. This hides all the low level data marshalling from you. Instead you simply call methods on a object of the server after writing a server side and a client side configuration file.

quote:Original post by Structural
It''s not about winning. I hope this structure is expandable enough and does what you want.


Well, I am aware of that, it was more a compliment about your explanation. I think I hung out too much in TA/The Lounge.

And yes, there is alot to think about. I think the server should be able to access all the users directly. In case I want to write a MUD, the system needs to be able to access the inventory, keychain, spellbook, etc.

Toolmaker


My site
/* -Earth is 98% full. Please delete anybody you can.*/

This topic is closed to new replies.

Advertisement