TcpClient.NetworkStream Async operations - Canceling / Disconnect - c#

Disclaimer: My C# isn't even close to as good as my C++
I am trying to learn how to do async sockets in C# in order to write a test app for a component of mine. I read about a variety of methods, but it seems the most modern is to use TcpClient, get the NetworkStream from it, and call async methods on the NetworkStream that return Task.
So, I set off on my adventure.
It is not clear how to cancel the async methods properly or if I need to. Am I supposed to manually cancel them when I want to disconnect? One would think TcpClient.Close would take care of that in some graceful way for me.
If I do use the manual cancellation, then it seems there is some mechanism needed to wait until all the async methods exit before the main thread continues on. Noted in the listing in the Disconnect() method. The result is that the main thread continues on disposing and exiting before MakeRequest() or Read() have exited, and therefore exceptions about using disposed objects.
In my test without manual cancellation, I get the same exception from the async read, about using an object that has been disposed, because the main thread still continues on disposing and exiting before those methods are exited.
I suppose I could put in my own synchronization mechanism...but what is the proper way to do this? Should I be cancelling at all? Is there some built in way to wait for those methods to exit? What did they expect us to do?
Here is my latest code attempt:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using log4net;
using System.IO;
using System.Threading;
namespace IntegrationTests
{
public class Client
{
private static readonly ILog log = LogManager.GetLogger("root");
static private ulong m_lastId = 1;
private ulong m_id;
private string m_host;
private uint m_port;
private uint m_timeoutMilliseconds;
private string m_clientId;
private TcpClient m_tcpClient;
private CancellationTokenSource m_cancelationSource;
public Client(string host, uint port, string clientId, uint timeoutMilliseconds)
{
m_id = m_lastId++;
m_host = host;
m_port = port;
m_clientId = clientId;
m_timeoutMilliseconds = timeoutMilliseconds;
m_tcpClient = null;
m_cancelationSource = null;
}
~Client()
{
Disconnect();
}
/// <summary>
/// Attempts to connect to the hostname and port specified in the constructor
/// </summary>
/// <throws cref="System.ApplicationException" on failure
public void Connect()
{
Disconnect();
m_tcpClient = new TcpClient();
m_cancelationSource = new CancellationTokenSource();
try
{
m_tcpClient.Connect(m_host, (int)m_port);
}
catch (SocketException e)
{
string msg = string.Format("Client #{0} failed to connect to {1} on port {2}"
, m_id, m_host, m_port);
throw new System.ApplicationException(msg, e);
}
if (m_tcpClient.Connected)
{
log.Debug(string.Format("Client #{0} connnected to the Component on {1}"
, m_id, m_tcpClient.Client.RemoteEndPoint.ToString()));
}
}
public void Disconnect()
{
if (m_cancelationSource != null)
{
m_cancelationSource.Cancel();
// TODO - There needs to be some kind of wait here until the async methods all return!
// How to do that?
// Are we even supposed to be manually canceling? One would think TcpClient.Close takes care of that,
// however when deleting all cancelation stuff, instead we get exceptions from the async methods about
// using TcpClient's members after it was disposed.
m_cancelationSource.Dispose();
m_cancelationSource = null;
}
if (m_tcpClient != null)
{
m_tcpClient.Close();
m_tcpClient = null;
}
}
public void Login()
{
string loginRequest = string.Format("loginstuff{0}", m_clientId);
var data = Encoding.ASCII.GetBytes(loginRequest);
NetworkStream stream = m_tcpClient.GetStream();
Task writeTask = stream.WriteAsync(data, 0, data.Count());
// This will block until the login is sent
// We want block until the login is sent, so we can be sure we logged in before making requests
if( !writeTask.Wait((int)m_timeoutMilliseconds) )
{
// Error - Send timed out
log.Error(string.Format("Client #{0} Timed out while sending login request to the Component"
, m_id));
}
else
{
log.Debug(string.Format("Client #{0} sent login request to the Component"
, m_id));
}
}
public async void Read()
{
byte[] buffer = new byte[1024];
MemoryStream memoryStream = new MemoryStream();
NetworkStream networkStream = m_tcpClient.GetStream();
Task<int> readTask = null;
bool disconnected = false;
try
{
while (!disconnected)
{
readTask = networkStream.ReadAsync(buffer, 0, buffer.Length, m_cancelationSource.Token);
int bytesReceived = await readTask;
if (readTask.Status == TaskStatus.RanToCompletion)
{
if( bytesReceived <= 0)
{
disconnected = true;
continue;
}
memoryStream.Write(buffer, 0, bytesReceived);
// TODO - Handle parsing of messages in the memory stream
memoryStream.Seek(0, SeekOrigin.Begin);
}
else if (readTask.Status == TaskStatus.Canceled)
{
// Error - Read was cancelled
log.Error(string.Format("Client #{0} Read operation was canceled."
, m_id));
disconnected = true;
continue;
}
else
{
// Error - Unexpected status
log.Error(string.Format("Client #{0} Read operation has unexpected status after returning from await."
, m_id));
}
}
}
catch (System.Exception e)
{
log.Error(string.Format("Client #{0} Exception caught while reading from socket. Exception: {1}"
, m_id, e.ToString()));
}
}
public async void MakeRequest(string thingy)
{
string message = string.Format("requeststuff{0}", thingy);
var data = Encoding.ASCII.GetBytes(message);
NetworkStream networkStream = m_tcpClient.GetStream();
Task writeTask = null;
try
{
writeTask = networkStream.WriteAsync(data, 0, data.Count(), m_cancelationSource.Token);
await writeTask;
if (writeTask.Status == TaskStatus.RanToCompletion)
{
log.Debug(string.Format("Client #{0} sent request for thingy {1} to the Component"
, m_id, thingy));
}
else if (writeTask.Status == TaskStatus.Canceled)
{
// Error - Write was cancelled
log.Error(string.Format("Client #{0} Write operation was canceled while requesting thingy {1} from the Component"
, m_id, thingy));
}
else
{
// Error - Unexpected status
log.Error(string.Format("Client #{0} Write operation has unexpected status after returning from await, while requesting thingy {1} from the Component"
, m_id, thingy));
}
}
catch (System.Exception e)
{
log.Error(string.Format("Client #{0} Exception caught while requesting thingy {1}. Exception: {2}"
, m_id, thingy, e.ToString()));
}
}
}
}
main:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using log4net;
using log4net.Config;
namespace IntegrationTests
{
class Program
{
private static readonly ILog log = LogManager.GetLogger("root");
static void Main(string[] args)
{
try
{
XmlConfigurator.Configure();
log.Info("Starting Component Integration Tests...");
Client client = new Client("127.0.0.1", 24001, "MyClientId", 60000);
client.Connect();
client.Read();
client.Login();
client.MakeRequest("Stuff");
System.Threading.Thread.Sleep(60000);
client.Disconnect();
}
catch (Exception e)
{
log.Error(string.Format("Caught an exception in main. Exception: {0}"
, e.ToString()));
}
}
}
}

Related

C# StreamReader Always receives null using StreamSocketListener

I tried to make a little test in C#, but it doesn't seems to work and honestly I cannot understand why.
The experiment is this. I write some strings in a list, then I start two threads: T1 the sender and T2 the receiver. T1 reads the strings I inserted before, and then sends them through the StreamWriter which is read by T2. At the end of the process, T2 returns what it did read, and the program terminates.
T1 seems to work fine: it sends the data, then closes the connection. T2 seems to receive the connection, but it doesn't read: the first reading is always null.
Anyone could help me pls?
Here's my code:
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using Windows.Foundation;
using Windows.Storage;
using System.Threading;
using System.Threading.Tasks;
using Windows.Storage.Streams;
using Windows.Networking;
using Windows.Networking.Sockets;
namespace UWPConsoleApp
{
class UWPConsoleApp
{
static void Main(string[] args)
{
List<String> toSend = new List<string>();
bool end = false;
do
{
Console.WriteLine("Please write something to send to the other task (write ';;;' to end)");
string s = Console.ReadLine();
if (s != ";;;")
{
toSend.Add(s);
Console.WriteLine($"The list now contains {toSend.Count} lines");
}
else
{
if(toSend.Count == 0)
{
Console.WriteLine("WARNING: you may insert atleast one line before going on");
continue;
}
end = true;
}
}
while (!end);
List<string> received;
Task[] tasks =
{
Task.Factory.StartNew( () =>
{
ThreadSender ts = new ThreadSender();
ts.buffer = toSend;
Console.WriteLine("(T1) waiting before starting...");
Thread.Sleep(7500);
ts.SendInfos();
} ),
Task.Factory.StartNew( async () =>
{
ThreadReceiver tr = new ThreadReceiver();
received = await tr.ReceiveInfos();
} )
};
Task.WaitAll(tasks);
Console.WriteLine("Done.");
Console.ReadKey();
}
}
class ThreadSender
{
// buffer di invio
public List<String> buffer;
// socket per l'invio dei dati
private StreamSocket ss;
public ThreadSender()
{
buffer = new List<string>();
ss = new StreamSocket();
}
public async void SendInfos()
{
HostName hn = new HostName("localhost");
await ss.ConnectAsync(hn, "3000");
StreamWriter sw = new StreamWriter(ss.OutputStream.AsStreamForWrite());
StreamReader sr = new StreamReader(ss.InputStream.AsStreamForRead());
Console.WriteLine($"(T1) Sending line count: {buffer.Count}");
sw.WriteLine(buffer.Count.ToString());
foreach(string line in buffer)
{
Console.WriteLine("(T1) sending line: {0}", line);
sw.WriteLine(line);
}
Thread.Sleep(5000);
Console.WriteLine("(T1) closing connection");
ss.Dispose();
ss = null;
}
}
class ThreadReceiver
{
// buffer di scrittura
public List<String> buffer = new List<string>();
private StreamSocketListener ssl;
private bool received = false;
private bool error = false;
public ThreadReceiver()
{
ssl = new StreamSocketListener();
}
public async Task<List<string>> ReceiveInfos()
{
ssl.ConnectionReceived += onConnectionReceived;
await ssl.BindServiceNameAsync("3000");
Console.WriteLine("(T2) waiting for connections...");
while (!received)
{
if(error)
{
Console.WriteLine("(T2) closing connection (error occurred)");
return null;
}
Thread.Sleep(2500);
}
return buffer;
}
public async void onConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
{
Console.WriteLine("received a incoming connection");
Thread.Sleep(2500);
StreamReader sr = new StreamReader(args.Socket.InputStream.AsStreamForRead());
string receivedCountString = await sr.ReadLineAsync();
if(receivedCountString == null)
{
Console.Error.WriteLine("(T2) ERROR: received null value");
error = true;
return;
}
int nToRead = Int32.Parse(receivedCountString);
while(nToRead > 0)
{
string receivedString = await sr.ReadLineAsync();
if (receivedString == null)
{
Console.Error.WriteLine("(T2) ERROR: received null value");
error = true;
return;
}
buffer.Add(receivedString);
nToRead--;
}
received = true;
}
}
}
Here's a example of output from the program above:
Please write something to send to the other task (write ';;;' to end)
aaa
The list now contains 1 lines
Please write something to send to the other task (write ';;;' to end)
bbb
The list now contains 2 lines
Please write something to send to the other task (write ';;;' to end)
ccc
The list now contains 3 lines
Please write something to send to the other task (write ';;;' to end)
ddd
The list now contains 4 lines
Please write something to send to the other task (write ';;;' to end)
;;;
(T1) waiting before starting...
(T2) waiting for connections...
received a incoming connection
(T1) Sending line count: 4
(T1) sending line: aaa
(T1) sending line: bbb
(T1) sending line: ccc
(T1) sending line: ddd
(T1) closing connection
(T2) ERROR: received null value
(T2) closing connection (error occurred)
Done.
I'm using Windows11 and Visual Studio 2019 to run the code (UWP project).

tcp client connect asynchronously with timeout in c# [duplicate]

This question already has answers here:
How to configure socket connect timeout
(12 answers)
How to set test TCP connection timeout?
(3 answers)
Closed 2 years ago.
I am looking for a way of setting up a tcpclient connection Asynchronously with timeout, I tried to find out the answers at How to set the timeout for a TcpClient?, but it's not working for me, I put the setup code into a task to avoid of blocking the main thread, timeout is needed cause the setup process may take more than 1 minutes to fail. Please help me to make timeout work.
using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace test
{
class MainClass
{
public static void Main(string[] args)
{
Connect();
int seconds = 0;
while (true)
{
Console.WriteLine("time elapsed: " + seconds.ToString());
Thread.Sleep(1000);
seconds += 1;
}
}
public static async void Connect()
{
Tcp tcp = new Tcp();
await tcp.BeginConnect("apple.com", 3);
Console.WriteLine("connect end.");
}
}
class Tcp
{
public async Task BeginConnect(string ip, int port)
{
var client = new TcpClient();
Console.WriteLine("start connecting.");
try
{
var succeed = false;
await Task.Run(() =>
{
var result = client.BeginConnect(ip, port, null, null);
succeed = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5)); // timeout doesn't work
client.EndConnect(result);
});
if (succeed && client.Connected)
{
Console.WriteLine("connected to server.");
}
else
{
Console.WriteLine("failed to connect to server.");
}
}
catch (Exception e)
{
Console.WriteLine("exception: " + e.ToString());
}
}
}
}
the connect method doesn't end after 5 seconds as expected, as long as exception thrown, the code after client.BeginConnect(ip, port, null, null); will never get executed.

"How to establish TCP connection with multiple IPs from single client C# application"

I had developed a C# TCP Client application to connect multiple IPs simultaneously or concurrently. I had programmed my application in such a way that, application will create thread for each IP and establish connection with the same and after finishing its job, that particular thread will be killed. The same thing will happen for all threads. (For Eg. If my application needs to connect 100 IPs simultaneously, 100 threads will be created for each IP. Every thread will be killed once they are done with their job). I had mentioned my code for thread creation below. I just wanted to know whether I'm going in a right way. Is this way of my approach is good? Please guide me in this regard. Thanks in advance
for (int i = 0; i < IPsCount; i++)
{
try
{
Thread serverThread = new Thread(Service);
serverThread.Start(IP_Add);
System.Threading.Thread.Sleep(100);
}
catch (Exception ex)
{
ex.ToString();
}
}
Every thread will be killed in Service method after finishing their job.
I would store all IP-Adresses in a collecton and do
Paralell.ForEach. Should be easier and saves you all the bare-metall thread handling :-)
UPDATE after discussion in comments:
I understood the OP that each connection is used for a short period, that is query some data then close. Then my method is good.
For long running tasks do create threads on your own or go to a boss-worker modell.
You could do something like below.
This code is an untested attempt at using System.IO.Pipelines to achieve your goal.
It should be a much better starting point than using Thread.Start directly.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
private static readonly IEnumerable<IPEndPoint> ipAddresses = new[]
{
new IPEndPoint(IPAddress.Loopback, 8087),
// more here.
};
internal static async Task Main()
{
await Task.WhenAll((await Task.WhenAll(
ipAddresses.Select(OpenSocket)))
.SelectMany(p => p));
// Handling code in ProcessLine.
}
private static async Task<IEnumerable<Task>> OpenSocket(
EndPoint iPEndPoint)
{
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(iPEndPoint);
var pipe = new Pipe();
var attendants = new[]
{
FillPipeAsync(socket, pipe.Writer),
ReadPipeAsync(socket, pipe.Reader)
};
return attendants;
}
private static async Task FillPipeAsync(Socket socket, PipeWriter writer)
{
const int minimumBufferSize = 512;
while (true)
{
try
{
// Request a minimum of 512 bytes from the PipeWriter
var memory = writer.GetMemory(minimumBufferSize);
var bytesRead = await socket.ReceiveAsync(
memory,
SocketFlags.None);
if (bytesRead == 0)
{
break;
}
// Tell the PipeWriter how much was read
writer.Advance(bytesRead);
}
catch
{
break;
}
// Make the data available to the PipeReader
var result = await writer.FlushAsync();
if (result.IsCompleted)
{
break;
}
}
// Signal to the reader that we're done writing
writer.Complete();
}
private static async Task ReadPipeAsync(Socket socket, PipeReader reader)
{
while (true)
{
var result = await reader.ReadAsync();
var buffer = result.Buffer;
SequencePosition? position;
do
{
// Find the EOL
position = buffer.PositionOf((byte)'\n');
if (position == null)
{
continue;
}
var line = buffer.Slice(0, position.Value);
ProcessLine(socket, line);
// This is equivalent to position + 1
var next = buffer.GetPosition(1, position.Value);
// Skip what we've already processed including \n
buffer = buffer.Slice(next);
} while (position != null);
// We sliced the buffer until no more data could be processed
// Tell the PipeReader how much we consumed and how much we
// left to process
reader.AdvanceTo(buffer.Start, buffer.End);
if (result.IsCompleted)
{
break;
}
}
reader.Complete();
}
private static void ProcessLine(
Socket socket,
in ReadOnlySequence<byte> buffer)
{
Console.Write($"[{socket.RemoteEndPoint}]: ");
foreach (var segment in buffer)
{
Console.Write(Encoding.UTF8.GetString(segment.Span));
}
Console.WriteLine();
}
}
}

Sending data over NetworkStream using multiple threads

I'm trying to build a command line chat room where the server is handling the connections and repeating input from one client back to all the other clients.
Currently the server is able to take in input from multiple clients, but can only send information back to those clients individually. I think my problem is that each connection is being handled on an individual thread. How would I allow for the threads to communicate with each other or be able to send data to each thread?
Server code:
namespace ConsoleApplication
{
class TcpHelper
{
private static object _lock = new object();
private static List<Task> _connections = new List<Task>();
private static TcpListener listener { get; set; }
private static bool accept { get; set; } = false;
private static Task StartListener()
{
return Task.Run(async () =>
{
IPAddress address = IPAddress.Parse("127.0.0.1");
int port = 5678;
listener = new TcpListener(address, port);
listener.Start();
Console.WriteLine($"Server started. Listening to TCP clients at 127.0.0.1:{port}");
while (true)
{
var tcpClient = await listener.AcceptTcpClientAsync();
Console.WriteLine("Client has connected");
var task = StartHandleConnectionAsync(tcpClient);
if (task.IsFaulted)
task.Wait();
}
});
}
// Register and handle the connection
private static async Task StartHandleConnectionAsync(TcpClient tcpClient)
{
// start the new connection task
var connectionTask = HandleConnectionAsync(tcpClient);
// add it to the list of pending task
lock (_lock)
_connections.Add(connectionTask);
// catch all errors of HandleConnectionAsync
try
{
await connectionTask;
}
catch (Exception ex)
{
// log the error
Console.WriteLine(ex.ToString());
}
finally
{
// remove pending task
lock (_lock)
_connections.Remove(connectionTask);
}
}
private static async Task HandleConnectionAsync(TcpClient client)
{
await Task.Yield();
{
using (var networkStream = client.GetStream())
{
if (client != null)
{
Console.WriteLine("Client connected. Waiting for data.");
StreamReader streamreader = new StreamReader(networkStream);
StreamWriter streamwriter = new StreamWriter(networkStream);
string clientmessage = "";
string servermessage = "";
while (clientmessage != null && clientmessage != "quit")
{
clientmessage = await streamreader.ReadLineAsync();
Console.WriteLine(clientmessage);
servermessage = clientmessage;
streamwriter.WriteLine(servermessage);
streamwriter.Flush();
}
Console.WriteLine("Closing connection.");
networkStream.Dispose();
}
}
}
}
public static void Main(string[] args)
{
// Start the server
Console.WriteLine("Hit Ctrl-C to close the chat server");
TcpHelper.StartListener().Wait();
}
}
}
Client Code:
namespace Client2
{
public class Program
{
private static void clientConnect()
{
TcpClient socketForServer = new TcpClient();
bool status = true;
string userName;
Console.Write("Input Username: ");
userName = Console.ReadLine();
try
{
IPAddress address = IPAddress.Parse("127.0.0.1");
socketForServer.ConnectAsync(address, 5678);
Console.WriteLine("Connected to Server");
}
catch
{
Console.WriteLine("Failed to Connect to server{0}:999", "localhost");
return;
}
NetworkStream networkStream = socketForServer.GetStream();
StreamReader streamreader = new StreamReader(networkStream);
StreamWriter streamwriter = new StreamWriter(networkStream);
try
{
string clientmessage = "";
string servermessage = "";
while (status)
{
Console.Write(userName + ": ");
clientmessage = Console.ReadLine();
if ((clientmessage == "quit") || (clientmessage == "QUIT"))
{
status = false;
streamwriter.WriteLine("quit");
streamwriter.WriteLine(userName + " has left the conversation");
streamwriter.Flush();
}
if ((clientmessage != "quit") && (clientmessage != "quit"))
{
streamwriter.WriteLine(userName + ": " + clientmessage);
streamwriter.Flush();
servermessage = streamreader.ReadLine();
Console.WriteLine("Server:" + servermessage);
}
}
}
catch
{
Console.WriteLine("Exception reading from the server");
}
streamreader.Dispose();
networkStream.Dispose();
streamwriter.Dispose();
}
public static void Main(string[] args)
{
clientConnect();
}
}
}
The main thing wrong in your code is that you make no attempt to send data received from one client to the other connected clients. You have the _connections list in your server, but the only thing stored in the list are the Task objects for the connections, and you don't even do anything with those.
Instead, you should maintain a list of the connections themselves, so that when you received a message from one client, you can then retransmit that message to the other clients.
At a minimum, this should be a List<TcpClient>, but because you are using StreamReader and StreamWriter, you'll want to initialize and store those objects in the list as well. In addition, you should include a client identifier. One obvious choice for this would be the name of the client (i.e. what the user enters as their name), but your example doesn't provide any mechanism in the chat protocol to transmit that identification as part of the connection initialization, so in my example (below) I just use a simple integer value.
There are some other irregularities in the code you posted, such as:
Starting a task in a brand new thread, just to execute a few statements that get you to the point of initiating an asynchronous operation. In my example, I simply omit the Task.Run() part of the code, as it's not needed.
Checking the connection-specific task when it's returned for IsFaulted. Since it's unlikely any I/O will actually have occurred by the time this Task object is returned, this logic has very little use. The call to Wait() will throw an exception, which will propagate to the main thread's Wait() call, terminating the server. But you don't terminate the server in the event of any other error, so it's not clear why you'd want to do that here.
There's a spurious call to Task.Yield(). I have no idea what you're trying to accomplish there, but whatever it is, that statement isn't useful. I simply removed it.
In your client code, you only attempt to receive data from the server when you've sent data. This is very wrong; you want clients to be responsive and receive data as soon as it's sent to them. In my version, I included a simple little anonymous method that is called immediately to start a separate message-receiving loop that will execute asynchronously and concurrently with the main user input loop.
Also in the client code, you were sending the "…has left…" message after the "quit" message that would cause the server to close the connection. This means that the server would never actually receive the "…has left…" message. I reversed the order of the messages so that "quit" is always the last thing the client ever sends.
My version looks like this:
Server:
class TcpHelper
{
class ClientData : IDisposable
{
private static int _nextId;
public int ID { get; private set; }
public TcpClient Client { get; private set; }
public TextReader Reader { get; private set; }
public TextWriter Writer { get; private set; }
public ClientData(TcpClient client)
{
ID = _nextId++;
Client = client;
NetworkStream stream = client.GetStream();
Reader = new StreamReader(stream);
Writer = new StreamWriter(stream);
}
public void Dispose()
{
Writer.Close();
Reader.Close();
Client.Close();
}
}
private static readonly object _lock = new object();
private static readonly List<ClientData> _connections = new List<ClientData>();
private static TcpListener listener { get; set; }
private static bool accept { get; set; }
public static async Task StartListener()
{
IPAddress address = IPAddress.Any;
int port = 5678;
listener = new TcpListener(address, port);
listener.Start();
Console.WriteLine("Server started. Listening to TCP clients on port {0}", port);
while (true)
{
var tcpClient = await listener.AcceptTcpClientAsync();
Console.WriteLine("Client has connected");
var task = StartHandleConnectionAsync(tcpClient);
if (task.IsFaulted)
task.Wait();
}
}
// Register and handle the connection
private static async Task StartHandleConnectionAsync(TcpClient tcpClient)
{
ClientData clientData = new ClientData(tcpClient);
lock (_lock) _connections.Add(clientData);
// catch all errors of HandleConnectionAsync
try
{
await HandleConnectionAsync(clientData);
}
catch (Exception ex)
{
// log the error
Console.WriteLine(ex.ToString());
}
finally
{
lock (_lock) _connections.Remove(clientData);
clientData.Dispose();
}
}
private static async Task HandleConnectionAsync(ClientData clientData)
{
Console.WriteLine("Client connected. Waiting for data.");
string clientmessage;
while ((clientmessage = await clientData.Reader.ReadLineAsync()) != null && clientmessage != "quit")
{
string message = "From " + clientData.ID + ": " + clientmessage;
Console.WriteLine(message);
lock (_lock)
{
// Locking the entire operation ensures that a) none of the client objects
// are disposed before we can write to them, and b) all of the chat messages
// are received in the same order by all clients.
foreach (ClientData recipient in _connections.Where(r => r.ID != clientData.ID))
{
recipient.Writer.WriteLine(message);
recipient.Writer.Flush();
}
}
}
Console.WriteLine("Closing connection.");
}
}
Client:
class Program
{
private const int _kport = 5678;
private static async Task clientConnect()
{
IPAddress address = IPAddress.Loopback;
TcpClient socketForServer = new TcpClient();
string userName;
Console.Write("Input Username: ");
userName = Console.ReadLine();
try
{
await socketForServer.ConnectAsync(address, _kport);
Console.WriteLine("Connected to Server");
}
catch (Exception e)
{
Console.WriteLine("Failed to Connect to server {0}:{1}", address, _kport);
return;
}
using (NetworkStream networkStream = socketForServer.GetStream())
{
var readTask = ((Func<Task>)(async () =>
{
using (StreamReader reader = new StreamReader(networkStream))
{
string receivedText;
while ((receivedText = await reader.ReadLineAsync()) != null)
{
Console.WriteLine("Server:" + receivedText);
}
}
}))();
using (StreamWriter streamwriter = new StreamWriter(networkStream))
{
try
{
while (true)
{
Console.Write(userName + ": ");
string clientmessage = Console.ReadLine();
if ((clientmessage == "quit") || (clientmessage == "QUIT"))
{
streamwriter.WriteLine(userName + " has left the conversation");
streamwriter.WriteLine("quit");
streamwriter.Flush();
break;
}
else
{
streamwriter.WriteLine(userName + ": " + clientmessage);
streamwriter.Flush();
}
}
await readTask;
}
catch (Exception e)
{
Console.WriteLine("Exception writing to server: " + e);
throw;
}
}
}
}
public static void Main(string[] args)
{
clientConnect().Wait();
}
}
There is still a lot you'll need to work on. You'll probably want to implement proper initialization of chat user names on the server side. At the very least, for real-world code you'd want to do more error checking, and make sure the client ID is generated reliably (if you only want positive ID values, you can't have more than 2^31-1 connections before it rolls back over to 0).
I also made some other minor changes that weren't strictly necessary, such as using the IPAddress.Any and IPAddress.Loopback values instead of parsing strings, and just generally simplifying and cleaning up the code here and there. Also, I'm not using a C# 6 compiler at the moment, so I changed the code where you were using C# 6 features so that it would compile using C# 5 instead.
To do a full-blown chat server, you still have your work cut out for you. But I hope that the above gets you back on the right track.

C# Networking : Server hangs after receiving more than 65535 bytes

UPDATE:
Due to problems with the admins here on Stackoverflow, I have posted a very trimmed-down version of the same problem on MSDN forum. This text below used MyNetworking.dll, but that is not the problem. Here is a very slimmed Client-Server thing and the problem is exactly the same. Feel free to try it out =)
http://social.msdn.microsoft.com/Forums/en-US/netfxnetcom/thread/d3d33eb9-7dce-4313-929e-a8a63d0f1e03
/UPDATE
So, I have a strange error.
Normally, we have a DLL that handles our networking. Lets call that MyNetworking.dll. We use it everywhere in our servers and clients and have done so for 5 years. I haven't had a problem with it, until now.
I have an "XMLPoller", that reads XML from a MySQL database, serializes that into a byte[] array and sends it over the network. These particular XML messages is 627 bytes in serialized form.
The XMLPoller connects to a port on a "remote server" (that happens to be localhost) and sends the packets, one at a time. At exactly packet nbr 105 the connection is closed. 104 packets are sent from XMLPoller and received by the Server. 104 x 627 = 65208 bytes. But packet 105, when the total number of bytes sent would be 65835 the connection is closed with this error:
System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
at System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult)
at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
This is the error on the server. However, I have stepped through the XMLPoller (client), and I see when the last 627 bytes are sent (thus sending up til 65835 bytes) and I see no errors on the client, it passes sending without problems.
UPDATE 20:15 SWEDISH TIME
I also get this error in the Client when I debug a little more:
Unable to read data from the transport connection: An established connection was aborted by the software in your host machine.
I think I have confirmed that it is in the Client the error exists. I am stepping through the code and before any Exceptions are caught on the server, I get an exception on the Client as stated above.
/ENDUPDATE
It seems to me that the Server never receives it, getting the error above. The server gets the connection closed because of something happening on the Client. However, the error on the client is in TCPInput; the stream reading data is dead for some reason?
I am not buffering anything in MyNetworking.dll.
When I get a new connection on a Socket (on the Server), I do this code:
public void setConnected(Socket thisClient)
{
NetworkStream stream = new NetworkStream(thisClient);
socket = thisClient;
output = new TCPOutput(stream, outputHandler,this);
remoteIP = this.socket.RemoteEndPoint.ToString();
changeState(State.Connected);
try
{
stream.BeginRead(inputBuffer, 0, 5000, new AsyncCallback(OnDataReceived), null);
}
catch (Exception e)
{
this.disconnect();
}
}
and then, the OnDataReceived method (where the data is actually received):
public void OnDataReceived(IAsyncResult asyn)
{
int nbrRead = 0;
byte[] tmp = null;
try
{
nbrRead = stream.EndRead(asyn);
tmp = new byte[nbrRead];
}
catch (Exception e)
{
// *** HERE IS WHERE THE EXCEPTION IS CAUGHT ***
System.Diagnostics.Debugger.Log(0, "Bla1", e.ToString());
this.disconnect();
}
if (nbrRead > 0)
{
try
{
Array.Copy(inputBuffer, 0, tmp, 0, nbrRead);
}
catch(Exception e)
{
System.Diagnostics.Debugger.Log(0, "Bla2", e.ToString());
this.disconnect();
}
preProcessMessage(tmp);
try
{
stream.BeginRead(inputBuffer, 0, 5000, new AsyncCallback(OnDataReceived), new object());
}
catch(Exception e)
{
System.Diagnostics.Debugger.Log(0, "Bla3", e.ToString());
this.disconnect();
}
}
else
this.disconnect();
}
Right now Im sort of clueless as to what is going on... Any ideas?
UPDATE 1:
Client code for sending data:
public bool sendData(byte[] data)
{
if(this.state == State.Connected)
{
if (data != null && data.Length > 0)
{
try
{
//data = Crypto.Encrypt("a1s2d3", data);
outputStream.Write(data, 0, data.Length);
}
catch(Exception e)
{
System.Diagnostics.Debug.WriteLine("ClientHandler.sendData> " + e.ToString());
}
//parent.outDataLog(data.Length);
}
}
return true;
}
Update 2
I tried to do a Flush on the outgoing stream from the client - no effect:
public bool sendData(byte[] data)
{
if(this.state == State.Connected)
{
if (data != null && data.Length > 0)
{
try
{
//data = Crypto.Encrypt("a1s2d3", data);
outputStream.Write(data, 0, data.Length);
outputStream.Flush();
}
catch(Exception e)
{
System.Diagnostics.Debug.WriteLine("ClientHandler.sendData> " + e.ToString());
}
//parent.outDataLog(data.Length);
}
}
return true;
}
UPDATE 3: Posting more code as per request
This code is old and not the pretties in the world, I know. But it has been working very well for 5 years so =)
ClientHandler.cs (what the actual Client is using for sending etc)
using System;
using System.Net.Sockets;
using System.Net;
using System.Threading;
namespace tWorks.tNetworking.tNetworkingCF
{
/// <summary>
/// Summary description for connectionHandler.
/// </summary>
public class ClientHandler
{
#region Fields (17) 
string address;
Connector connector;
DataHandler dataHandler;
int id;
TCPInput input;
int interval;
string localAddress;
IPEndPoint localPoint;
int localPort;
NetworkStream outputStream;
public TTCPClientInterface parent;
int port;
tWorks.tNetworking.Protocol.Protocol protocol;
bool reconnect;
string remoteIP;
Socket socket;
public State state;
#endregion Fields 
#region Enums (1) 
public enum State {Disconnected,Connecting,Connected}
#endregion Enums 
#region Constructors (4) 
public ClientHandler(int id, TTCPClientInterface parent, Socket socket, tWorks.tNetworking.Protocol.Protocol protocol)
{
this.id=id;
this.parent = parent;
this.protocol = protocol;
dataHandler = new DataHandler(protocol, this);
setConnected(socket);
}
public ClientHandler(int id, TTCPClientInterface parent, Protocol.Protocol protocol)
{
this.id=id;
this.parent = parent;
this.protocol = protocol;
dataHandler = new DataHandler(protocol, this);
state = State.Disconnected;
}
public ClientHandler(int id, TTCPClientInterface parent, Socket socket)
{
this.id=id;
this.parent = parent;
setConnected(socket);
}
public ClientHandler(int id, TTCPClientInterface parent)
{
this.id=id;
this.parent = parent;
this.protocol = null;
changeState(State.Disconnected);
}
#endregion Constructors 
#region Delegates and Events (4) 
// Delegates (2) 
public delegate void ConnectionLostDelegate(string message);
public delegate void exceptionDelegate(Exception ex);
// Events (2) 
public event exceptionDelegate ConnectionFailed;
public event ConnectionLostDelegate ConnectionLostEvent;
#endregion Delegates and Events 
#region Methods (17) 
// Public Methods (16) 
public void connect(string address, int port, int retryInterval, bool reestablish)
{
System.Random rand = new Random();
localPort = rand.Next(40000, 60000);
IPAddress localIP = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0]; // new IPAddress(Dns.GetHostByName(Dns.GetHostName()).AddressList[0].Address);
connect(address, port, retryInterval, reestablish, localIP.ToString(), localPort);
}
/// <summary>
/// Will connect to the address and port specified. If connection failed a new attempt will be made according to the Interval parameter.
/// If connection is lost attempts to reastablish it will be made if Reestablish is set to true.
/// </summary>
/// <param name="address"></param>
/// <param name="port"></param>
/// <param name="retryInterval"></param>
/// <param name="reestablish"></param>
public void connect(string address, int port, int retryInterval, bool reestablish, string localAddress, int localPort)
{
this.reconnect = reestablish;
this.address = address;
this.port = port;
this.interval = retryInterval;
this.localAddress = localAddress;
this.localPort = localPort;
changeState(State.Connecting);
connector = new Connector(address, port, this, interval, localPoint, reestablish);
connector.Connect();
}
public void disconnect()
{
reconnect = false;
if (connector != null)
{
connector.stopConnecting();
}
setDisconnected();
}
public void dispose()
{
}
public void failedConnect(Exception e)
{
if (ConnectionFailed != null)
ConnectionFailed(e);
}
public int getID()
{
return this.id;
}
public string getIP()
{
return remoteIP;
}
public bool isConnected()
{
return this.state == State.Connected;
}
public void outDataLog(int nbrBytes)
{
parent.outDataLog(nbrBytes, id);
}
public void preProcessMessage(byte[] data)
{
//data = Crypto.Decrypt("a1s2d3", data);
if(protocol != null)
dataHandler.addData(data);
else
processMessage(data);
}
public void processMessage(byte[] data)
{
parent.processMessage(data,this);
}
public bool sendData(byte[] data)
{
if(this.state == State.Connected)
{
if (data != null && data.Length > 0)
{
try
{
//data = Crypto.Encrypt("a1s2d3", data);
outputStream.Write(data, 0, data.Length);
outputStream.Flush();
}
catch(Exception e)
{
System.Diagnostics.Debug.WriteLine("ClientHandler.sendData> " + e.ToString());
}
//parent.outDataLog(data.Length);
}
}
return true;
}
public void setConnected(Socket thisClient)
{
socket = thisClient;
outputStream = new NetworkStream(thisClient);
input = new TCPInput(outputStream, this);
remoteIP = this.socket.RemoteEndPoint.ToString();
changeState(State.Connected);
}
public void setDisconnected()
{
try
{
if (this.state == State.Connected)
{
changeState(State.Disconnected);
//socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
}
catch { }
if (reconnect)
this.connect(address, port, interval, true, localAddress, localPort);
}
public void stopConnect()
{
connector.stopConnecting();
changeState(State.Disconnected);
}
public override string ToString()
{
string returnString = "(D)";
if(this.state == State.Connected)
returnString = this.getIP();
return returnString;
}
// Private Methods (1) 
private void changeState(State state)
{
if (this.state == State.Connected && state == State.Disconnected)
{
if (ConnectionLostEvent != null)
ConnectionLostEvent("Uppkoppling bröts.");
}
this.state = state;
parent.connStateChange(this);
}
#endregion Methods 
}
}
This is TCPInput.cs that is listening on incoming data and forwarding that to the ClientHandler (seen above):
using System;
using System.Net.Sockets;
using System.Net;
using System.Threading;
namespace tWorks.tNetworking.tNetworkingCF
{
public class TCPInput
{
NetworkStream stream;
ClientHandler client;
public TCPInput(NetworkStream nS, ClientHandler client)
{
stream = nS;
this.client = client;
Thread t = new Thread(new ThreadStart(run));
t.IsBackground = true;
t.Name = "TCPInput";
t.Start();
}
public void run()
{
bool continueRead = true;
byte[] readBuffer = new byte[32768];
byte[] receivedBuffer = null;
int nbrBytesRead = 0;
int receivedBufferPos = 0;
while(continueRead)
{
try
{
nbrBytesRead = 0;
nbrBytesRead = stream.Read(readBuffer, 0, 10000);
receivedBuffer = new byte[nbrBytesRead];
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine("TCPInput> Exception when stream.Read: " + e.ToString());
continueRead = false;
}
if(nbrBytesRead > 0)
{
try
{
Array.Copy(readBuffer, 0, receivedBuffer, receivedBufferPos, nbrBytesRead);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine("TCPInput> Exception when Array.Copy: " + e.ToString());
continueRead = false;
}
client.preProcessMessage(receivedBuffer);
}
else
{
// *** I can break here, the nbrOfBytes read is 0 when this whole thing explodes =)
System.Diagnostics.Debug.WriteLine("TCPInput> Number of bytes read == 0! Setting continueRead = false");
continueRead = false;
}
}
client.setDisconnected();
}
}
}
The problem is in your other code, the 'client'. It closes the connection after sending all the 'packets'. You must wait until the server has received all of them. A simple approach, beyond negotiating it explicitly, is to wait for the server to close the connection.
That number ("thus sending up til 65835 bytes") is magically close to 2^16-1 (65535) -- looks like just one packet over!
(I'm assuming it's just the larger size that made things go kaboom! -- this can be tested reliably.)
I suspect there is an unsigned 16-bit variable used (in the library) where you need something with more range. Perhaps you can "empty" the internals of the library periodically or perform the operation in multiple connection? (Okay, just trying to throw out some 'quick hack' ideas :-)
So, after much testing and discussing with my partner-in-crime we found out that instead of using port 21 and taking for example port 22 - the problem goes away.
I have no idea why it behaves like this, but it does...
You post raises questions for me. Like why are you choosing well known ports for this service? I don't believe in coincidences and suspect your use of the term "partner-in-crime" may have more truth then I would care to be associated with.
Then also I am wondering why you assume a Windows bug and not one in the MyNetowrking.dll. Sure, you have been using this for five years. But it still hasn't had the level of vetting that Microsoft gives their code.

Categories