How to run async task in background? - c#

I need to forward the port of running service in k8s cluster. So I have use the example provided in github repo to do port forward for specific service. Now I would like to use the example in my test but when I call port forward method then I always get web socket exception but if I written the same code in console application then I don't get any exception it run perfectly fine.
private async Task<bool> ForwardPortOperation(string podName, int[] originalPort, int forwardPort)
{
var pod = GetPodObject(podName);
var webSocket = await _config.GetK8SClient().WebSocketNamespacedPodPortForwardAsync(pod.Metadata.Name, pod.Metadata.NamespaceProperty, originalPort, WebSocketProtocol.V4BinaryWebsocketProtocol);
var demux = new StreamDemuxer(webSocket);
demux.Start();
var stream = demux.GetStream((byte?)0, (byte?)0);
IPAddress ipAddress = IPAddress.Loopback;
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, forwardPort);
Socket listener = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(localEndPoint);
listener.Listen(100);
Socket handler = null;
var accept = Task.Run(() =>
{
while (true)
{
handler = listener.Accept();
var bytes = new byte[4096];
while (true)
{
int bytesRec = handler.Receive(bytes);
stream.Write(bytes, 0, bytesRec);
if (bytesRec == 0 || Encoding.Default.GetString(bytes, 0, bytesRec).IndexOf("<EOF>", StringComparison.InvariantCultureIgnoreCase) > -1)
{
break;
}
listener.Close();
}
}
});
var copy = Task.Run(() =>
{
var buff = new byte[4096];
while (true)
{
var read = stream.Read(buff, 0, 4096);
handler.Send(buff, read, 0);
}
});
await accept;
await copy;
return true;
}
Port forward method:
public bool PortForward(string podName, int[] originalPort, int forwardPort)
{
return Task.Run(() => ForwardPortOperation(podName, originalPort, forwardPort)).Result;
}
Nunit test project:
[Test]
public void VerifyPortForward()
{
new Pod(_config).PortForward("web_service_application", new int[3100], 1615);
}
How can I run such async method in background? so that once port forward successful then I can continue to another test.
Exception:
https://gist.github.com/gittadesushil/bc3bf008b7a4fc62c33e97ab1fbf687f

I assume the method PortForward should be rewritten to be asynchronous in the following way:
public async Task<bool> PortForward(string podName, int[] originalPort, int forwardPort)
{
return await Task.Run(() => ForwardPortOperation(podName, originalPort, forwardPort));
}
After that you can write a NUnit test:
[Test]
public async Task VerifyPortForward()
{
var pod = new Pod(_config);
var result = await pod.PortForward("web_service_application", new int[3100], 1615);
// TODO: assert the result
}

Related

Multiple TCPServer/Client C#

I am trying to learn C# programming language. I have a very simple question here. I wanted to make TCPServer and TCPClient. but I couldn't quite do what I wanted to do. What I want is to have 4 clients on 1 server and they can talk to each other. I want the names of the people speaking to be determined individually. Please can anyone help with this? (Sorry for bad english)
/Server Code/
namespace SimpleTcpSrvr
{
class Program
{
//private static object mTcpClient;
static void Main(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
int asd;
byte[] data = new byte[1024];
IPEndPoint asd2 = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1302);
Socket newsocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
newsocket.Bind(asd2);
newsocket.Listen(100);
Console.WriteLine("waiting for connection...");
Socket client = newsocket.Accept();
IPEndPoint clientep = (IPEndPoint)client.RemoteEndPoint;
var socket = (Socket)client;
Console.WriteLine((string.Format("New connection: " + socket.RemoteEndPoint.ToString())));
string welcome = "Chat Server";
data = Encoding.UTF8.GetBytes(Welcome my chat server);
client.Send(data, data.Length, SocketFlags.None);
string input;
while (true)
{
data = new byte[1024];
asd = client.Receive(data);
if (asd == 0)
break;
Console.WriteLine("TCPClient: " + Encoding.UTF8.GetString(data, 0, asd));
input = Console.ReadLine();
Console.SetCursorPosition(0, Console.CursorTop - 1);
Console.WriteLine("You: " + input);
client.Send(Encoding.UTF8.GetBytes(input));
}
Console.WriteLine("{0}'sinin bağlantısı kesildi.", clientep.Address);
client.Close();
newsocket.Close();
Console.ReadLine();
}
/Client Code/
namespace TCPClient
{
public class TcpClient
{
static void Main(string[] args)
{
Console.OutputEncoding = Encoding.ASCII;
byte[] data = new byte[1024];
string input, stringData;
IPEndPoint asd2 = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1302);
Socket newsocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
newsocket.Connect(asd2);
}
catch (SocketException e)
{
Console.WriteLine("cant connect server");
Console.WriteLine(e.ToString());
return;
}
int asd = newsocket.Receive(data);
stringData = Encoding.UTF8.GetString(data, 0, asd);
Console.WriteLine(stringData);
while (true)
{
input = Console.ReadLine();
Console.SetCursorPosition(0, Console.CursorTop - 1);
Console.WriteLine("You: " + input);
newsocket.Send(Encoding.UTF8.GetBytes(input));
data = new byte[1024];
asd = newsocket.Receive(data);
stringData = Encoding.UTF8.GetString(data, 0, asd);
byte[] utf8string = Encoding.UTF8.GetBytes(stringData);
Console.WriteLine("TCPServer:" + stringData);
}
Console.WriteLine("connection lost from server...");
newsocket.Shutdown(SocketShutdown.Both);
newsocket.Close();
Console.WriteLine("disconnected!");
Console.ReadLine();
}
}
}
Firstly, it has to be said, there are a million-and-one edge cases when working with sockets - unless you are actually writing a new transport-protocol to run over TCP, you'd be much better off investing your time into learning an existing transport such as HTTP or gRPC.
With that disclaimer out of the way:
Use TcpListener() instead of Socket() in your server.
Use TcpClient() instead of Socket() in your client.
Your server needs to be able to simultaneously handle multiple clients, for this, use the Task-based Asyncronous Pattern (TAP).
So in your server, you need an instance of TcpClient() for every connection that your server accepts().
Start with a place to store all of your Tasks, e.g.,
static List<Task> Tasks = new();
Next you need a TcpListener which accepts incomming connections and spawns a Task to manage the TcpClient associated with that connection e.g.,
static async Task RunServerAsync()
{
TcpListener tcpListener = new(IPAddress.Loopback, 9999);
tcpListener.Start();
while (true)
{
var tcpClient = await tcpListener.AcceptTcpClientAsync();
Tasks.Add(Task.Run(() => RunClientAsync(tcpClient)));
}
}
A simple TcpClient Task would look something like this
static async Task RunClientAsync(TcpClient tcpClient)
{
Console.WriteLine($"Connection from: [{tcpClient.Client.RemoteEndPoint}]");
var reader = new StreamReader(tcpClient.GetStream());
while (true)
{
var line = await reader.ReadLineAsync();
Console.WriteLine($"{tcpClient.Client.RemoteEndPoint}: {line}");
}
}
You can tie this together in a Main() like this:
static async Task Main(string[] args)
{
await Task.Run(() => RunServerAsync());
}
Now you have a very simple echo-line server that will accept connections from as many clients as you can throw at it.
To wrap it up, you could use the TAP to create clients, for your testing, maybe something like:
static async Task RunClientAsync(string message)
{
var tcpClient = new TcpClient("127.0.0.1", 9999);
StreamWriter sw = new(tcpClient.GetStream());
tcpClient.NoDelay = true;
while(true)
{
await sw.WriteLineAsync(message);
await sw.FlushAsync();
await Task.Delay(1000);
}
}
And of course you'd need to update your Main to support this, e.g.,
static async Task Main(string[] args)
{
_ = Task.Run(() => RunServerAsync());
await Task.Delay(1000); // give the server a sec to start
_ = Task.Run(() => RunClientAsync("This is from client1"));
_ = Task.Run(() => RunClientAsync("Client2 is here!!"));
_ = Task.Run(() => RunClientAsync("And I am client3"));
await Task.Delay(int.MaxValue);
}
Of course in a real application you'd never fire and forget your Tasks like that.
Full code for the sake of completeness:
class Program
{
static List<Task> Tasks = new();
static async Task RunClientAsync(TcpClient tcpClient)
{
Console.WriteLine($"Connection from: [{tcpClient.Client.RemoteEndPoint}]");
var reader = new StreamReader(tcpClient.GetStream());
while (true)
{
var line = await reader.ReadLineAsync();
Console.WriteLine($"{tcpClient.Client.RemoteEndPoint}: {line}");
}
}
static async Task RunServerAsync()
{
TcpListener tcpListener = new(IPAddress.Loopback, 9999);
tcpListener.Start();
while (true)
{
var tcpClient = await tcpListener.AcceptTcpClientAsync();
Tasks.Add(Task.Run(() => RunClientAsync(tcpClient)));
}
}
static async Task Main(string[] args)
{
_ = Task.Run(() => RunServerAsync());
await Task.Delay(1000); // give the server a sec to start
_ = Task.Run(() => RunClientAsync("This is from client1"));
_ = Task.Run(() => RunClientAsync("Client2 is here!!"));
_ = Task.Run(() => RunClientAsync("And I am client3"));
await Task.Delay(int.MaxValue);
}
static async Task RunClientAsync(string message)
{
var tcpClient = new TcpClient("127.0.0.1", 9999);
StreamWriter sw = new(tcpClient.GetStream());
tcpClient.NoDelay = true;
while(true)
{
await sw.WriteLineAsync(message);
await sw.FlushAsync();
await Task.Delay(1000);
}
}
}

HTTPS proxy implementation, how to detect a completed request

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.

C# - What's the best way to use TcpListener (async)

I would create a tcp server with TcpListener, but I don't know what's the best solution to do that.
I tried with 3 examples. See below.
Example 1
(I used BeginAcceptTcpClient)
class Program
{
static void Main(string[] args)
{
var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 4567);
var listener = new TcpListener(endPoint);
listener.Start();
AcceptTcpClient(listener);
while (true)
{
}
}
public static void AcceptTcpClient(TcpListener listener)
{
listener.BeginAcceptTcpClient(ClientConnected, listener);
}
public static void ClientConnected(IAsyncResult asyncResult)
{
var listener = (TcpListener)asyncResult.AsyncState;
var client = listener.EndAcceptTcpClient(asyncResult);
AcceptTcpClient(listener);
DoAsync(client);
}
}
Example 2
(I used BeginAcceptTcpClient with AutoResetEvent)
class Program1
{
private static readonly AutoResetEvent CONNECTION_WAIT_HANDLE = new AutoResetEvent(false);
static void Main(string[] args)
{
var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 4567);
var listener = new TcpListener(endPoint);
listener.Start();
while (true)
{
listener.BeginAcceptTcpClient(ClientConnectedHandle, listener);
CONNECTION_WAIT_HANDLE.WaitOne();
CONNECTION_WAIT_HANDLE.Reset();
}
}
public static void ClientConnectedHandle(IAsyncResult asyncResult)
{
var listener = (TcpListener)asyncResult.AsyncState;
var client = listener.EndAcceptTcpClient(asyncResult);
CONNECTION_WAIT_HANDLE.Set();
DoAsync(client);
}
}
Example 3
(I used AcceptTcpClientAsync)
class Program2
{
static async Task Main(string[] args)
{
var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 4567);
var listener = new TcpListener(endPoint);
listener.Start();
while (true)
{
var client = await listener.AcceptTcpClientAsync();
DoAsync(client);
}
}
public static void AcceptTcpClient(TcpListener listener)
{
listener.BeginAcceptTcpClient(ClientConnected, listener);
}
public static void ClientConnected(IAsyncResult asyncResult)
{
var listener = (TcpListener)asyncResult.AsyncState;
var client = listener.EndAcceptTcpClient(asyncResult);
AcceptTcpClient(listener);
DoAsync(client);
}
}
I think the best solution is the last (Example 3) but I'm not sure. What do you think of that?
This is the code that I'm using in my project. It's a receive only asynchronous server but you can modify it to your liking according to your needs in Task.Run(). I have commented out the code so that you can understand how it works.
static async Task Main(string[] args)
{
await RunServer();
}
static async Task RunServer()
{
TcpListener Listener = new TcpListener(IPAddress.Any, YOURPORTHERE); // Set your listener
Listener.Start(); // Start your listener
while (true) // Permanent loop, it may not be the best solution
{
TcpClient Client = await Listener.AcceptTcpClientAsync(); // Waiting for a connection
_ = Task.Run(() => { // Connection opened. Queues the specified job to run in the ThreadPool, meanwhile the server is ready to accept other connections in parallel
try
{
var Stream = Client.GetStream(); // (read-only) get data bytes
if (Stream.CanRead) // Verify if the stream can be read.
{
byte[] Buffer = new byte[Client.ReceiveBufferSize]; // Initialize a new empty byte array with the data length.
StringBuilder SB = new StringBuilder();
do // Start converting bytes to string
{
int BytesReaded = Stream.Read(Buffer, 0, Buffer.Length);
SB.AppendFormat("{0}", Encoding.ASCII.GetString(Buffer, 0, BytesReaded));
} while (Stream.DataAvailable); // Until stream data is available
if (SB != null) // Stream data is ready and converted to string
// Do some stuffs
}
}
catch (Exception Ex) // In case of errors catch it to avoid the app crash
{
ConsoleMessage.Error(Ex.ToString()); // Detailed exception
}
});
}
}

Trouble with Task.WhenAny

I am using .NET framework to create a server, which listens on two ports on localhost. It is a simple console application.
It works when I keep connecting to one of the ports, but after I connect to first, another does not respond. First still is alive.
Here is my code:
static void Main(string[] args)
{
IPAddress hostIP = Dns.GetHostAddresses("127.0.0.1")[0];
List<TcpListener> listeners = new List<TcpListener>()
{
new TcpListener(hostIP, 6060),
new TcpListener(hostIP, 6061)
};
foreach (TcpListener listener in listeners)
{
listener.Start();
}
try
{
while (true)
{
Socket socket = AcceptAnyConnection(listeners).Result;
NetworkStream stream = new NetworkStream(socket);
byte[] bytes = Encoding.UTF8.GetBytes(DateTime.Now.ToString());
stream.Write(bytes, 0, bytes.Length);
//stream.Close();
socket.Close();
}
}
finally
{
foreach (TcpListener listener in listeners)
{
listener.Stop();
}
}
}
private static async Task<Socket> AcceptAnyConnection(List<TcpListener> listeners)
{
List<Task<Socket>> tasks = new List<Task<Socket>>();
foreach (TcpListener listener in listeners)
{
tasks.Add(AcceptConnection(listener));
}
Task<Socket> completedTask = await Task.WhenAny(tasks);
return await completedTask;
}
private static async Task<Socket> AcceptConnection(TcpListener listener)
{
Socket socket = await listener.AcceptSocketAsync();
return socket;
}
await Task.WhenAny() blocks if I connect to another port.
I must be doing something wrong, but I am not sure what.
BTW, I did try the same with .NET Core console application, and it works fine.
Thanks
I'd suggest refactoring your code to something like this, and taking it from there. That is, run an infinite loop for each listener. That avoids problems with making sure that you only call AcceptTcpClientAsync once at a time for each client.
(Note there's no code to actually stop the listeners. Also completely untested - please use it only as an indication of the sort of approach to take)
static void Main(string[] args)
{
IPAddress hostIP = Dns.GetHostAddresses("127.0.0.1")[0];
List<TcpListener> listeners = new List<TcpListener>()
{
new TcpListener(hostIP, 6060),
new TcpListener(hostIP, 6061)
};
var listenerTasks = listeners.Select(x => RunTcpListener(x)).ToArray();
Task.WaitAll(listenerTasks);
}
private static async Task RunTcpListener(TcpListener listener)
{
listener.Start();
try
{
while (true)
{
using (var client = await listener.AcceptTcpClientAsync())
{
var stream = client.GetStream();
byte[] bytes = Encoding.UTF8.GetBytes(DateTime.Now.ToString());
stream.Write(bytes, 0, bytes.Length);
client.Close();
}
}
}
finally
{
listener.Stop();
}
}
Here is my code, after I followed a suggestion by canton7 to move the task list outside of loop and move add and remove tasks "as we go". Probably, I will have to look at it to clean it up, but the idea works:
static void Main(string[] args)
{
IPAddress hostIP = Dns.GetHostAddresses("127.0.0.1")[0];
List<TcpListener> listeners = new List<TcpListener>()
{
new TcpListener(hostIP, 6060),
new TcpListener(hostIP, 6061)
};
ProcessConnections(listeners).Wait();
}
private async static Task SendData(Socket socket)
{
NetworkStream stream = new NetworkStream(socket);
byte[] bytes = Encoding.UTF8.GetBytes(DateTime.Now.ToString());
await stream.WriteAsync(bytes, 0, bytes.Length);
socket.Close();
}
private static async Task ProcessConnections(List<TcpListener> listeners)
{
foreach (TcpListener listener in listeners)
{
listener.Start();
}
try
{
List<Task<Socket>> tasks = new List<Task<Socket>>();
foreach (TcpListener listener in listeners)
{
tasks.Add(AcceptConnection(listener));
}
while (true)
{
int i = Task.WaitAny(tasks.ToArray());
Socket socket = await tasks[i];
await SendData(socket);
tasks.RemoveAt(i);
tasks.Insert(i, AcceptConnection(listeners[i]));
}
}
finally
{
foreach (TcpListener listener in listeners)
{
listener.Stop();
}
}
}
private static async Task<Socket> AcceptConnection(TcpListener listener)
{
Socket socket = await listener.AcceptSocketAsync();
return socket;
}

Multiple socket connections using asynchronous sockets

I have the following code that makes a TCP socket connection to multiple end points as such:
private async void button1_Click(object sender, EventArgs e)
{
var listofIps = new List<string> { "192.168.168.193", "192.168.168.221" };
foreach (var ip in listofIps)
{
IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse(ip), 4001);
Socket client = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
sockets.Add(client);
await client.ConnectTaskAsync(remoteEP);
await ReadAsync(client);
}
}
async Task ReadAsync(Socket s)
{
var args = new SocketAsyncEventArgs();
args.SetBuffer(new byte[1024], 0, 1024);
var awaitable = new SocketAwaitable(args);
while (true)
{
await s.ReceiveAsync(awaitable);
int bytesRead = args.BytesTransferred;
if (bytesRead <= 0) break;
var data = new ArraySegment<byte>(args.Buffer, 0, bytesRead);
AppendLog("RX: " + data.DumpHex());
}
}
public static string DumpHex(this ArraySegment<byte> data)
{
return string.Join(" ", data.Select(b => b.ToString("X2")));
}
public static Task ConnectTaskAsync(this Socket socket, EndPoint endpoint)
{
return Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, endpoint, null);
}
However, the loop doesn't iterate pass the first IP address, because of the await ReadAsync.
Qn 1) How do I modify this code such that it iterates through my whole list of IPs without "awaiting" for the data be received completely. Do I remove the await keyword from await ReadAsync(client)?
Qn 2) How do I modify this code such that it connects to all the IPs at once. (is this possible?)
How do I modify this code such that it iterates through my whole list of IPs
Your code will iterate the whole list. But it will get to the second address only after it receives all data from the first address.
How do I modify this code such that it connects to all the IPs at once.
If you encapsulate the code for a single address into a separate async method, you can then first call it for each address and then await all the returned Tasks. Or you could use LINQ:
private async void button1_Click(object sender, EventArgs e)
{
var listOfIps = new List<string> { "192.168.168.193", "192.168.168.221" };
await Task.WhenAll(listOfIps.Select(ip => ReadFromAddress(ip)));
}
private async Task ReadFromAddress(string address)
{
IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse(ip), 4001);
Socket client = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
sockets.Add(client);
await client.ConnectTaskAsync(remoteEP);
await ReadAsync(client);
}

Categories