How to make sure I wont lose data from TCP? - c#

I'm sorry if I'm asking something asked before.
I'm developing a program that reads data received via TCP and using a StreamReader, and I just can't find how to make sure that any data won't be missed. Is there any way to create a middle buffer to read from there or something like that?
Here are the methods I've created for receiving data and write it to a text box:
public static void Connect(string IP, string port)
{
try
{
client = new TcpClient();
IPEndPoint IP_End = new IPEndPoint(IPAddress.Parse(IP), int.Parse(port));
client.Connect(IP_End);
if (client.Connected)
{
connected = "Connected to Exemys!" + "\r\n";
STR = new StreamReader(client.GetStream());
bgWorker = true;
}
}
catch (Exception x)
{
MessageBox.Show(x.Message.ToString());
}
}
-
public static void MessageReceiving(TextBox textBox)
{
try
{
string values =Conection.STR.ReadLine();
textBox.Invoke(new MethodInvoker(delegate () { textBox.AppendText("Exemys : " + values.Substring(2) + Environment.NewLine); }));
try
{
string messagetype = values.Substring(5, 1);
string ID = values.Substring(3, 2);
string checksum = values.Substring(values.Length - 2, 2);
if (checksum == CalcularChecksum(values.Substring(3, values.Length - 5)))
{
if (messagetype == "N")
{
if (ID == "01")
{
ID1 = values.Substring(3, 2);
messagetype1 = values.Substring(5, 1);
capacity1 = values.Substring(6, 1);
pressure1 = values.Split(',')[1];
sequencetime1 = values.Split(',')[2];
runstatus1 = values.Split(',')[3];
mode1 = values.Split(',')[4].Substring(0, 1);
checksum1 = CalcularChecksum(values.Substring(3, values.Length - 5));
}
if (ID == "02")
{
ID2 = values.Substring(3, 2);
messagetype2 = values.Substring(5, 1);
capacity2 = values.Substring(6, 1);
pressure2 = values.Split(',')[1];
sequencetime2 = values.Split(',')[2];
runstatus2 = values.Split(',')[3];
mode2 = values.Split(',')[4].Substring(0, 1);
checksum2 = CalcularChecksum(values.Substring(3, values.Length - 5));
}
}
}
}
catch(Exception x)
{
MessageBox.Show(x.Message.ToString());
}
}
catch (Exception)
{
MessageBox.Show("Client disconnected.");
}
}
Edit: what I'm trying to ask is how to always process the entire data before continue receiving? That would be the question.

A TCP stream is a stream of bytes that ends when the socket is closed by you or the remote peer or breaks because of network issues. In order to get everything from the stream you need to call the StreamReader.ReadLine method inside a loop into a buffer until some stop condition applies.
...
try
{
while(true)
{
...
input = STR.ReadLine();
if (input == <some stop condition>)
break;
...
}
}
...
That's a highly simplified example. TCP reading with partial buffer handling can be a complex beast so I recommend to use a library or framework if you're doing more than some hobby project.

Thanks for the response, but after searching, I've found what I was looking for. I wanted to store those messages (data) that were entering to make sure that I won't lose them (for any reason, more precisely that the receiving process would be faster than the message processing operation), so I used Queue to achieve this.
public static void RecepcionMensajes(TextBox textBox)
{
if (client.Connected == true)
{
try
{
string fifo = Conexion.STR.ReadLine();
Queue mensajes = new Queue();
//AquĆ­ se ponen en cola los mensajes que van llegando, utilizando el sistema FIFO.
mensajes.Enqueue(fifo);
string values = mensajes.Dequeue().ToString();
textBox.Invoke(new MethodInvoker(delegate () { textBox.AppendText("Exemys : " + values.Substring(2) + Environment.NewLine); }));

Related

Get the live data on TCP server from UWP (UPDATED)

Below is one method that basically sends the data to TCP Server.
UPDATE BEGINS HERE:
//////////////////////////////////
private string FormatValueByPresentation(IBuffer buffer, GattPresentationFormat format)
{
// BT_Code: For the purpose of this sample, this function converts only UInt32 and
// UTF-8 buffers to readable text. It can be extended to support other formats if your app needs them.
byte[] data;
CryptographicBuffer.CopyToByteArray(buffer, out data);
if (format != null)
{
if (format.FormatType == GattPresentationFormatTypes.UInt32 && data.Length >= 4)
{
return BitConverter.ToInt32(data, 0).ToString();
}
else if (format.FormatType == GattPresentationFormatTypes.Utf8)
{
try
{
return Encoding.UTF8.GetString(data);
}
catch (ArgumentException)
{
return "(error: Invalid UTF-8 string)";
}
}
else
{
// Add support for other format types as needed.
return "Unsupported format: " + CryptographicBuffer.EncodeToHexString(buffer);
}
}
else if (data != null)
{
// We don't know what format to use. Let's try some well-known profiles, or default back to UTF-8.
if (selectedCharacteristic.Uuid.Equals(GattCharacteristicUuids.HeartRateMeasurement))
{
try
{
///////LOOK HERE/////
**string b = ParseHeartRateValue(data).ToString();
TrySend(b);
//return "Heart Rate: " + ParseHeartRateValue(data).ToString();
return "Heart Rate: " + b;**
}
catch (ArgumentException)
{
return "Heart Rate: (unable to parse)";
}
}
else if (selectedCharacteristic.Uuid.Equals(GattCharacteristicUuids.BatteryLevel))
{
try
{
// battery level is encoded as a percentage value in the first byte according to
// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.battery_level.xml
return "Battery Level: " + data[0].ToString() + "%";
}
catch (ArgumentException)
{
return "Battery Level: (unable to parse)";
}
}
// This is our custom calc service Result UUID. Format it like an Int
else if (selectedCharacteristic.Uuid.Equals(Constants.ResultCharacteristicUuid))
{
return BitConverter.ToInt32(data, 0).ToString();
}
// No guarantees on if a characteristic is registered for notifications.
else if (registeredCharacteristic != null)
{
// This is our custom calc service Result UUID. Format it like an Int
if (registeredCharacteristic.Uuid.Equals(Constants.ResultCharacteristicUuid))
{
return BitConverter.ToInt32(data, 0).ToString();
}
}
else
{
try
{
return "Unknown format: " + Encoding.UTF8.GetString(data);
}
catch (ArgumentException)
{
return "Unknown format";
}
}
}
else
{
return "Empty data received";
}
return "Unknown format";
}
///////// END OF UPDATE //////
private async void TrySend(string data)
{
// Create the StreamSocket and establish a connection to the echo server.
StreamSocket socket = new StreamSocket();
try
{
var streamSocket = new Windows.Networking.Sockets.StreamSocket();
{
//The server hostname that we will be establishing a connection to. In this example, the server and client are in the same process.
var hostName = new Windows.Networking.HostName("127.0.0.1");
await streamSocket.ConnectAsync((new Windows.Networking.HostName("127.0.0.1")), "9999");
// Send a request to the echo server.
using (Stream outputStream = streamSocket.OutputStream.AsStreamForWrite())
{
using (var streamWriter = new StreamWriter(outputStream))
{
while (true)
{
await streamWriter.WriteLineAsync(data);
await streamWriter.FlushAsync();
}
//await streamWriter.WriteLineAsync(data);
//await streamWriter.FlushAsync();
}
}
}
}
catch (Exception)
{
}
}
And here is my TCP Server code that receives the data:
public class EchoServer {
public static void Main() {
TcpListener listener = null;
try
{
listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 9999);
listener.Start();
Console.WriteLine("TCP Server Has Started....");
while (true)
{
Console.WriteLine(" ");
Console.WriteLine("Waiting for incoming client connections....");
Console.WriteLine(" ");
Console.WriteLine("A message will display below once the client starts and establishes a connection ");
TcpClient client = listener.AcceptTcpClient();
Console.WriteLine(" ");
Console.WriteLine("Okay, Accepting Client connection now");
Console.WriteLine(" ");
Console.WriteLine("Accepted new client connection.....");
StreamReader reader = new StreamReader(client.GetStream());
StreamWriter writer = new StreamWriter(client.GetStream());
string s = string.Empty;
while (!(s = reader.ReadLine()).Equals("Exit") || (s == null)) {
Console.WriteLine("From client -> " + s);
writer.WriteLine("From server -> " + s);
writer.Flush();
}
reader.Close();
writer.Close();
client.Close();
}
} catch (Exception e)
{
Console.WriteLine(e);
} finally
{
if (listener != null)
{
listener.Stop();
}
}
}
}
Now, the data I am trying to get are the heart rates and it changes every two seconds. However on TCP server I only get the first recorded value of a heart rate and it keeps repeating instead of getting new one.
There is a similar post I saw here on stackoverflow : UWP TCP receive data continuously
and someone suggested to use while loop which I did as you can see in the code.
Are there any other suggestions on what should I do?
Thanks
while (true)
{
await streamWriter.WriteLineAsync(data);
await streamWriter.FlushAsync();
}
The while(true) will keep repeating, meaning that it will always send 'data' at its current value. This is what causes your issue.
In my opinion, you should keep a connection to your TCP server open outside of your 'TrySend' method, and use this method only to send the data. This way you won't need to use this loop.
EDIT :
Try this :
private async void CharacteristicReadButton_Click()
{
while(true)
{
// BT_Code: Read the actual value from the device by using Uncached.
GattReadResult result = await selectedCharacteristic.ReadValueAsync(BluetoothCacheMode.Uncached);
if (result.Status == GattCommunicationStatus.Success)
{
string formattedResult = FormatValueByPresentation(result.Value, presentationFormat);
rootPage.NotifyUser($"Read result: {formattedResult}", NotifyType.StatusMessage);
//string formattedResult = FormatValueByPresentation(result.Value, presentationFormat);
//rootPage.NotifyUser($"Read result: {formattedResult}", NotifyType.StatusMessage);
}
else
{
rootPage.NotifyUser($"Read failed: {result.Status}", NotifyType.ErrorMessage);
}
}
}

How to create TCP message framing for the stream

Here is how my Client connects to the server:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Net.Sockets;
using System.IO;
using System;
using System.Text.RegularExpressions;
using UnityEngine.SceneManagement;
using Newtonsoft.Json;
using System.Linq;
public class ClientWorldServer : MonoBehaviour {
public bool socketReady;
public static TcpClient socket;
public static NetworkStream stream;
public static StreamWriter writer;
public static StreamReader reader;
public void ConnectToWorldServer()
{
if (socketReady)
{
return;
}
//Default host and port values;
string host = "127.0.0.1";
int port = 8080;
try
{
socket = new TcpClient(host, port);
stream = socket.GetStream();
writer = new StreamWriter(stream);
reader = new StreamReader(stream);
socketReady = true;
}
catch (Exception e)
{
Debug.Log("Socket error : " + e.Message);
}
}
}
Here is how i send data to the server using my Send function:
public void Send(string header, Dictionary<string, string> data)
{
if (stream.CanRead)
{
socketReady = true;
}
if (!socketReady)
{
return;
}
JsonData SendData = new JsonData();
SendData.header = "1x" + header;
foreach (var item in data)
{
SendData.data.Add(item.Key.ToString(), item.Value.ToString());
}
SendData.connectionId = connectionId;
string json = JsonConvert.SerializeObject(SendData);
var howManyBytes = json.Length * sizeof(Char);
writer.WriteLine(json);
writer.Flush();
Debug.Log("Client World:" + json);
}
As you can see i'm sending the data to the Stream like a string not like a byte array. As far as i know i should send the data as byte array prepending the size of the message and following the message. On the server side i have no clue how i can read that data.
Here is how i read it now(it works for now, but it will not work if i try to send more messages at once):
class WorldServer
{
public List<ServerClient> clients = new List<ServerClient>();
public List<ServerClient> disconnectList;
public List<CharactersOnline> charactersOnline = new List<CharactersOnline>();
public int port = 8080;
private TcpListener server;
private bool serverStarted;
private int connectionIncrementor;
private string mysqlConnectionString = #"server=xxx;userid=xxx;password=xxx;database=xx";
private MySqlConnection mysqlConn = null;
private MySqlDataReader mysqlReader;
static void Main(string[] args)
{
WorldServer serverInstance = new WorldServer();
Console.WriteLine("Starting World Server...");
try
{
serverInstance.mysqlConn = new MySqlConnection(serverInstance.mysqlConnectionString);
serverInstance.mysqlConn.Open();
Console.WriteLine("Connected to MySQL version: " + serverInstance.mysqlConn.ServerVersion + "\n");
}
catch (Exception e)
{
Console.WriteLine("MySQL Error: " + e.ToString());
}
finally
{
if (serverInstance.mysqlConn != null)
{
serverInstance.mysqlConn.Close();
}
}
serverInstance.clients = new List<ServerClient>();
serverInstance.disconnectList = new List<ServerClient>();
try
{
serverInstance.server = new TcpListener(IPAddress.Any, serverInstance.port);
serverInstance.server.Start();
serverInstance.StartListening();
serverInstance.serverStarted = true;
Console.WriteLine("Server has been started on port: " + serverInstance.port);
}
catch (Exception e)
{
Console.WriteLine("Socket error: " + e.Message);
}
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
/* run your code here */
while (true)
{
string input = Console.ReadLine();
string[] commands = input.Split(':');
if (commands[0] == "show online players")
{
Console.WriteLine("Showing connections\n");
foreach (CharactersOnline c in serverInstance.charactersOnline)
{
Console.WriteLine("Character name: " + c.characterName + "Character ID: " + c.characterId + "Connection id: " + c.connectionId + "\n");
}
}
continue;
}
}).Start();
while (true)
{
serverInstance.Update();
}
}
private void Update()
{
//Console.WriteLine("Call");
if (!serverStarted)
{
return;
}
foreach (ServerClient c in clients.ToList())
{
// Is the client still connected?
if (!IsConnected(c.tcp))
{
c.tcp.Close();
disconnectList.Add(c);
Console.WriteLine(c.connectionId + " has disconnected.");
CharacterLogout(c.connectionId);
continue;
//Console.WriteLine("Check for connection?\n");
}
else
{
// Check for message from Client.
NetworkStream s = c.tcp.GetStream();
if (s.DataAvailable)
{
StreamReader reader = new StreamReader(s, true);
string data = reader.ReadLine();
if (data != null)
{
OnIncomingData(c, data);
}
}
//continue;
}
}
for (int i = 0; i < disconnectList.Count - 1; i++)
{
clients.Remove(disconnectList[i]);
disconnectList.RemoveAt(i);
}
}
private void OnIncomingData(ServerClient c, string data)
{
Console.WriteLine(data);
dynamic json = JsonConvert.DeserializeObject(data);
string header = json.header;
//Console.WriteLine("Conn ID:" + json.connectionId);
string connId = json.connectionId;
int.TryParse(connId, out int connectionId);
string prefix = header.Substring(0, 2);
if (prefix != "1x")
{
Console.WriteLine("Unknown packet: " + data + "\n");
}
else
{
string HeaderPacket = header.Substring(2);
switch (HeaderPacket)
{
default:
Console.WriteLine("Unknown packet: " + data + "\n");
break;
case "004":
int accountId = json.data["accountId"];
SelectAccountCharacters(accountId, connectionId);
break;
case "005":
int characterId = json.data["characterId"];
getCharacterDetails(characterId, connectionId);
break;
case "006":
int charId = json.data["characterId"];
SendDataForSpawningOnlinePlayers(charId, connectionId);
break;
case "008":
Dictionary<string, string> dictObj = json.data.ToObject<Dictionary<string, string>>();
UpdateCharacterPosition(dictObj, connectionId);
break;
}
}
private bool IsConnected(TcpClient c)
{
try
{
if (c != null && c.Client != null && c.Client.Connected)
{
if (c.Client.Poll(0, SelectMode.SelectRead))
{
return !(c.Client.Receive(new byte[1], SocketFlags.Peek) == 0);
}
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}
private void StartListening()
{
server.BeginAcceptTcpClient(OnConnection, server);
}
private void OnConnection(IAsyncResult ar)
{
connectionIncrementor++;
TcpListener listener = (TcpListener)ar.AsyncState;
clients.Add(new ServerClient(listener.EndAcceptTcpClient(ar)));
clients[clients.Count - 1].connectionId = connectionIncrementor;
StartListening();
//Send a message to everyone, say someone has connected!
Dictionary<string, string> SendDataBroadcast = new Dictionary<string, string>();
SendDataBroadcast.Add("connectionId", clients[clients.Count - 1].connectionId.ToString());
Broadcast("001", SendDataBroadcast, clients, clients[clients.Count - 1].connectionId);
Console.WriteLine(clients[clients.Count - 1].connectionId + " has connected.");
}
And this is how the server send back data to the client:
private void Send(string header, Dictionary<string, string> data, int cnnId)
{
foreach (ServerClient c in clients.ToList())
{
if (c.connectionId == cnnId)
{
try
{
//Console.WriteLine("Sending...");
StreamWriter writer = new StreamWriter(c.tcp.GetStream());
if (header == null)
{
header = "000";
}
JsonData SendData = new JsonData();
SendData.header = "0x" + header;
foreach (var item in data)
{
SendData.data.Add(item.Key.ToString(), item.Value.ToString());
}
SendData.connectionId = cnnId;
string JSonData = JsonConvert.SerializeObject(SendData);
writer.WriteLine(JSonData);
writer.Flush();
//Console.WriteLine("Trying to send data to connection id: " + cnnId + " data:" + sendData);
}
catch (Exception e)
{
Console.WriteLine("Write error : " + e.Message + " to client " + c.connectionId);
}
}
}
}
Here is my ServerClient class:
public class ServerClient
{
public TcpClient tcp;
public int accountId;
public int connectionId;
public ServerClient(TcpClient clientSocket)
{
tcp = clientSocket;
}
}
Can you please show me how i should modify my Send function on the client to send the data as byte array so i can create "TCP Message Framing" and how should i change my the following part on the server:
foreach (ServerClient c in clients.ToList())
{
// Is the client still connected?
if (!IsConnected(c.tcp))
{
c.tcp.Close();
disconnectList.Add(c);
Console.WriteLine(c.connectionId + " has disconnected.");
CharacterLogout(c.connectionId);
continue;
//Console.WriteLine("Check for connection?\n");
}
else
{
// Check for message from Client.
NetworkStream s = c.tcp.GetStream();
if (s.DataAvailable)
{
StreamReader reader = new StreamReader(s, true);
string data = reader.ReadLine();
if (data != null)
{
OnIncomingData(c, data);
}
}
//continue;
}
}
which is responsible for receving data on the server ?
Is it possible to change only these parts from the Client and on the Server and make it continue to work but this time properly with TCP Message Framing ?
Of course the listener on the client and the Send function of the server i'll remake once i understand how this framing should look like.
Your frames are already defined by cr/lf - so that much already exists; what you need to do is to keep a back buffer per stream - something like a MemoryStream might be sufficient, depending on how big you need to scale; then essentially what you're looking to do is something like:
while (s.DataAvailable)
{
// try to read a chunk of data from the inbound stream
int bytesRead = s.Read(someBuffer, 0, someBuffer.Length);
if(bytesRead > 0) {
// append to our per-socket back-buffer
perSocketStream.Position = perSocketStream.Length;
perSocketStream.Write(someBuffer, 0, bytesRead);
int frameSize; // detect any complete frame(s)
while((frameSize = DetectFirstCRLF(perSocketStream)) >= 0) {
// decode it as text
var backBuffer = perSocketStream.GetBuffer();
string message = encoding.GetString(
backBuffer, 0, frameSize);
// remove the frame from the start by copying down and resizing
Buffer.BlockCopy(backBuffer, frameSize, backBuffer, 0,
(int)(backBuffer.Length - frameSize));
perSocketStream.SetLength(backBuffer.Length - frameSize);
// process it
ProcessMessage(message);
}
}
}

C# Socket: Client Mishandle 'a' as the Client's id

There are two programs that I made that didn't work. There are the server and the client. The server accepts many client by giving a user an ID (starting from 0). The server sends out the command to the specific client based up the server's id. (Example: 200 client are connected to 1 server. The server's selected id is '5', so the server will send the command to all of the client, and the client will ask the server what ID he wants to execute his command on, and if it's '5', that client will execute and send data to the server). The client has many commands, but to create the smallest code with the same error, I only use 1 command (dir). Basically, the server sends the command to the client and if it matches with the client current id and the server current id, it will process the command. By default, the server's current ID is 10. Here are the list of the commands to help the people who wants to answer:
Server Command:
list (Shows all of the users ID connected and the server's current ID) --> Happens on server
dir (request the client to send its dir listing) --> Sent by the client, read by the Server
set (set the server's current id to any number) (example: 'set 4')
Client:
using System;
using System.Speech.Synthesis;
using System.Windows.Forms;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Net;
namespace clientControl
{
class Program
{
public static string directory = #"C:\";
public static int id = -10;
public static Socket sck = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
static void Main(string[] args)
{
Connect();
getSession();
ReadResponse(sck);
}
static byte[] readResponseFunc()
{
long fileSize = 0;
string fileSizeInString = null;
byte[] fileSizeInByteArray = new byte[1024];
int fileSizeLength = sck.Receive(fileSizeInByteArray);
for (int i = 0; i < fileSizeLength; i++)
{
fileSizeInString = fileSizeInString + (char)fileSizeInByteArray[i];
}
try
{
fileSize = Convert.ToInt64(fileSizeInString);
}
catch { Console.WriteLine(fileSizeInString); }
sck.Send(Encoding.ASCII.GetBytes("a"));
byte[] responseUnknown = new byte[1];
sck.Receive(responseUnknown);
if (Encoding.ASCII.GetString(responseUnknown) == "b")
{
byte[] dataInByteArray = new byte[fileSize];
int dataLength = sck.Receive(dataInByteArray);
return dataInByteArray;
}
return Encoding.ASCII.GetBytes("ERROR RESPONSE FUNC");
}
static void getSession()
{
byte[] message_1 = Encoding.ASCII.GetBytes("make_id");
sck.Send(Encoding.ASCII.GetBytes(message_1.Length.ToString()));
byte[] responseUnknown = new byte[1];
if (Encoding.ASCII.GetString(responseUnknown) == "a")
{
sck.Send(Encoding.ASCII.GetBytes("b"));
sck.Send(message_1);
}
byte[] receivedID = readResponseFunc();
id = Convert.ToInt32(Encoding.ASCII.GetString(receivedID));
}
static bool SocketConnected(Socket s)
{
bool part1 = s.Poll(1000, SelectMode.SelectRead);
bool part2 = (s.Available == 0);
if (part1 && part2)
return false;
else
return true;
}
static void ReadResponse(Socket sck)
{
while (true)
{
if (SocketConnected(sck) == true)
{
try
{
string response = Encoding.ASCII.GetString(readResponseFunc());
byte[] message_1 = Encoding.ASCII.GetBytes("get_id");
sck.Send(Encoding.ASCII.GetBytes(message_1.Length.ToString()));
byte[] responseUnknown = new byte[1];
if (Encoding.ASCII.GetString(responseUnknown) == "a")
{
sck.Send(Encoding.ASCII.GetBytes("b"));
sck.Send(message_1);
}
byte[] response_2InByteArray = readResponseFunc();
string response_2 = Encoding.ASCII.GetString(response_2InByteArray);
if (Convert.ToInt32(response_2) == id)
{
if (response == "dir")
{
string resultOfDirring = "Current Directory: " + directory + "\n\n";
string[] folderListingArray = Directory.GetDirectories(directory);
foreach (string dir in folderListingArray)
{
string formed = "DIRECTORY: " + Path.GetFileName(dir);
resultOfDirring = resultOfDirring + formed + Environment.NewLine;
}
string[] fileListingArray = Directory.GetFiles(directory);
foreach (var file in fileListingArray)
{
FileInfo fileInfo = new FileInfo(file);
string formed = "FILE: " + Path.GetFileName(file) + " - FILE SIZE: " + fileInfo.Length + " BYTES";
resultOfDirring = resultOfDirring + formed + Environment.NewLine;
}
byte[] message_11 = Encoding.ASCII.GetBytes(resultOfDirring);
sck.Send(Encoding.ASCII.GetBytes(message_11.Length.ToString()));
byte[] responseUnknown_11 = new byte[1];
if (Encoding.ASCII.GetString(responseUnknown_11) == "a")
{
sck.Send(Encoding.ASCII.GetBytes("b"));
sck.Send(message_11);
}
}
}
else { }
}
catch { if (SocketConnected(sck) == false) { Console.WriteLine("Client Disconnected: " + sck.RemoteEndPoint); break; }; }
}
else if (SocketConnected(sck) == false) { Console.WriteLine("Client Disconnected: " + sck.RemoteEndPoint); break; }
}
}
static void Connect()
{
while (true)
{
try
{
sck.Connect(IPAddress.Parse("127.0.0.1"), 80);
break;
}
catch { }
}
}
}
}
Server:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Text;
using System.Threading;
namespace serverControl
{
class Program
{
public static int ftpNum = 1;
public static List<int> listOfClient = new List<int>();
public static TcpListener server = new TcpListener(IPAddress.Parse("127.0.0.1"), 80);
public static string message = null;
public static int id = 0;
public static int selected_id = 10;
static void Main(string[] args)
{
server.Start();
Thread startHandlingClientThread = new Thread(startHandlingClient);
startHandlingClientThread.Start();
while (true)
{
Console.Write(":> ");
string rawmessage = Console.ReadLine();
if (rawmessage == "list")
{
Console.WriteLine("SELECTED ID: " + selected_id);
Console.WriteLine("List of Clients ID:");
for (int i = 0; i < listOfClient.Count; i++)
{
Console.WriteLine(listOfClient[i]);
}
message = rawmessage+"PREVENT_REPETITION_IN_COMMAND";
}
else if (rawmessage.Contains("set ")) { int wantedChangeId = Convert.ToInt32(rawmessage.Replace("set ", "")); selected_id = wantedChangeId; message = rawmessage+ "PREVENT_REPETITION_IN_COMMAND"; }
else
{
message = rawmessage;
}
}
}
static byte[] readResponseFunc(Socket sck)
{
long fileSize = 0;
string fileSizeInString = null;
byte[] fileSizeInByteArray = new byte[1024];
int fileSizeLength = sck.Receive(fileSizeInByteArray);
for (int i = 0; i < fileSizeLength; i++)
{
fileSizeInString = fileSizeInString + (char)fileSizeInByteArray[i];
}
fileSize = Convert.ToInt64(fileSizeInString);
sck.Send(Encoding.ASCII.GetBytes("a"));
byte[] responseUnknown = new byte[1];
sck.Receive(responseUnknown);
if (Encoding.ASCII.GetString(responseUnknown) == "b")
{
byte[] dataInByteArray = new byte[fileSize];
int dataLength = sck.Receive(dataInByteArray);
return dataInByteArray;
}
return Encoding.ASCII.GetBytes("ERROR RESPONSE FUNC");
}
static void startHandlingClient()
{
while (true)
{
handleClient(server);
}
}
static void handleClient(TcpListener clientToAccept)
{
Socket sck = clientToAccept.AcceptSocket();
Thread myNewThread = new Thread(() => ReadResponse(sck));
myNewThread.Start();
}
static bool SocketConnected(Socket s)
{
bool part1 = s.Poll(1000, SelectMode.SelectRead);
bool part2 = (s.Available == 0);
if (part1 && part2)
return false;
else
return true;
}
static void ReadResponse(Socket sck)
{
Thread myNewThread = new Thread(() => SendtoClient(sck));
myNewThread.Start();
Thread.Sleep(2000);
while (true)
{
if (SocketConnected(sck) == true)
{
try
{
byte[] dataInByteArray = readResponseFunc(sck);
string response = Encoding.ASCII.GetString(dataInByteArray);
Console.WriteLine("res: " + response);
if (response != "make_id" && response != "get_id") { Console.WriteLine(response); }
if (response == "make_id")
{
Console.WriteLine("Someone wants an ID");
byte[] message_1 = Encoding.ASCII.GetBytes(id.ToString());
listOfClient.Add(id);
// START
sck.Send(Encoding.ASCII.GetBytes(message_1.Length.ToString()));
byte[] responseUnknown = new byte[1];
if (Encoding.ASCII.GetString(responseUnknown) == "a")
{
sck.Send(Encoding.ASCII.GetBytes("b"));
sck.Send(message_1);
}
id++;
}
if (response == "get_id")
{
byte[] message_1 = Encoding.ASCII.GetBytes(selected_id.ToString());
sck.Send(Encoding.ASCII.GetBytes(message_1.Length.ToString()));
byte[] responseUnknown = new byte[1];
if (Encoding.ASCII.GetString(responseUnknown) == "a")
{
sck.Send(Encoding.ASCII.GetBytes("b"));
sck.Send(message_1);
}
}
}
catch { if (SocketConnected(sck) == false) { Console.WriteLine("Client Disconnected: " + sck.RemoteEndPoint); break; }; }
}
else if (SocketConnected(sck) == false) { Console.WriteLine("Client Disconnected: " + sck.RemoteEndPoint); break; }
}
}
static void SendtoClient(Socket sck)
{
string tempmessage = null;
while (true)
{
if (SocketConnected(sck) == true)
{
if (tempmessage != message)
{
if (!message.Contains("PREVENT_REPETITION_IN_COMMAND"))
{
byte[] message_1 = Encoding.ASCII.GetBytes(message);
sck.Send(Encoding.ASCII.GetBytes(message_1.Length.ToString()));
byte[] responseUnknown = new byte[1];
if (Encoding.ASCII.GetString(responseUnknown) == "a")
{
sck.Send(Encoding.ASCII.GetBytes("b"));
sck.Send(message_1);
}
}
tempmessage = message;
}
}
else if (SocketConnected(sck) == false)
{ Console.WriteLine("Client Disconnected: " + sck.RemoteEndPoint); break; }
}
}
}
}
Problem:
The problem is within the GetSession or the ReadResponseFunc function. The client thinks that his ID received by the server is 'a' (it's suppose to be an integer). I don't want a solution that suggest me to use other libs or
the TcpClient class
Bounty:
I'll put up a bounty with no expiry time to those who solve the problem.
The logic in your code is very confusing. My question to you: Why are you sending 'a' and 'b' back and forth between the server and client? Is it some sort of confirmation that the message has been received?
Anyways, throughout the quick tests I've done just now, it seems that the problem is Line 59 of your server:
sck.Send(Encoding.ASCII.GetBytes("a"));
Here's what I figured out during testing:
Server executes.
Client executes.
Client sends server the length of "make_id" (Line 51 of client)
Client waits for a response to return.
Server reads the value and sends back 'a' (Line 59 of server)
You may want to spend some time to straighten out your protocol so it's less confusing and more organized. That would help people like me and you spot bugs much more easily.
The user 'Bobby' has already found your problem. Credits go to him.
I further suggest that you use less threads, as thread synchronisation needs some effort when doing it right: all data that is accessed from different threads must be secured by locks, so that no outdated values remain in the CPU caches. Use .net Monitor from the "threading synchronisation primitives" for that job.
About the threads themselves:
You should have only one thread in the server. This thread takes all clients from a list (secured by Monitor), in which they were added when connection attempts were received. On each client it checks for incoming messages, evaluates them and replies with own messages if needed.
The client also has just one thread, that will loop (dont forget a sleep in the loop or you will have 100% usage of the used CPU core), send messages when desired and wait for replies when messages were sent.
About the messages:
I already gave some proposals in a comment to Bobby's answer. I suggest you create a CMessage class that has a Serialize() and Deserialze() to create a byte array to send from the CMessage members (serializing the content) or vice versa filling the members from the received bytes. You then may use this class in both programs and you'll have common solution.

Exception: Acess Violation in client C#

I am trying to learn C# through trials and errors. I have a server in Python, and the client in C#.
The client is supposed to get data from the server, save it to disk ((which it doesn't)), then continue using that data to communicate until the user decides to exit it.
Unfortunately, it has been raising an Exception Access Violation for quite some time, and provides no helpful information as to why.
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Scripting.Hosting;
using System.Threading;
using Rage;
using System.Net.Sockets;
using System.IO;
[assembly: Rage.Attributes.Plugin("LSPDFROnlineClient", Description = "LSPDFR Online Client. Used to connect to LSPDFR Online.", Author = "Thecheater887")]
namespace LSPDFROnlineClient
{
public class EntryPoint
{
private static int IsDead;
public static void Main()
{
IsDead = 0;
Thread mt = new Thread(new ThreadStart(Run));
mt.Start();
while (true)
{
if (IsDead == 1)
{
break;
}
GameFiber.Yield();
}
return;
}
public static void Run()
{
File.WriteAllText("C:/ProgramData/dbg.log","1");
try
{
Byte[] header;
Byte[] packet;
Byte[] data;
Byte[] kad;
Byte[] td;
int paylen;
String rd;
String nd;
String msgid;
File.WriteAllText("C:/ProgramData/dbg.log", "2");
TcpClient connector = new TcpClient();
connector.Connect("127.0.0.1", 5773);
NetworkStream conn = connector.GetStream();
try {
File.WriteAllText("C:/ProgramData/dbg.log", "3");
FileStream savedat = File.OpenRead("C:/Users/Public/Documents/save.dat");
BinaryReader savdat = new BinaryReader(savedat);
nd = savdat.ReadString();
savdat.Close();
if (nd.Length == 16)
{
} else {
File.WriteAllText("C:/ProgramData/save.dat", "user000000000000");
nd = "user000000000000";
}
}
catch
{
File.WriteAllText("C:/ProgramData/save.dat", "user000000000000");
nd = "user000000000000";
}
File.WriteAllText("C:/ProgramData/dbg.log", "4");
data = new Byte[26];
data = Encoding.ASCII.GetBytes("clogr00000" + nd);
conn.Write(data, 0, data.Length);
while (true)
{
try
{
// Get header of packet
header = new Byte[26];
Int32 rcvhead = conn.Read(header, 0, header.Length);
String hd = Encoding.ASCII.GetString(header, 0, rcvhead);
//Deal with it 8-)
msgid = hd.Substring(0, 5);
paylen = Convert.ToInt32(hd.Substring(5, 5));
string servkey = hd.Substring(10, 16);
}
catch
{
continue;
}
File.WriteAllText("C:/ProgramData/dbg.log", "5");
try
{
//Receive packet data
if (paylen > 0)
{
packet = new Byte[paylen];
Int32 newdata = conn.Read(packet, 0, packet.Length);
rd = Encoding.ASCII.GetString(packet, 0, newdata);
}
else
{
rd = null;
}
File.WriteAllText("C:/ProgramData/dbg.log", "6");
if (msgid == "ConOK")
{
File.WriteAllText("C:/ProgramData/dbg.log", "7");
string userid = rd.Substring(0, 16);
Game.DisplayHelp(rd.Substring(16, (rd.Length - 16)));
File.WriteAllText("C:/ProgramData/save.dat", userid);
File.WriteAllText("C:/ProgramData/dbg.log", "8");
}
else if (msgid == "savdt")
{
File.WriteAllText("C:/ProgramData/dbg.log", "9");
string[] tnd = rd.Split(',');
var nud = new List<string>();
nud.Add("Player1");
nud.AddRange(tnd);
File.WriteAllText("C:/ProgramData/dbg.log", "A");
string name = nud[0];
string streetname = nud[1];
int money = Convert.ToInt32(nud[2]);
int bounty = Convert.ToInt32(nud[3]);
int playerrep = Convert.ToInt32(nud[4]);
File.WriteAllText("C:/ProgramData/dbg.log", "B");
int rep = Convert.ToInt32(nud[5]);
string pclass = nud[6];
bool canbecop = Convert.ToBoolean(nud[7]);
int rank = Convert.ToInt32(nud[8]);
int stars = Convert.ToInt32(nud[9]);
int cites = Convert.ToInt32(nud[10]);
File.WriteAllText("C:/ProgramData/dbg.log", "C");
int citesgiven = Convert.ToInt32(nud[11]);
int citesdismissed = Convert.ToInt32(nud[12]);
int arrestsmade = Convert.ToInt32(nud[13]);
int arrested = Convert.ToInt32(nud[14]);
int convictionsmade = Convert.ToInt32(nud[15]);
int convitced = Convert.ToInt32(nud[16]);
string warrant = nud[17];
File.WriteAllText("C:/ProgramData/dbg.log", "D");
int prisontimeremaining = Convert.ToInt32(nud[18]);
int copskilled = Convert.ToInt32(nud[19]);
int crimskilled = Convert.ToInt32(nud[20]);
int civskilled = Convert.ToInt32(nud[21]);
int bountyclaimed = Convert.ToInt32(nud[22]);
int overflowprep = Convert.ToInt32(nud[23]);
string title = nud[24];
bool banned = Convert.ToBoolean(nud[25]);
bool vip = Convert.ToBoolean(nud[26]);
int viprank = Convert.ToInt32(nud[27]);
File.WriteAllText("C:/ProgramData/dbg.log", "E");
var v3 = new Vector3();
float posx = Convert.ToSingle(nud[29]);
float posy = Convert.ToSingle(nud[30]);
float posz = Convert.ToSingle(nud[31]);
v3.X = posx;
v3.Y = posy;
v3.Z = posz;
File.WriteAllText("C:/ProgramData/dbg.log", "EE");
int rot = Convert.ToInt32(nud[32]);
File.WriteAllText("C:/ProgramData/dbg.log", "FF");
World.TeleportLocalPlayer(v3, false);
File.WriteAllText("C:/ProgramData/dbg.log", "F");
string custommessage = nud[28];
if (custommessage == "null")
{
} else {
Game.DisplayNotification(custommessage);
}
}
else if (msgid == "isalv")
{
kad = new Byte[26];
kad = Encoding.ASCII.GetBytes("yesil00000" + nd);
conn.Write(kad, 0, kad.Length);
}
else if (msgid == "pospk")
{
}
else
{
Game.DisplayNotification("Unknown packet recieved! ID: " + msgid);
}
//send end client turn
td = new Byte[26];
td = Encoding.ASCII.GetBytes("endmt00000" + nd);
conn.Write(td, 0,td.Length);
File.WriteAllText("C:/ProgramData/dbg.log", "0");
//
}
catch (Exception e)
{
Game.DisplayHelp(Convert.ToString(e));
Game.DisplayNotification("LSPDFR Online has crashed. Try reloading it maybe..?");
}
}
} catch (Exception e) {
Game.DisplayHelp(Convert.ToString(e));
Game.DisplayNotification("Connection interrupted! Reconnecting....");
IsDead = 1;
return;
}
}
}
}
The protocol goes as such;
Client -> Server: LoginRequest
Server -> Client: LoginOkay
Client -> Server: EndTurnMessage
Server -> Client: SaveDataMessage
Client -> Server: EndTurnMessage
Server -> Client: PositionUpdatePacket
Client -> Server: EndTurnMessage
Then continue routine, however, the server only receives one of these EndTurnMessage packets, which means it is choking on the save data portion, right?
Possibly, but that was working at an earlier time without flaw, and hasn't been modified since.
It is a class file, so it can't be debugged, and I've been tearing my hair out as to what is causing it.
Yes, it is crap-code, and needs rewritten at some point, I am aware, but I'd like it to work before I rewrite it entirely.
TL;DR: Why is this code raising an Access Violation? It's around the savdt sector or after.
UPDATE: I fixed the issue posted in the first answer, however, that didn't do much, so, as posted in both the answer and comments, it's rather difficult to debug with a program, so I'll try the old fashioned route of logging info every so many lines of code. I'll keep this updated.
UPDATE 2: I have figured out from the log debugging, that the line causing the error is World.TeleportLocalPlayer(v3, false);. Unfortunatley, World can't be inherited, and the documentation claims that Vector3 requires you to set it's internal values using get and set. I saw that on MSDN previously, but have no clue about how to search it, and there is nor get or set methods available within that Vector3 object.
You might have a stream that remained open, which prevents a new one to be created. If the msgid is "ConOK" you are creating a new instance without closing it after the write operation is done.
if (msgid == "ConOK"){
string userid = rd.Substring(0, 16);
Game.DisplayHelp(rd.Substring(16, (rd.Length - 16)));
FileStream savedat = File.OpenWrite(("C:/ProgramData/save.dat"));
BinaryWriter savdat = new BinaryWriter(savedat);
savdat.Write(userid);
// Close file stream here
}
But that's just a first guess. You can help us and yourself by making use of the debugger. The fact that your code is contained by a "class file" is no problem but a requirement.
Hava a look at this article for more information about debugging in the world of C#:
http://www.dotnetperls.com/debugging
At a first glance, you can have different class of bugs. Disregarding the logic flows and intended behaviour of the program, let's start with basic debugging.
at this step, don't use threads and fibers, from Main just call Run
there isn't strong input validation
use a lot more of try catch, isolating small pieces of code
in the catch, print ex.Message and ex.StackTrace
read carefully the docs about the methods you call and their possible exceptions
it is weird you write a file inside a exception (catch)
possible race conditions on global variables?
inside Run to set IsDead use Interlocked.Increment
https://msdn.microsoft.com/en-us/library/dd78zt0c(v=vs.110).aspx
...
remove the unused ( I think like Microsoft.Scripting.Hosting ), it only confuse-a-cat

Xml over tcp without message frame

I have to implement a tcp connection where raw xml data is passed.
Unfortunately there is no message framing, I now this is realy bad, but I have to deal with this...
The Message would look like this:
<?xml version="1.0" encoding="utf-8"?>
<DATA></DATA>
or this
<?xml version="1.0" encoding="utf-8"?>
<DATA />
Now I have to receive messages that could have self closed tags. The message is always the same, it is always like xml description and a data tag with inner xml that is the message content.
So if it would be without self closed tags, this would be easy, but how can I read both?
By the way I am using the TcpListener.
Edit :
Everything is fine if there is no self closed tag.
if (_clientSocket != null)
{
NetworkStream networkStream = _clientSocket.GetStream();
_clientSocket.ReceiveTimeout = 100; // 1000 miliseconds
while (_continueProcess)
{
if (networkStream.DataAvailable)
{
bool isMessageComplete = false;
String messageString = String.Empty;
while (!isMessageComplete)
{
var bytes = new byte[_clientSocket.ReceiveBufferSize];
try
{
int bytesReaded = networkStream.Read(bytes, 0, (int) _clientSocket.ReceiveBufferSize);
if (bytesReaded > 0)
{
var data = Encoding.UTF8.GetString(bytes, 0, bytesReaded);
messageString += data;
if (messageString.IndexOf("<DATA", StringComparison.OrdinalIgnoreCase) > 0 &&
messageString.IndexOf("</DATA", StringComparison.OrdinalIgnoreCase) > 0)
{
isMessageComplete = true;
}
}
}
catch (IOException)
{
// Timeout
}
catch (SocketException)
{
Console.WriteLine("Conection is broken!");
break;
}
}
}
Thread.Sleep(200);
} // while ( _continueProcess )
networkStream.Close();
_clientSocket.Close();
}
Edit 2 (30.03.2015 12:00)
Unfortunately it is not possible to use some kind of message frame.
So I ended up to use this part of code (DATA is my root node):
if (_clientSocket != null)
{
NetworkStream networkStream = _clientSocket.GetStream();
_clientSocket.ReceiveTimeout = 100;
string data = string.Empty;
while (_continueProcess)
{
try
{
if (networkStream.DataAvailable)
{
Stopwatch sw = new Stopwatch();
sw.Start();
var bytes = new byte[_clientSocket.ReceiveBufferSize];
int completeXmlLength = 0;
int bytesReaded = networkStream.Read(bytes, 0, (int) _clientSocket.ReceiveBufferSize);
if (bytesReaded > 0)
{
message.AddRange(bytes);
data += Encoding.UTF8.GetString(bytes, 0, bytesReaded);
if (data.IndexOf("<?", StringComparison.Ordinal) == 0)
{
if (data.IndexOf("<DATA", StringComparison.Ordinal) > 0)
{
Int32 rootStartPos = data.IndexOf("<DATA", StringComparison.Ordinal);
completeXmlLength += rootStartPos;
var root = data.Substring(rootStartPos);
int rootCloseTagPos = root.IndexOf(">", StringComparison.Ordinal);
Int32 rootSelfClosedTagPos = root.IndexOf("/>", StringComparison.Ordinal);
// If there is an empty tag that is self closed.
if (rootSelfClosedTagPos > 0)
{
string rootTag = root.Substring(0, rootSelfClosedTagPos +1);
// If there is no '>' between the self closed tag and the start of '<DATA'
// the root element is empty.
if (rootTag.IndexOf(">", StringComparison.Ordinal) <= 0)
{
completeXmlLength += rootSelfClosedTagPos;
string messageXmlString = data.Substring(0, completeXmlLength + 1);
data = data.Substring(messageXmlString.Length);
try
{
// parse complete xml.
XDocument xmlDocument = XDocument.Parse(messageXmlString);
}
catch(Exception)
{
// Invalid Xml.
}
continue;
}
}
if (rootCloseTagPos > 0)
{
Int32 rootEndTagStartPos = root.IndexOf("</DATA", StringComparison.Ordinal);
if (rootEndTagStartPos > 0)
{
var endTagString = root.Substring(rootEndTagStartPos);
completeXmlLength += rootEndTagStartPos;
Int32 completeEndPos = endTagString.IndexOf(">", StringComparison.Ordinal);
if (completeEndPos > 0)
{
completeXmlLength += completeEndPos;
string messageXmlString = data.Substring(0, completeXmlLength + 1);
data = data.Substring(messageXmlString.Length);
try
{
// parse complete xml.
XDocument xmlDocument = XDocument.Parse(messageXmlString);
}
catch(Exception)
{
// Invalid Xml.
}
}
}
}
}
}
}
sw.Stop();
string timeElapsed = sw.Elapsed.ToString();
}
}
catch (IOException)
{
data = String.Empty;
}
catch (SocketException)
{
Console.WriteLine("Conection is broken!");
break;
}
}
This code I had use if ther were some kind of message framing, in this case 4 bytes of message length:
if (_clientSocket != null)
{
NetworkStream networkStream = _clientSocket.GetStream();
_clientSocket.ReceiveTimeout = 100;
string data = string.Empty;
while (_continueProcess)
{
try
{
if (networkStream.DataAvailable)
{
Stopwatch sw = new Stopwatch();
sw.Start();
var lengthBytes = new byte[sizeof (Int32)];
int bytesReaded = networkStream.Read(lengthBytes, 0, sizeof (Int32) - offset);
if (bytesReaded > 0)
{
offset += bytesReaded;
message.AddRange(lengthBytes.Take(bytesReaded));
}
if (offset < sizeof (Int32))
{
continue;
}
Int32 length = BitConverter.ToInt32(message.Take(sizeof(Int32)).ToArray(), 0);
message.Clear();
while (length > 0)
{
Int32 bytesToRead = length < _clientSocket.ReceiveBufferSize ? length : _clientSocket.ReceiveBufferSize;
byte[] messageBytes = new byte[bytesToRead];
bytesReaded = networkStream.Read(messageBytes, 0, bytesToRead);
length = length - bytesReaded;
message.AddRange(messageBytes);
}
try
{
string xml = Encoding.UTF8.GetString(message.ToArray());
XDocument xDocument = XDocument.Parse(xml);
}
catch (Exception ex)
{
// Invalid Xml.
}
sw.Stop();
string timeElapsed = sw.Elapsed.ToString();
}
}
catch (IOException)
{
data = String.Empty;
}
catch (SocketException)
{
Console.WriteLine("Conection is broken!");
break;
}
}
Like you can see I wanted to measure the elapsed time, to see witch methode has a better performance. The strange thing is that the methode whith no message framing has an average time of 0,2290 ms, the other methode has an average time of 1,2253 ms.
Can someone explain me why? I thought the one without message framing would be slower...
Hand the NetworkStream to the .NET XML infrastructure. For example create an XmlReader from the NetworkStream.
Unfortunately I did not find a built-in way to easily create an XmlDocument from an XmlReader that has multiple documents in it. It complains about multiple root elements (which is correct). You would need to wrap the XmlReader and make it stop returning nodes when the first document is done. You can do that by keeping track of some state and by looking at the nesting level. When the nesting level is zero again the first document is done.
This is just a raw sketch. I'm pretty sure this will work and it handles all possible XML documents.
No need for this horrible string processing code that you have there. The existing code looks quite slow as well but since this approach is much better it serves no purpose to comment on the perf issues. You need to throw this away.
I had the same problem - 3rd party system sends messages in XML format via TCP but my TCP client application may receive message partially or several messages at once. One of my colleagues proposed very simple and quite generic solution.
The idea is to have a string buffer which should be populated char by char from TCP stream, after each char try to parse buffer content with regular .Net XML parser. If parser throws an exception - continue adding chars to the buffer. Otherwise - message is ready and can be processed by application.
Here is the code:
private object _dataReceiverLock = new object();
private string _messageBuffer;
private Stopwatch _timeSinceLastMessage = new Stopwatch();
private List<string> NormalizeMessage(string rawMsg)
{
lock (_dataReceiverLock)
{
List<string> result = new List<string>();
//following code prevents buffer to store too old information
if (_timeSinceLastMessage.ElapsedMilliseconds > _settings.ResponseTimeout)
{
_messageBuffer = string.Empty;
}
_timeSinceLastMessage.Restart();
foreach (var ch in rawMsg)
{
_messageBuffer += ch;
if (ch == '>')//to avoid extra checks
{
if (IsValidXml(_messageBuffer))
{
result.Add(_messageBuffer);
_messageBuffer = string.Empty;
}
}
}
return result;
}
}
private bool IsValidXml(string xml)
{
try
{
//fastest way to validate XML format correctness
using (XmlTextReader reader = new XmlTextReader(new StringReader(xml)))
{
while (reader.Read()) { }
}
return true;
}
catch
{
return false;
}
}
Few comments:
Need to control lifetime of string buffer, otherwise in case network disconnection old information may stay in the buffer forever
There major problem here is the performance - parsing after every new character is quite slow. So need to add some optimizations, such as parse only after '>' character.
Make sure this method is thread safe, otherwise several threads may flood string buffer with different XML pieces.
The usage is simple:
private void _tcpClient_DataReceived(byte[] data)
{
var rawMsg = Encoding.Unicode.GetString(data);
var normalizedMessages = NormalizeMessage(rawMsg);
foreach (var normalizedMessage in normalizedMessages)
{
//TODO: your logic
}
}

Categories

Resources