Eric Feiveson
Jack Chi
Comp 410: SkyNet
May 7th, 2006
Network Team
The Network Common project is designed to abstract the implementation details
of sending information over the network between the client and the server.
All communication between the client and server goes through the network interface.
Code outside the network implementation does not use sockets, .NET remoting, or any othermechanism to bypass the network interface to transfer data. The network interfaceis used only for client-server communication. It is not used for sending databetween different server computers if the server is clustered.
The network interface uses a send/receive/reply model and supports three basic
data transfer operations, which can be used by either the client or the server:
- send data (no reply expected)
- register a listener to be called when the other computer sends data.
The arguments to the listener specifies the data sent.
- send data and wait for a reply
All datagrams and requests contain the following parameters:
1) WellKnownServiceId - this identifies what type of request is sent.
You can register a different listener for each service id. This avoids
having to implement a listener with ugly cascading if-statements like:
if(request is logon)
...
else if(request is do behavior)
...
else if ...
2) request id - this is a guid which uniquely identifies the request sent.
For datagrams, the request id is Guid.Empty. For normal requests,
the request id is a randomly-generated guid. When the receiver replies
to a request, the reply code passes in the request id, which gets transmitted
back to the sender. The network implementation inside the sender uses
to the request id to know which request it has received a reply for.
3) session id - This uniquely identifies the session information is being
transmitted across. The network architecture supports multiple
clients using the same network, so the session id is used to uniquely
identify clients among a network. When you register a receive listener,
you can specify a session. If you specify a session, your listener will
only get called by requests sent along that session. If the session
has an id of Guid.Empty, the listener will be called for all sessions.
For example, the logon listener must be able to handle any session.
4) data - this is a .NET object that represents the actual data to convey.
Internally, everything is transmitted as a byte array, so the object
graph must be serializable.
There are two network implementations - the real network and dummy network.
The real network uses sockets. The dummy network uses threading - the client(s)
and server are all in the same process. When the dummy network is used, the server
is initialized via DummyNetworkAdapter from the client controller.
When the real network is used, there are a few limitations:
1) When you send a message, you must wait for a reply before sending another message. This is because if you send messages faster than the receiver can consume them, messages tend to get mysteriously dropped and ignored. Waiting for a reply guarentees that this cannot happen. Nevertheless, even with the real network, the asynchronous features are still useful. Even though you cannot safely send another message until a reply comes from an existing message, you can still use the asynchronous features to keep the program (in particular, the user interface) responsive while waiting for the network to send a reply. For simplicity, the current implementation does not do this – it uses synchronous network transactions for everything and when the client sends a message to the server, the UI is completely shut down until the server sends back a reply. This is one thing that will need to be fixed.
Otherwise, some messages might get dropped.
2) Each request sent is limited to a size defined in NetworkConfig. If the request
exceeds the limit, it will be truncated, which will probably cause
random SerializationExceptions. The current request limit is 2 MB. The requestsize limit is not enforced by the dummy network.
3) The underlying real network implementation allows communication to travel over any TCP port that can be agreed on between the client and the server. Our current implementation allows the user to specify the port number in the client and server dialog boxes. These ports must agree for the client and server to be able to communicate. The default port number that appears when the dialog box is first launched is defined in SkyNet.Network.NetworkConfig, which appears in the Network Common project, is current set to 1234. The port specified in the client/server dialog boxes is the ONLY port used by SkyNet. Also, the client only establishes connections to the server – the server never connects back to the client. So scenarios where incoming connections to the client along SkyNet port are blocked by a router or firewall will not prevent the current implementation from functioning correctly. The SkyNet server, however, must have an IP address visible to all clients and the firewall must allow the SkyNet server to accept remote connections through the SkyNet port.
Some implementation details:
-Synchronous request-and-wait calls are implemented in Network by adding the request id to an internal list and waiting for an event handle. The network class then calls the asynchronous version of Request() and registers a callback handler that signals the event handle. There is one shared event handle for all synchronous requests. To prevent RequestAndWait() from returning prematurely, it waits on the event handle in a loop, not stopping until the particular request being waited on is no longer pending.
-When Network receives a datagram packet from the low-level INetworkEndPoint object, the network parses the data itself to determine whether it is a request or a reply. The datagram object sent is assumed to a serializable representation of either RequestData or ReplyData (internal classes within Network Common) and the network uses the type of the deserialized object to decide what action to take.
-The socket-based RealNetworkEndPoint implementation of INetworkEndPoint uses one socket that is in listen mode – this is what clients connect to – plus one additional socket for each connected client that represents the connection with that client. Since each RealNetworkEndPoint object only has one socket that it sends data on, when a connection comes in, RealNetworkEndPoint creates a new RealNetworkEndPoint object, whose socket is the connection specific to the client that connected. It is this connection-specific socket that gets passed to the Network implementation so the Network implementation knows where to send the reply. The connectionless socket is used only to listen for new connections.