ASClient.rar
CSharpTcpServer.rar
I will be writing some articles on Network Programming using various languages to create both the server and the clients in different parts. In this part I will be creating a basic C# TCP Server to accept incoming connections and simply read the data which is sent to it. I will be sending the information to the server using AS3.
I hope to do these different parts using models programmed in C#, Java and C++, although I ma not sure when the C++ versions will be complete as I am still trawling through self learning with it. Either way, this is interesting stuff.
Resources
Before I begin I will mention some resources you can go get which will enable you to follow and run the examples yourself.
- C#
- JAVA
- Action Script 3.0
- C++
Assumptions
You have a medium to advanced understanding of C#, as this example will be demonstrating communication over TCP using Flash as the client and C# as the server. I will be using Asynchronous programming in the C# application. You should also have an understanding of a similar level in Action Script 3.0 .
Goals
- To establish a connection over TCP using Sockets from Flash to C#.
- To pass textual data from Flash to C# over TCP using Sockets.
- To pass a stream of data of varying size from the Flash Client to the Simple C# TCP Server
- To accept textual data sent from C# to Flash over TCP using Sockets.
- To serialize and post objects over TCP using XML as the vehicle and TCP as the transport going from Flash to C# and visa-versa.
The Socket class which I have using inside the action script is simply a derivative of the Socket class itself. This is for future posts where it will be expanded to include further functionality.
BasicSocket.as
package
{
/**
* ...
* @author Andrew Rea
*/
import flash.events.*;
import flash.net.Socket;
import flash.utils.*;
public class BasicSocket extends Socket {
public function BasicSocket(host:String = null, port:uint = 8200){
super(host, port);
}
}
}
Establishing a Connection over TCP using Sockets from Flash to C# and Java
Click here for the MSDN Asynchronous Programming Overview
The first thing that must be done is to establish that a connection can be made and accepted going from Flash to C# and Java. Once this is confirmed we can move onto, using that connection to send and ultimately receive data and thus create a client server process.
Create the C# Basic TCP Server
- Create a new project
- Select windows and then a console application
- Give it a descriptive name like CSharpTcpServer…
- Add a new class to your solution and call it Server.
On the C# side we now need to create a Socket which will bind to a specific port and wait for incoming connections. We will then trace out into the console window when we receive the connection. The connection will be initialized from the Flash client.
A Simple Server implementation in C# using Sockets.
The Simple Server will have two public methods of Start() and Stop(). It will then contain a protected asynchronous method of OnClientConnected(IAsyncResult result) which will be called once a connection is accepted on the Server Socket. Inside the OnClientConnected(IAsyncResult result) the final line of code will instruct the server to start listening again so that we can accept multiple clients. If we were not using Asynchronous methods in C'# we would have to implement a while loop, and upoon receiving a connection request, instantiate a new Thread for the client. Asynchronous methods deal with all the work under the hood and allow for an extremely efficient work flow.
I am using the screen toaster for the example previews in this site, below you can see an example of the server using two telnet clients. You can set up multiple clients to test this out. If you have not used telnet before visit http://technet.microsoft.com/en-us/library/c.aspx for sample commands you can use. For this example we are simply opening a connection to an IP and port.
Server.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace CSharpTcpServer
{
/// <summary>
/// A simple C# TCP Server
/// </summary>
public class Server
{
private string host;
private int port;
private Socket server;
/// <summary>
/// Constructor
/// </summary>
/// <param name="port">The port for the server to listen for connections on</param>
public Server(int port)
{
//Set the port
this.port = port;
//Instantiate the socket for the server and we want to use Streams and TCP
this.server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
/// <summary>
/// Method to start the server and wait for the intial client
/// </summary>
public void Start()
{
//Makesure we can accept a connectoin from any IP but only on port 888
this.server.Bind(new IPEndPoint(IPAddress.Any, this.port));
//Allow a maximum backlog of 10 connections
this.server.Listen(10);
//Instruct the server to accept the next client connection
this.server.BeginAccept(OnClientConneced, this.server);
Console.WriteLine("Server started and waiting for clients...");
}
/// <summary>
/// This will shut down the server and stop any tranmission from and to the server
/// </summary>
public void Stop()
{
//Makesure the server is connected before attempting to shut it down
if (this.server.Connected)
{
Console.WriteLine("Server Shutting Down...");
this.server.Shutdown(SocketShutdown.Both);
}
}
/// <summary>
/// This will be called when a client connected to the server
/// </summary>
/// <param name="result">The asyncronous result containing the server socket</param>
protected void OnClientConneced(IAsyncResult result)
{
//Makesure the server is connected before accepting a new client
if (!this.server.Connected)
{
//Cast the state of the Async Result as the Server Socket
Socket serverSocket = (Socket)result.AsyncState;
//Ending the Accept method will result in the Socket of the client
Socket clientSocket = serverSocket.EndAccept(result);
Console.WriteLine("Client Connected");
//Now we have the client inform the server to continue listening for further clients
this.server.BeginAccept(OnClientConneced, this.server);
}
}
}
}
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CSharpTcpServer
{
class Program
{
private static Server server;
static void Main(string[] args)
{
//Instantiate the server and use port 8888
server = new Server(8888);
//Start the server
server.Start();
//Subscribe to the console cancel key press event so we can shut down the server
Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);
//Block the application until it ends.
Console.ReadLine();
}
static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
server.Stop();
}
}
}
So from the above code, if all has gone well with your application and as the quick video shows, you can debug your server and then test it out using the Telnet client. The next part is considerably shorter in code, is in Action Script 3.0 and also acts as the client instead of the telnet client. The following when run will simply attempt to connect to the Simple C# TCP Server, the result of which will be seen inside the console window showing that it has successfully accepted the client connection from the flash client.
Main.as
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.utils.ByteArray;
/**
* ...
* @author Andrew Rea
*/
public class Main extends Sprite
{
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
var a:BasicSocket = new BasicSocket();
a.connect("127.0.0.1", 8888);
}
}
}
Pass textual data from Flash to C# over TCP using Sockets.
The next step is being able to send data from the Flash client to the C# TCP Server. All we want to send at this stage is simple text which is by default ASCII encoded. We need to create a function which will handle the event of data being received from the client, for this we will create OnClientReceiveData(IAsyncResult). We will initialise the function call before we instruct the server to go and wait for another client connection. This time for the state object we need two things, which are the client socket and also the data container which we can read from the client has sent. I have simply created an object array for this, but for the next instances we will create a dedicated object which we can an type/size of data. Below is the revised Simple C# TCP Server and also the amended Action Script which sends the data.
Server.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace CSharpTcpServer
{
/// <summary>
/// A simple C# TCP Server
/// </summary>
public class Server
{
private string host;
private int port;
private Socket server;
/// <summary>
/// Constructor
/// </summary>
/// <param name="port">The port for the server to listen for connections on</param>
public Server(int port)
{
//Set the port
this.port = port;
//Instantiate the socket for the server and we want to use Streams and TCP
this.server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
/// <summary>
/// Method to start the server and wait for the intial client
/// </summary>
public void Start()
{
//Makesure we can accept a connectoin from any IP but only on port 888
this.server.Bind(new IPEndPoint(IPAddress.Any, this.port));
//Allow a maximum backlog of 10 connections
this.server.Listen(10);
//Instruct the server to accept the next client connection
this.server.BeginAccept(OnClientConneced, this.server);
Console.WriteLine("Server started and waiting for clients...");
}
/// <summary>
/// This will shut down the server and stop any tranmission from and to the server
/// </summary>
public void Stop()
{
//Makesure the server is connected before attempting to shut it down
if (this.server.Connected)
{
Console.WriteLine("Server Shutting Down...");
this.server.Shutdown(SocketShutdown.Both);
}
}
/// <summary>
/// This will be called when a client connected to the server
/// </summary>
/// <param name="result">The asyncronous result containing the server socket</param>
protected void OnClientConneced(IAsyncResult result)
{
//Makesure the server is connected before accepting a new client
if (!this.server.Connected)
{
//Cast the state of the Async Result as the Server Socket
Socket serverSocket = (Socket)result.AsyncState;
//Ending the Accept method will result in the Socket of the client
Socket clientSocket = serverSocket.EndAccept(result);
Console.WriteLine("Client Connected");
byte[] data = new byte[1024];
object[] dataToSend = new object[] { data, clientSocket };
clientSocket.BeginReceive(data, 0, 1024, SocketFlags.None, OnClientRecieveData, dataToSend);
//Now we have the client inform the server to continue listening for further clients
this.server.BeginAccept(OnClientConneced, this.server);
}
}
/// <summary>
/// When data is received from the client, it is output to the console window.
/// Currently this function will only read from the input stream once.
/// </summary>
/// <param name="result">The asyncronous result containing the server socket and the incoming data</param>
protected void OnClientRecieveData(IAsyncResult result)
{
object[] data = (object[])result.AsyncState;
byte[] dataSent = (byte[])data[0];
Socket clientSocket = (Socket)data[1];
int dataLength = clientSocket.EndReceive(result);
Console.WriteLine(Encoding.ASCII.GetString(dataSent, 0, dataLength));
}
}
}
Main.as
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.utils.ByteArray;
import slideshow.event.PageChangeEvent;
/**
* ...
* @author DefaultUser (Tools -> Custom Arguments...)
*/
public class Main extends Sprite
{
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
var a:BasicSocket = new BasicSocket();
a.connect("127.0.0.1", 8888);
var messageToSend:String = "Hello World";
var dataToSend:ByteArray = new ByteArray();
dataToSend.writeUTFBytes(messageToSend);
a.writeUTFBytes(messageToSend);
}
}
}
Now we have established a connection and also sent data from Flash to the C# Server we now want to enable a constant flow of data from the Flash Client to the Simple C# TCP Server. At present, the server would only accept a maximum of 1024 bytes of data due to the buffer size and the the function not recurring. So now, we will change the function to check if it needs to again listen for a next chunk to the message and also introduce a class which will make it easy for us to collect the data sent from the client / s.
Pass a stream of data of varying size from the Flash Client to the Simple C# TCP Server
The flow which needs to happen is that the client sends what ever data it needs to sends. It is the server’s job to accept this data, in the necessary sized chunks decided by the size of the buffer. At present and for this example we will be using a 1024 byte buffer, which means if we receive data of a greater size, the server will read 1024 bytes, and then request more of the clients data. Until the length of the data received is less than the buffer length, it will continue to loop and ask the client for more data. It should be noted here that upon accepting the data from the client, valid handling in case the client disconnects should be present.
Before I paste the amended code of the server I will first introduce the StateObject, which I first saw on the MSDN site. The role of this object is to persist data over multiple receive calls so that when you have collected all the data being sent you can then either cast/ read or perform the actions you need, because assuming the client or the server has not disconnected it is at this point where you have the entire data for the request.
StateObject.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net.Sockets;
namespace CSharpTcpServer
{
/// <summary>
/// A state object for persisting data over multiple data communications
/// </summary>
public class StateObject : IDisposable
{
//The max size of the chunks it can read at any one time
public const int BUFFER_SIZE = 1024;
/// <summary>
/// Constructor
/// </summary>
/// <param name="clientSocket">The client which is connected</param>
/// <param name="serverSocket">The server socket itself which is serving the requests of the clients</param>
public StateObject(Socket clientSocket, Socket serverSocket)
{
this.clientSocket = clientSocket;
this.serverSocket = serverSocket;
//Instantiate the stream which will collate the data sent
this.stream = new MemoryStream();
//Instantiate the data array
this.data = new byte[BUFFER_SIZE];
}
private MemoryStream stream;
public MemoryStream Stream
{
get
{
return this.stream;
}
}
private Socket clientSocket;
public Socket ClientSocket
{
get
{
return this.clientSocket;
}
}
private Socket serverSocket;
public Socket ServerSocket
{
get
{
return this.serverSocket;
}
}
private byte[] data;
public byte[] Data
{
get
{
return this.data;
}
set
{
this.data = value;
}
}
#region IDisposable Members
/// <summary>
/// Clean up
/// </summary>
public void Dispose()
{
this.stream.Close();
this.stream = null;
}
#endregion
}
}
Now we have the state object, we need to incorporate this into the Simple TCP Server. Every time we receieve data we need to check to see if the amount of data received is less than the buffer, if it is then we need to repeat a receive async event until we have received the entire payload. The following is the revised Server.cs class now with the ability to accept any length of data.
Server.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace CSharpTcpServer
{
/// <summary>
/// A simple C# TCP Server
/// </summary>
public class Server
{
private string host;
private int port;
private Socket server;
/// <summary>
/// Constructor
/// </summary>
/// <param name="port">The port for the server to listen for connections on</param>
public Server(int port)
{
//Set the port
this.port = port;
//Instantiate the socket for the server and we want to use Streams and TCP
this.server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
/// <summary>
/// Method to start the server and wait for the intial client
/// </summary>
public void Start()
{
//Makesure we can accept a connectoin from any IP but only on port 888
this.server.Bind(new IPEndPoint(IPAddress.Any, this.port));
//Allow a maximum backlog of 10 connections
this.server.Listen(10);
//Instruct the server to accept the next client connection
this.server.BeginAccept(OnClientConneced, this.server);
Console.WriteLine("Server started and waiting for clients...");
}
/// <summary>
/// This will shut down the server and stop any tranmission from and to the server
/// </summary>
public void Stop()
{
//Makesure the server is connected before attempting to shut it down
if (this.server.Connected)
{
Console.WriteLine("Server Shutting Down...");
this.server.Shutdown(SocketShutdown.Both);
}
}
/// <summary>
/// This will be called when a client connected to the server
/// </summary>
/// <param name="result">The asyncronous result containing the server socket</param>
protected void OnClientConneced(IAsyncResult result)
{
//Makesure the server is connected before accepting a new client
if (!this.server.Connected)
{
//Cast the state of the Async Result as the Server Socket
Socket serverSocket = (Socket)result.AsyncState;
//Ending the Accept method will result in the Socket of the client
Socket clientSocket = serverSocket.EndAccept(result);
Console.WriteLine("Client Connected");
StateObject stateObj = new StateObject(clientSocket, serverSocket);
clientSocket.BeginReceive(stateObj.Data, 0, StateObject.BUFFER_SIZE,
SocketFlags.None, OnClientRecieveData, stateObj);
//Now we have the client inform the server to continue listening for further clients
this.server.BeginAccept(OnClientConneced, this.server);
}
}
/// <summary>
/// When data is received from the client, it is output to the console window.
/// Currently this function will only read from the input stream once.
/// </summary>
/// <param name="result">The asyncronous result containing the server socket and the incoming data</param>
protected void OnClientRecieveData(IAsyncResult result)
{
//Cast the state back to a state object
StateObject data = (StateObject)result.AsyncState;
//If the client is not connected then simply return as this session has ended
if (!data.ClientSocket.Connected)
return;
//Ending the async receive method will return the length of bytes in the current chunk
int dataLength = data.ClientSocket.EndReceive(result);
//If it is zero then we display a message of client disconnected
//This is not complete but for the purposes of this stage of the demo this is how it is
if (dataLength == 0)
{
Console.WriteLine("Client Disconnected");
}
else if (dataLength < StateObject.BUFFER_SIZE)
{
//The chunk is less than the size of the buffer which means there are no further chunks
//inside this transmission
//Write the data into the stream of the state object to persist it
data.Stream.Write(data.Data, 0, dataLength);
//Rewind the stream back the start
data.Stream.Position = 0;
//We are assuming that the data being sent is plain text so we can output this to the console
//window
Console.WriteLine(Encoding.Default.GetString(data.Stream.ToArray()));
//Dispose of the data
data.Dispose();
//Instantiate a new state object for further transmissions
data = new StateObject(data.ClientSocket, data.ServerSocket);
//Instruct the server to wait for more data from the client
data.ClientSocket.BeginReceive(data.Data, 0, StateObject.BUFFER_SIZE,
SocketFlags.None, OnClientRecieveData, data);
}
else
{
//Write the data to the state object
data.Stream.Write(data.Data, 0, dataLength);
//Instruct the server to wait for more data from the client
data.ClientSocket.BeginReceive(data.Data, 0, StateObject.BUFFER_SIZE,
SocketFlags.None, OnClientRecieveData, data);
}
}
}
}
Finally we still have to update the flash client now, so that we can allow for the user to type in and submit multiple messages. For the design I have included the requirement for the last part in this blog post which will be used for data which is sent from the server to the action script client. This will ultimately lead to the situation where I can pop open multiple flash clients and enable a chat style application using a Flash Client and a C# Server.
Main.as
package
{
import flash.display.SimpleButton;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.MouseEvent;
import flash.events.ProgressEvent;
import flash.net.Socket;
import flash.text.TextField;
import flash.utils.ByteArray;
import flash.text.TextFieldType;
/**
* ...
* @author DefaultUser (Tools -> Custom Arguments...)
*/
public class Main extends Sprite
{
//This is the text field which will accept the input from the user
private var dataEntry:TextField;
//This is the screen which will be used as the output from the server and the client
private var console:TextField;
//This is the button used to initiate the sending of data to the server
private var button:Sprite;
private var buttonLabel:TextField;
private var consoleHeight:int = 300;
private var consoleWidth:int = 500;
//The socket which will handle all the communication
private var socket:BasicSocket;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
//Create the interface
removeEventListener(Event.ADDED_TO_STAGE, init);
dataEntry = new TextField();
console = new TextField();
button = new Sprite
buttonLabel = new TextField();
console.border = true;
console.width = consoleWidth;
console.height = consoleHeight;
console.x = 50;
console.y = 50;
dataEntry.border = true;
dataEntry.multiline = true;
dataEntry.wordWrap = true;
dataEntry.type = TextFieldType.INPUT;
dataEntry.textColor = 0x000000;
dataEntry.width = consoleWidth - 100;
dataEntry.height = 100;
dataEntry.x = 50;
dataEntry.y = consoleHeight + 50;
buttonLabel = new TextField();
buttonLabel.text = "SEND";
button.graphics.lineStyle(1, 0x000000);
button.graphics.drawRoundRect(0, 0, 90, 90, 10, 10);
button.mouseChildren = false;
button.buttonMode = true;
button.x = consoleWidth - 40;
button.y = consoleHeight + 60;
button.addEventListener(MouseEvent.CLICK, OnSend, false, 0, true);
buttonLabel.x = ((button.width - buttonLabel.textWidth) / 2) - 2;
buttonLabel.y = ((button.height - buttonLabel.textHeight) / 2) - 2;
buttonLabel.mouseEnabled = false;
addChild(console);
addChild(dataEntry);
addChild(button);
button.addChild(buttonLabel);
}
private function OnSend(e:MouseEvent):void {
//Send the data to the server
Send(dataEntry.text);
//Clear the text entry box for more data
dataEntry.text = "";
}
private function Send(data:String):void {
//If the socket has not yet been initiated
if (socket == null)
{
//Instantiate the socket
socket = new BasicSocket();
//Add event listeners for the relevant events we are interested in
socket.addEventListener(Event.CONNECT, onConnect, false, 0, true);
socket.addEventListener(Event.CLOSE, onClose, false, 0, true);
socket.addEventListener(IOErrorEvent.IO_ERROR, onError, false, 0, true);
socket.addEventListener(ProgressEvent.SOCKET_DATA, OnReceiveData, false, 0 , true);
//Attempt to conect to the server
socket.connect("127.0.0.1", 8888);
}
//Write the data to the server
socket.writeUTFBytes(data);
//Flush the data to the server. We want to send all of the data to the server whenever
//this function is called
socket.flush();
}
private function OnReceiveData(e:ProgressEvent):void {
//Read the data from the socket
var str:String = socket.readUTFBytes(socket.bytesAvailable);
//Write the data to the console window
console.appendText(str + "\r\n");
}
private function onConnect(e:Event):void{
console.appendText("connected\r\n");
}
private function onClose(e:Event):void{
console.appendText("closed\r\n");
}
private function onError(e:IOErrorEvent):void{
console.appendText("connection error!\r\n");
}
}
}
At this stage we are ready to test this client and server so we can simply keep typing and sending the data and this will be output on the server. No data will appear inside the client as of yet, this will be the final part to this post. I will use screen toaster again to demo this.
Nearly finished, the flash client and C# TCP Server in action
Accept textual data sent from C# to Flash over TCP using Sockets
So from here we want to send data the other way from the server to the flash client. The way I will code this is as follows. I will alter the Server.cs to contain a list of the currently connected client sockets. Every time a new message is sent to the server, the server will send the message out to all the clients. This will straight away provide the necessary plumbing to have a multi client chat application, albeit I am using on the same computer, for the moment.
If the client disconnects, we want to remove it from the list, so as the server will not try and send data to a disconnected client. After all that is complete, we can loop through all the client sockets and begin to send the data.
I will create two new functions inside the server which are involved in sending the data. The first overload of this method will simply take a string parameter which will be the data it needs to send. The second overload will take the client socket which originates the message and also the data of that message. The reason being is that messages may originate from the server as well as other clients. Both functions will then loop through the list of client sockets and send the data to them.
At this stage, the Action Script Flash client will not change as everything we need for the current time has been coded for, so please find below the nearly finished Simple C# TCP Server code.
Server.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace CSharpTcpServer
{
/// <summary>
/// A simple C# TCP Server
/// </summary>
public class Server
{
private string host;
private int port;
private Socket server;
private List<Socket> clientSockets;
/// <summary>
/// Constructor
/// </summary>
/// <param name="port">The port for the server to listen for connections on</param>
public Server(int port)
{
//Set the port
this.port = port;
//Instantiate the socket for the server and we want to use Streams and TCP
this.server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//A container for the client connections
this.clientSockets = new List<Socket>();
}
/// <summary>
/// Method to start the server and wait for the intial client
/// </summary>
public void Start()
{
//Makesure we can accept a connectoin from any IP but only on port 888
this.server.Bind(new IPEndPoint(IPAddress.Any, this.port));
//Allow a maximum backlog of 10 connections
this.server.Listen(10);
//Instruct the server to accept the next client connection
this.server.BeginAccept(OnClientConneced, this.server);
Console.WriteLine("Server started and waiting for clients...");
}
/// <summary>
/// This will shut down the server and stop any tranmission from and to the server
/// </summary>
public void Stop()
{
//Makesure the server is connected before attempting to shut it down
if (this.server.Connected)
{
Console.WriteLine("Server Shutting Down...");
this.server.Shutdown(SocketShutdown.Both);
}
}
/// <summary>
/// This will be called when a client connected to the server
/// </summary>
/// <param name="result">The asyncronous result containing the server socket</param>
protected void OnClientConneced(IAsyncResult result)
{
//Makesure the server is connected before accepting a new client
if (!this.server.Connected)
{
//Cast the state of the Async Result as the Server Socket
Socket serverSocket = (Socket)result.AsyncState;
//Ending the Accept method will result in the Socket of the client
Socket clientSocket = serverSocket.EndAccept(result);
//Add the client socket to the connection container
clientSockets.Add(clientSocket);
//Inform the other clients of the new connection
SendData(clientSocket, " has connected");
Console.WriteLine("Client Connected");
StateObject stateObj = new StateObject(clientSocket, serverSocket);
clientSocket.BeginReceive(stateObj.Data, 0, StateObject.BUFFER_SIZE,
SocketFlags.None, OnClientRecieveData, stateObj);
//Now we have the client inform the server to continue listening for further clients
this.server.BeginAccept(OnClientConneced, this.server);
}
}
/// <summary>
/// When data is received from the client, it is output to the console window.
/// Currently this function will only read from the input stream once.
/// </summary>
/// <param name="result">The asyncronous result containing the server socket and the incoming data</param>
protected void OnClientRecieveData(IAsyncResult result)
{
//Cast the state back to a state object
StateObject data = (StateObject)result.AsyncState;
//If the client is not connected then simply return as this session has ended
if (!data.ClientSocket.Connected)
return;
//Ending the async receive method will return the length of bytes in the current chunk
int dataLength = data.ClientSocket.EndReceive(result);
//If it is zero then we display a message of client disconnected
//This is not complete but for the purposes of this stage of the demo this is how it is
if (dataLength == 0)
{
//Remove the disconnected client from the list
clientSockets.Remove(data.ClientSocket);
//Inform the other client of the disconnection
SendData(data.ClientSocket, " has disconnected");
//Clean up
data.Dispose();
//Make the state object null
data = null;
Console.WriteLine("Client Disconnected");
}
else if (dataLength < StateObject.BUFFER_SIZE)
{
//The chunk is less than the size of the buffer which means there are no further chunks
//inside this transmission
//Write the data into the stream of the state object to persist it
data.Stream.Write(data.Data, 0, dataLength);
//Rewind the stream back the start
data.Stream.Position = 0;
//We are assuming that the data being sent is plain text so we can output this to the console
//window
string dataToWrite = Encoding.Default.GetString(data.Stream.ToArray());
//Output the data onto the server
Console.WriteLine(dataToWrite);
//Pump out the message so all clients can see the data
SendData(data.ClientSocket, dataToWrite);
//Dispose of the data
data.Dispose();
//Instantiate a new state object for further transmissions
data = new StateObject(data.ClientSocket, data.ServerSocket);
//Instruct the server to wait for more data from the client
data.ClientSocket.BeginReceive(data.Data, 0, StateObject.BUFFER_SIZE,
SocketFlags.None, OnClientRecieveData, data);
}
else
{
//Write the data to the state object
data.Stream.Write(data.Data, 0, dataLength);
//Instruct the server to wait for more data from the client
data.ClientSocket.BeginReceive(data.Data, 0, StateObject.BUFFER_SIZE,
SocketFlags.None, OnClientRecieveData, data);
}
}
protected void OnDataSent(IAsyncResult result)
{
Socket clientSocket = (Socket)result.AsyncState;
clientSocket.EndSend(result);
}
//The server will use this method when the message has originated from itself
private void SendData(string data)
{
var message = String.Format("{1}", data);
for (int i = 0; i < clientSockets.Count; i++)
{
byte[] dataToSend = Encoding.Default.GetBytes(message);
if (clientSockets[i].Connected)
clientSockets[i].BeginSend(dataToSend, 0, dataToSend.Length, SocketFlags.None, OnDataSent, clientSockets[i]);
}
}
//This will be used the when the server pumps out the messages and the message originated from
//the client.
private void SendData(Socket fromClient, string data)
{
var message = String.Format("{0} : {1}", fromClient.RemoteEndPoint, data);
for (int i = 0; i < clientSockets.Count; i++)
{
byte[] dataToSend = Encoding.Default.GetBytes(message);
if (clientSockets[i].Connected)
clientSockets[i].BeginSend(dataToSend, 0, dataToSend.Length, SocketFlags.None, OnDataSent, clientSockets[i]);
}
}
}
}
Serialize and post objects over TCP using XML as the vehicle and TCP as the transport going from Flash to C# and visa-versa
So the final part in this post, is that I want to send an object from Flash to the C# server and also vica versa. The two languages of course are not interoperable so we have to find a middle ground which in this case will be XML. It would be brilliant of course to make this use SOAP over TCP, but for the purposes of this demo I will use simple XML, and also as the object I want to send is simple, it will provide a clear, simple, and interoperable way to interface between environments.
The C# Side
For this final part of this post, the C# side requires the most work to get this functioning. We want to do the following:
- Create an Abstract class called Packet
- Create a class called XmlPacket which inherits from Packet
- Create another another overload of the SendData function which will accept a Packet object
- Create a function on the server to Read and Convert the received data into a Packet
- Create a function on the server which takes the Packet and sends to the clients
The Flash Side
- Create a class called AbstractPacket which all other types of Packets will inherit from
- Create a class called XmlPacket which inherits from AbstractPacket
- Change the OnReceiveData function to cast the received data into XML
- Change the Send function to send the XML to the server.
So the following is the final pieces of the code and also a short screen toaster video showing the application in action. The code for both the C# App and the Flash client can be found at the start of the post.
Packet.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CSharpTcpServer
{
public abstract class Packet
{
public abstract byte[] GetData();
}
}
XmlPacket.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.ComponentModel;
using System.Xml.Serialization;
namespace CSharpTcpServer
{
[Serializable]
[XmlRoot (ElementName="xmlpacket")]
public class XmlPacket : Packet
{
[XmlElement(ElementName="handle")]
public string Handle { get; set; }
[XmlElement(ElementName = "date")]
public DateTime Date { get; set; }
[XmlElement(ElementName="message")]
public string Message { get; set; }
private StringBuilder builder;
private XmlWriter writer;
public XmlPacket()
{
}
public override byte[] GetData()
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
builder = new StringBuilder();
writer = XmlWriter.Create(builder, settings);
writer.WriteStartElement("xmlpacket");
writer.WriteElementString("handle", Handle);
writer.WriteElementString("date", "2009-06-08");
writer.WriteElementString("message", Message);
writer.WriteEndElement();
writer.Flush();
return Encoding.Default.GetBytes(builder.ToString());
}
}
}
Server.cs
This is the final and updated version of the Server.cs class for the purposes of this post.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Xml.Serialization;
using System.IO;
using System.Xml;
using System.Xml.Schema;
namespace CSharpTcpServer
{
/// <summary>
/// A simple C# TCP Server
/// </summary>
public class Server
{
private string host;
private int port;
private Socket server;
private List<Socket> clientSockets;
/// <summary>
/// Constructor
/// </summary>
/// <param name="port">The port for the server to listen for connections on</param>
public Server(int port)
{
//Set the port
this.port = port;
//Instantiate the socket for the server and we want to use Streams and TCP
this.server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//A container for the client connections
this.clientSockets = new List<Socket>();
}
/// <summary>
/// Method to start the server and wait for the intial client
/// </summary>
public void Start()
{
//Makesure we can accept a connectoin from any IP but only on port 888
this.server.Bind(new IPEndPoint(IPAddress.Any, this.port));
//Allow a maximum backlog of 10 connections
this.server.Listen(10);
//Instruct the server to accept the next client connection
this.server.BeginAccept(OnClientConneced, this.server);
Console.WriteLine("Server started and waiting for clients...");
}
/// <summary>
/// This will shut down the server and stop any tranmission from and to the server
/// </summary>
public void Stop()
{
//Makesure the server is connected before attempting to shut it down
if (this.server.Connected)
{
Console.WriteLine("Server Shutting Down...");
this.server.Shutdown(SocketShutdown.Both);
}
}
/// <summary>
/// This will be called when a client connected to the server
/// </summary>
/// <param name="result">The asyncronous result containing the server socket</param>
protected void OnClientConneced(IAsyncResult result)
{
//Makesure the server is connected before accepting a new client
if (!this.server.Connected)
{
//Cast the state of the Async Result as the Server Socket
Socket serverSocket = (Socket)result.AsyncState;
//Ending the Accept method will result in the Socket of the client
Socket clientSocket = serverSocket.EndAccept(result);
//Add the client socket to the connection container
clientSockets.Add(clientSocket);
//Inform the other clients of the new connection
SendData(clientSocket, " has connected");
Console.WriteLine("Client Connected");
StateObject stateObj = new StateObject(clientSocket, serverSocket);
clientSocket.BeginReceive(stateObj.Data, 0, StateObject.BUFFER_SIZE,
SocketFlags.None, OnClientRecieveData, stateObj);
//Now we have the client inform the server to continue listening for further clients
this.server.BeginAccept(OnClientConneced, this.server);
}
}
/// <summary>
/// When data is received from the client, it is output to the console window.
/// Currently this function will only read from the input stream once.
/// </summary>
/// <param name="result">The asyncronous result containing the server socket and the incoming data</param>
protected void OnClientRecieveData(IAsyncResult result)
{
//Cast the state back to a state object
StateObject data = (StateObject)result.AsyncState;
//If the client is not connected then simply return as this session has ended
if (!data.ClientSocket.Connected)
return;
//Ending the async receive method will return the length of bytes in the current chunk
int dataLength = data.ClientSocket.EndReceive(result);
//If it is zero then we display a message of client disconnected
//This is not complete but for the purposes of this stage of the demo this is how it is
if (dataLength == 0)
{
//Remove the disconnected client from the list
clientSockets.Remove(data.ClientSocket);
//Inform the other client of the disconnection
SendData(data.ClientSocket, " has disconnected");
//Clean up
data.Dispose();
//Make the state object null
data = null;
Console.WriteLine("Client Disconnected");
}
else if (dataLength < StateObject.BUFFER_SIZE)
{
//The chunk is less than the size of the buffer which means there are no further chunks
//inside this transmission
//Write the data into the stream of the state object to persist it
data.Stream.Write(data.Data, 0, dataLength);
//Rewind the stream back the start
data.Stream.Position = 0;
//We are assuming that the data being sent is plain text so we can output this to the console
//window
//string dataToWrite = Encoding.Default.GetString(data.Stream.ToArray());
XmlPacket packetFromClient = GetDataPacket(data.Stream);
Console.WriteLine("{0} [{1}] : {2}", packetFromClient.Handle, packetFromClient.Date, packetFromClient.Message);
//Output the data onto the server
//Console.WriteLine(dataToWrite);
//Pump out the message so all clients can see the data
SendData(data.ClientSocket, packetFromClient);
//Dispose of the data
data.Dispose();
//Instantiate a new state object for further transmissions
data = new StateObject(data.ClientSocket, data.ServerSocket);
//Instruct the server to wait for more data from the client
data.ClientSocket.BeginReceive(data.Data, 0, StateObject.BUFFER_SIZE,
SocketFlags.None, OnClientRecieveData, data);
}
else
{
//Write the data to the state object
data.Stream.Write(data.Data, 0, dataLength);
//Instruct the server to wait for more data from the client
data.ClientSocket.BeginReceive(data.Data, 0, StateObject.BUFFER_SIZE,
SocketFlags.None, OnClientRecieveData, data);
}
}
protected void OnDataSent(IAsyncResult result)
{
Socket clientSocket = (Socket)result.AsyncState;
clientSocket.EndSend(result);
}
//The server will use this method when the message has originated from itself
private void SendData(string data)
{
var message = String.Format("{1}", data);
for (int i = 0; i < clientSockets.Count; i++)
{
byte[] dataToSend = Encoding.Default.GetBytes(message);
if (clientSockets[i].Connected)
clientSockets[i].BeginSend(dataToSend, 0, dataToSend.Length, SocketFlags.None, OnDataSent, clientSockets[i]);
}
}
//This will be used the when the server pumps out the messages and the message originated from
//the client.
private void SendData(Socket fromClient, Packet data)
{
//var message = String.Format("{0} : {1}", fromClient.RemoteEndPoint, data);
for (int i = 0; i < clientSockets.Count; i++)
{
byte[] dataToSend = data.GetData(); //Encoding.Default.GetBytes(message);
if (clientSockets[i].Connected)
clientSockets[i].BeginSend(dataToSend, 0, dataToSend.Length, SocketFlags.None, OnDataSent, clientSockets[i]);
}
}
private void SendData(Socket fromClient, string data)
{
var message = String.Format("{0} : {1}", fromClient.RemoteEndPoint, data);
for (int i = 0; i < clientSockets.Count; i++)
{
byte[] dataToSend = Encoding.Default.GetBytes(message);
if (clientSockets[i].Connected)
clientSockets[i].BeginSend(dataToSend, 0, dataToSend.Length, SocketFlags.None, OnDataSent, clientSockets[i]);
}
}
/// <summary>
/// Deserializes the stream back into a XML Packet so it can be read and processed by the server
/// </summary>
/// <param name="data">The data sent from the client</param>
/// <returns>XmlPacket</returns>
private XmlPacket GetDataPacket(Stream data)
{
XmlSerializer serializer = new XmlSerializer(typeof(XmlPacket));
Console.WriteLine(Encoding.Default.GetString(((MemoryStream)data).ToArray()));
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationFlags = XmlSchemaValidationFlags.None;
settings.ValidationType = ValidationType.None;
settings.ConformanceLevel = ConformanceLevel.Fragment;
settings.IgnoreProcessingInstructions = true;
settings.NameTable = new NameTable();
settings.NameTable.Add("");
XmlReader reader = XmlReader.Create(data, settings);
XmlPacket packet = (XmlPacket)serializer.Deserialize(reader);
return packet;
}
private byte[] GetDataFromPacket(XmlPacket packet)
{
return packet.GetData();
}
}
}
The GetDataPacket function of the Server handles the deserialization of the data for you in a clean and efficient way. One key aspect of the XmlPacket class is that the root attribute only stores the element name and not the namespace as, like you can see in the following action script class we do not give a namespace to the data structure so we can avoid any parsing errors of the XML.
I write out the data sent to the server just for debugging purposes so you can see the format of the data when it arrives. You will also notice that I have hard coded the date into the XML Structure. This is not for any particular reason other than at the time of writing I have not found or written a function in Action Script which will give me the CC in the date format, as this seems to be a requirement when sending the XML to the server over the TCP.
So finally the three peices of the ActionScript.
AbstractPacket.as
package
{
import flash.utils.ByteArray;
/**
* ...
* @author Andrew Rea
*/
public class AbstractPacket
{
public function AbstractPacket()
{
}
public function GetData():ByteArray {
//Available for override
throw new Error("Must be overridden");
}
}
}
XmlPacket.as
package
{
import flash.utils.ByteArray;
/**
* ...
* @author Andrew Rea
*/
public class XmlPacket extends AbstractPacket
{
private var _handle:String;
private var _date:Date = new Date();
private var _message:String;
private var _xml:XML
public function XmlPacket()
{
}
public override function GetData():ByteArray
{
_date = new Date();
_xml = <xmlpacket>
< handle > { _handle }</handle>
< date > 2009-06-08</date>
< message > { _message }</message>
</xmlpacket>;
var byteData:ByteArray = new ByteArray();
byteData.writeUTFBytes(_xml.toXMLString());
byteData.position = 0;
return byteData;
}
/**
* A great way to include cdata section into the XML where you want to inject variables
* @param theURL
* @return
*/
function cdata(theURL:Date):XML
{
var x:XML = new XML("<![CDATA[" + theURL + "]]>");
return x;
}
public function set Handle(value:String):void {
_handle = value;
}
public function get Handle():String {
return _handle;
}
public function set DateValue(value:Date):void {
_date = value;
}
public function get DateValue():Date {
return _date;
}
public function set Message(value:String):void {
_message = value;
}
public function get Message():String {
return _message;
}
}
}
Main.as
package
{
import flash.display.SimpleButton;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.MouseEvent;
import flash.events.ProgressEvent;
import flash.net.Socket;
import flash.text.TextField;
import flash.utils.ByteArray;
import slideshow.event.PageChangeEvent;
import flash.text.TextFieldType;
/**
* ...
* @author DefaultUser (Tools -> Custom Arguments...)
*/
public class Main extends Sprite
{
//This is the text field which will accept the input from the user
private var dataEntry:TextField;
//This is the screen which will be used as the output from the server and the client
private var console:TextField;
//This is the button used to initiate the sending of data to the server
private var button:Sprite;
private var buttonLabel:TextField;
private var consoleHeight:int = 300;
private var consoleWidth:int = 500;
//The socket which will handle all the communication
private var socket:BasicSocket;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
//Create the interface
removeEventListener(Event.ADDED_TO_STAGE, init);
dataEntry = new TextField();
console = new TextField();
button = new Sprite
buttonLabel = new TextField();
console.border = true;
console.width = consoleWidth;
console.height = consoleHeight;
console.x = 50;
console.y = 50;
dataEntry.border = true;
dataEntry.multiline = true;
dataEntry.wordWrap = true;
dataEntry.type = TextFieldType.INPUT;
dataEntry.textColor = 0x000000;
dataEntry.width = consoleWidth - 100;
dataEntry.height = 100;
dataEntry.x = 50;
dataEntry.y = consoleHeight + 50;
buttonLabel = new TextField();
buttonLabel.text = "SEND";
button.graphics.lineStyle(1, 0x000000);
button.graphics.drawRoundRect(0, 0, 90, 90, 10, 10);
button.mouseChildren = false;
button.buttonMode = true;
button.x = consoleWidth - 40;
button.y = consoleHeight + 60;
button.addEventListener(MouseEvent.CLICK, OnSend, false, 0, true);
buttonLabel.x = ((button.width - buttonLabel.textWidth) / 2) - 2;
buttonLabel.y = ((button.height - buttonLabel.textHeight) / 2) - 2;
buttonLabel.mouseEnabled = false;
addChild(console);
addChild(dataEntry);
addChild(button);
button.addChild(buttonLabel);
}
private function OnSend(e:MouseEvent):void {
//Send the data to the server
//Send(dataEntry.text);
var packet:XmlPacket = new XmlPacket();
packet.Handle = "REA_ANDREW";
packet.Message = dataEntry.text;
Send(packet);
//Clear the text entry box for more data
dataEntry.text = "";
}
private function Send(data:AbstractPacket):void {
//If the socket has not yet been initiated
if (socket == null)
{
//Instantiate the socket
socket = new BasicSocket();
//Add event listeners for the relevant events we are interested in
socket.addEventListener(Event.CONNECT, onConnect, false, 0, true);
socket.addEventListener(Event.CLOSE, onClose, false, 0, true);
socket.addEventListener(IOErrorEvent.IO_ERROR, onError, false, 0, true);
socket.addEventListener(ProgressEvent.SOCKET_DATA, OnReceiveData, false, 0 , true);
//Attempt to conect to the server
socket.connect("127.0.0.1", 8888);
}
var byteData:ByteArray = data.GetData();
//Write the data to the server
socket.writeBytes(byteData, 0, byteData.length);
//Flush the data to the server. We want to send all of the data to the server whenever
//this function is called
socket.flush();
}
private function OnReceiveData(e:ProgressEvent):void {
//Read the data from the socket
var data:XML = new XML(socket.readUTFBytes(socket.bytesAvailable));
//Write the data to the console window
console.appendText("[" + data.handle +"] : " + data.message + "\r\n");
}
private function onConnect(e:Event):void{
console.appendText("connected\r\n");
}
private function onClose(e:Event):void{
console.appendText("closed\r\n");
}
private function onError(e:IOErrorEvent):void{
console.appendText("connection error!\r\n");
}
}
}
So this here ends the first part in the series which I am writing on Socket Communication between various languages both being used as both the server and the client in most instances. This post has dealt with C# holding the server side of the bargain and flash having the client role. There are also some variations of each which I want to make including doing the above but using WCF and TCP for the server part of the application.
Cheers for now.
Andy