For practicing I wanted to create client and server applications to simulate a lobby.
Therefore, in the server-application I accept incoming connections, create a ClientInfo object containing the TcpClient object, usernames, id, etc. and the methods for sending and receiving data, and store that ClientInfo object in a List in my lobby class. When the user does something like chatting, the message is being sent to the server and broadcasted to all available clients.
The problem I have is:
The first client connects. Broadcasts go to DefaultUser1.
The second client connects. Broadcasts go to DefaultUser2 + DefaultUser2.
As you can see, the first Client is not receiving data anymore, nor can the Server receive data from him. Somehow the data in the list must be corrupted. Here is the relevant bit of code:
Accepting incoming conenctions and creating the ClientInfo object and storing it to the lobby:
while (mWorking)
{
TcpClient client = mListener.AcceptTcpClient();
mNumberOfClients++;
Console.WriteLine("New Tcp-Connection with client: " + client.Client.LocalEndPoint.ToString());
ClientInfo newInfo = new ClientInfo(client, mNumberOfClients);
mLobby.AddClient(newInfo);
}
The ClientInfo constructor:
public ClientInfo(TcpClient client, int clientNumber)
{
mClient = client;
mClientNumber = clientNumber;
mUsername = "DefaultUser" + mClientNumber.ToString();
mStream = client.GetStream();
mEncoding = new ASCIIEncoding();
}
The sending method in ClientInfo:
public void Send(String message)
{
mCurrentMessage = message;
Thread sendThread = new Thread(this.WriteTask);
sendThread.Start();
}
private void WriteTask()
{
byte[] data = mEncoding.GetBytes(mCurrentMessage);
byte[] sizeinfo = new byte[4];
sizeinfo[0] = (byte)data.Length;
sizeinfo[1] = (byte)(data.Length >> 8);
sizeinfo[2] = (byte)(data.Length >> 16);
sizeinfo[3] = (byte)(data.Length >> 24);
mStream.Write(sizeinfo, 0, sizeinfo.Length);
mStream.Write(data, 0, data.Length);
}
Relevant code in the lobby class:
private static List<ClientInfo> mClients;
private static processDel mProcessDel;
public Lobby(processDel del)
{
mProcessDel = del;
mClients = new List<ClientInfo>();
}
public void AddClient(ClientInfo client)
{
mClients.Add(client);
client.Listen(mProcessDel);
Broadcast("UJOIN§" + client.username + "$");
}
public void Broadcast(String message)
{
for (int i = 0; i < mClients.Count; i++)
{
Console.WriteLine("Broadcasting to " + mClients[i].username);
mClients[i].Send(message);
}
}
I also tried the broadcasting with foreach, same result. The processDel is a delegate method i need for processing the received data. Receiving is handled by a seperate thread for each client.
As a guess, it seems that you misunderstood what static means in C#.
static means that the method or field is part of the type, rather than the instance of a type. So if all of your fields are static, you don't actually have any instance data, and all the state is shared across all instances of your class - so the second client overwrites all the data associated with the first client as well. The solution is simple - just remove the statics, and you should be fine.
Other than that, your code has some thread-safety issues. Most types in .NET are not thread-safe by default, and you need to add appropriate locking to make sure that consistency is maintained. This is more of a topic for CodeReview, perhaps, so I'll just note the first things that come to mind:
Send always starts a new thread to send the message. However, this also means that if it's called twice in succession under just the right conditions, it can completely corrupt your TCP stream - for example, the first thread might write the length data, then the second writes its length data before the first writes the actual data and you're in trouble. It's also possible that you'd just send the second message twice, since you're passing the text to send through a field.
List<T> isn't thread-safe. That means that you can only safely use it from a single thread - it's not entirely clear from your code, but it seems like you might have trouble with that. Using something like ConcurrentDictionary<IPEndPoint, ClientInfo> might be a better idea, but that really depends on what you're doing.
You could also explore some alternative options, like using asynchronous I/O instead of spamming threads, but that's a bit more advanced option (mind you, multi-threading is even worse :)). Regardless, a good start for thread-safety would be http://www.albahari.com/threading/ It's somewhat long, but multi-threading is a very complex and dangerous topic, and it will tend to produce errors that are hard to find and reproduce, especially while running in a debugger.
Related
I have a Node.Js server running on localhost listening to some API requests.
These requests are transferred to my console application via TCP/IP. Here's my c# code which receives data from Node server (hosted at localhost:9999) via GetData() and pass it to another function SendData().
namespace Datatransfer
{
/* global variable declaration*/
class Global
{
public static string receive_data;
}
class Program
{
static string HOST = "localhost";
static int PORT = 9999;
static TcpClient client;
/*Function to receive data*/
static string GetData()
{
while (true)
{
NetworkStream nwStream = client.GetStream();
byte[] bytesToRead = new byte[client.ReceiveBufferSize];
int bytesRead = nwStream.Read(bytesToRead, 0, client.ReceiveBufferSize);
Global.receive_data= Encoding.ASCII.GetString(bytesToRead, 0, bytesRead);
Console.WriteLine("Received data : " + Global.receive_data);
SendData(Global.receive_data)
}
}
/*Function to send data*/
static void SendData(string val)
{
/*Code to process recevied_data..*/
Console.WriteLine("Data to Send : " + Global.receive_data);
/*some codes....*/
}
static void Main(string[] args)
{
client = new TcpClient();
client.Connect(HOST, PORT);
GetData();
}
}
}
I have declared the receive_data as global so as to use it across the application. The code works and I am getting output. Everytime I make an API request to port 9999 am getting output as :
Connection Successfull...
Received data : somestring
Data to Send : somestring
I was wondering if this is an efficient way or not ?
Is there another way by which the receive_data can be passed to other functions 'without' using the function ( ie;SendData() ) inside the while loop.? Or to put it simply, pass data from an infinite while loop to main or other functions.
Any suggestions?
You basically have two options for further processing the data you receive:
Store it somewhere like you did (from a design perspective it doesn't matter how you implement this). Just one thing to think about would be if you want to store a list of received data-"messages", and what happens if you receive another message.
Call a method an pass the received data. This would be the better approach, because you abstract away the implementation and are free to change it (e.g. from storing global to a message-sink mechanism or whatever) without changing your receiving-code.
Approach 2) has more information and more context, because you trigger the method at the point you receive data. In option 1) you have no information about how old the information is, or even if the same information was sent multiple times. So more information is (always) better, if you don't need it in the method call, you are free to condense it again to say a global variable.
For approach 2) you should keep in mind, that the method is running "inside" your loop, so all long-running operations would block the loop. But still you are free to implement it in a way, that allows the message to be processed in another thread (asynchronous).
I am trying to send commands to the server , like for example requesting the server to send back the list of files in it's directory. The problem is that when I send the "list" command to the server, I have to send it twice in order for the server to send back the list of files to the client. I am sure that the server receives the command in both times as on the server side I print the result that is supposed to be sent to the client on the console and it appears both times.
I am using C# and TCPListeners to listen for incoming responses or commands, and TCPClient to send responses or commands between the server and the client.
The client code
private TcpListener tcpListener = new TcpListener(9090);
private void button3_Click(object sender, EventArgs e)
{
Byte[] bytesToSend = ASCIIEncoding.ASCII.GetBytes("list");
try
{
TcpClient clientSocket = new TcpClient(serverIPFinal, 8080);
if (clientSocket.Connected)
{
NetworkStream networkStream = clientSocket.GetStream();
networkStream.Write(bytesToSend, 0, bytesToSend.Length);
// networkStream.Close();
// clientSocket.Close();
thdListener = new Thread(new ThreadStart(listenerThreadList));
thdListener.Start();
}
}
catch
{
isConnectedLbl.Text = "Server not running";
}
}
//Listener Thread to receive list of files.
public void listenerThreadList()
{
tcpListener.Start();
while (true)
{
handlerSocket = tcpListener.AcceptSocket();
if (handlerSocket.Connected)
{
Control.CheckForIllegalCrossThreadCalls = false;
lock (this)
{
if (handlerSocket != null)
{
nSockets.Add(handlerSocket);
}
}
ThreadStart thdstHandler = new
ThreadStart(handlerThreadList);
Thread thdHandler = new Thread(thdstHandler);
thdHandler.Start();
}
}
}
//Handler Thread to receive list of files.
public void handlerThreadList()
{
Socket handlerSocketList = (Socket)nSockets[nSockets.Count - 1];
NetworkStream networkStreams = new NetworkStream(handlerSocketList);
int requestRead = 0;
string dataReceived;
byte[] buffer = new byte[1024];
//int iRx = soc.Receive(buffer);
requestRead = networkStreams.Read(buffer, 0, 1024);
char[] chars = new char[requestRead];
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(buffer, 0, requestRead, chars, 0);
dataReceived = new System.String(chars);
Console.WriteLine(dataReceived);
MessageBox.Show(dataReceived);
//tcpListener.Stop();
thdListener.Abort();
}
The Server code:
TcpListener tcpListener = new TcpListener(8080);
public void listenerThreadCommands()
{
tcpListener.Start();
while (true)
{
handlerSocket = tcpListener.AcceptSocket();
if (handlerSocket.Connected)
{
Control.CheckForIllegalCrossThreadCalls = false;
connections.Items.Add(
handlerSocket.RemoteEndPoint.ToString() + " connected.");
// clientIP = handlerSocket.RemoteEndPoint.ToString();
lock (this)
{
nSockets.Add(handlerSocket);
}
ThreadStart thdstHandler = new
ThreadStart(handlerThreadCommands);
Thread thdHandler = new Thread(thdstHandler);
thdHandler.Start();
//tcpListener.Stop();
//handlerSocket.Close();
}
}
}
//Handler Thread to receive commands
public void handlerThreadCommands()
{
Socket handlerSocketCommands = (Socket)nSockets[nSockets.Count - 1];
NetworkStream networkStream = new NetworkStream(handlerSocketCommands);
int requestRead = 0;
string dataReceived;
byte[] buffer = new byte[1024];
requestRead = networkStream.Read(buffer, 0, 1024);
char[] chars = new char[requestRead];
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(buffer, 0, requestRead, chars, 0);
dataReceived = new System.String(chars);
//connections.Items.Add(dataReceived);
if (dataReceived.Equals("list"))
{
localDate = DateTime.Now;
Files = Directory.GetFiles(System.IO.Directory.GetCurrentDirectory())
.Select(Path.GetFileName)
.ToArray();
String FilesString = "";
for (int i = 0; i < Files.Length; i++)
{
FilesString += Files[i] + "\n";
}
String clientIP = handlerSocketCommands.RemoteEndPoint.ToString();
int index = clientIP.IndexOf(":");
clientIP = clientIP.Substring(0, index);
WriteLogFile(logFilePath, clientIP, localDate.ToString(), " ", "list");
Console.WriteLine(clientIP);
Console.WriteLine(FilesString);
Byte[] bytesToSend = ASCIIEncoding.ASCII.GetBytes(FilesString);
try
{
WriteLogFile(logFilePath, clientIP, localDate.ToString(), " ", "list-response");
TcpClient clientSocket = new TcpClient(clientIP, 9090);
if (clientSocket.Connected)
{
NetworkStream networkStreamS = clientSocket.GetStream();
networkStreamS.Write(bytesToSend, 0, bytesToSend.Length);
networkStreamS.Close();
clientSocket.Close();
networkStream.Close();
//tcpListener.Stop();
// handlerSocketAuthenticate.Close();
}
}
catch
{
Console.WriteLine("Cant send");
}
}
else if (dataReceived.Equals("downloadfile"))
{
// handlerSocketAuthenticate.Close();
// tcpListener.Stop();
networkStream.Close();
thdListenerDownload = new Thread(new ThreadStart(listenerThreadDownloading));
thdListenerDownload.Start();
}
else
{
String clientIP1 = handlerSocketCommands.RemoteEndPoint.ToString();
int index = clientIP1.IndexOf(":");
clientIP1 = clientIP1.Substring(0, index);
// handlerSocketAuthenticate.Close();
CommandExecutor(dataReceived, clientIP1);
}
}
There are so many different things wrong with the code you posted, it's hard to know where to start, and it's impossible to have confidence that in the context of a Stack Overflow, one could sufficiently address all of the deficiencies. That said, in the interest of helping, it seems worth a try:
Sockets are bi-directional. There is no need for the client to use TcpListener at all. (By convention, the "server" is the endpoint that "listens" for new connections, and the "client" is the endpoint that initiates new connections, by connecting to a listening server.)You should just make a single connection from client to server, and then use that socket both for sending to and receiving from the server.
You are setting the CheckForIllegalCrossThreadCalls property to false. This is evil. The exceptions that occur are there to help you. Setting that property to false disables the exceptions, but does nothing to prevent the problems that the exceptions are designed to warn you about.You should use some mechanism to make sure that when you access UI objects, you do so only in the thread that owns those objects. The most primitive approach to this is to use Control.Invoke(). In modern C#, you are better off using async/await. With TcpClient, this is easy: you already are using GetStream() to get the NetworkStream object that represents the socket, so just use the asynchronous methods on that object, such as ReadAsync(), or if you wrap the stream in a StreamWriter and StreamReader, use the asynchronous methods on that object, such as ReadLineAsync().
You are checking the Connected property of the TcpClient object. This is pointless. When the Connect() method returns, you are connected. If you weren't, an exception would have been thrown.
You are not sufficiently synchronizing access to your nSockets object. In particular, you use its indexer in the handlerThreadList() method. This is safe when using the object concurrently only if you have guaranteed that no other thread is modifying the object, which is not the case in your code.
You are writing to the stream using ASCII encoding, but reading using UTF8 encoding. In practice, this is not really a problem, because ASCII includes only the code points 0-127, and those map exactly to the same character code points in UTF8. But it's really bad form. Pick one encoding, stick with it.
You are accepting using AcceptSocket(), but then just wrapping that in a NetworkStream anyway. Why not just use AcceptTcpClient() and call GetStream() on that? Both Socket and TcpClient are fine APIs, but it's a bit weird to mix and match in the same program, and will likely lead to some confusion later on, trying to keep straight which you're using where and why.
Your code assumes that the handlerThreadCommands() method will always be called in exactly the same order in which connections are accepted. That is, you retrieve the current socket with nSockets[nSockets.Count - 1]. But, due to the way Windows thread scheduling works, it is entirely possible that two or more connections could be accepted before any one of the threads meant to handle the connection is started, with the result that only the most recent connection is handled, and it is handled by those multiple threads.
You are assuming that command strings will be received as complete units. But this isn't how TCP works. TCP guarantees only that if you receive a byte, it will be in order relative to all the bytes sent before it. But you can receive any number of bytes. In particular, you can receive just a single byte, or you can receive multiple commands concatenated with each other, or you can receive half a command string, then the other half later, or the second half of one command and the first half of the next, etc. In practice, these problems don't show up in early testing because the server isn't operating under load, but later on they very well may be. And the code needs to be designed from the outset to work properly under these conditions; trying to patch bad code later is much more difficult.
I can't say that's the above are the only things wrong with the code, but they are most glaring, and in any case I think the above is sufficient food for thought for you at the moment.
Bottom line: you really should spend more time looking at good networking examples, and really getting to understand how they work and why they are written the way they do. You'll need to develop a good mental model for yourself of how the TCP protocol works, and make sure you are being very careful to follow the rules.
One resource I recommend highly is The Winsock Programmer's FAQ. It was written long ago, for a pre-.NET audience, but most of the information contained within is still very much relevant when using the higher-level networking APIs.
Alternatively, don't try to write low-level networking code yourself. There are a number of higher-level APIs that use various serialization techniques to encode whole objects and handle all of the lower-level network transport mechanics for you, allowing you to concentrate on the value-added features in your own program, instead of trying to reinvent the wheel.
Alright, so I'm pretty new to C# and I'm definitely pretty new to graphical programming. I'm using Visual Studio 2015 and writing my application in C#.
I have this hunk of code that I've been toying around with for a while. Essentially my program will send the HELLO, to the server, but the server isn't sending HELLO back. I have no firewall in the middle of client and server right now, but the process is getting hung waiting for the reply back. I honestly don't even want to do it this way, I want the listener to always run in the background while the user does other stuff so that my program functions, well normal. So I come to you oh great Stackoverflow... because I am definitely doing it wrong! Could someone please point me the right direction?
Current Code:
private void button1_Click(object sender, EventArgs e)
{
byte[] data = new byte[512];
byte[] result;
SHA512 shaM = new SHA512Managed();
result = shaM.ComputeHash(Encoding.UTF8.GetBytes(this.password.Text));
var hash = BitConverter.ToString(result).Replace("-", "");
this.send_message("127.0.0.1", 10545, 10545, "HELLO");
this.send_message("127.0.0.1", 10545, 10545, "AUTH:" + this.login.Text + ":" + hash);
//ListenForData.Start();
}
private void send_message(string server, int localPort, int remotePort, string message)
{
label4.Text = "Listening on port:" + localPort;
IPEndPoint lep = new IPEndPoint(IPAddress.Any, localPort);
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPAddress loginServer = IPAddress.Parse(server.ToString());
byte[] sendbuf = Encoding.ASCII.GetBytes(message);
IPEndPoint ep = new IPEndPoint(loginServer, remotePort);
s.SendTo(sendbuf, ep);
try
{
UdpClient udpClient = new UdpClient(lep);
byte[] bytes = udpClient.Receive(ref lep);
label4.Text = ep.ToString();
} catch ( Exception ex )
{
Console.WriteLine(ex.ToString());
}
}
UDPATE:
private static void UDPListener(object obj)
{
Task.Run(async () =>
{
using (var udpClient = new UdpClient(10545))
{
string rMessage = "";
string[] rArgs = new string[0];
while (true)
{
//IPEndPoint object will allow us to read datagrams sent from any source.
var receivedResults = await udpClient.ReceiveAsync();
rMessage += Encoding.ASCII.GetString(receivedResults.Buffer);
rArgs = rMessage.Split(new char[] { ':' });
if( rArgs[0] == "HELLO")
{
Console.Write("Received HELLO from server.");
byte[] data = new byte[512];
byte[] result;
SHA512 shaM = new SHA512Managed();
result = shaM.ComputeHash(Encoding.UTF8.GetBytes(obj.password.Text));
var hash = BitConverter.ToString(result).Replace("-", "");
send_message("127.0.0.1", 10545, 10545, "AUTH:" + obj.login.Text + ":" + hash);
}
}
}
});
}
You can safely ignore the advice from Blindy. The UdpClient.ReceiveAsync() method is specifically designed around the Task paradigm, and is much more efficient than dedicating a thread to receiving data. Because the Task is awaitable, it's also easier to integrate the ReceiveAsync() approach with a GUI program (e.g. Winforms or WPF). That said, for all that to work, you want to execute the ReceiveAsync() call in the UI thread, rather than in another Task.
Unfortunately, lacking a good, minimal, complete code example that clearly illustrates your question, it's not possible to say for sure how that would look. But it most likely would involve making your UDPListener() method an async method and calling it from the UI thread. Also, since you say the method can't access non-static members, the obvious solution to that is to make the method itself non-static. E.g.:
private static async Task UDPListener(object obj)
{
using (var udpClient = new UdpClient(10545))
{
string rMessage = "";
string[] rArgs = new string[0];
while (true)
{
//IPEndPoint object will allow us to read datagrams sent from any source.
var receivedResults = await udpClient.ReceiveAsync();
rMessage += Encoding.ASCII.GetString(receivedResults.Buffer);
rArgs = rMessage.Split(new char[] { ':' });
if( rArgs[0] == "HELLO")
{
Console.Write("Received HELLO from server.");
byte[] data = new byte[512];
byte[] result;
SHA512 shaM = new SHA512Managed();
result = shaM.ComputeHash(Encoding.UTF8.GetBytes(obj.password.Text));
var hash = BitConverter.ToString(result).Replace("-", "");
send_message("127.0.0.1", 10545, 10545, "AUTH:" + obj.login.Text + ":" + hash);
}
}
}
}
It is not clear from your updated question whether you have fixed your failed to receive a reply yet. But if you have not changed your send_message() method, it has some obvious problems, especially in the context of creating a separate UdpClient instance to receive datagrams.
The most obvious issue is that you do not create the UdpClient instance which you're expecting to use to receive the response until after you have sent the message. This is wrong for two reasons:
It's entirely possible that the remote endpoint will send the response before you've created the socket. If that happens, the datagram will just be dropped.
More importantly, the usual design for a server is to send a response to the remote endpoint that sent it the message in the first place. I.e. in this case that would be the socket s. Even if you did create the udpClient object before calling s.SendTo(), the reply would actually be sent to the s socket, not the udpClient.Client socket.
Again, without a good code example it's not possible to know what your server actually does. But whether it behaves like a normal server, or is some non-standard implementation, the code you've posted cannot reliably receive a response from the server.
You should fix your design so that your client creates only a single socket (e.g. an initial UdpClient instance). You would prepare for communication by calling ReceiveAsync(), and only once you've done that, thus ensuring you're ready to receive a response, then you can send data.
Make sure that you create only this single object.
Note also that client-side implementations typically do not bind to a specific port. Instead, you let the OS assign a port. Only the server needs to bind to a specific port, so that inbound requests from unknown endpoints can know to what port to send their request. The server's receive operation will include the port number of the client, so the server will know to what port to send its response, without the client having selected any special port number.
Finally, I strongly recommend you study existing socket programming tutorials, and especially the Winsock Programmer's FAQ. None of the material there is directly applicable to the .NET socket API, but most of the issues you will have trouble with are exactly the kinds of things documented there. It is not possible to use the .NET socket API without also having a good basic comprehension of socket programming generally.
If the above does not get you headed in the right direction, please make sure that any future questions you ask include a good code example, as described in the link I provided above. Note that for any networking question, a complete code example includes both the client and server. It is not possible for anyone to fully understand your question, never mind to test and fix your code example, without implementations of both.
So, I have this game, written in Unity, which is supposed to receive data in real-time over UDP. The data will be coming over wireless from an android device, written in Java, while the Unity program is written in C#. My problem is, whenever I try to declare a UdpClient object, and call its Receive() method inside the Update() method of Unity, the game hangs. Here's the code that I am trying to put inside my Update() method -
UdpClient client = new UdpClient(9877);
IPEndPoint receivePoint = new IPEndPoint(IPAddress.Parse("192.168.1.105"), 9877);
byte[] recData = client.Receive(ref receivePoint);
But it's causing the game to hang.
I then tried a different approach - I tried to receive the data in a separate thread. Works like magic if all I have to do is receive the byte array. No issues. Except that I also need the data received to be used as parameters to functions used in the actual game (for now, let's just say I need to display the received data bytes as a string in the main game window). But, I do not have knowledge of how cross-threading works in Unity. I tried this -
string data = string.Empty;
private IPEndPoint receivePoint;
void OnGUI()
{
GUI.Box(new Rect(20, 20, 100, 40), "");
GUI.Label(new Rect(30, 30, 100, 40), data);
}
void Start()
{
LoadClient();
}
public void LoadClient()
{
client = new UdpClient(9877);
receivePoint = new IPEndPoint(IPAddress.Parse("192.168.1.105"), 9877);
Thread startClient = new Thread(new ThreadStart(StartClient));
startClient.Start();
}
public void StartClient()
{
try
{
while (true)
{
byte[] recData = client.Receive(ref receivePoint);
System.Text.ASCIIEncoding encode = new System.Text.ASCIIEncoding();
data = encode.GetString(recData);
}
}
catch { }
}
But my program hangs if I try the above. So, what exactly am I missing here?
The reason it hangs for you is because that's the way Receive is defined. It blocks your current code until there is data available on the network connection (i.e. the underlying socket). You are correct that you should use a background thread for that.
Please note though, that creating threads in your game object scripts can be dangerous business in case you for example attach the script to multiple objects at the same time. You don't want multiple version of this script running at the same time because they would all try to bind to the same socket address (which won't work).
You also need to pay attention to closing down the threads if the game object dies (this is not automatically done in C# - you have to stop threads).
That said, when you are using multiple threads you need to ensure thread safety. This means that you need to protect the data so that you cannot read it while it is being written to. The simplest way to do this is to use C# locks:
private readonly Object _dataLock = new Object();
private string _sharedData = String.Empty;
void OnGUI()
{
string text = "";
lock (_dataLock)
text = _sharedData;
}
void StartClient()
{
// ... [snip]
var data = Encoding.ASCII.GetString(recData);
lock (_dataLock)
_sharedData = data;
}
Note that locks can hurt performance a bit, especially if used frequently. There are other ways to protect data in c# that are more performant (but slightly more complex). See this guideline from Microsoft for a few examples.
I recently wrote a quick-and-dirty proof-of-concept proxy server in C# as part of an effort to get a Java web application to communicate with a legacy VB6 application residing on another server. It's ridiculously simple:
The proxy server and clients both use the same message format; in the code I use a ProxyMessage class to represent both requests from clients and responses generated by the server:
public class ProxyMessage
{
int Length; // message length (not including the length bytes themselves)
string Body; // an XML string containing a request/response
// writes this message instance in the proper network format to stream
// (helper for response messages)
WriteToStream(Stream stream) { ... }
}
The messages are as simple as could be: the length of the body + the message body.
I have a separate ProxyClient class that represents a connection to a client. It handles all the interaction between the proxy and a single client.
What I'm wondering is are they are design patterns or best practices for simplifying the boilerplate code associated with asynchronous socket programming? For example, you need to take some care to manage the read buffer so that you don't accidentally lose bytes, and you need to keep track of how far along you are in the processing of the current message. In my current code, I do all of this work in my callback function for TcpClient.BeginRead, and manage the state of the buffer and the current message processing state with the help of a few instance variables.
The code for my callback function that I'm passing to BeginRead is below, along with the relevant instance variables for context. The code seems to work fine "as-is", but I'm wondering if it can be refactored a bit to make it clearer (or maybe it already is?).
private enum BufferStates
{
GetMessageLength,
GetMessageBody
}
// The read buffer. Initially 4 bytes because we are initially
// waiting to receive the message length (a 32-bit int) from the client
// on first connecting. By constraining the buffer length to exactly 4 bytes,
// we make the buffer management a bit simpler, because
// we don't have to worry about cases where the buffer might contain
// the message length plus a few bytes of the message body.
// Additional bytes will simply be buffered by the OS until we request them.
byte[] _buffer = new byte[4];
// A count of how many bytes read so far in a particular BufferState.
int _totalBytesRead = 0;
// The state of the our buffer processing. Initially, we want
// to read in the message length, as it's the first thing
// a client will send
BufferStates _bufferState = BufferStates.GetMessageLength;
// ...ADDITIONAL CODE OMITTED FOR BREVITY...
// This is called every time we receive data from
// the client.
private void ReadCallback(IAsyncResult ar)
{
try
{
int bytesRead = _tcpClient.GetStream().EndRead(ar);
if (bytesRead == 0)
{
// No more data/socket was closed.
this.Dispose();
return;
}
// The state passed to BeginRead is used to hold a ProxyMessage
// instance that we use to build to up the message
// as it arrives.
ProxyMessage message = (ProxyMessage)ar.AsyncState;
if(message == null)
message = new ProxyMessage();
switch (_bufferState)
{
case BufferStates.GetMessageLength:
_totalBytesRead += bytesRead;
// if we have the message length (a 32-bit int)
// read it in from the buffer, grow the buffer
// to fit the incoming message, and change
// state so that the next read will start appending
// bytes to the message body
if (_totalBytesRead == 4)
{
int length = BitConverter.ToInt32(_buffer, 0);
message.Length = length;
_totalBytesRead = 0;
_buffer = new byte[message.Length];
_bufferState = BufferStates.GetMessageBody;
}
break;
case BufferStates.GetMessageBody:
string bodySegment = Encoding.ASCII.GetString(_buffer, _totalBytesRead, bytesRead);
_totalBytesRead += bytesRead;
message.Body += bodySegment;
if (_totalBytesRead >= message.Length)
{
// Got a complete message.
// Notify anyone interested.
// Pass a response ProxyMessage object to
// with the event so that receivers of OnReceiveMessage
// can send a response back to the client after processing
// the request.
ProxyMessage response = new ProxyMessage();
OnReceiveMessage(this, new ProxyMessageEventArgs(message, response));
// Send the response to the client
response.WriteToStream(_tcpClient.GetStream());
// Re-initialize our state so that we're
// ready to receive additional requests...
message = new ProxyMessage();
_totalBytesRead = 0;
_buffer = new byte[4]; //message length is 32-bit int (4 bytes)
_bufferState = BufferStates.GetMessageLength;
}
break;
}
// Wait for more data...
_tcpClient.GetStream().BeginRead(_buffer, 0, _buffer.Length, this.ReadCallback, message);
}
catch
{
// do nothing
}
}
So far, my only real thought is to extract the buffer-related stuff into a separate MessageBuffer class and simply have my read callback append new bytes to it as they arrive. The MessageBuffer would then worry about things like the current BufferState and fire an event when it received a complete message, which the ProxyClient could then propagate further up to the main proxy server code, where the request can be processed.
I've had to overcome similar problems. Here's my solution (modified to fit your own example).
We create a wrapper around Stream (a superclass of NetworkStream, which is a superclass of TcpClient or whatever). It monitors reads. When some data is read, it is buffered. When we receive a length indicator (4 bytes) we check if we have a full message (4 bytes + message body length). When we do, we raise a MessageReceived event with the message body, and remove the message from the buffer. This technique automatically handles fragmented messages and multiple-messages-per-packet situations.
public class MessageStream : IMessageStream, IDisposable
{
public MessageStream(Stream stream)
{
if(stream == null)
throw new ArgumentNullException("stream", "Stream must not be null");
if(!stream.CanWrite || !stream.CanRead)
throw new ArgumentException("Stream must be readable and writable", "stream");
this.Stream = stream;
this.readBuffer = new byte[512];
messageBuffer = new List<byte>();
stream.BeginRead(readBuffer, 0, readBuffer.Length, new AsyncCallback(ReadCallback), null);
}
// These belong to the ReadCallback thread only.
private byte[] readBuffer;
private List<byte> messageBuffer;
private void ReadCallback(IAsyncResult result)
{
int bytesRead = Stream.EndRead(result);
messageBuffer.AddRange(readBuffer.Take(bytesRead));
if(messageBuffer.Count >= 4)
{
int length = BitConverter.ToInt32(messageBuffer.Take(4).ToArray(), 0); // 4 bytes per int32
// Keep buffering until we get a full message.
if(messageBuffer.Count >= length + 4)
{
messageBuffer.Skip(4);
OnMessageReceived(new MessageEventArgs(messageBuffer.Take(length)));
messageBuffer.Skip(length);
}
}
// FIXME below is kinda hacky (I don't know the proper way of doing things...)
// Don't bother reading again. We don't have stream access.
if(disposed)
return;
try
{
Stream.BeginRead(readBuffer, 0, readBuffer.Length, new AsyncCallback(ReadCallback), null);
}
catch(ObjectDisposedException)
{
// DO NOTHING
// Ends read loop.
}
}
public Stream Stream
{
get;
private set;
}
public event EventHandler<MessageEventArgs> MessageReceived;
protected virtual void OnMessageReceived(MessageEventArgs e)
{
var messageReceived = MessageReceived;
if(messageReceived != null)
messageReceived(this, e);
}
public virtual void SendMessage(Message message)
{
// Have fun ...
}
// Dispose stuff here
}
I think the design you've used is fine that's roughly how I would and have done the same sort of thing. I don't think you'd gain much by refactoring into additional classes/structs and from what I've seen you'd actually make the solution more complex by doing so.
The only comment I'd have is as to whether the two reads where the first is always the messgae length and the second always being the body is robust enough. I'm always wary of approaches like that as if they somehow get out of sync due to an unforseen circumstance (such as the other end sending the wrong length) it's very difficult to recover. Instead I'd do a single read with a big buffer so that I always get all the available data from the network and then inspect the buffer to extract out complete messages. That way if things do go wrong the current buffer can just be thrown away to get things back to a clean state and only the current messages are lost rather than stopping the whole service.
Actually at the moment you would have a problem if you message body was big and arrived in two seperate receives and the next message in line sent it's length at the same time as the second half of the previous body. If that happened your message length would end up appended to the body of the previous message and you'd been in the situation as desecribed in the previous paragraph.
You can use yield return to automate the generation of a state machine for asynchronous callbacks. Jeffrey Richter promotes this technique through his AsyncEnumerator class, and I've played around with the idea here.
There's nothing wrong with the way you've done it. For me, though, I like to separate the receiving of the data from the processing of it, which is what you seem to be thinking with your proposed MessageBuffer class. I have discussed that in detail here.