Abstracting client-server communication...
For a project I'm working on, I'm planning on separating client and server even when it is a local game by abstracting the communication between the client and the server (when running locally, the client/server will both be executing in the same process, but possibly other thread). This way, when the game is written, it should both run multi-player via client/remote server as well as single-player locally. The only downside might be that single-player performance wouldn't be optimal.
My initial thought is to have an abstract class "CommuncationChannel" that sends "CommunicationPacket" from the client to the server. "LocalCommunicationChannel" would simply pass the packet to the client or server, where the "RemoteCommunicationChannel" would serialize it and send the packet to the remote server, etc.
Does anyone who has done this before foresee any problems with this design? Can anyone suggest a good article about this? I have google'd around but haven't found anything on this.
I would like to have a stronger grasp of this before I begin coding it.
Any help would be appreciated.
I have recently been thinking about how I'd do something similar to this, so maybe my thoughts will be of some use to you. Note that I haven't implemeted much of this, they're just ideas, and windows specific for now...
Two main things spring to mind - first and probably simplest is to simply use the networking interface for local and remote connections. Obviously this might not work as well over UDP, i'm not sure, most of my work has been using TCP. My experience of developing a network API at work, and experiments at home, has made it pretty clear that transmitting data over sockets to and from the same network adapter is very fast. With an overlapped (but non-iocp) system I noted times of 1 to 15ms during a send or recv operation with 786432 bytes per transmission. Obviously for any sort of game packet size would rarely, if ever, be this big. If the system was being designed for local and remote communications, then any local socket connection would be an instant improvement over remote connection. How intense do you expect the network usage to be? If you do things too taxing for a local connection, then chances are the remote connection wouldn't stand a chance. I would use this method first of all to establish the system and then decide whether I thought it was worth providing a different communication interface for local connections.
That leads me on to my second thought, IO Completion Ports (IOCP). I have used IOCP purely in a networking environment, but from what I understand, it doesn't matter what sort of IO it is. Handles to sockets can be used by ReadFile functions, for example, and assuming they are overlapped operations, the IOCP queue can store the completion status of any overlapped io you post. To this end I thought of using sockets for remote communications, registered with the IOCP interface, and possibly named pipes for inter-process (maybe inter-thread too?) communications. This way I could use the same transmission functions (TransmitPacket, ReadFile, other general IO operations) on the handles, be they sockets or named pipes. That is assuming I have the concept of IO completion ports completely understood. The correct handle would be created depending on whether the system was running in "local" mode, or otherwise. I'm not sure about a mixture of the two.
In the systems I have written, I have tried to hide all this raw networking code behind a number of interface classes. For example, I have TCPConnection, which maintains an overlapped socket, as well as a list of pending IO operations and functions for posting overlapped sends. Then I have an IO class, which manages all of the IOCP operations, from initialising all the worker threads, waiting on WSAAccept for new clients and posting WSARecvs when they connect and waiting on the completion status, to notifying a connection manager of completed io on any given TCPConnection. The connection manager is an abstract class allowing me to derive classes to manage network events in any way I require (new client, read complete, drop client, etc) and the IO class is given a base class pointer to my connection manager to call when any io operation completes.
But back to the point of my ideas, I don't see why you need to subclass the channel as such - in either case you need only use one type of interface; the first one assumes a remote connection is the same as a local one, since a socket is used for both, the second assumes you use IOCP in which case you can associate it with many types of io handles (file, socket, named pipe, etc).
Sorry for the blocks of text, hope it's helped in some way. I'd like to hear about how you go about implementing your system.
Two main things spring to mind - first and probably simplest is to simply use the networking interface for local and remote connections. Obviously this might not work as well over UDP, i'm not sure, most of my work has been using TCP. My experience of developing a network API at work, and experiments at home, has made it pretty clear that transmitting data over sockets to and from the same network adapter is very fast. With an overlapped (but non-iocp) system I noted times of 1 to 15ms during a send or recv operation with 786432 bytes per transmission. Obviously for any sort of game packet size would rarely, if ever, be this big. If the system was being designed for local and remote communications, then any local socket connection would be an instant improvement over remote connection. How intense do you expect the network usage to be? If you do things too taxing for a local connection, then chances are the remote connection wouldn't stand a chance. I would use this method first of all to establish the system and then decide whether I thought it was worth providing a different communication interface for local connections.
That leads me on to my second thought, IO Completion Ports (IOCP). I have used IOCP purely in a networking environment, but from what I understand, it doesn't matter what sort of IO it is. Handles to sockets can be used by ReadFile functions, for example, and assuming they are overlapped operations, the IOCP queue can store the completion status of any overlapped io you post. To this end I thought of using sockets for remote communications, registered with the IOCP interface, and possibly named pipes for inter-process (maybe inter-thread too?) communications. This way I could use the same transmission functions (TransmitPacket, ReadFile, other general IO operations) on the handles, be they sockets or named pipes. That is assuming I have the concept of IO completion ports completely understood. The correct handle would be created depending on whether the system was running in "local" mode, or otherwise. I'm not sure about a mixture of the two.
In the systems I have written, I have tried to hide all this raw networking code behind a number of interface classes. For example, I have TCPConnection, which maintains an overlapped socket, as well as a list of pending IO operations and functions for posting overlapped sends. Then I have an IO class, which manages all of the IOCP operations, from initialising all the worker threads, waiting on WSAAccept for new clients and posting WSARecvs when they connect and waiting on the completion status, to notifying a connection manager of completed io on any given TCPConnection. The connection manager is an abstract class allowing me to derive classes to manage network events in any way I require (new client, read complete, drop client, etc) and the IO class is given a base class pointer to my connection manager to call when any io operation completes.
But back to the point of my ideas, I don't see why you need to subclass the channel as such - in either case you need only use one type of interface; the first one assumes a remote connection is the same as a local one, since a socket is used for both, the second assumes you use IOCP in which case you can associate it with many types of io handles (file, socket, named pipe, etc).
Sorry for the blocks of text, hope it's helped in some way. I'd like to hear about how you go about implementing your system.
CSP is based on that very idea. Processes communicating via channels. The channels can be via shared memory or even wan links.
http://en.wikipedia.org/wiki/Communicating_Sequential_Processes
There exists libraries for several languages based on CSP.
But in general I think what you are trying to do is a good approach, as you can implement one client/server logic regardless of where they are located respectively.
The small overhead by using communication over a local socket communication is hardly notable anyways.
I'd go further and say that there is little point in implementing different logic whether communicate with a local or remote server.
But first I'd consider if the game design it self should be client/server based or peer to peer.
http://en.wikipedia.org/wiki/Communicating_Sequential_Processes
There exists libraries for several languages based on CSP.
But in general I think what you are trying to do is a good approach, as you can implement one client/server logic regardless of where they are located respectively.
The small overhead by using communication over a local socket communication is hardly notable anyways.
I'd go further and say that there is little point in implementing different logic whether communicate with a local or remote server.
But first I'd consider if the game design it self should be client/server based or peer to peer.
Quote:Original post by Annoyed
...
I'd go further and say that there is little point in implementing different logic whether communicate with a local or remote server.
...
Can you elaborate on this? In my design the only difference is the implementation of communication channel used.
For example (psuedo c++):
ServerState* serverState;ClientState* clientState;/// Local gameCommunicationChannel* channel = new LocalCommunicationChannel(serverState);clientState->startGameClient(channel);/// Remote gameCommunicationChannel* channel = new RemoteCommunicationChannel("234.211.114.351", 56);clientState->startGameClient(channel);
With this design the client doesn't know or care where the server is because it has a channel to communicate to it. It will be analogous for the server.
Quote:Original post by Annoyed
...
But first I'd consider if the game design it self should be client/server based or peer to peer.
...
For my game it will always be client/server.
Quote:Original post by c_olinIn my design the only difference is the implementation of communication channel used.
Which was exactly my point. You only implement different channels but what comes in and out of them is the same.
But why implement different channels? You can easily do socket communication for client/server on same host. How messages come from A to B is irrelevant. So pick the channel that fits all cases and stick with that. You can always look into different channel types later :)
So you are saying....
The overhead of sending the data through the network card to itself would be negligible?
edit: I guess this question was answered multiple times already... just having a hard time believing it... :p
ServerState* serverState;ClientState* clientState;/// Local gameCommunicationChannel* channel = new CommunicationChannel("localhost", 56);clientState->startGameClient(channel);/// Remote gameCommunicationChannel* channel = new CommunicationChannel("234.211.114.351", 56);clientState->startGameClient(channel);
The overhead of sending the data through the network card to itself would be negligible?
edit: I guess this question was answered multiple times already... just having a hard time believing it... :p
In my opinion yes.
Think of it this way. You (as host) will have the same game experience as any remote client without the latency.
Again you can always change the channel type as long as you keep a messaging approach.
Think of it this way. You (as host) will have the same game experience as any remote client without the latency.
Again you can always change the channel type as long as you keep a messaging approach.
you can also use the loopback address (127.0.0.1) , effectively making your client network card talk to itself.
I know that the Quake series used to do exactly that. I also know that Torque (and thus the Tribes series) also did that, as does the C4 game engine. I'm sure there's others -- it's a popular way to structure game code because it allows you to cleanly separate client and server.
Would the fact that the client/server would be running in the same process and thread change anything? I don't have much experience with sockets however I do remember there being functions that wait for a packet; if they were in the same thread then I can see it deadlocking when one of them waits.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement