I'm trying to use my Socket wrapper to connect to specified whois server to receive some information (yes I know TcpClient exists). To do so I made two classes. One - SocketClient - takes in Socket and has methods which handle all the connection stuff.(I didn't include IDisposable implementation)
public class SocketClient : ISocketClient
{
private const int HEADER_SIZE = 4;
private Socket _socket;
private bool disposedValue;
public SocketClient(Socket socket) { _socket = socket; }
public virtual Stream Connect(IPEndPoint endPoint)
{
_socket.Connect(endPoint);
return new NetworkStream(_socket);
}
public void Send(Stream networkStream, string message, Encoding encoding)
{
var bodyBytes = encoding.GetBytes(message);
var headerBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(bodyBytes.Length + HEADER_SIZE));
networkStream.Write(headerBytes, 0, HEADER_SIZE);
networkStream.Write(bodyBytes, 0, bodyBytes.Length);
}
public string Receive(Stream networkStream)
{
var header = ReadStream(networkStream, HEADER_SIZE);
var bodyLength = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(header)) -HEADER_SIZE;
var bodyBytes = ReadStream(networkStream, bodyLength); //Here exception is thrown
return Encoding.UTF8.GetString(bodyBytes);
}
static byte[] ReadStream(Stream networkStream, int bytesToRead)
{
var buffer = new byte[bytesToRead];
var bytesRead = 0;
while (bytesRead < bytesToRead)
{
var bytesReceived = networkStream.Read(buffer, bytesRead, bytesToRead - bytesRead);
if (bytesReceived == 0)
throw new SocketIsClosedException("Tried to read from closed socket.");
bytesRead += bytesReceived;
}
return buffer;
}
Second - WhoisResolver - Takes in SocketClient and uses it to send and receive whois info.
public class WhoisResolver : IWhoisResolver
{
private ISocketClient _socketClient;
private bool disposedValue;
public WhoisResolver(ISocketClient socketClient)
{
_socketClient = socketClient;
}
public string? Lookup(string fullDomainName, string whoisServerAddress, int whoisServerPort = 43)
{
try
{
var ip = Dns.GetHostAddresses(whoisServerAddress).First();
var stream = _socketClient.Connect(new IPEndPoint(ip, whoisServerPort));
_socketClient.Send(stream, $"{fullDomainName}\r\n",System.Text.Encoding.ASCII); //Here socket is still open
var response = _socketClient.Receive(stream);
_socketClient.Disconnect(false);
return response;
}catch
{
return null;
}
}
}
Here's how I use it:
var whoisServer = "whois.crsnic.net";
var domainName = "testdomain";
var tld = "net";
var socketClient = new SocketClient(new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp));
var resolver = new WhoisResolver(socketClient);
var result = resolver.Lookup($"{domainName}.{tld}", whoisServer);
Everytime my program tries to read from the stream i get 2 types of exception randomly:
-Unable to read data from the transport connection : An existing connection was forcibly closed by the remote host.
-Tried to read from closed socket.
When i use TcpClient whith buffered stream and stream reader/writer everything works fine.
As stated in the comment above the problem was with the way the message length was read. I thought that the message header was the first 4 bytes but when I read it, it were over 20000000 bytes of message lenght. Seems obvious but the exceptions were pretty misleading.
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.
I'm trying to receive data via bluetooth in Xamarin.Android. If I have data to receive, my method work fine. But if nothing has been sent, nothing is returned from Stream.Read method (_inputStream.Read(buffer, 0, buffer.Length)).
Any idea what am I doing wrong?
private Stream _inputStream;
public string ReceiveData()
{
byte[] buffer = new byte[30];
try
{
while (true)
{
int read = _inputStream.Read(buffer, 0, buffer.Length);
if (read <= 0)
{
return null;
}
char[] result = Encoding.ASCII.GetChars(buffer);
if (!result[0].Equals("\0"))
{
return result[0].ToString();
}
}
}
catch (Exception)
{
// Não implementado
}
return null;
}
The problem is in the line _inputStream.Read(buffer, 0, buffer.Length). If nothing has been sent via bluetooth, I get stuck in it. Nothing is returned, no exception is raised.
EDIT 1
As #rene suggested, I tried with Task/Async with a CancellationToken with 1500 ms. I get the same behavior.
public async Task<string> ReceiveData()
{
byte[] buffer = new byte[30];
CancellationTokenSource token = new CancellationTokenSource();
token.CancelAfter(TimeSpan.FromMilliseconds(1500));
try
{
while (true)
{
int read = await _inputStream.ReadAsync(buffer, 0, buffer.Length, token.Token);
if (read <= 0)
{
return null;
}
char[] result = Encoding.ASCII.GetChars(buffer);
if (!result[0].Equals("\0"))
{
return result[0].ToString();
}
}
}
catch (Exception ex)
{
// Não implementado
}
return null;
}
As told by James Harmon in Cancel ReadAsync, it is not possible to cancel an Stream.ReadAsync since the internal call is unmanaged and uses IOCompletion ports.
He suggest three options to get around this:
Use Socket.Shutdown(). This will return ReadAsync with a socket error of OperationAborted.
Wait for the read to timeout.
Check if data is available before reading from the socket.
I opted for the third option by checking if data is available before reading the socket.
My final code:
public async Task<string> ReceiveData()
{
byte[] buffer = new byte[30];
try
{
while (true)
{
if(_inputStream.IsDataAvailable())
{
int read = await _inputStream.ReadAsync(buffer, 0, buffer.Length);
if (read <= 0)
{
return null;
}
char[] result = Encoding.ASCII.GetChars(buffer);
if (!result[0].Equals("\0"))
{
return result[0].ToString();
}
}
}
}
catch (Exception)
{
// Não implementado
}
return null;
}
Thank you very much H.G. Sandhagen.
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.
I'm trying to save binary data recieved from remote server with code:
public static WebObjectResponse<byte[]> RequestBinary(string uri)
{
var streamData = GetResponseStream(uri);
if (streamData.StatusCode != 200)
{
return new WebObjectResponse<byte[]> {Result = new WebRequestResult(streamData.StatusCode)};
}
if (streamData.ContentLength == -1)
{
return new WebObjectResponse<byte[]> {Result = new WebRequestResult(204)}; // 204 = No Content
}
using (streamData.Stream)
{
var responseBuffer = new byte[streamData.ContentLength];
streamData.Stream.Read(responseBuffer, 0, responseBuffer.Length);
return new WebObjectResponse<byte[]>
{
Data = responseBuffer,
Result = WebRequestResult.CreateSuccessfull()
};
}
}
private static WebStreamResponse GetResponseStream(string uri)
{
try
{
var response = CreateRequest(uri).GetResponse();
return new WebStreamResponse
{
StatusCode = 200,
Stream = response.GetResponseStream(),
ContentLength = response.ContentLength
};
}
catch (WebException e)
{
return new WebStreamResponse {StatusCode = (int) ((HttpWebResponse) e.Response).StatusCode};
}
catch (Exception e)
{
throw;
}
}
response.ContentLength has value -1, but Fiddler shows me another picture:
That's why i had to add a double check (response.ContentLength != -1) to prevent exception throwing.
How I have to change C# code to recieve a valid response stream?