Improving TCP forwarder using Socket in C# - c#

I have Implemented a simple TCP forwarder which is connected to a backend server. This server is set on http://localhost:5000 and this TCP forwarder is listening to http://localhost:5001. Using another machine to generate load using wrk which is a HTTP benchmarking tool to generator load, I have 2 different results. When I send the load directly to the service which is based on asp.net core web api on the kestrel more than 230K requests/second are handled but when I send the load to this TCP forwarder 83Krequest/second can be handled. Here is the code:
using System;
using System.Net;
using System.Net.Sockets;
namespace BrunoGarcia.Net
{
static void Main(string[] args)
{
new TcpForwarderSlim().Start(
new IPEndPoint(IPAddress.Parse(args[0]), int.Parse(args[1])),
new IPEndPoint(IPAddress.Parse(args[2]), int.Parse(args[3])));
}
public class TcpForwarderSlim
{
private readonly Socket _mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
public void Start(IPEndPoint local, IPEndPoint remote)
{
_mainSocket.Bind(local);
_mainSocket.Listen(10);
while (true)
{
var source = _mainSocket.Accept();
var destination = new TcpForwarderSlim();
var state = new State(source, destination._mainSocket);
destination.Connect(remote, source);
source.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state);
}
}
private void Connect(EndPoint remoteEndpoint, Socket destination)
{
var state = new State(_mainSocket, destination);
_mainSocket.Connect(remoteEndpoint);
_mainSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, OnDataReceive, state);
}
private static void OnDataReceive(IAsyncResult result)
{
var state = (State)result.AsyncState;
try
{
var bytesRead = state.SourceSocket.EndReceive(result);
if (bytesRead > 0)
{
state.DestinationSocket.Send(state.Buffer, bytesRead, SocketFlags.None);
state.SourceSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state);
}
}
catch
{
state.DestinationSocket.Close();
state.SourceSocket.Close();
}
}
private class State
{
public Socket SourceSocket { get; private set; }
public Socket DestinationSocket { get; private set; }
public byte[] Buffer { get; private set; }
public State(Socket source, Socket destination)
{
SourceSocket = source;
DestinationSocket = destination;
Buffer = new byte[8192];
}
}
}
}
What is the problem do you think?! How can I improve the result when I use TCP forwarder?!Or is there a better way to make a tunnel or a forwarder to listen to a port and send the TCP requests to one of 2 or more back end service?!

You do not start listening for a more data till state.DestinationSocket.Send completes. You can start listening for more data as soon as you start processing the Send, the order of multiple BeginSend calls is preserved so if you switched to that it would allow you to start processing the next request before the previous one finished.
Important note! You will now need to create a new buffer (or use a pool of buffers) for each new BeginReceive request. Below is untested code but hopefully is close enough to get you on the right path.
public class TcpForwarderSlim
{
private readonly Socket _mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
public void Start(IPEndPoint local, IPEndPoint remote)
{
_mainSocket.Bind(local);
_mainSocket.Listen(10);
while (true)
{
var source = _mainSocket.Accept();
var destination = new TcpForwarderSlim();
var state = new State(source, destination._mainSocket);
destination.Connect(remote, source);
source.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state);
}
}
private void Connect(EndPoint remoteEndpoint, Socket destination)
{
var state = new State(_mainSocket, destination);
_mainSocket.Connect(remoteEndpoint);
_mainSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, OnDataReceive, state);
}
private static void OnDataReceive(IAsyncResult result)
{
var state = (State)result.AsyncState;
try
{
var bytesRead = state.SourceSocket.EndReceive(result);
if (bytesRead > 0)
{
//Start an asyncronous send.
var sendAr = state.DestinationSocket.BeginSend(state.Buffer, 0, bytesRead, SocketFlags.None,null,null);
//Get or create a new buffer for the state object.
var oldBuffer = state.ReplaceBuffer();
state.SourceSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state);
//Wait for the send to finish.
state.DestinationSocket.EndSend(sendAr);
//Return byte[] to the pool.
state.AddBufferToPool(oldBuffer);
}
}
catch
{
state.DestinationSocket.Close();
state.SourceSocket.Close();
}
}
private class State
{
private readonly ConcurrentBag<byte[]> _bufferPool = new ConcurrentBag<byte[]>();
private readonly int _bufferSize;
public Socket SourceSocket { get; private set; }
public Socket DestinationSocket { get; private set; }
public byte[] Buffer { get; private set; }
public State(Socket source, Socket destination)
{
SourceSocket = source;
DestinationSocket = destination;
_bufferSize = Math.Min(SourceSocket.ReceiveBufferSize, DestinationSocket.SendBufferSize);
Buffer = new byte[_bufferSize];
}
/// <summary>
/// Replaces the buffer in the state object.
/// </summary>
/// <returns>The previous buffer.</returns>
public byte[] ReplaceBuffer()
{
byte[] newBuffer;
if (!_bufferPool.TryTake(out newBuffer))
{
newBuffer = new byte[_bufferSize];
}
var oldBuffer = Buffer;
Buffer = newBuffer;
return oldBuffer;
}
public void AddBufferToPool(byte[] buffer)
{
_bufferPool.Add(buffer);
}
}
}

Related

c# Xamarin UWP/Android server - client socket tcp can't connect properly

The core element of my project is the connection through local network between two applications ( client and server ) using sockets. I've followed many tutorials and the most stable version is the one I am about to post below.
I've run it on c# console applications and it works fine
I've run it on Windows Forms application and it works fine
So I was ready to implement it on my Xamarin application and for one time ( the first time ) it worked. I've even tested it on my android smartphone ( as client ) and UWP on windows ( as server ). After that first time it never worked again. Neither on my Desktop nor my Laptop. I've literally changed nothing and it stopped working.
At my first touch with sockets and Xamarin I though that it just don't work. But after that one-working time. It must not be that.
TROUBLESHOOTING
I am getting on the client classes ( ClientSocket ) on ClientSocket.Connect -> _socket.BeginConnect = false
I've checked the firewalls, I've uninstalled and disable the
firewalls
I've checked the manifest and even there I tried after the (
must-have permissions ) I tried to enable all permissions.
I will try and upload a dropbox link ( for my files )
Server code :
namespace Control
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class HomePage : ContentPage
{
public HomePage()
{
InitializeComponent();
}
private void ServerConnectBtn_Clicked(object sender, EventArgs e)
{
ServerSocket.Bind(9000);
ServerSocket.Listen(500);
ServerSocket.Accept();
msg_lbl.Text = PacketHandler.status;
}
}
}
My server classes :
namespace Control.Server
{
class ServerSocket
{
private static Socket _socket;
private static byte[] _buffer = new byte[1024];
public ServerSocket()
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
public static void Bind(int port)
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_socket.Bind(new IPEndPoint(IPAddress.Any, port));
}
public static void Listen(int backlog)
{
_socket.Listen(500);
}
public static void Accept()
{
_socket.BeginAccept(AcceptedCallback, null);
}
private static void AcceptedCallback(IAsyncResult result)
{
Socket clientSocket = _socket.EndAccept(result);
_buffer = new byte[1024];
clientSocket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, ReceivedCallback, clientSocket);
Accept();
}
private static void ReceivedCallback(IAsyncResult result)
{
Socket clientSocket = result.AsyncState as Socket;
int bufferSize = clientSocket.EndReceive(result);
byte[] packet = new byte[bufferSize];
Array.Copy(_buffer, packet, packet.Length);
//Handle the packet
PacketHandler.Handle(packet, clientSocket);
_buffer = new byte[1024];
clientSocket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, ReceivedCallback, clientSocket);
}
}
}
namespace Control.Server
{
public abstract class PacketStructure
{
private byte[] _buffer;
public PacketStructure(ushort length, ushort type)
{
_buffer = new byte[length];
WriteUshort(length, 0);
WriteUshort(type, 2);
}
public PacketStructure(byte[] packet)
{
_buffer = packet;
}
public void WriteUshort(ushort value, int offset)
{
byte[] tempbuffer = new byte[2];
tempbuffer = BitConverter.GetBytes(value);
Buffer.BlockCopy(tempbuffer, 0, _buffer, offset, 2);
}
public short ReadUshort(int offset)
{
return BitConverter.ToInt16(_buffer, offset);
}
public void WriteUint(uint value, int offset)
{
byte[] tempbuffer = new byte[4];
tempbuffer = BitConverter.GetBytes(value);
Buffer.BlockCopy(tempbuffer, 0, _buffer, offset,4);
}
public void WriteString(string value, int offset)
{
byte[] tempbuffer = new byte[value.Length];
tempbuffer = Encoding.UTF8.GetBytes(value);
Buffer.BlockCopy(tempbuffer, 0, _buffer, offset, value.Length);
}
public string ReadString(int offset, int count)
{
return Encoding.UTF8.GetString(_buffer, offset, count);
}
public byte[] Data { get { return _buffer; } }
}
}
namespace Control.Server
{
public static class PacketHandler
{
public static string status;
public static void Handle(byte[] packet, Socket clientSocket)
{
ushort packetLength = BitConverter.ToUInt16(packet, 0);
ushort packetType = BitConverter.ToUInt16(packet, 2);
status = "Received packet! Length: "+ packetLength + " | Type: "+ packetType;
switch (packetType)
{
case 2000:
Message msg = new Message(packet);
Console.WriteLine(msg.Text);
break;
}
}
}
}
namespace Control.Server
{
public class Message : PacketStructure
{
private string _message;
public Message(string message)
: base((ushort)(4 + message.Length), 2000)
{
Text = message;
}
public Message(byte[] packet)
: base(packet)
{
}
public string Text
{
get { return ReadString(4, Data.Length - 4); }
set
{
_message = value;
WriteString(value, 4);
}
}
}
}
Client code:
namespace Remote
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SettingsPage : ContentPage
{
public SettingsPage()
{
InitializeComponent();
}
private void ClientConnectBtn_Clicked(object sender, EventArgs e)
{
ClientSocket.Connect("192.168.1.17",9000);
Status_lbl.Text = "Status : " +ClientSocket.status;
}
private void Send_Clicked(object sender, EventArgs e)
{
string msg = msgEntry.Text;
Message packet = new Message(msg);
ClientSocket.Send(packet.Data);
Status_lbl.Text = "Status : " + ClientSocket.status;
}
}
}
Client Classes
namespace Remote.Client
{
public abstract class PacketStructure
{
private byte[] _buffer;
public PacketStructure(ushort length, ushort type)
{
_buffer = new byte[length];
WriteUshort(length, 0);
WriteUshort(type, 2);
}
public PacketStructure(byte[] packet)
{
_buffer = packet;
}
public void WriteUshort(ushort value, int offset)
{
byte[] tempbuffer = new byte[2];
tempbuffer = BitConverter.GetBytes(value);
Buffer.BlockCopy(tempbuffer, 0, _buffer, offset, 2);
}
public short ReadUshort(int offset)
{
return BitConverter.ToInt16(_buffer, offset);
}
public void WriteUint(uint value, int offset)
{
byte[] tempbuffer = new byte[4];
tempbuffer = BitConverter.GetBytes(value);
Buffer.BlockCopy(tempbuffer, 0, _buffer, offset, 4);
}
public void WriteString(string value, int offset)
{
byte[] tempbuffer = new byte[value.Length];
tempbuffer = Encoding.UTF8.GetBytes(value);
Buffer.BlockCopy(tempbuffer, 0, _buffer, offset, value.Length);
}
public string ReadString(int offset, int count)
{
return Encoding.UTF8.GetString(_buffer, offset, count);
}
public byte[] Data { get { return _buffer; } }
}
}
namespace Remote.Client
{
public class Message : PacketStructure
{
private string _message;
public Message(string message)
:base((ushort)(4 + message.Length), 2000)
{
Text = message;
}
public Message(byte[] packet)
:base(packet)
{
}
public string Text
{
get { return ReadString(4, Data.Length - 4); }
set
{
_message = value;
WriteString(value, 4);
}
}
}
}
namespace Remote.Client
{
class ClientSocket
{
private static Socket _socket;
private static byte[] _buffer;
public static string status;
public ClientSocket()
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
public static void Connect(string ipAddress, int port)
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_socket.BeginConnect(new IPEndPoint(IPAddress.Parse(ipAddress), port), ConnectCallback, null);
}
private static void ConnectCallback(IAsyncResult result)
{
if (_socket.Connected)
{
status = "Connected to the server!";
_buffer = new byte[1024];
_socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, ReceiveCallback, null);
// throw new Exception("Conencted");
}
else
{
status = "Could not connect";
// throw new Exception("Could not connect");
}
}
private static void ReceiveCallback(IAsyncResult result)
{
int bufLength = _socket.EndReceive(result);
byte[] packet = new byte[bufLength];
Array.Copy(_buffer, packet, packet.Length);
//Handle packet
_buffer = new byte[1024];
_socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, ReceiveCallback, null);
}
public static void Send(byte[] data)
{
_socket.Send(data);
}
}
}
I managed to achieve the proper connections by enabling loopback as #NicoZhi-MSFT said.
Have you enabled uwp app's loopback and private network capability?
Please check this document to enable this app loop back
I've only ran the commands for the server's side from this link and it seems to be working just fine. But by have Private Network always enabled.
So if you can make the UWP application run cmd commands every time the server needs to start or by settings an automatic task (following the instructions for the link above) it should be ok.
TIP:
UWP is not very handy on running CMD commands so if needed for anyone can look for UWP fulltrust or/and by setting an external application that runs when needed in the background
Many thanks

refactoring async socket programming in C#

I have a bunch of async methods that I want to expose via C# sockets. The general pattern in the MSDN documentaions Has the following form:
public static void StartListening()
{
...
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 11000);
...
listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
...
}
public static void AcceptCallback(IAsyncResult ar)
{
...
handler.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
}
public static void ReadCallback(IAsyncResult ar)
{
...
StateObject state = (StateObject) ar.AsyncState;
...
CalculateResult(state);
...
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
...
}
So writing all this in a nice and clean form without repetition of code has been a challenge. I am thinking along these lines but have not been able to connect the dots:
public static void StartListeningMaster()
{
string ipAddress = "localhost";
IPHostEntry ipHost = Dns.GetHostEntry(ipAddress);
IPAddress address = ipHost.AddressList[0];
StartListening(50000, address, AcceptCallback1);
StartListening(50001, address, AcceptCallback2);
StartListening(50002, address, AcceptCallback3);
...
}
public static void StartListening(int port, IPAddress ipAddress,
Action<IAsyncResult> acceptCallback) {...}
public static void AcceptCallback1(IAsyncResult ar)
{
...
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize,
0, new AsyncCallback1(ReadCallback1), state);
}
...
This works fine up to this point. But to refactor it properly I would like to have one AcceptCallback method that takes as its parameter a generic ReadCallback that takes as its parameter a CalculateResult method. This way I would not have any repetition of code. However, if I modify my AcceptCallback method to take any more parameters than IAsyncResult (for example something like:
public static void StartListening(int port, IPAddress ipAddress, Action<IAsyncResult, Action<IAsyncResult>> acceptCallback) {...}
public static void AcceptCallback(IAsyncResult ar, Action<IAsyncResult> readCallback) {}
I break the AsyncCallback delegate contract.
public delegate void AsyncCallback(IAsyncResult ar);
Then I looked into extending the existing interfaces to allow the functionality. I looked into extending
public interface IAsyncResult
But that does not seem to be the right approach either. So, how do I write this code so I do not copy and paste pretty much the same code all over the place?
So the way I tackle this is by moving the basic components in to their own abstract objects. Then build upon those objects. For example, the server only needs to accept/track connections. So I would make a server object that looks something like this:
namespace MultiServerExample.Base
{
public interface IAsyncServerBase
{
void StartListening();
bool IsListening { get; }
void StopListening();
void WriteDataToAllClients(byte[] data);
}
public abstract class AsyncServerBase<TClientBase> : IAsyncServerBase
where TClientBase : IAsyncClientBase, new()
{
// implement a TcpListener to gain access to Active property
private sealed class ActiveTcpListener : TcpListener
{
public ActiveTcpListener(IPAddress localaddr, int port)
: base(localaddr, port) { }
public bool IsActive => Active;
}
// our listener object
private ActiveTcpListener Listener { get; }
// our clients
private ConcurrentDictionary<string, TClientBase> Clients { get; }
// construct with a port
public AsyncServerBase(int port)
{
Clients = new ConcurrentDictionary<string, TClientBase>();
Listener = new ActiveTcpListener(IPAddress.Any, port);
}
// virtual methods for client action
public virtual void OnClientConnected(TClientBase client) { }
public virtual void OnClientDisconnected(TClientBase client, Exception ex) { }
// start the server
public void StartListening()
{
if(!IsListening)
{
Listener.Start();
Listener.BeginAcceptTcpClient(OnAcceptedTcpClient, this);
}
}
// check if the server is running
public bool IsListening =>
Listener.IsActive;
// stop the server
public void StopListening()
{
if (IsListening)
{
Listener.Stop();
Parallel.ForEach(Clients, x => x.Value.DetachClient(null));
Clients.Clear();
}
}
// async callback for when a client wants to connect
private static void OnAcceptedTcpClient(IAsyncResult res)
{
var me = (AsyncServerBase<TClientBase>)res.AsyncState;
if (!me.IsListening) { return; }
try
{
TcpClient client = null;
try
{
client = me.Listener.EndAcceptTcpClient(res);
}
catch(Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Warning: unable to accept client:\n{ex}");
}
if(client != null)
{
// create a new client
var t = new TClientBase();
// set up error callbacks
t.Error += me.OnClientBaseError;
// notify client we have attached
t.AttachClient(client);
// track the client
me.Clients[t.Id] = t;
// notify we have a new connection
me.OnClientConnected(t);
}
}
finally
{
// if we are still listening, wait for another connection
if(me.IsListening)
{
me.Listener.BeginAcceptSocket(OnAcceptedTcpClient, me);
}
}
}
// Event callback from a client that an error has occurred
private void OnClientBaseError(object sender, AsyncClientBaseErrorEventArgs e)
{
var client = (TClientBase)sender;
client.Error -= OnClientBaseError;
OnClientDisconnected(client, e.Exception);
client.DetachClient(e.Exception);
Clients.TryRemove(client.Id, out _);
}
// utility method to write data to all clients connected
public void WriteDataToAllClients(byte[] data)
{
Parallel.ForEach(Clients, x => x.Value.WriteData(data));
}
}
}
At this point all the basics of running a server have been accounted for. Now for the client that runs on the server:
namespace MultiServerExample.Base
{
public interface IAsyncClientBase
{
event EventHandler<AsyncClientBaseErrorEventArgs> Error;
void AttachClient(TcpClient client);
void WriteData(byte[] data);
void DetachClient(Exception ex);
string Id { get; }
}
public abstract class AsyncClientBase : IAsyncClientBase
{
protected virtual int ReceiveBufferSize { get; } = 1024;
private TcpClient Client { get; set; }
private byte[] ReceiveBuffer { get; set; }
public event EventHandler<AsyncClientBaseErrorEventArgs> Error;
public string Id { get; }
public AsyncClientBase()
{
Id = Guid.NewGuid().ToString();
}
public void AttachClient(TcpClient client)
{
if(ReceiveBuffer != null) { throw new InvalidOperationException(); }
ReceiveBuffer = new byte[ReceiveBufferSize];
Client = client;
try
{
Client.GetStream().
BeginRead(ReceiveBuffer, 0, ReceiveBufferSize, OnDataReceived, this);
OnAttachedToServer();
}
catch (Exception ex)
{
Error?.Invoke(this,
new AsyncClientBaseErrorEventArgs(ex, "BeginRead"));
}
}
public void DetachClient(Exception ex)
{
try
{
Client.Close();
OnDetachedFromServer(ex);
}
catch { /* intentionally swallow */ }
Client = null;
ReceiveBuffer = null;
}
public virtual void OnDataReceived(byte[] buffer) { }
public virtual void OnAttachedToServer() { }
public virtual void OnDetachedFromServer(Exception ex) { }
public void WriteData(byte[] data)
{
try
{
Client.GetStream().BeginWrite(data, 0, data.Length, OnDataWrote, this);
}
catch(Exception ex)
{
Error?.Invoke(this, new AsyncClientBaseErrorEventArgs(ex, "BeginWrite"));
}
}
private static void OnDataReceived(IAsyncResult iar)
{
var me = (AsyncClientBase)iar.AsyncState;
if(me.Client == null) { return; }
try
{
var bytesRead = me.Client.GetStream().EndRead(iar);
var buf = new byte[bytesRead];
Array.Copy(me.ReceiveBuffer, buf, bytesRead);
me.OnDataReceived(buf);
}
catch (Exception ex)
{
me.Error?.Invoke(me, new AsyncClientBaseErrorEventArgs(ex, "EndRead"));
}
}
private static void OnDataWrote(IAsyncResult iar)
{
var me = (AsyncClientBase)iar.AsyncState;
try
{
me.Client.GetStream().EndWrite(iar);
}
catch(Exception ex)
{
me.Error?.Invoke(me,
new AsyncClientBaseErrorEventArgs(ex, "EndWrite"));
}
}
}
}
Now all your base code is written. You don't need to change this in any way. You simply implement your own client and server to respond accordingly. For example, here is a basic server implementation:
public class MyServer : AsyncServerBase<MyClient>
{
public MyServer(int port) : base(port)
{
}
public override void OnClientConnected(MyClient client)
{
Console.WriteLine($"* MyClient connected with Id: {client.Id}");
base.OnClientConnected(client);
}
public override void OnClientDisconnected(MyClient client, Exception ex)
{
Console.WriteLine($"***** MyClient disconnected with Id: {client.Id} ({ex.Message})");
base.OnClientDisconnected(client, ex);
}
}
And here is a client that the server above uses for communication:
public class MyClient : AsyncClientBase
{
public override void OnAttachedToServer()
{
base.OnAttachedToServer();
Console.WriteLine($"{Id}: {GetType().Name} attached. Waiting for data...");
}
public override void OnDataReceived(byte[] buffer)
{
base.OnDataReceived(buffer);
Console.WriteLine($"{Id}: {GetType().Name} recieved {buffer.Length} bytes. Writing 5 bytes back.");
WriteData(new byte[] { 1, 2, 3, 4, 5 });
}
public override void OnDetachedFromServer(Exception ex)
{
base.OnDetachedFromServer(ex);
Console.WriteLine($"{Id}: {GetType().Name} detached.");
}
}
And to drive the point home, here is another client that simply would plug in to the same server implementation, but gives it different characteristics:
public class MyOtherClient : AsyncClientBase
{
public override void OnAttachedToServer()
{
base.OnAttachedToServer();
Console.WriteLine($"{Id}: {GetType().Name} attached. Writing 4 bytes back.");
WriteData(new byte[] { 1, 2, 3, 4 });
}
public override void OnDataReceived(byte[] buffer)
{
base.OnDataReceived(buffer);
Console.WriteLine($"{Id}: {GetType().Name} recieved {buffer.Length} bytes.");
}
public override void OnDetachedFromServer(Exception ex)
{
base.OnDetachedFromServer(ex);
Console.WriteLine($"{Id}: {GetType().Name} detached.");
}
}
As far as using this, here is a small test program that puts it through a stress-test:
class Program
{
static void Main(string[] args)
{
var servers = new IAsyncServerBase[]
{
new MyServer(50000),
new MyServer(50001),
new MyOtherServer(50002)
};
foreach (var s in servers)
{
s.StartListening();
}
RunTestUsingMyServer("1", 89, 50000);
RunTestUsingMyServer("2", 127, 50001);
RunTestUsingMyOtherServer("3", 88, 50002);
Console.Write("Press any key to exit... ");
Console.ReadKey(true);
foreach (var s in servers)
{
s.WriteDataToAllClients(new byte[] { 1, 2, 3, 4, 5 });
s.StopListening();
}
}
private static void RunTestUsingMyServer(string name, int clientCount, int port)
{
Parallel.For(0, clientCount, x =>
{
using (var t = new TcpClient())
{
t.Connect(IPAddress.Loopback, port);
t.GetStream().Write(new byte[] { 1, 2, 3, 4, 5 }, 0, 5);
t.GetStream().Read(new byte[512], 0, 512);
t.Close();
}
Console.WriteLine($"FINISHED PASS {name} #{x}");
});
}
private static void RunTestUsingMyOtherServer(string name, int clientCount, int port)
{
Parallel.For(0, clientCount, x =>
{
using (var t = new TcpClient())
{
t.Connect(IPAddress.Loopback, port);
t.GetStream().Read(new byte[512], 0, 512);
t.GetStream().Write(new byte[] { 1, 2, 3, 4, 5, 6 }, 0, 6);
t.Close();
}
Console.WriteLine($"FINISHED PASS {name} #{x}");
});
}
}
If interested, here is the full source code you can check out. Hopefully this gets you to where you want to be as it pertains to reusing code.
I don't know if this can help. You can define a state object with all the information related to every port:
public class StateObject
{
public string Name;
public Socket Listener;
public IPEndPoint LocalEndPoint;
//...
public StateObject(Socket listener, IPEndPoint endPoint, string name)
{
Listener = listener;
LocalEndPoint = endPoint;
Name = name;
}
}
Then, you can use it as you need:
public static void StartListeningMaster()
{
string ipAddress = "localhost";
IPHostEntry ipHost = Dns.GetHostEntry(ipAddress);
IPAddress address = ipHost.AddressList[0];
StartListening(50000, address, "Main port");
StartListening(50001, address, "Service port");
StartListening(50002, address, "Extra");
//...
}
public static void StartListening(int port, IPAddress ipAddress, string name = "")
{
//...
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, port);
//...
StateObject state = new StateObject(listener, localEndPoint);
listener.BeginAccept(AcceptCallback, state);
//...
}
public static void AcceptCallback(IAsyncResult ar)
{
StateObject state = (StateObject)ar.AsyncState;
//...
handler.BeginReceive(client.buffer, 0, StateObject.BufferSize,
0, new AsyncCallback(ReadCallback), state);
}
public static void ReadCallback(IAsyncResult ar)
{
StateObject state = (StateObject)ar.AsyncState;
// Always have the info related to every socket
Socket listener = state.Listener;
string address = state.LocalEndPoint.Address.ToString();
int port = state.LocalEndPoint.Port;
string name = state.Name;
//...
StateObject state = (StateObject)ar.AsyncState;
//...
CalculateResult(state);
//...
handler.BeginReceive(client.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
//...
}
The CalculateResult(state) method will have all the necessary info to do whatever. This way, you only have one StartListening(), one AcceptCallback() and one ReadCallback() for all the ports to manage.

Netcore Socket Server Not Closing

Hi All I'm developing a quite simple Socket Server on .NetCore. This server must allow multiple clients and start reading from them while it's not canceled.
This is the code of it:
public class ClientHandler
{
public ClientHandler(Socket workSocket, int bufferSize)
{
WorkSocket = workSocket;
BufferSize = bufferSize;
receiveBuffer = new byte[BufferSize];
currentBuffer = new byte[0];
}
public Socket WorkSocket { get; }
// Size of receive buffer.
public int BufferSize { get; }
// Receive buffer.
public byte[] receiveBuffer { get; set; }
// Received data.
public byte[] currentBuffer { get; set; }
}
public class MyServer
{
public MyServer(string ipAddress, int port,
IProtocolParser parser, CancellationToken token, ILogger logger)
{
_ipAddress = ipAddress;
_port = port;
InternalCts = new CancellationTokenSource();
_token = InternalCts.Token;
_parser = parser;
_logger = logger.ForContext(GetType());
}
private const int BUFFER_SIZE = 1024;
private readonly IProtocolParser _parser;
private readonly ILogger _logger;
private readonly int _port;
private readonly string _ipAddress;
private readonly CancellationToken _token;
private readonly ManualResetEvent _allDone = new ManualResetEvent(false);
private CancellationTokenSource InternalCts { get; set; }
private Socket Server { get; set; }
public void Start()
{
try
{
var ipAddress = IPAddress.Parse(_ipAddress);
var endpoint = new IPEndPoint(ipAddress, _port);
_logger.Debug("Creating Socket Server On {ipAddress}:{port}", _ipAddress, _port);
Server = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
Server.Bind(endpoint);
Server.Listen(10);
while(!_token.IsCancellationRequested)
{
_allDone.Reset();
_logger.Debug("Waiting For Client");
Server.BeginAccept(AcceptCallback, Server);
_logger.Debug("Waiting One!");
_allDone.WaitOne();
_logger.Debug("Begin Accept Finished");
}
_logger.Debug("Task Finished!");
return;
}
catch (Exception e)
{
_logger.Error("error");
}
}
private void AcceptCallback(IAsyncResult ar)
{
// Signal the main thread to continue.
_allDone.Set();
// Get the socket that handles the client request.
var listener = (Socket)ar.AsyncState;
var handler = listener.EndAccept(ar);
// Create the state object.
var client = new ClientHandler(handler, BUFFER_SIZE);
handler.BeginReceive(client.receiveBuffer, 0, client.BufferSize, 0,
new AsyncCallback(ReadCallback), client);
}
private void ReadCallback(IAsyncResult ar)
{
// Retrieve the state object and the handler socket
// from the asynchronous state object.
var client = (ClientHandler)ar.AsyncState;
var handler = client.WorkSocket;
// Read data from the client socket.
var bytesRead = handler.EndReceive(ar);
if (bytesRead > 0)
{
client.currentBuffer = ArrayExtensions.Combine(
client.currentBuffer, client.receiveBuffer.SubArray(0, bytesRead));
var (message, newBuffer) = _parser.UnpackMessage(client.currentBuffer);
if (!string.IsNullOrEmpty(message))
{
_logger.Debug("New Message Received: {message}", message);
}
client.currentBuffer = newBuffer;
}
if (!_token.IsCancellationRequested)
{
// Not all data received. Get more.
handler.BeginReceive(client.receiveBuffer, 0, client.BufferSize, 0,
new AsyncCallback(ReadCallback), client);
}
}
public void Stop()
{
InternalCts.Cancel();
_logger.Debug("Stopping Server...");
_logger.Debug("Closing Socket...");
Server.Close();
_allDone.Set();
_logger.Debug("Socket Closed!");
}
}
And this is the main program:
static void Main(string[] args)
{
var parser = new VenomOEMProtocolParser();
var cts = new CancellationTokenSource();
var logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.FromLogContext()
.Enrich.WithThreadId()
.WriteTo.Console().CreateLogger();
var venomOem = new VenomOEM("192.168.0.107", 100, parser, cts.Token, logger);
AppDomain.CurrentDomain.ProcessExit += (s, e) =>
{
venomOem.Stop();
};
Console.CancelKeyPress += delegate
{
venomOem.Stop();
};
try
{
venomOem.Start();
logger.Debug("FINISHED!");
}
catch (OperationCanceledException oce)
{
logger.Debug(oce, "Operation Canceled Exception");
}
catch (Exception e)
{
logger.Error(e, "Unexpected Exception!");
}
}
As you can see I start the sever and stop it when Ctrl+C keys are pressed on the console app and the stop method is excecuted but somehow the application freezes and doesn't close. I think that it's something related to the reset events but cannot find the problem.
Any suggestion?
Thanks!

async socket client once connected to a third-party software continue listening for incoming messages

I'm trying to create a WPF application that once sent a message via TCP to a third-party application, listen for the answer and continue listening to incoming communication (This third-party application sends feedback to the most recently connected or authenticated controller) until another message has to be sent and so on.
It has to be async because my application must run continuously being able to perform other actions.
Basically I've adapted this (https://learn.microsoft.com/it-it/dotnet/framework/network-programming/asynchronous-client-socket-example) example to WPF using a backgoundWorker and never closing the socket.
The problem seems to be that
client.BeginReceive(state.buffer, 0, state.buffer.Length, 0,
new asyncCallback(ReceiveCallback), state);
Won't loopback if the last bytes read is less than the buffer size.
my code is:
using System;
using System.ComponentModel;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows;
namespace socket_WPF
{
/// <summary>
/// Logica di interazione per MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private readonly BackgroundWorker BWI;
public AsyncSocketReceiver ASR;
public string risposta = "";
public string rispostaOLD;
public bool newMessage = false;
public string Extmessage;
public int Mcounter = 0;
public MainWindow()
{
InitializeComponent();
BWI = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
BWI.DoWork += new DoWorkEventHandler(BWI_DoWork);
BWI.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BWI_RunWorkerCompleted);
BWI.ProgressChanged += new ProgressChangedEventHandler(BWI_ProgressChangedEventHandler);
}
private void BWI_DoWork(object sender, DoWorkEventArgs e)
{
int i = -1;
BackgroundWorker myBW = sender as BackgroundWorker;
ASR = new AsyncSocketReceiver("192.168.1.106", 3040);
ASR.Connect();
ASR.Send(string.Format("[{0}] ping \r", Mcounter));
ASR.sendDone.WaitOne();
newMessage = false;
while (AsyncSocketReceiver.client.Connected)
{
if (newMessage)
{
var toSend = string.Format("[{0}] " + Extmessage + "\r", Mcounter++);
ASR.Send(toSend);
ASR.sendDone.WaitOne();
newMessage = false;
}
ASR.Receive();
ASR.receiveDone.WaitOne();
risposta = ASR.response;
myBW.ReportProgress(i++);
}
}
private void BWI_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
BWI.RunWorkerAsync();
}
private void BWI_ProgressChangedEventHandler(object sender, ProgressChangedEventArgs e)
{
CueAnswer.Document.Blocks.Clear();
CueAnswer.AppendText(risposta);
}
private void AsyncSocket_Click(object sender, RoutedEventArgs e) => BWI.RunWorkerAsync();
private void AskC_Click(object sender, RoutedEventArgs e)
{
ASR.receiveDone.Set();
Extmessage = MessageToSend.Text;
newMessage = true;
}
}
public class AsyncSocketReceiver
{
private static IPEndPoint remoteEP;
public static Socket client;
public ManualResetEvent connectDone = new ManualResetEvent(false);
public ManualResetEvent sendDone = new ManualResetEvent(false);
public ManualResetEvent receiveDone = new ManualResetEvent(false);
public String response;
public String query;
public AsyncSocketReceiver(String myIp, int myPort)
{
remoteEP = new IPEndPoint(IPAddress.Parse(myIp), myPort);
client = new Socket(IPAddress.Parse(myIp).AddressFamily,
SocketType.Stream, ProtocolType.Tcp)
{
Blocking = false
};
}
public void Connect()
{
client.BeginConnect(remoteEP,
new AsyncCallback(ConnectCallback), client);
connectDone.WaitOne();
}
private void ConnectCallback(IAsyncResult ar)
{
try
{
Socket client = (Socket)ar.AsyncState;
client.EndConnect(ar);
connectDone.Set();
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
public void Send(String data)
{
query = data;
response = "";
byte[] byteData = Encoding.ASCII.GetBytes(data);
client.BeginSend(byteData, 0, byteData.Length, 0,
new AsyncCallback(SendCallback), client);
}
private void SendCallback(IAsyncResult ar)
{
try
{
Socket client = (Socket)ar.AsyncState;
int bytesSent = client.EndSend(ar);
sendDone.Set();
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
public void Receive()
{
try
{
StateObject state = new StateObject
{
workSocket = client
};
receiveDone.Reset();
client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
private void ReceiveCallback(IAsyncResult ar)
{
try
{
StateObject state = (StateObject)ar.AsyncState;
int bytesRead = client.EndReceive(ar);
if (bytesRead > 0)
{
state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
client.BeginReceive(state.buffer, 0, state.buffer.Length, 0,
new AsyncCallback(ReceiveCallback), state);
}
else
{
response = state.sb.ToString();
receiveDone.Set();
}
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
}
public class StateObject
{
public Socket workSocket = null;
public const int BufferSize = 256;
public byte[] buffer = new byte[BufferSize];
public StringBuilder sb = new StringBuilder();
}
}

Double reverse TCP proxy in C#?

Edit: realized I need an implementation of this on C#:
https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT
I'll try to find one, if anyone has one I'll appreciate it, thanks!
I might have just made up that name on the spot.
What I'm trying to do is have a computer that is behind NAT and will need to provide a service connect to a server.
Then, I would connect to that server using a third computer and interact with the TCP stream initiated by the first computer, in a way that would work just as if I had connected straight to it.
Found a way to create a forward proxy, which works great:
blog.brunogarcia.com/2012/10/simple-tcp-forwarder-in-c.html
using System;
using System.Net;
using System.Net.Sockets;
namespace TcpProxy
{
class Program
{
static void Main(string[] args)
{
new TcpForwarderSlim().Start(
new IPEndPoint(IPAddress.Parse("127.0.0.1"), int.Parse("69")),
new IPEndPoint(IPAddress.Parse("91.198.174.192"), int.Parse("80")));
}
}
public class TcpForwarderSlim
{
private readonly Socket _mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
public void Start(IPEndPoint local, IPEndPoint remote)
{
_mainSocket.Bind(local);
_mainSocket.Listen(10);
while (true)
{
var source = _mainSocket.Accept();
var destination = new TcpForwarderSlim();
var state = new State(source, destination._mainSocket);
destination.Connect(remote, source);
source.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state);
}
}
private void Connect(EndPoint remoteEndpoint, Socket destination)
{
var state = new State(_mainSocket, destination);
_mainSocket.Connect(remoteEndpoint);
_mainSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, OnDataReceive, state);
}
private static void OnDataReceive(IAsyncResult result)
{
var state = (State)result.AsyncState;
try
{
var bytesRead = state.SourceSocket.EndReceive(result);
if (bytesRead > 0)
{
state.DestinationSocket.Send(state.Buffer, bytesRead, SocketFlags.None);
state.SourceSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state);
}
}
catch
{
state.DestinationSocket.Close();
state.SourceSocket.Close();
}
}
private class State
{
public Socket SourceSocket { get; private set; }
public Socket DestinationSocket { get; private set; }
public byte[] Buffer { get; private set; }
public State(Socket source, Socket destination)
{
SourceSocket = source;
DestinationSocket = destination;
Buffer = new byte[8192];
}
}
}
}
Could anyone point me to the right direction?
UPNP can open a port on a properly configured router.
STUN, ICE, and TURN use intermediary servers to connect when ports can't be opened. There are open source server implementations.
UDP can use UDP Hole Punching.
This answer provides a more detailed explaination but not much C# help.

Categories