Table of contents
- Introduction
- Background
- Optional
- Purpose of the Template Server-Client Framework
- Framework Classes
- Server side
- Client side
- General
- Conclusion
- Reference
Introduction
After writing the EpServerEngine framework with C++, I thought it would be much easier to build prototype if there is template server-client framework with C#. I would recommend this for building quick prototypes or testing packet communications. Moreover, take this as a guideline for building your own template server-client framework.
Background
For detailed information related to "Socket", please refer to MSDN by Microsoft. Microsoft provides easy guides on how to create a client and a server using System.Net.Sockets. I highly recommend you to follow this tutorial using Microsoft Visual Studio.
Optional
- Utility Library used in EpServerEngine.cs
- The original idea used in EpServerEngine.cs
Purpose of the Template Server-Client Framework
As a software developer, many people will agree that writing the same code over and over again is a very lame job to do. It is even worse when if the code is just a bunch of nasty initializers. This template server-client framework will do all those jobs for you, and you only have to design and implement your packet structure and packet processing routine for the server and client, and leave the rest of the jobs to the framework (initialization/packet waiting/creating the worker threads/mutual exclusion for the network, etc.).
Framework Classes
Server side
IocpTcpServer class
This is a IOCP TCP server class which is responsible for the following:
- Starts the server
- Stops the server
- Sets up the port to use
- Broadcast the messages to all clients that are connected.
- Disconnects all the clients that are connected (for emergency use)
In addition to the above functionalities, it internally also creates a listening thread, which accepts the clients, and creates a socket for each clients connected, in order to communicate with the clients that are connected.
What do I have to do to create my own server?
That is very simple.
using EpServerEngine.cs;
public class MyServer
{
...
INetworkServer m_server = new IocpTcpServer();
...
};
- Create your Server class (ex.
MyServer
) - Create a member variable with new
IocpTcpServer
object. - Done!
How to start/stop the server
You can very easily start/stop your server as below:
using EpServerEngine.cs;
public class MyServer
{
...
INetworkServer m_server = new IocpTcpServeR();
...
void StartServer(string portString)
{
ServerOps ops = new ServerOps(callbackObj, portString);
m_server.StartServer(ops);
}
...
void StopServer()
{
if(m_server.IsServerStarted())
{
m_server.StopServer();
}
}
...
};
What is this ServerOps class and callbackObj?
IocpTcpServer
requires a ServerOps
object to start the server.
ServerOps
takes in two parameters, callback object(which inherits INetworkServerCallback
) for the server and port number as string for server listening.
IocpTcpServer
will call the callback methods on three situations with the info.
OnServerStarted
[ void OnServerStarted
(INetworkServer
server, StartStatus
status) ]
OnServerStarted
will be called when either server is started successfully or failed to start. - You can check the status with
StartStatus
whether server started successfully or not, and try to start the server again if it failed.
OnAccept
[ INetworkSocketCallback OnAccept
(INetworkServer
server, IPInfo
ipInfo) ]
OnAccept
has two parameters, server and ipInfo. server refers to the server instance that accepted the client for the rest of callback. ipInfo refers to the IP information of the client trying to connect. - You can check the ipInfo and reject the connection by returning
null
, or you can return the callback object for socket connected to this client to accept. (this will be explained in detail later.)
OnServerStopped
[ void OnServerStopped
(INetworkServer
server) ]
OnServerStopped
will be called when the given server is completely stopped.
So you can make your callback object for the server as your own server class, MyServer.cs.
using EpServerEngine.cs;
public class MyServer: INetworkServerCallback
{
...
INetworkServer m_server = new IocpTcpServer();
...
void StartServer(string portString)
{
ServerOps ops=new ServerOps(this, portString);
m_server.StartServer(ops);
}
...
public void OnServerStarted(INetworkServer server, StartStatus status)
{
if(status==StartStatus.SUCCESS || status == StartStatus.FAIL_ALREADY_STARTED)
{
}
else
{
}
}
public INetworkSocketCallback OnAccept(INetworkServer server, IPInfo ipInfo)
{
if (ipInfo.GetIPAddress().Equals("10.231.44.124"))
{
return null;
}
return new ServerSocketCallback();
}
public void OnServerStopped(INetworkServer server)
{
}
...
}
Or you can also create a separate class and pass an instance of that class to the ServerOps
as below:
using EpServerEngine.cs;
public class ServerCallbackObj: INetworkServerCallback
{
...
public void OnServerStarted(INetworkServer server, StartStatus status)
{
if(status==StartStatus.SUCCESS || status == StartStatus.FAIL_ALREADY_STARTED)
{
}
else
{
}
}
public INetworkSocketCallback OnAccept(INetworkServer server, IPInfo ipInfo)
{
if (ipInfo.GetIPAddress().Equals("10.231.44.124"))
{
return null;
}
return new ServerSocketCallback();
}
public void OnServerStopped(INetworkServer server)
{
}
...
};
public class MyServer{
...
void StartServer(string portString)
{
INetworkServerCallback callbackObj = new ServerCallbackObj();
ServerOps ops = new ServerOps(callbackObj, portString);
m_server.StartServer(ops);
}
...
}
For more detail, please refer to the EpServerEngine.cs' Wiki page, here.
IocpTcpSocket class
The socket is automatically created by IocpTcpServer
instance when client is accepted with socket callback object. This IocpTcpSocket
class is responsible for the following:
- Send/Receive messages to/from client
- Disconnect client
You can also check the connection status or IP information of client with the socket.
How do I create this socket callback class?
This is very simple job as creating the server above.
using EpServerEngine.cs;
class MyServerSocketCallback: INetworkSocketCallback
{
void OnNewConnection(INetworkSocket socket)
{
}
void OnReceived(INetworkSocket socket, Packet receivedPacket)
{
byte[] receivedPacketBytes=receivedPacket.GetPacket();
}
void OnSent(INetworkSocket socket, SendStatus status)
{
if(status==SendStatus.SUCCESS)
{
}
else
{
}
}
void OnDisconnect(INetworkSocket socket)
{
}
};
- Create your socket callback class (ex.
MyServerSocketCallback
) - Make your class to inherit
INetworkSocketCallback
- Implement four callback functions (OnNewConnection, OnReceived, OnSent, OnDisconnect)
- Done!
What are these four callbacks?
IocpTcpSocket
will call the callback methods on four situations with the info.
OnNewConnection
[ void OnNewConnection
(INetworkSocket
socket) ]
OnNewConnection
will be called when the client is accepted from the server.
OnReceived
[ void OnReceived(INetworkSocket
socket, Packet
receivedPacket) ]
OnReceived
has two parameters, socket and receivedPacket. socket refers to the socket instance that connected to the client for the rest of callback. receivedPacket referes to the packet class which contains data sent by client.
OnSent
[ void OnSent
(INetworkSocket
socket, SendStatus
status) ]
OnSent
will be called when either the data is sent to client successfully or not. - You can check the status with
SendStatus
on whether the data is successfully sent or not, and try to send the data again if it failed.
OnDisconnect
[ void OnDisconnect
(INetworkSocket
socket) ]
OnDisconnect
will be called when the client is disconnected from the server
How to use this server socket callback class?
return the instance of this socket callback class on "OnAccept"
call within your server callback class, which inherits INetworkServerCallback
.
So how do I send data to the client?
With the instance of INetworkSocket
, you can simply send the data to the client as below:
...
String helloString="hello";
byte[] packet = new byte[helloString.Length*sizeof(char)];
System.Buffer.BlockCopy(helloString.ToCharArray(), 0, packet, 0, packet.Length);
socket.Send(new Packet(packet, packet.Count(), false));
...
Or you can use the instance of INetworkServer
to broadcast the data to all the clients connected as below:
...
String helloString="hello";
byte[] packet = new byte[helloString.Length*sizeof(char)];
System.Buffer.BlockCopy(helloString.ToCharArray(), 0, packet, 0, packet.Length);
server.Broadcast(new Packet(packet, packet.Count(), false));
...
You can use Stream
or Serializer
and make your data into byte array (byte[]
) to incorporate into Packet
class, then send the packet with Send
/Broadcast
method.
For more detail, please refer to the EpServerEngine.cs's Wiki page, here.
Client side
IocpTcpClient class
This is IOCP TCP client class which is responsible for the following:
- Connect to a server
- Disconnect from a server
- Send/Receive the messages to/from a server.
What do I have to do to create my own client?
using EpServerEngine.cs;
public class MyClient
{
...
INetworkClient m_client = new IocpTcpClient();
...
};
- Create your Client class (e.g.,
MyClient
) - Create a member variable with new
IocpTcpClient
object. - Done!
How to connect/disconnect to/from the server
You can very easily connect/disconnect to/from your server as below:
using EpServerEngine.cs;
public class MyClient
{
...
INetworkClient m_client = new IocpTcpClient();
...
void ConnectToServer(String hostname, String port)
{
ClientOps ops= new ClientOps(callbackObj, hostname,port);
m_client.Connect(ops);
}
void DisconnectFromServer()
{
if(m_client.IsConnectionAlive())
{
m_client.Disconnect();
}
}
...
};
What is this ClientOps class and callbackObj?
IocpTcpClient
requires a ClientOps
object to connect to the server similart to ServerOps
.
ClientOps
takes in three parameters, callback object(which inherits INetworkClientCallback
) for the client, hostname as string, and port number as string for server connection.
IocpTcpClient
will call the callback methods on four situations with the info.
OnConnected
[ void OnConnected(INetworkClient
client, ConnectStatus
status) ]
- OnConnected will be called when either client is connected to the server successfully or failed to connect.
- You can check the status with
ConnectStatus
whether client connected to the server successfully or not, and try to connect to the server again if it failed.
OnReceived
[ void OnReceived
(INetworkClient
client, Packet
receivedPacket) ]
OnReceived
has two parameters, client and receivedPacket. client refers to the client instance that connected to the server for the rest of callback. receivedPacket refers to the packet class which contains data sent by client.
OnSent
[ void OnSent(INetworkClient
client, SendStatus
status) ]
OnSent
will be called when either the data is sent to server successfully or not. - You can check the status with
SendStatus
whether the data is successfully sent or not, and try to send the data again if it failed.
OnDisconnect
[ void OnDisconnect(INetworkClient
client) ]
OnDisconnect
will be called when the client is disconnected from the server.
So you can make your callback object for the client as your own client class, MyClient.cs
:
using EpServerEngine.cs;
public class MyClient: INetworkClientCallback
{
...
INetworkClient m_client = new IocpTcpClient();
...
void ConnectToServer(string hostname, string portString)
{
ClientOps ops=new ClientOps(this, hostname, portString);
m_server.Connect(ops);
}
...
public void OnConnected(INetworkClient client, ConnectStatus status)
{
if(status==ConnectStatus.SUCCESS || status == ConnectStatus.FAIL_ALREADY_CONNECTED)
{
}
else
{
}
}
void OnReceived(INetworkClient client, Packet receivedPacket)
{
byte[] receivedPacketBytes=receivedPacket.GetPacket();
}
void OnSent(INetworkClient client, SendStatus status)
{
if(status==SendStatus.SUCCESS)
{
}
else
{
}
}
void OnDisconnect(INetworkClient client)
{
}
...
}
Or you can also create separate class and pass an instance of that class to ClientOps
class as below:
using EpServerEngine.cs;
public class ClientCallbackObj: INetworkClientCallback
{
...
public void OnConnected(INetworkClient client, ConnectStatus status)
{
if(status==ConnectStatus.SUCCESS || status == ConnectStatus.FAIL_ALREADY_CONNECTED)
{
}
else
{
}
}
void OnReceived(INetworkClient client, Packet receivedPacket)
{
byte[] receivedPacketBytes=receivedPacket.GetPacket();
}
void OnSent(INetworkClient client, SendStatus status)
{
if(status==SendStatus.SUCCESS)
{
}
else
{
}
}
void OnDisconnect(INetworkClient client)
{
}
...
};
public class MyClient{
...
void ConnectToServer(string hostname, string portString)
{
INetworkClientCallback callbackObj = new ClientCallbackObj();
ClientOps ops = new ClientOps(callbackObj, hostname, portString);
m_client.Connect(ops);
}
...
}
So how do I send data to the server?
With the instance of INetworkClient
, you can simply send the data to the server as below:
...
String helloString="hello";
byte[] packet = new byte[helloString.Length*sizeof(char)];
System.Buffer.BlockCopy(helloString.ToCharArray(), 0, packet, 0, packet.Length);
client.Send(new Packet(packet, packet.Count(), false));
...
You can use Stream
or Serializer
and make your data into byte array (byte[]
) to incorporate into Packet
class, then send the packet with Send
method.
For more detail, please refer to the EpServerEngine.cs's Wiki page, here.
General
PacketSerializer class
This is a packet serializer class which is responsible for the following:
- Serialize the instance of a serializable class to byte array.
- Deserialize the byte array to given class.
[Serializable]
public class SomeData: ISerializable
{
public int packetCode;
public long data;
public SomeData()
{}
public SomeData(int iPacketcode, long iData)
{
packetCode= iPacketcode;
data= iData;
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("packetCode", packetCode, typeof(int));
info.AddValue("data", data,typeof(long));
}
public SomeData(SerializationInfo info, StreamingContext context)
{
packetCode = (int)info.GetValue("packetCode", typeof(int));
data = (long)info.GetValue("data",typeof(long));
}
};
...
PacketSerializer<SomeData> serializer =new PacketSerializer<SomeData>(new SomeData(1,10));
byte[] byetarr=serializer.GetPacketRaw();
...
void OnReceived(INetworkSocket socekt, Packet receivedPacket)
{
PacketSerializer<SomeData> serializer = new PacketSerializer<SomeData>(receivedPacket.GetPacket(), 0, receivedPacketByteSize());
SomeData someData = serializer.GetPacket();
Debug.Log(someData.packetCode+" " + someData.data);
}
For more detail, please refer to the EpServerEngine.cs's Wiki page, here.
Packet class
This is a packet class which is responsible for the following:
- Basic unit of data to communicate between the server and the client for EpServerEngine.cs.
How to use this packet class?
- PacketSerializer
- MemoryStream
- etc.
For example, if you define the packet structure as above, you can create Packet object as below to send it to the server/client.
PacketSerializer<SomeData> serializer = new PacketSerializer<SomeData>(new SomeData(14));
byte[] packet = serializer.GetPacketRaw();
Packet sendPacket= new Packet(packet
, packet.Count()
, false);
byte[] packet = new byte[12];
MemoryStream stream = new MemoryStream(packet);
stream.Write(BitConverter.GetBytes((int)PacketCode.SOME_CODE), 0, 4);
stream.Write(BitConverter.GetBytes((long)14),0,8);
Packet sendPacket = new Packet(packet, packet.Count(), false);
- Note that since copying memory for packet is critical on the performance in Server development, you can choose whether to allocate the memory for the
Packet
object, or just hold the reference to the data byte array within the Packet
object according to the situation.
For more detail, please refer to the EpServerEngine.cs's Wiki page, here.
Conclusion
As I said in Introduction, this might not be the best framework out there. However, since using this framework will allow you to build the server and client very quickly, I believe this can be very helpful for developing prototype server/client applications, or testing packet communications, since this enhances you to start from server/client communication design by letting you ignore all the nasty initialization and handling of IOCP Sockets and threads. Hope this helps for your server/client developments.
Reference
History
- 06.12.2015: - Minor update on Article
- 11.11.2014: - Title changed.
- 11.10.2014: - Typo fixed.
- 10.27.2014: - Source updated.
- 10.25.2014: - Submitted the article.