I have been trying to setup a client-server application using StreamSockets in c#. I am able to initially connect to the server (ConnectAsync) and following to write and read the stream. If the client sends another stream to the server using the method WriteToServer the event on the server side is not being triggered (SocketListener_ConnectionReceived). I'm sending a xmlSerialized object "Message" to the server.
While debugging I don't receive any errors. On the client side though the after having used "WriteToServer" and moving forward to "ReadFromServer" the code gets obviously stuck in the line below since the server doesn't reply
int bufferLength = inStream.ReadByte();
I hope that someone call help. I am honestly not sure what the issues is because the "Write"-method is being used the same way both times the client attempts to write to the server.
Client is a Windows 10 computer and Server a Raspberry pi running Windows 10 IoT.
The class inside the client application which handles the connection looks like this.
StreamSocket socket;
HostName remoteHostName, localHostName;
string serviceAddress = "1337";
EndpointPair endPointPair;
Boolean isConnected;
Socket test;
public Connection()
{
remoteHostName = new HostName("172.16.20.202"); // might have to change based on pi's ip address
//remoteHostName = new HostName("172.16.20.4");
localHostName = new HostName(getLocalIpAddress());
endPointPair = new EndpointPair(localHostName, serviceAddress, remoteHostName, serviceAddress);
socket = new StreamSocket();
socket.Control.NoDelay = true;
socket.Control.QualityOfService = SocketQualityOfService.LowLatency;
}
private string getLocalIpAddress()
{
var icp = NetworkInformation.GetInternetConnectionProfile();
if (icp?.NetworkAdapter == null) return null;
var hostname =
NetworkInformation.GetHostNames()
.SingleOrDefault(
hn =>
hn.IPInformation?.NetworkAdapter != null && hn.IPInformation.NetworkAdapter.NetworkAdapterId
== icp.NetworkAdapter.NetworkAdapterId);
// the ip address
return hostname?.CanonicalName;
}
public async Task StartConnection()
{
try
{
if (isConnected)
{
await socket.CancelIOAsync();
socket.Dispose();
socket = null;
isConnected = false;
}
await socket.ConnectAsync(endPointPair);
isConnected = true;
}
catch (Exception exc)
{
if (Windows.Networking.Sockets.SocketError.GetStatus(exc.HResult) == SocketErrorStatus.Unknown)
{
throw;
}
Debug.WriteLine("Connect failed with error: " + exc.Message);
socket.Dispose();
isConnected = false;
socket = null;
//return null;
}
}
public async Task WriteToServer(Message msg)
{
try
{
using (DataWriter writer = new DataWriter(socket.OutputStream))
{
writer.WriteBytes(serialize(msg));
await writer.StoreAsync();
writer.DetachStream();
writer.Dispose();
}
}
catch (Exception exc)
{
Debug.WriteLine("Write failed with error: " + exc.Message);
}
}
public async Task<Library.Message> ReadFromServer()
{
try
{
Stream inStream = socket.InputStream.AsStreamForRead();
int bufferLength = inStream.ReadByte();
byte[] serializedMessage = new byte[bufferLength];
await inStream.ReadAsync(serializedMessage, 0, bufferLength);
await inStream.FlushAsync();
Library.Message incomingMessage;
using (var stream = new MemoryStream(serializedMessage))
{
var serializer = new XmlSerializer(typeof(Library.Message));
incomingMessage = (Library.Message)serializer.Deserialize(stream);
}
return incomingMessage;
}
catch (Exception exc)
{
Debug.WriteLine("Read failed with error: " + exc.Message);
return null;
}
}
private byte[] serialize(Message msg)
{
byte[] serializedMessage, returnArray;
var serializer = new XmlSerializer(typeof(Library.Message));
using (var stream = new MemoryStream())
{
serializer.Serialize(stream, msg);
serializedMessage = stream.ToArray();
stream.Dispose();
}
int bufferLength = serializedMessage.Length;
returnArray = new byte[serializedMessage.Length + 1];
serializedMessage.CopyTo(returnArray, 1);
returnArray[0] = (byte)bufferLength;
Debug.WriteLine("ClientReturnArrayLength: " + returnArray.Length);
Debug.WriteLine("ClientFirstByte: " + returnArray[0]);
return returnArray;
}
The server side looks like this
public Task DispatcherPriority { get; private set; }
public MainPage()
{
this.InitializeComponent();
dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
hostName = new HostName(getLocalIpAddress());
clients = new List<Client>();
}
/// <summary>
/// Gets the ip address of the host
/// </summary>
/// <returns></returns>
private string getLocalIpAddress()
{
var icp = NetworkInformation.GetInternetConnectionProfile();
if (icp?.NetworkAdapter == null) return null;
var hostname =
NetworkInformation.GetHostNames()
.SingleOrDefault(
hn =>
hn.IPInformation?.NetworkAdapter != null && hn.IPInformation.NetworkAdapter.NetworkAdapterId
== icp.NetworkAdapter.NetworkAdapterId);
// the ip address
return hostname?.CanonicalName;
}
async void setupSocketListener()
{
if (socketListener != null)
{
await socketListener.CancelIOAsync();
socketListener.Dispose();
socketListener = null;
}
socketListener = new StreamSocketListener();
socketListener.Control.QualityOfService = SocketQualityOfService.LowLatency;
socketListener.ConnectionReceived += SocketListener_ConnectionReceived;
await socketListener.BindServiceNameAsync("1337");
listBox.Items.Add("server started.");
clients.Clear();
}
private async void SocketListener_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
{
HostName ip = args.Socket.Information.RemoteAddress;
string port = args.Socket.Information.RemotePort;
try
{
Stream inStream = args.Socket.InputStream.AsStreamForRead();
int bufferLength = inStream.ReadByte();
byte[] serializedMessage = new byte[bufferLength];
await inStream.ReadAsync(serializedMessage, 0, bufferLength);
await inStream.FlushAsync();
Message incomingMessage;
using (var stream = new MemoryStream(serializedMessage))
{
var serializer = new XmlSerializer(typeof(Message));
incomingMessage = (Message)serializer.Deserialize(stream);
}
/// <summary>
/// 1 = Connected
/// 2 = SentNote
/// 3 = Login
/// </summary>
switch (incomingMessage.Mode)
{
case 1:
onClientConnect(ip, port, incomingMessage.Username, args.Socket);
break;
case 2:
onNoteReceived(incomingMessage);
break;
case 3:
//handle login
break;
}
}
catch (Exception msg)
{
Debug.WriteLine(msg);
}
}
private async void onNoteReceived(Message msg)
{
foreach (var client in clients)
{
//if (client.Username != msg.Username)
//{
using (DataWriter writer = new DataWriter(client.Socket.OutputStream))
{
writer.WriteBytes(serialize(msg));
await writer.StoreAsync();
writer.DetachStream();
writer.Dispose();
}
//}
}
}
private void buttonStartServer_Click(object sender, RoutedEventArgs e)
{
setupSocketListener();
}
private async void notifyClients(string username)
{
Message msg = new Message();
msg.Username = username;
foreach (var client in clients)
{
//if (client.Username != msg.Username)
//{
using (DataWriter writer = new DataWriter(client.Socket.OutputStream))
{
writer.WriteBytes(serialize(msg));
await writer.StoreAsync();
writer.DetachStream();
writer.Dispose();
}
//}
}
}
private async void onClientConnect(HostName ip, string port, string username, StreamSocket socket)
{
clients.Add(new Client(ip, port, username, socket));
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
listBox.Items.Add("User: " + username + " on IP " + ip + " is connected.");
});
notifyClients(username);
}
private byte[] serialize(Message msg)
{
byte[] serializedMessage, returnArray;
var serializer = new XmlSerializer(typeof(Message));
using (var stream = new MemoryStream())
{
serializer.Serialize(stream, msg);
serializedMessage = stream.ToArray();
stream.Dispose();
}
int bufferLength = serializedMessage.Length;
returnArray = new byte[serializedMessage.Length + 1];
serializedMessage.CopyTo(returnArray, 1);
returnArray[0] = (byte)bufferLength;
Debug.WriteLine("ClientReturnArrayLength: " + returnArray.Length);
Debug.WriteLine("ClientFirstByte: " + returnArray[0]);
return returnArray;
}
}
This is the message class which I am using to send over the network.
public string Username { get; set; }
/// <summary>
/// 1 = startConnection
/// 2 = SendNote
/// 3 = Login
/// </summary>
public int Mode { get; set; }
public Piano PianoNote { get; set; }
public string Instrument { get; set; }
public Message()
{
}
}
public enum Piano { a1, a1s, b1, c1 };
**Edit: **
Message framing:
byte[] prefix = BitConverter.GetBytes(serializedMessage.Length);
returnArray = new byte[serializedMessage.Length + prefix.Length];
prefix.CopyTo(returnArray, 0);
serializedMessage.CopyTo(returnArray, prefix.Length);
Reading the message:
byte[] prefix = new byte[4];
await inStream.ReadAsync(prefix, 0, 4);
int bufferLength = BitConverter.ToInt32(prefix, 0);
Half-Open:
Instead of reading synchronous I switched to async reading the first 4 bytes as seen above.
I am able to initially connect to the server (ConnectAsync) and following to write and read the stream. If the client sends another stream to the server using the method WriteToServer the event on the server side is not being triggered (SocketListener_ConnectionReceived).
Take a good look at those names. You're calling ConnectAsync once and then WriteToServer twice, and only seeing SocketListener_ConnectionReceived once. There's only once connection, so yes, ConnectionReceived would only trigger once.
That's just scratching the surface, though. There's a few other very subtle issues wrong with this code.
One that sticks out is a lack of proper message framing, as I describe on my blog. You're using a single-byte length prefix, so on the wire it's OK (though limited to 256 bytes, which doesn't go far with XML). But the reading of the messages is incorrect; in particular, Stream.ReadAsync may read between 1 and bufferLength bytes, or it may return 0.
Another problem is that it's subject to the half-open problem, as I describe on my blog. In particular, int bufferLength = inStream.ReadByte(); will block indefinitely in a half-open situation. You should only use asynchronous methods for all network streams, and periodically write while waiting for data to arrive.
In summary, I strongly recommend that you use self-hosted SignalR instead of raw sockets. Raw socket programming is extremely difficult to do correctly, especially because incorrect code often happens to work correctly in a local environment.
Related
Hello I am working on some code which envolves a hello protocol between two servers as a keep alive and to process files on the brother if a server goes down.
I run the same service on two different machines but on one machine it does not recieve and gets stuck on socket.recieve().
i have traced a wireshark and c# debug but the client sends but the server never responds, which is causing a problem for this feature
Thanks
Richard
public static void Listen(ServiceController serviceController, Server server, List<Server> RemoteServers)
{
while (!serviceController.StopService)
{
if (server.ThisDevice)
{
TcpListener listener = new TcpListener(1);
string response = "Hello Recieved";
byte[] responsebyte = Encoding.ASCII.GetBytes(response);
try
{
listener = new TcpListener(server.IPAddress, 11000);
listener.Start();
byte[] Bytes = new byte[256];
Listening = true;
while (!serviceController.StopService)
{
Socket socket = listener.AcceptSocket();
int result = socket.Receive(Bytes);
ASCIIEncoding aSCII = new ASCIIEncoding();
string strres = aSCII.GetString(Bytes,0,result);
string stripped = strres.Substring(0, 5);
if (stripped == "Hello")
{
Console.WriteLine(stripped);
socket.Send(responsebyte);
string clientIP = ((IPEndPoint)socket.RemoteEndPoint).Address.ToString();
ProcessHello(stripped,clientIP,RemoteServers);
}
socket.Close();
}
}
catch (Exception ex)
{
listener.Stop();
Listening = false;
}
}
}
}
}
public static void StartClient(Server server)
{
if (!server.ThisDevice)
{
TcpClient tcpClient = new TcpClient(server.IPAddress.ToString(),11000);
NetworkStream stream = tcpClient.GetStream();
string msg = "Hello";
string response = "";
byte[] bytes = new byte[256];
bytes = Encoding.ASCII.GetBytes(msg);
stream.Write(bytes, 0, bytes.Length);
StreamReader reader = new StreamReader(stream, Encoding.ASCII);
try
{
response = reader.ReadToEnd();
}
finally
{
// Close the reader
reader.Close();
}
tcpClient.Close();
}
public void StartClient(List<Server> servers)
{
_ClientStarted = true;
var remote = servers.Where(s => !s.ThisDevice).ToList();
Thread t = new Thread(() => ExecuteThreadedClient(remote));
t.Start();
}
void ExecuteThreadedClient(List<Server> servers)
{
while (!_ServiceController.StopService)
{
try
{
//Token.ThrowIfCancellationRequested();
foreach (var server in servers)
{
TCPHelloClient.StartClient(server);
}
Thread.Sleep(_HelloTimer);
}
catch (OperationCanceledException e)
{
Thread.Sleep(60000);
}
catch(Exception e)
{
Thread.Sleep(_HelloTimer);
}
}
_ClientStarted = false;
// Task.Delay(_HelloTimer).Wait();
}
public void StartServer(List<Server> servers)
{
try
{
//var task = Task.Run(() => ExecuteThreadedServer(server), Token);
Server local = servers.First(s => s.ThisDevice);
var remote = servers.Where(s => !s.ThisDevice).ToList();
Thread t = new Thread(() => TCPHelloServer.Listen(_ServiceController, local, remote));
t.Start();
}
catch(Exception e)
{
}
}
I'm attempting to write a simple async https proxy server in c#.
I would like to know how I should detect/handle when the request is complete, and how to exit my bActive loop, assuming a loop like this is appropriate.
Would really appreciate some pointers on if my approach is correct and what I could do to improve the logic.
The issue I seem to be running into is that the time it takes for an endpoint to respond along with the network delay means I DataAvailable doenst always have data but there may still be some sending. Requiring a sleep and another attmempt which in turn causes the long completion time in requests.
Listen for TCP connection
Extract CONNECT header and open a connection to the requested server
Copy the requestStream to proxyStream
Copy the proxyStream to the requestStream
Sleep waiting for data and repeat 3 - 4 until no data is avaiable on both streams. Then break out of the loop and close connection.
public async Task Start()
{
listener.Start();
while (listen)
{
if (listener.Pending())
{
HandleClient(await listener.AcceptTcpClientAsync());
}
else
{
await Task.Delay(100); //<--- timeout
}
}
}
private static async Task HandleClient(TcpClient clt)
{
var bytes = new byte[clt.ReceiveBufferSize];
var hostHeaderAvailable = 0;
NetworkStream requestStream = null;
int count;
const string connectText = "connect";
const string hostText = "Host: ";
bool bActive = true;
List<Task> tasks = new List<Task>();
try
{
using (NetworkStream proxyStream = clt.GetStream())
using (TcpClient requestClient = new TcpClient())
{
proxyStream.ReadTimeout = 100;
proxyStream.WriteTimeout = 100;
while (bActive)
{
if (proxyStream.DataAvailable && hostHeaderAvailable == 0)
{
count = await proxyStream.ReadAsync(bytes, 0, bytes.Length);
var text = Encoding.UTF8.GetString(bytes);
Console.WriteLine(text);
if (text.ToLower().StartsWith(connectText))
{
// extract the url and port
var host = text.Remove(0, connectText.Length + 1);
var hostIndex = host.IndexOf(" ", StringComparison.Ordinal);
var hostEntry = host.Remove(hostIndex).Split(new[] { ":" }, StringSplitOptions.None);
// connect to the url and prot supplied
await requestClient.ConnectAsync(hostEntry[0], Convert.ToInt32(hostEntry[1]));
requestStream = requestClient.GetStream();
requestStream.ReadTimeout = 100;
requestStream.WriteTimeout = 100;
// send 200 response to proxyStream
const string sslResponse = "HTTP/1.0 200 Connection established\r\n\r\n";
var sslResponseBytes = Encoding.UTF8.GetBytes(sslResponse);
await proxyStream.WriteAsync(sslResponseBytes, 0, sslResponseBytes.Length);
// delay here seems to prevent the following proxyStream.read from failing as data is not yet avaiable
// without it the loop runs and has to timeout before running again
await Task.Delay(1);
}
}
hostHeaderAvailable++;
if (requestStream == null || !requestClient.Connected || !clt.Connected)
{
bActive = false;
break;
}
Console.WriteLine(proxyStream.DataAvailable || requestStream.DataAvailable);
if (proxyStream.DataAvailable || requestStream.DataAvailable)
{
Task task = proxyStream.CopyToAsync(requestStream);
Task task2 = requestStream.CopyToAsync(proxyStream);
tasks.Add(task);
tasks.Add(task2);
await Task.WhenAll(tasks).ConfigureAwait(false);
bActive = false;
break;
}
await Task.Delay(10);
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
clt.Close();
}
An older attempt that used ReadAsync/WriteAsync too longer to response and still had the timeout issue.
Listen for TCP connection
Extract CONNECT header and open a connection to the requested server
Read data from requestStream and copy to proxyStream
Wait checking if data is avaiable on either stream
If data avaiable read from proxyStream and write to requestStream
If data avaiable read from requestStream and write to proxyStream
Sleep waiting for data and repeat 5 - 6 until no data is avaiable on eitboth streams. Then break out of the loop and close connection.
private static TcpListener listener = new TcpListener(IPAddress.Parse("192.168.0.25"), 13000);
private static bool listen = true;
public async Task Start()
{
listener.Start();
while (listen)
{
if (listener.Pending())
{
await HandleClient(await listener.AcceptTcpClientAsync());
}
else
{
await Task.Delay(100);
}
}
}
private static async Task HandleClient(TcpClient clt)
{
var bytes = new byte[clt.ReceiveBufferSize];
var hostHeaderAvailable = 0;
NetworkStream requestStream = null;
int count;
const string connectText = "connect";
const string hostText = "Host: ";
bool bActive = true;
try
{
using (NetworkStream proxyStream = clt.GetStream())
using (TcpClient requestClient = new TcpClient())
{
while (bActive)
{
while (proxyStream.DataAvailable)
{
// handle connect
if (hostHeaderAvailable == 0)
{
count = await proxyStream.ReadAsync(bytes, 0, bytes.Length);
var text = Encoding.UTF8.GetString(bytes);
Console.WriteLine(text);
if (text.ToLower().StartsWith(connectText))
{
// extract the url and port
var host = text.Remove(0, connectText.Length + 1);
var hostIndex = host.IndexOf(" ", StringComparison.Ordinal);
var hostEntry = host.Remove(hostIndex).Split(new[] { ":" }, StringSplitOptions.None);
// connect to the url and prot supplied
await requestClient.ConnectAsync(hostEntry[0], Convert.ToInt32(hostEntry[1]));
requestStream = requestClient.GetStream();
// send 200 response to proxyStream
const string sslResponse = "HTTP/1.0 200 Connection established\r\n\r\n";
var sslResponseBytes = Encoding.UTF8.GetBytes(sslResponse);
await proxyStream.WriteAsync(sslResponseBytes, 0, sslResponseBytes.Length);
// delay here seems to prevent the following proxyStream.read from failing as data is not yet avaiable
// without it the loop runs and has to timeout before running again
await Task.Delay(20);
}
}
hostHeaderAvailable++;
if (requestClient.Connected && hostHeaderAvailable > 1)
{
count = await proxyStream.ReadAsync(bytes, 0, bytes.Length);
await requestStream.WriteAsync(bytes, 0, count);
}
}
while (requestStream.DataAvailable)
{
count = await requestStream.ReadAsync(bytes, 0, bytes.Length);
await proxyStream.WriteAsync(bytes, 0, count);
}
// attempt to detect a timeout / end of data avaiable
var timeout = 0;
while (!proxyStream.DataAvailable && !requestStream.DataAvailable)
{
if (timeout > 5)
{
bActive = false;
break;
}
await Task.Delay(10);
timeout++;
}
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
UPDATE
As per AgentFire's answer I have now come to the following working code:
public static async Task HandleDisconnect(TcpClient tcp, TcpClient tcp2, CancellationToken cancellationToken)
{
while (true)
{
if (tcp.Client.Poll(0, SelectMode.SelectRead))
{
byte[] buff = new byte[1];
if (tcp.Client.Receive(buff, SocketFlags.Peek) == 0)
{
// Client disconnected
Console.WriteLine("The requesting client has dropped its connection.");
cancellationToken = new CancellationToken(true);
break;
}
}
if (tcp2.Client.Poll(0, SelectMode.SelectRead))
{
byte[] buff = new byte[1];
if (tcp2.Client.Receive(buff, SocketFlags.Peek) == 0)
{
// Server disconnected
Console.WriteLine("The destination client has dropped its connection.");
cancellationToken = new CancellationToken(true);
break;
}
}
await Task.Delay(1);
}
}
private static async Task HandleClient(TcpClient clt)
{
List<Task> tasks = new List<Task>();
var bytes = new byte[clt.ReceiveBufferSize];
var hostHeaderAvailable = 0;
NetworkStream requestStream = null;
const string connectText = "connect";
try
{
using (NetworkStream proxyStream = clt.GetStream())
using (TcpClient requestClient = new TcpClient())
{
proxyStream.ReadTimeout = 100;
proxyStream.WriteTimeout = 100;
if (proxyStream.DataAvailable && hostHeaderAvailable == 0)
{
await proxyStream.ReadAsync(bytes, 0, bytes.Length);
var text = Encoding.UTF8.GetString(bytes);
Console.WriteLine(text);
if (text.ToLower().StartsWith(connectText))
{
// extract the url and port
var host = text.Remove(0, connectText.Length + 1);
var hostIndex = host.IndexOf(" ", StringComparison.Ordinal);
var hostEntry = host.Remove(hostIndex).Split(new[] { ":" }, StringSplitOptions.None);
// connect to the url and prot supplied
await requestClient.ConnectAsync(hostEntry[0], Convert.ToInt32(hostEntry[1]));
requestStream = requestClient.GetStream();
requestStream.ReadTimeout = 100;
requestStream.WriteTimeout = 100;
// send 200 response to proxyStream
const string sslResponse = "HTTP/1.0 200 Connection established\r\n\r\n";
var sslResponseBytes = Encoding.UTF8.GetBytes(sslResponse);
await proxyStream.WriteAsync(sslResponseBytes, 0, sslResponseBytes.Length);
}
}
hostHeaderAvailable++;
CancellationToken cancellationToken = new CancellationToken(false);
Task task = proxyStream.CopyToAsync(requestStream, cancellationToken);
Task task2 = requestStream.CopyToAsync(proxyStream, cancellationToken);
Task handleConnection = HandleDisconnect(clt, requestClient, cancellationToken);
tasks.Add(task);
tasks.Add(task2);
tasks.Add(handleConnection);
await Task.WhenAll(tasks).ConfigureAwait(false);
// close conenctions
clt.Close();
clt.Dispose();
requestClient.Close();
requestClient.Dispose();
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
UPDATE
Attempt at using CancellationTokenSource
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken cancellationToken = source.Token;
TaskFactory factory = new TaskFactory(cancellationToken);
tasks.Add(factory.StartNew(() => {proxyStream.CopyToAsync(requestStream);}, cancellationToken));
tasks.Add(factory.StartNew(() => {requestStream.CopyToAsync(proxyStream);}, cancellationToken));
tasks.Add(factory.StartNew(async () => {
//wait for this to retur, then cancel the token
await HandleDisconnect(clt, requestClient);
source.Cancel();
}, cancellationToken));
try
{
await factory.ContinueWhenAll(tasks.ToArray(),
(results) =>
{
Console.WriteLine("Tasks complete");
}, cancellationToken);
}
catch (AggregateException ae)
{
foreach (Exception e in ae.InnerExceptions)
{
if (e is TaskCanceledException)
Console.WriteLine("Unable to compute mean: {0}",
((TaskCanceledException)e).Message);
else
Console.WriteLine("Exception: " + e.GetType().Name);
}
}
finally
{
source.Dispose();
}
UPDATE
public static class extensionTcpClient{
public static bool CheckIfDisconnected(this TcpClient tcp)
{
if (tcp.Client.Poll(0, SelectMode.SelectRead))
{
byte[] buff = new byte[1];
if (tcp.Client.Receive(buff, SocketFlags.Peek) == 0)
{
// Client disconnected
return false;
}
}
return true;
}
}
class ProxyMaintainer
{
private static TcpListener listener = new TcpListener(IPAddress.Parse("192.168.0.25"), 13000);
public ProxyMaintainer()
{
}
public async Task Start()
{
Console.WriteLine("###############################");
Console.WriteLine("Listening on 192.168.0.25:13000");
Console.WriteLine("###############################\n");
listener.Start();
while (listen)
{
if (listener.Pending())
{
HandleClient(await listener.AcceptTcpClientAsync());
}
else
{
await Task.Delay(100); //<--- timeout
}
}
}
private static async Task Transport(NetworkStream from, NetworkStream to, Func<bool> isAlivePoller, CancellationToken token)
{
byte[] buffer = new byte[4096];
while (isAlivePoller())
{
while (from.DataAvailable)
{
int read = await from.ReadAsync(buffer, 0, buffer.Length, token).ConfigureAwait(false);
await to.WriteAsync(buffer, 0, read, token);
}
// Relieve the CPU a bit.
await Task.Delay(10, token).ConfigureAwait(false);
}
}
private static async Task HandleClient(TcpClient clientFrom)
{
var hostHeaderAvailable = 0;
int count;
var bytes = new byte[clientFrom.ReceiveBufferSize];
const string connectText = "connect";
NetworkStream toStream = null;
using (var fromStream = clientFrom.GetStream())
using(TcpClient clientTo = new TcpClient())
using (var manualStopper = new CancellationTokenSource())
{
count = await fromStream.ReadAsync(bytes, 0, bytes.Length);
var text = Encoding.UTF8.GetString(bytes);
Console.WriteLine(text);
if (text.ToLower().StartsWith(connectText))
{
// extract the url and port
var host = text.Remove(0, connectText.Length + 1);
var hostIndex = host.IndexOf(" ", StringComparison.Ordinal);
var hostEntry = host.Remove(hostIndex).Split(new[] { ":" }, StringSplitOptions.None);
// connect to the url and prot supplied
await clientTo.ConnectAsync(hostEntry[0], Convert.ToInt32(hostEntry[1]));
toStream = clientTo.GetStream();
// send 200 response to proxyStream
const string sslResponse = "HTTP/1.0 200 Connection established\r\n\r\n";
var sslResponseBytes = Encoding.UTF8.GetBytes(sslResponse);
await fromStream.WriteAsync(sslResponseBytes, 0, sslResponseBytes.Length);
}
bool Poller() => clientFrom.CheckIfDisconnected() && clientTo.CheckIfDisconnected();
Task one = Transport(fromStream, toStream, Poller, manualStopper.Token);
Task two = Transport(toStream, fromStream, Poller, manualStopper.Token);
await Task.WhenAll(one, two).ConfigureAwait(false);
//await one; await two; // To get exceptions if you want them and there are any.
// Alternatively, you can use Task.WhenAll to get exceptions aggregated for you.
}
Console.WriteLine("Closing connection");
}
}
Well, tell you what. The data availability, when it comes to HTTP, lies only in one parameter (if we omit things like WebSocket), which is called Connection and is passed as a Header as a one of two possible states: Close or Keep-Alive.
If Close is chosen by the client, the server is obliged to close the conection as soon as the request is served, whereas Keep-Alive tells the server that, if it doesn't want to, it may leave connection open for another request.
Let's consider both cases.
If client chooses Keep-Alive, the connection will persist and work as intended, indefinetely. But:
If either side drops the connection, there is an easy way to detect that. This piece of code was found on StackOverflow and it was told that it still works perfectly:
public static bool CheckIfDisconnected(this TcpClient tcp)
{
if (tcp.Client.Poll(0, SelectMode.SelectRead))
{
byte[] buff = new byte[1];
if (tcp.Client.Receive(buff, SocketFlags.Peek) == 0)
{
// Client disconnected
return true;
}
}
return false;
}
So I believe that you, as a proxy-server, are not obliged to manage connection states at all and can leave it to the actual communication parties. All you have to do is to detect when either of your connections - proxy or request - is dropped, drop the other one and call it a day.
P.S. Now, you also asked about asynchronicity.
I must add that TCP connections are considered full-duplex. which means you are free to create two async-running tasks, both reading and writing to their own sinks. My thoughts, it would be the optimal course of action.
To answer your other question
You are still using Stream.CopyToAsync which, as I have told you, is not going to succeed as long as any communicating party decides to wait a bit before sending another chunk of data.
You are also somewhat overcomplicating your solution.
I would put it this way:
async Task Transport(NetworkStream from, NetworkStream to, Func<bool> isAlivePoller, CancellationToken token)
{
byte[] buffer = new byte[4096];
while (isAlivePoller())
{
while (from.DataAvailable)
{
int read = await from.ReadAsync(buffer, 0, buffer.Length, token).ConfigureAwait(false);
await to.WriteAsync(buffer, 0, read, token).ConfigureAwait(false);
}
// Relieve the CPU a bit.
await Task.Delay(100, token).ConfigureAwait(false);
}
}
And then in your main code:
using TcpClient clientFrom = ...;
using TcpClient clientTo = ...;
using var fromStream = clientFrom.GetStream();
using var toStream = clientTo.GetStream();
using var manualStopper = new CancellationTokenSource();
bool Poller() => clientFrom.CheckIfDisconnected() && clientTo.CheckIfDisconnected();
Task one = Transport(fromStream, toStream, Poller, stopper.Token);
Task two = Transport(toStream, fromStream, Poller, stopper.Token);
await Task.WhenAny(one, two).ConfigureAwait(false);
//await one; await two; // To get exceptions if you want them and there are any.
// Alternatively, you can use Task.WhenAll to get exceptions aggregated for you.
And you are pretty much done here.
Well, I need to connect to a TCP socket, however, it only accepts one connection at a time and has a 60 second timeout.
The problem is that in the process of sending information and receiving a response, the application is not performative.
I would like to know, if in this case, it is possible to do something asynchronous/parallel.
Below is my socket client class.
public class TcpSocketClient
{
public TcpClient TCPConnection { get; private set; }
private int BUFFER_SIZE = 259;
public TcpSocketClient Connect(IPEndPoint endpoint)
{
try
{
this.TCPConnection = new TcpClient(endpoint);
return this;
}
catch (SocketException ex)
{
throw new Exception($"{ex.Message} - {ex.StackTrace}");
}
catch (Exception ex)
{
throw new Exception($"Err: {ex.Message} - {ex.StackTrace}");
}
}
public T SendRequest<T>(IFrame request) where T : IFrame, new()
{
var data = request.ToByteArray();
var stream = this.TCPConnection.GetStream();
stream.Write(data, 0, data.Length);
data = new byte[this.BUFFER_SIZE];
var quantityBytes = stream.Read(data, 0, data.Length);
var frameResponse = data.Take(quantityBytes).ToArray();
var response = new T
{
FrameHeader = frameResponse[0],
Lenght = frameResponse[1],
FunctionCode = frameResponse[2],
Data = (frameResponse[1] == 0) ? null : data.ToDataField(quantityBytes),
Checksum = frameResponse[frameResponse.Length - 1]
};
(response.Checksum != response.VerifyChecksum()) { throw new Exception("Err in Checksum!"); }
return response;
}
public void Disconnect()
{
this.TCPConnection.Dispose();
}
}
You can modify your code to be async:
public async Task<T> SendRequest<T>(IFrame request) where T : IFrame, new()
{
var data = request.ToByteArray();
var stream = this.TCPConnection.GetStream();
await stream.WriteAsync(data, 0, data.Length);
data = new byte[this.BUFFER_SIZE];
var quantityBytes = await stream.ReadAsync(data, 0, data.Length);
var frameResponse = data.Take(quantityBytes).ToArray();
var response = new T
{
FrameHeader = frameResponse[0],
Lenght = frameResponse[1],
FunctionCode = frameResponse[2],
Data = (frameResponse[1] == 0) ? null : data.ToDataField(quantityBytes),
Checksum = frameResponse[frameResponse.Length - 1]
};
if(response.Checksum != response.VerifyChecksum())
throw new Exception("Err in Checksum!");
return response;
}
To call it use:
MyFrameResponseType response = await SendRequest(yourRequest);
Also, as a note, that code assumes you will receive a response with only one read, that will fail on a future, if the network is slow or the sent response is big enough it will be received in multiple read operations, you should have some mechanism to detect a frame start/end and read until you have received a full frame.
Okay, I've been looking around and have found tutorials on both sending custom objects over TCP and also connecting multiple clients to one server, however I haven't been able to find an example of both so have decided to try it myself however I'm stuck. I so far have written a server that can handle an unlimited number of clients but have failed to implement sending of objects. This is the code I have so far for sending custom objects:
Client code:
Person p = new Person("Tyler", "Durden", 30); // create my serializable object
string serverIp = "127.0.0.1";
TcpClient client = new TcpClient(serverIp, 9050); // have my connection established with a Tcp Server
IFormatter formatter = new BinaryFormatter(); // the formatter that will serialize my object on my stream
NetworkStream strm = client.GetStream(); // the stream
formatter.Serialize(strm, p); // the serialization process
strm.Close();
client.Close();
Server code:
TcpListener server = new TcpListener(9050);
server.Start();
TcpClient client = server.AcceptTcpClient();
NetworkStream strm = client.GetStream();
IFormatter formatter = new BinaryFormatter();
Person p = (Person)formatter.Deserialize(strm); // you have to cast the deserialized object
strm.Close();
client.Close();
server.Stop();
This is the code I currently have for multiple clients:
Server code:
private ArrayList m_aryClients = new ArrayList(); //List of connected clients (Used for threading)
public Program()
{
//Empty constructor
}
static Program()
{
}
#region Main Enty Point
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
//Set log file location
setLogFileLocation("/usr/local/bin/ClipCloud/" + DateTime.Now.ToShortDateString() + ".log");
//Start the application
printAndLog("Starting server");
Program appmain = new Program();
//Get local hostname
IPAddress[] addressList = null;
string hostName = "";
try
{
hostName = Dns.GetHostName();
addressList = Dns.GetHostByName(hostName).AddressList;
}
catch (Exception ex)
{
printAndLog("Failed to get local address (" + ex.Message + ")", 1);
}
//Start listening and set up threads
if ((addressList == null ? false : (int)addressList.Length >= 1))
{
object[] objArray = new object[] { "Listening on : (", hostName, ") ", addressList[0], ",", 399 };
printAndLog(string.Concat(objArray), 0);
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(addressList[0], 399));
socket.Listen(10);
socket.BeginAccept(new AsyncCallback(appmain.OnConnectRequest), socket);
//Wait until user types exit
while (!(Console.ReadLine().ToLower() == "exit"))
{}
printAndLog("Shutting down server");
socket.Close();
//Clean up after yourself
GC.Collect();
GC.WaitForPendingFinalizers();
printAndLog("Application successfully shutdown");
}
else
{
printAndLog("Unable to obtain local address" , 2);
}
}
#endregion
#region Server requests
public void NewConnection(Socket sockClient)
{
//new client connected
ClientHandler clientHandler = new ClientHandler(sockClient);
m_aryClients.Add(clientHandler);
printAndLog(string.Concat("Client (",clientHandler.Sock.RemoteEndPoint, ") connected"));
//Send client welcome message
DateTime now = DateTime.Now;
string str = string.Concat("{", now.ToString("G"), "}");
byte[] bytes = Encoding.ASCII.GetBytes(str.ToCharArray());
clientHandler.Sock.Send(bytes, (int)bytes.Length, SocketFlags.None);
clientHandler.SetupRecieveCallback(this);
}
public void OnConnectRequest(IAsyncResult ar)
{
try
{
Socket asyncState = (Socket)ar.AsyncState;
NewConnection(asyncState.EndAccept(ar));
asyncState.BeginAccept(new AsyncCallback(OnConnectRequest), asyncState);
}
catch (Exception ex)
{
printAndLog(ex.Message);
}
}
public void OnRecievedData(IAsyncResult ar)
{
ClientHandler asyncState = (ClientHandler)ar.AsyncState;
byte[] recievedData = asyncState.GetRecievedData(ar);
if ((int)recievedData.Length >= 1)
{
foreach (ClientHandler mAryClient in this.m_aryClients)
{
try
{
//This is where all data is sent out to all users!
mAryClient.Sock.Send(recievedData);
}
catch
{
printAndLog(string.Concat("Failed to send data to client (", asyncState.Sock.RemoteEndPoint, ")"), 2);
mAryClient.Sock.Close();
this.m_aryClients.Remove(asyncState);
return;
}
}
asyncState.SetupRecieveCallback(this);
}
else
{
printAndLog(string.Concat("Client (", asyncState.Sock.RemoteEndPoint, ") disconnected"), 0);
asyncState.Sock.Close();
this.m_aryClients.Remove(asyncState);
}
}
#endregion
}
internal class ClientHandler
{
private Socket m_sock;
private byte[] m_byBuff = new byte[50];
public Socket Sock
{
get
{
return this.m_sock;
}
}
public ClientHandler(Socket sock)
{
this.m_sock = sock;
}
public byte[] GetRecievedData(IAsyncResult ar)
{
int num = 0;
try
{
num = this.m_sock.EndReceive(ar);
}
catch
{
}
byte[] numArray = new byte[num];
Array.Copy(this.m_byBuff, numArray, num);
return numArray;
}
public void SetupRecieveCallback(Program app)
{
try
{
AsyncCallback asyncCallback = new AsyncCallback(app.OnRecievedData);
this.m_sock.BeginReceive(this.m_byBuff, 0, (int)this.m_byBuff.Length, SocketFlags.None, asyncCallback, this);
}
catch (Exception ex)
{
Console.WriteLine("Recieve callback setup failed! {0}", ex.Message);
}
}
}
Can someone please help me connecting the two.
i write this TCP communication library. the problem is that. when a client connects. the CPU usages boosts to maximum....this causes other application to become slow...
please take a look at the code and correct me where i did wrong..
the main code of my TCP library is
TCP Server Class
public class TCPServerEndPoint : ICommunication
{
private string channelName;
private string localIP;
private int localPort;
private string remoteIP;
private int remotePort;
private TcpListener tcpListenter;
/// <summary>
/// Accept the incomming connection and pass it to a thread to handle communication.
/// </summary>
private TCPServerWorker worker;
/// <summary>
/// List of threads created for connected clients.
/// </summary>
List<TCPServerWorker> workerThreads;
/// <summary>
/// Thread to keep listening process in seperate thread.
/// </summary>
private Thread serverThread;
/// <summary>
/// Flag to keep status of Endpoint.
/// </summary>
private bool keepRunning;
public TCPServerEndPoint()
{
this.keepRunning = false;
Guid guid = Guid.NewGuid();
channelName = guid.ToString();
workerThreads = new List<TCPServerWorker>();
}
public TCPServerEndPoint(string localIP, int localPort, string remoteIP, int remotePort)
{
this.localIP = localIP;
this.localPort = localPort;
this.remoteIP = remoteIP;
this.remotePort = remotePort;
workerThreads = new List<TCPServerWorker>();
this.keepRunning = false;
}
public event EventHandler<CommEventArgs> OnCommReceive;
public int CommStart()
{
if (this.IsStarted == true)
{
Console.WriteLine("TCP Server is already running");
return -1;
}
serverThread = new Thread(new ThreadStart(StartListening));
serverThread.IsBackground = true;
serverThread.Start();
return 0;
}
private void StartListening()
{
try
{
IPAddress localAddress = IPAddress.Parse(this.localIP);
tcpListenter = new TcpListener(localAddress, this.localPort);
tcpListenter.Start();
Console.WriteLine("TCP Server started");
Console.WriteLine("Server is listening on port : {0}", this.localPort);
this.keepRunning = true;
// look for incomming connections
while (this.keepRunning)
{
// connection received
TcpClient client = tcpListenter.AcceptTcpClient();
// create a new WorkerThread and pass the connected client to handle.
worker = new TCPServerWorker(client);
worker.dataReceived += new EventHandler<CommEventArgs>(worker_dataReceived);
workerThreads.Add(worker);
worker.Start();
}
tcpListenter.Stop();
Console.WriteLine("TCP Server stopped");
this.keepRunning = false;
}
catch
{
return;
}
}
void worker_dataReceived(object sender, CommEventArgs e)
{
if (this.OnCommReceive != null)
{
e.commChannel = this;
this.OnCommReceive(this, e);
}
}
public int CommStop()
{
if (this.IsStarted == false)
return -1;
// Close all worker threads created for connected clients.
foreach (TCPServerWorker item in workerThreads)
{
item.KeepRunning = false;
}
// break the listening loop
this.keepRunning = false;
// clear the worker thread list
workerThreads.Clear();
// force server to receive message to break while(keepRunning) loop
byte[] data = new byte[4];
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse(this.localIP), localPort);
Socket tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
tcpClient.Connect(ipEndPoint);
tcpClient.SendTo(data, ipEndPoint);
tcpClient.Close();
return 0;
}
public int CommSend(CommEventArgs obj)
{
obj.destAddress = this.remoteIP;
obj.destPort = this.remotePort;
return CommSendTo(obj);
}
public int CommSendTo(CommEventArgs obj)
{
int n;
byte[] buf;
try
{
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse(obj.destAddress), obj.destPort);
buf = (byte[])obj.data;
Socket tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
tcpClient.Connect(ipEndPoint);
n = tcpClient.SendTo(buf, ipEndPoint);
tcpClient.Close();
}
catch (Exception ex)
{
Console.WriteLine("Exception :: {0}", ex.Message);
return -1;
}
if (n == buf.Length)
{
if (OnCommSendComplete != null)
{
OnCommSendComplete(this, obj);
}
Console.WriteLine("Sent {0} bytes to {1}:{2}", n, obj.destAddress, obj.destPort);
}
else
{
return -1;
}
return n;
}
}
}
TCPServerWorker.cs
class TCPServerWorker
{
private TcpClient client;
private bool keepRunning;
public event EventHandler<CommEventArgs> dataReceived;
private const int MAX_TCP_DATA = 64000;
public bool KeepRunning
{
get
{
return this.keepRunning;
}
set
{
this.keepRunning = value;
}
}
public TCPServerWorker(TcpClient client)
{
this.client = client;
this.keepRunning = false;
}
public void Start()
{
Thread thread = new Thread(new ThreadStart(Process));
thread.IsBackground = true;
thread.Start();
}
private void Process()
{
if (client.Connected == true)
{
Console.WriteLine("Client connected :: {0}", client.Client.RemoteEndPoint);
this.keepRunning = true;
while (this.keepRunning)
{
// in my view. here is the main problem. this loop run for infinite time and causes CPU to reach at 100
byte[] buffer = new byte[MAX_TCP_DATA];
NetworkStream stream = client.GetStream();
StreamWriter writer = new StreamWriter(client.GetStream());
if (stream.DataAvailable == true)
{
int receivedBytesCount = stream.Read(buffer, 0, buffer.Length);
byte[] receivedBuffer = new byte[receivedBytesCount];
Array.Copy(buffer, receivedBuffer, receivedBytesCount);
String msg = Encoding.UTF8.GetString(receivedBuffer);
Console.WriteLine("Received MSG ::: " + msg);
writer.WriteLine("Server : Received {0} bytes", receivedBytesCount);
CommEventArgs comEventArg = new CommEventArgs();
comEventArg.data = (byte[])receivedBuffer;
IPEndPoint remoteIPEndPoint = (IPEndPoint)client.Client.RemoteEndPoint;
comEventArg.srcAddress = remoteIPEndPoint.Address.ToString();
comEventArg.srcPort = remoteIPEndPoint.Port;
comEventArg.length = receivedBytesCount;
this.OnDataReceived(comEventArg);
writer.Flush();
}
}
client.Close();
}
}
protected void OnDataReceived(CommEventArgs e)
{
if (this.dataReceived != null)
{
this.dataReceived(this, e);
}
}
}
}
You're using nonblocking I/O which leads to a loop (at least) in your client
while (this.keepRunning) {...}
which is consuming all your CPU resources by busy waiting.
You should consider to use blocking I/O or Socket.Select
Look at the first remark here
Details about select
One thing to note is that you never SET IsStarted .. you only GET it ._. Maybe you're spawning hundreds of threads =/ I'm talking about the TCPServerEndPoint class =/
Yes, you are busy waiting for a connection. I don't know socket programming so I can't give you details, but what you need to do is wait for a connection using the blocking system call.
I solved the issue after modifying the Process method of TCPServerWorker.cs
here is the changes
private void Process()
{
if (client.Connected == true)
{
Console.WriteLine("Client connected :: {0}", client.Client.RemoteEndPoint);
Byte[] bytes = new Byte[MAX_TCP_DATA];
String data = null;
NetworkStream stream = client.GetStream();
int i;
try
{
// Loop to receive all the data sent by the client.
while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
{
// bytes contains received data in byte[].
// Translate data bytes to a UTF-8 string.
byte[] receivedBuffer = new byte[i];
Array.Copy(bytes, receivedBuffer, i);
data = System.Text.Encoding.UTF8.GetString(receivedBuffer);
Console.WriteLine("Received MSG ::: " + data);
// Process the data sent by the client.
byte[] msg = System.Text.Encoding.UTF8.GetBytes(data);
// Send back a response.
stream.Write(msg, 0, msg.Length);
CommEventArgs comEventArg = new CommEventArgs();
comEventArg.data = receivedBuffer;
IPEndPoint remoteIPEndPoint = (IPEndPoint)client.Client.RemoteEndPoint;
comEventArg.srcAddress = remoteIPEndPoint.Address.ToString();
comEventArg.srcPort = remoteIPEndPoint.Port;
comEventArg.length = i;
this.OnDataReceived(comEventArg);
}
}
catch (Exception ex)
{
Console.WriteLine("Exception : " + ex.Message);
}
finally
{
client.Close();
}
}
}