Many TcpListener incoming connections - c#

I need to make a server that accepts and keeps for a long time many connections (perspectively over 100k).
My code is below:
public delegate Task ClientConnectedEventHandler(Stream stream);
public class Listener
{
public event ClientConnectedEventHandler OnClientConnected;
private readonly TcpListener _tcpListener;
public Listener()
{
_tcpListener = new TcpListener(new IPEndPoint(IPAddress.Any, 8082));
}
public void Start()
{
_tcpListener.Start();
_tcpListener.BeginAcceptTcpClient(Accept, null);
}
private void Accept(IAsyncResult asyncResult)
{
_tcpListener.BeginAcceptTcpClient(Accept, null);
var client = _tcpListener.EndAcceptTcpClient(asyncResult);
var stream = client.GetStream();
OnClientConnected?.Invoke(stream).ContinueWith(_ => client.Close());
}
}
class Program
{
static void Main(string[] args)
{
var listener = new Listener();
var count = 0;
var infoLock = new object();
listener.OnClientConnected += async stream =>
{
lock (infoLock)
{
count++;
Console.Title = count.ToString();
}
while (true)
{
// Some logic
await Task.Delay(100);
}
};
listener.Start();
while (true)
{
Thread.Sleep(100);
}
}
}
There is no problem when the logic takes up to 300-400 ms. But if I want to keep incoming connections for a long time, count variable increments very slow after accepting 8 clients, moreover appears a trouble with huge memory usage. What I'm doing wrong and how to resolve this?

Your memory issue may be caused by not disposing unmanaged resources. Both TcpClient and NetworkStream implement IDisposable and should be wrapped in Using blocks or manually Closed/Disposed. See How to properly and completely close/reset a TcpClient connection? for starters.

Related

Why is the socket being blocked from receiving, while I sleep on another thread?

I have a simple socket listener application. It needs to be able to receive requests and give a response and also send requests itself and receive the responses for them.
As soon as my application starts, it will start receiving in a separate thread and send a response. This part works fine.
However when I send requests through the SendRequest()-Method, I need to filter incoming responses, so the correct responses go to the correct requets earlier made. I do this (as seen in code below) with the class ResponseHandler, which lets me register a request and in return notifies my registered request, as soon as the correct response came in. A placed request should however time out after 10 seconds, so I used a CountdownEvent, which waits these 10 seconds, but releases earlier, if the response came in earlier.
Problem: My CountdownEvent waits always the whole 10 seconds and only after that, the thread, where I receive messages will continue and thus receive the response. How is this possible, when I receive on a different thread?
I would think, that my program continues to receive in that separate thread, even when the CountdownEvent.Wait() is active.
Note: The awaited response really comes back instantly after I placed the request as seen with the NetworkTool WireShark. So the timeout is not correct.
Edit: In a simple WPF-Application, where the SendRequest() is called from a button, it works. Unfortunately, this means my big program is the problem.
Service:
public class Service
{
private readonly ResponseHandler _responseHandler;
private readonly SyncSocketServer _serverSocket;
private static readonly int ServerPort = 9090;
public Service()
{
_responseHandler = new ResponseHandler();
_serverSocket = new SyncSocketServer(ServerPort);
_serverSocket.StartListening();
_serverSocket.DataReceived += ServerSocket_DataReceived;
}
public void ServerSocket_DataReceived(object sender, string message)
{
// Here I left irrelevant code out: Originally, I check here,
// whether the message is a request or response and so on, and
// I only forward the message to the _responseHandler, if it is
// indeed a response. If it is a request I send an answer.
string messageId = GetIdFromMessage(message);
_responseHandler.DataReceived(messageId, message);
}
public void SendRequest(string message)
{
string messageId = Guid.NewGuid().ToString();
string request = CreateRequest(messageId, message);
_responseHandler.Register(messageId);
_serverSocket.Send(request);
string response = _responseHandler.WaitForResponse(messageId);
Debug.WriteLine("I got the correct response: " + response);
}
}
SyncSocketServer:
public class SyncSocketServer
{
public event EventHandler<string> DataReceived;
private const int BufferSize = 1024;
private const string EndDelimiter = "\n";
private Socket _listenerSocket;
private Socket _client;
private string _data;
private Byte[] _buffer;
private readonly int _port;
public SyncSocketServer(int port)
{
_port = port;
_buffer = new Byte[BufferSize];
}
public void StartListening()
{
IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[3];
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, _port);
_listenerSocket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_listenerSocket.Bind(localEndPoint);
_listenerSocket.Listen(5);
_client = _listenerSocket.Accept();
Debug.WriteLine("Local socket opened on: {0}", _listenerSocket.LocalEndPoint);
StartReceiving();
}
private void StartReceiving()
{
Thread d = new Thread(() => {
Thread.CurrentThread.IsBackground = true;
while (true)
{
_data = null;
while (true)
{
int bytesReceived = _client.Receive(_buffer);
_data += Encoding.ASCII.GetString(_buffer, 0, bytesReceived);
if (_data.IndexOf(EndDelimiter, StringComparison.OrdinalIgnoreCase) > -1)
break;
}
Debug.WriteLine("Message received:" + _data);
OnDataReceived(_data);
}
});
d.Start();
}
public void Send(string message)
{
byte[] bytesMessage = Encoding.ASCII.GetBytes(message + EndDelimiter);
_client.Send(bytesMessage);
Debug.WriteLine("Message sent: " + message);
}
protected virtual void OnDataReceived(string data)
{
EventHandler<string> handler = DataReceived;
if (handler != null)
handler(this, data);
}
}
ResponseHandler:
public class ResponseHandler
{
private const int WaitForResponseTimeout = 10000;
private readonly Dictionary<string, PendingRequest> _pendingRequests;
public ResponseHandler()
{
_pendingRequests = new Dictionary<string, PendingRequest>();
}
public void DataReceived(string messageId, string response)
{
_pendingRequests.TryGetValue(messageId, out var pendingRequest);
if (pendingRequest == null)
Debug.WriteLine("Received response for request, that has been removed");
else
{
pendingRequest.ResponseReceived(response);
_pendingRequests.Remove(messageId);
}
}
public void Register(string messageId)
{
_pendingRequests.Add(messageId, new PendingRequest());
}
public string WaitForResponse(string messageId)
{
_pendingRequests.TryGetValue(messageId, out var pendingRequest);
if (pendingRequest == null)
return null;
pendingRequest.Await();
return pendingRequest.Response;
}
private class PendingRequest
{
public string Response { get; private set; }
private readonly CountdownEvent _countdownEvent;
public PendingRequest()
{
_countdownEvent = new CountdownEvent(1);
}
public void Await()
{
// Here, the current thread gets blocked, but
// I expect, that the thread, where I receive
// would continue receiving
_countdownEvent.Wait(WaitForResponseTimeout);
}
public void ResponseReceived(stringresponse)
{
Response = response;
_countdownEvent.Signal();
}
}
}
So, your PendingRequest and ResponseHandler classes are being accessed from different threads. So, there are a couple of things you need to do, for the sanity of your program:
a) Make sure that when you are adding and removing requests from your pending requests dictionary, you get a lock, because you are simultaneously accessing a shared datastructure from different threads. Otherwise you can corrupt your datastructure.
b) Your more immediate problem is the Await() method in PendingRequest. You are calling CountdownEvent.Wait() without verifying if your response is already set. If your response is already set, it would mean that you would wait for 10 seconds before you process it. This can happen if your response arrives, even before you invoke CountdownEvent.Wait(). In that case, CountdownEvent.Signal() will just be ignored. You should change the PendingRequest.Wait() as follows:
while (Response is not set) {
CountdownEvent.Await();
}
Also, doesn't your CountdownEvent.Wait() semaphore require a mutex to be passed to it ? Remember that your Response object is being shared between threads. This is the general paradigm for using the wait() method:
mutex.lock();
while (Response is not set) {
CountdownEvent.Await(mutex);
}
// Do your stuff, since your condition is satisfied
mutext.unlock();
The problem is actually the false assumption, that firing an event, like I did below, would result in a fire and forget:
protected virtual void OnDataReceived(string data)
{
EventHandler<string> handler = DataReceived;
if (handler != null)
handler(this, data);
}
In the function StartReceiving(), where I receive data and forward it to the subscribers, it would pause at the call, that fires the event and wait for all subscribers to finish their work (which includes, of course, waiting 10 seconds for the response). This leads to the fact, that my receiver-thread, waits for the other thread.
The solution is, to implement the call, so it will do a fire and forget:
protected virtual void OnDataReceived(string data)
{
EventHandler<string> handler = DataReceived;
if (handler != null)
handler.BeginInvoke(this, data, null, null);
}

Exit Thread and TcpListener in C#

I've the following problem: I created a simple HTTP server component. The server should be controlled with buttons on the GUI. I can start the server without any problems, but if I want to stop the server the whole program is killed. I think that's an error of aborting the thread but I don't know how I can solve this problem.
Here's my code:
public class HttpServer {
private int port;
public HttpServer(int port) {
this.port = port;
}
public void Listen() {
TcpListener listener = new TcpListener(IPAddress.Any, port);
listener.Start();
try {
while (true) {
TcpClient client = listener.AcceptTcpClient();
HttpProcessor processor = new HttpProcessor(client);
Thread thread = new Thread(new ThreadStart(processor.Process));
thread.Start();
Thread.Sleep(1);
}
}
catch { }
listener.Stop();
}
}
public class HttpProcessor {
private TcpClient client;
private StreamReader reader;
private StreamWriter writer;
public HttpProcessor(TcpClient client) {
this.client = client;
this.reader = null;
this.writer = null;
}
public void Process() {
reader = new StreamReader(client.GetStream());
writer = new StreamWriter(client.GetStream());
ParseRequest();
// some method calls to process the request and generate the response
SendResponse();
client.Close();
}
}
public partial class MainForm : Form {
private HttpServer server;
private Thread servthread;
private void Form_Load(object sender, EventArgs e) {
server = new HttpServer(8080);
}
private void Button1_Click(object sender, EventArgs e) {
servthread = new Thread(new ThreadStart(server.Listen));
servthread.Start();
Thread.Sleep(1);
}
private void Button2_Click(object sender, EventArgs e) {
servthread.Abort();
}
}
Do not use Thread.Abort(), ever! Use other means of communicating to the thread that it should stop, like a WaitHandle or even a private volatile bool stopThread; flag!
If you ever feel the need to call any other methods on a Thread than Start and Join you're probably doing something wrong and you should think about your design ;-)
See this: How to: Create and Terminate Threads (C# Programming Guide)
On your comment about AcceptTcpClient being a blocking call: Yes, it is. However, as others have noted too, you could easily change your class to avoid this problem:
public class HttpServer {
private int port;
private TcpListener listener; // Make the listener an instance member
public HttpServer(int port) {
this.port = port;
this.listener = new TcpListener(IPAddress.Any, port); // Instantiate here
}
public void Listen() {
listener.Start();
try {
while (true) {
TcpClient client = listener.AcceptTcpClient();
HttpProcessor processor = new HttpProcessor(client);
Thread thread = new Thread(new ThreadStart(processor.Process));
thread.Start();
Thread.Sleep(1);
}
}
catch { }
listener.Stop();
}
public void StopListening()
{
listener.Server.Close();
}
}
Then, instead of servthread.Abort(); you'd call server.StopListening();.
You may need to wrap the listener.Stop() line in a try/catch as well, but you'll have to try.
To make everything "kinda" work "kinda" correctly:
in HttpServer move listener variable from local var to class member
in HttpServer introduce a method:
public void Stop()
{
listener.Stop();
}
Change your Button2_Click method to:
private void Button2_Click(object sender, EventArgs e)
{
server.Stop();
servthread.Join();
}
PS: I assume that this is one of your first projects, so instead of writing a long post of how to do your stuff correctly, I suggested the changes that will allow you to continue your project. Bugs and architecture issues may come or may not come)
Happy learning.

C# UdpClient does not return from EndReceive

I am using an c# UdpClient to receive data via udp. Everything works fine, but after a while the method receiveClient does not return.
First aI thought it is a synchronisation problem so I enclosed the shared resource (lastPackage) with a lock, but this did not help.
I debugged the program (which was hard, due to the asynchron receiveing) and finally found out that the method endReceive does not return. The udp source does continue to send the packages.
Below I have printed the relevant program parts.
public class UDPSocket {
private IPEndPoint receiveEndPoint;
private UdpClient receiveClient;
private byte[] lastPackage;
private bool unhandledPackage;//shared resource
private Dictionary<string, string> agentsMap;
public UDPSocket(string sendIp, int sendPort, int receivePort){
receiveEndPoint = new IPEndPoint (IPAddress.Any, receivePort);
receiveClient = new UdpClient (receiveEndPoint);
singleton.receiveClient.BeginReceive(new AsyncCallback(singleton.ReceiveCallback), null);
unhandledPackage = false;
agentsMap = new Dictionary<string, string> ();
}
public void Update () {//is called once per second
if (unhandledPackage) {
string rawString = Encoding.UTF8.GetString(lastPackage);
agentsMap = Parser.parseString(rawString);
unhandledPackage = false;
receiveClient.BeginReceive(new AsyncCallback(ReceiveCallback), null);
}
}
public string getEmotion(string id){
string emotion;
if(!agentsMap.TryGetValue(id, out emotion)){
return "No connection";
}
return emotion;
}
public void OnDestroy()
{
if (receiveClient != null) {
receiveClient.Close ();
}
}
public void ReceiveCallback(IAsyncResult ar)
{
lastPackage = receiveClient.EndReceive(ar, ref receiveEndPoint);//DOES NOT RETURN FROM THIS METHOD CALL
unhandledPackage = true;
}
I have simplified the program to show only possibly relevant parts.
I would be thankfull, if somebody could help me.
Regards,
Jan
If you are using .net 4.5, simply use the async&await keywords, this way:
private static async void Foo(int port)
{
UdpClient udpClient=new UdpClient(port);
UdpReceiveResult result = await udpClient.ReceiveAsync();
}
EndReceive will block if the async operation has not finished.
I assume that receiveClient in ReceiveCallback is another receiveClient as that was used with BeginReceive. So make sure you use the same instance.

Network stream write is blocked

I am working on a c# application (.net 4) that accepts multiple tcp connections from different clients. There is a single tcp listener that accepts socket. Communication b/w nodes in duplex. Data is sent using Networkstream.Write method and read using Networkstream.read method. For each tcp connection a seperate thread is created.
The problem is, a few days ago we noticed that one of the clients stopped reading data (due to a bug) for 20 minutes. As the connection was not broken, there was no (IO) exception at the server. However, we noticed that data at the other clients was also not going. After 20 minutes, that client again started receiving the data and soon other clients also started receiving the data.
I know that Network stream's write method is a blocking method and we are not using any timeouts. So there is a potential that write has blocked (described here). But as I understood it, there has to be a seperate write buffer for each tcp connection or is there something more at play. Can a send blockage at a tcp connection, effect other tcp connections in the same application?
Here is the pseudo code for write operation. For each connection there is a seperate outgoing queue process by a seperate thread.
public class TCPServerListener : baseConnection
{
private readonly int _Port;
private TcpListener _tcpListener;
private Thread _thread;
private List<TcpClientData> _tcpClientDataList = new List<TcpClientData>();
private long _messageDiscardTimeout;
private bool LoopForClientConnection = true;
public TCPServerListener(int port, ThreadPriority threadPriority)
{
try
{
// init property
}
catch (Exception ex)
{
// log
}
}
public void SendMessageToAll(int type)
{
base.EnqueueMessageToSend(type, _tcpClientDataList);
}
public void SendMessageToList(int type, IList<TcpClient> tcpClientList)
{
base.EnqueueMessageToSend(type, tcpClientList);
}
public void SendMessage(int type, TcpClient tcpClient)
{
base.EnqueueMessageToSend(type, tcpClient);
}
private void AcceptClientConnections()
{
while (LoopForClientConnection)
{
try
{
Socket socket = _tcpListener.AcceptSocket();
TcpClientData tcpClientData = new TcpClientData();
tcpClientData.tcpClientThread = new Thread(new ParameterizedThreadStart(StartAsync));
tcpClientData.tcpClientThread.Priority = _threadPriority;
tcpClientData.tcpClientThread.IsBackground = true;
tcpClientData.tcpClientThread.Name = "CD" + tcpClientData.tcpClientThread.ManagedThreadId;
tcpClientData.tcpClient = new TcpClient();
tcpClientData.tcpClient.Client = socket;
_tcpClientDataList.Add(tcpClientData);
tcpClientData.tcpClientThread.Start(tcpClientData.tcpClient);
}
catch (ThreadAbortException ex)
{
//log
}
catch (Exception ex)
{
//log
}
}
}
public override void Start()
{
base.Start();
_tcpListener = new TcpListener(System.Net.IPAddress.Any, _Port);
_thread = new Thread(AcceptClientConnections);
_thread.Priority = _threadPriority;
_thread.IsBackground = true;
_tcpListener.Start();
_thread.Start();
}
public override void Stop()
{
// stop listener and terminate threads
}
}
public class baseConnection
{
private Thread _InCommingThread;
private Thread _OutGoingThread;
protected ThreadPriority _threadPriority;
protected BlockingCollection<MessageReceived> _InComingMessageQueue = new BlockingCollection<MessageReceived>();
protected BlockingCollection<MessageToSend> _OutgoingMessageQueue = new BlockingCollection<MessageToSend>();
public void StartAsync(Object oTcpClient)
{
TcpClient tcpClient = oTcpClient as TcpClient;
if (tcpClient == null)
return;
using (tcpClient)
{
using (NetworkStream stream = tcpClient.GetStream())
{
stream.ReadTimeout = Timeout.Infinite;
stream.WriteTimeout = Timeout.Infinite;
BinaryReader bodyReader = new BinaryReader(stream);
while (tcpClient.Connected)
{
try
{
int messageType = bodyReader.ReadInt32();
// checks to verify messages
// enqueue message in incoming queue
_InComingMessageQueue.Add(new MessageReceived(messageType, tcpClient));
}
catch (EndOfStreamException ex)
{
// log
break;
}
catch (Exception ex)
{
// log
Thread.Sleep(100);
}
}
//RaiseDisconnected(tcpClient);
}
}
}
public virtual void Start()
{
_InCommingThread = new Thread(HandleInCommingMessnge);
_InCommingThread.Priority = _threadPriority;
_InCommingThread.IsBackground = true;
_InCommingThread.Start();
_OutGoingThread = new Thread(HandleOutgoingQueue);
_OutGoingThread.Priority = _threadPriority;
_OutGoingThread.IsBackground = true;
_OutGoingThread.Start();
}
public virtual void Stop()
{
// stop the threads and free up resources
}
protected void EnqueueMessageToSend(int type, List<TcpClientData> tcpClientDataList)
{
tcpClientDataList.ForEach(x => _OutgoingMessageQueue.Add(new MessageToSend(type, x.tcpClient)));
}
protected void EnqueueMessageToSend(int type, IList<TcpClient> tcpClientList)
{
foreach (TcpClient tcpClient in tcpClientList)
{
_OutgoingMessageQueue.Add(new MessageToSend(type, tcpClient));
}
}
protected void EnqueueMessageToSend(int type, TcpClient tcpClient)
{
_OutgoingMessageQueue.Add(new MessageToSend(type, tcpClient));
}
private void HandleOutgoingQueue()
{
while (true)
{
try
{
MessageToSend message = _OutgoingMessageQueue.Take();
if (message.tcpClient.Connected)
{
BinaryWriter writer = new BinaryWriter(message.tcpClient.GetStream());
writer.Write(message.type);
}
}
catch (ThreadAbortException ex)
{
// log
return;
}
catch (Exception ex)
{
//_logger.Error(ex.Message, ex);
}
}
}
private void HandleInCommingMessnge()
{
while (true)
{
try
{
MessageReceived messageReceived = _InComingMessageQueue.Take();
// handle message
}
catch (ThreadAbortException ex)
{
// log
return;
}
catch (Exception ex)
{
// log
//_logger.Error(ex.Message, ex);
}
}
}
public class MessageReceived
{
public MessageReceived(int type, TcpClient tcpClient)
{
this.tcpClient = tcpClient;
this.type = type;
}
public int type;
public TcpClient tcpClient;
}
public class MessageToSend
{
public MessageToSend(int type, TcpClient tcpClient)
{
this.tcpClient = tcpClient;
this.type = type;
}
public int type;
public TcpClient tcpClient;
}
public class TcpClientData
{
public Thread tcpClientThread;
public TcpClient tcpClient;
}
}
You mention that for each connection a separate thread is created, but the code you have shown seems to be able to dequeue a message for any connection.
If this code is running on multiple threads, the program will block as soon as every thread is currently trying to send a message to the blocking connection. Another problem you may face if this loop runs on multiple threads is that messages may not arrive in the correct order for the same connection.

TCPIP networking with C#

HI everyone,
I'm going to be writing some code that has to listen for TCPIP messages coming from GSM mobile phones over GPRS. In the fullness of time, I see this as running on a Virtual Private Server, and it could well be processing multiple messages every second.
I'm a bit of a network programming virgin, so I've done a bit of research on the internet, and read a few tutorials. The approach I am considering at the moment is a windows service using sockets to monitor the port. If my understanding is correct, I need one socket to listen for connections from clients, and each time someone tries to connect with the port I will be passed another socket with which to communicate with them? Does this sound about right to more experienced ears?
I'm planning on using asynchronous communication, but on of the bigger design questions is whether to use threading or not. Threading isn't something I've really played with, and I am aware of a number of pitfalls - race conditions and debugging problems being but two.
If I avoid threads, I know I have to supply an object that acts as an identifier for a particular conversation. I was thinking GUIDs for this - any opinions?
Thanks in advance for any responses...
Martin
Starting from .net framework 2.0 SP1 there are some changings in socket libraries related to asyncronous sockets.
All multithreading used under the hood. We have no need to use multithreading manually (we don't need to use even ThreadPool explicitly). All what we do - using BeginAcceptSocket for starting accepting new connections, and using SocketAsyncEventArgs after accepting new connection .
Short implementation:
//In constructor or in method Start
var tcpServer = new TcpListener(IPAddress.Any, port);
tcpServer.Start();
tcpServer.BeginAcceptSocket(EndAcceptSocket, tcpServer);
//In EndAcceptSocket
Socket sock= lister.EndAcceptSocket(asyncResult);
var e = new SocketAsyncEventArgs();
e.Completed += ReceiveCompleted; //some data receive handle
e.SetBuffer(new byte[SocketBufferSize], 0, SocketBufferSize);
if (!sock.ReceiveAsync(e))
{//IO operation finished syncronously
//handle received data
ReceiveCompleted(sock, e);
}//IO operation finished syncronously
//Add sock to internal storage
Full implementation:
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
namespace Ample
{
public class IPEndPointEventArgs : EventArgs
{
public IPEndPointEventArgs(IPEndPoint ipEndPoint)
{
IPEndPoint = ipEndPoint;
}
public IPEndPoint IPEndPoint { get; private set; }
}
public class DataReceivedEventArgs : EventArgs
{
public DataReceivedEventArgs(byte[] data, IPEndPoint ipEndPoint)
{
Data = data;
IPEndPoint = ipEndPoint;
}
public byte[] Data { get; private set; }
public IPEndPoint IPEndPoint { get; private set; }
}
/// <summary>
/// TcpListner wrapper
/// Encapsulates asyncronous communications using TCP/IP.
/// </summary>
public sealed class TcpServer : IDisposable
{
//----------------------------------------------------------------------
//Construction, Destruction
//----------------------------------------------------------------------
/// <summary>
/// Creating server socket
/// </summary>
/// <param name="port">Server port number</param>
public TcpServer(int port)
{
connectedSockets = new Dictionary<IPEndPoint, Socket>();
tcpServer = new TcpListener(IPAddress.Any, port);
tcpServer.Start();
tcpServer.BeginAcceptSocket(EndAcceptSocket, tcpServer);
}
~TcpServer()
{
DisposeImpl(false);
}
public void Dispose()
{
DisposeImpl(true);
}
//----------------------------------------------------------------------
//Public Methods
//----------------------------------------------------------------------
public void SendData(byte[] data, IPEndPoint endPoint)
{
Socket sock;
lock (syncHandle)
{
if (!connectedSockets.ContainsKey(endPoint))
return;
sock = connectedSockets[endPoint];
}
sock.Send(data);
}
//----------------------------------------------------------------------
//Events
//----------------------------------------------------------------------
public event EventHandler<IPEndPointEventArgs> SocketConnected;
public event EventHandler<IPEndPointEventArgs> SocketDisconnected;
public event EventHandler<DataReceivedEventArgs> DataReceived;
//----------------------------------------------------------------------
//Private Functions
//----------------------------------------------------------------------
#region Private Functions
//Обработка нового соединения
private void Connected(Socket socket)
{
var endPoint = (IPEndPoint)socket.RemoteEndPoint;
lock (connectedSocketsSyncHandle)
{
if (connectedSockets.ContainsKey(endPoint))
{
theLog.Log.DebugFormat("TcpServer.Connected: Socket already connected! Removing from local storage! EndPoint: {0}", endPoint);
connectedSockets[endPoint].Close();
}
SetDesiredKeepAlive(socket);
connectedSockets[endPoint] = socket;
}
OnSocketConnected(endPoint);
}
private static void SetDesiredKeepAlive(Socket socket)
{
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
const uint time = 10000;
const uint interval = 20000;
SetKeepAlive(socket, true, time, interval);
}
static void SetKeepAlive(Socket s, bool on, uint time, uint interval)
{
/* the native structure
struct tcp_keepalive {
ULONG onoff;
ULONG keepalivetime;
ULONG keepaliveinterval;
};
*/
// marshal the equivalent of the native structure into a byte array
uint dummy = 0;
var inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0);
BitConverter.GetBytes((uint)time).CopyTo(inOptionValues, Marshal.SizeOf(dummy));
BitConverter.GetBytes((uint)interval).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);
// of course there are other ways to marshal up this byte array, this is just one way
// call WSAIoctl via IOControl
int ignore = s.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
}
//socket disconnected handler
private void Disconnect(Socket socket)
{
var endPoint = (IPEndPoint)socket.RemoteEndPoint;
lock (connectedSocketsSyncHandle)
{
connectedSockets.Remove(endPoint);
}
socket.Close();
OnSocketDisconnected(endPoint);
}
private void ReceiveData(byte[] data, IPEndPoint endPoint)
{
OnDataReceived(data, endPoint);
}
private void EndAcceptSocket(IAsyncResult asyncResult)
{
var lister = (TcpListener)asyncResult.AsyncState;
theLog.Log.Debug("TcpServer.EndAcceptSocket");
if (disposed)
{
theLog.Log.Debug("TcpServer.EndAcceptSocket: tcp server already disposed!");
return;
}
try
{
Socket sock;
try
{
sock = lister.EndAcceptSocket(asyncResult);
theLog.Log.DebugFormat("TcpServer.EndAcceptSocket: remote end point: {0}", sock.RemoteEndPoint);
Connected(sock);
}
finally
{
//EndAcceptSocket can failes, but in any case we want to accept
new connections
lister.BeginAcceptSocket(EndAcceptSocket, lister);
}
//we can use this only from .net framework 2.0 SP1 and higher
var e = new SocketAsyncEventArgs();
e.Completed += ReceiveCompleted;
e.SetBuffer(new byte[SocketBufferSize], 0, SocketBufferSize);
BeginReceiveAsync(sock, e);
}
catch (SocketException ex)
{
theLog.Log.Error("TcpServer.EndAcceptSocket: failes!", ex);
}
catch (Exception ex)
{
theLog.Log.Error("TcpServer.EndAcceptSocket: failes!", ex);
}
}
private void BeginReceiveAsync(Socket sock, SocketAsyncEventArgs e)
{
if (!sock.ReceiveAsync(e))
{//IO operation finished syncronously
//handle received data
ReceiveCompleted(sock, e);
}//IO operation finished syncronously
}
void ReceiveCompleted(object sender, SocketAsyncEventArgs e)
{
var sock = (Socket)sender;
if (!sock.Connected)
Disconnect(sock);
try
{
int size = e.BytesTransferred;
if (size == 0)
{
//this implementation based on IO Completion ports, and in this case
//receiving zero bytes mean socket disconnection
Disconnect(sock);
}
else
{
var buf = new byte[size];
Array.Copy(e.Buffer, buf, size);
ReceiveData(buf, (IPEndPoint)sock.RemoteEndPoint);
BeginReceiveAsync(sock, e);
}
}
catch (SocketException ex)
{
//We can't truly handle this excpetion here, but unhandled
//exception caused process termination.
//You can add new event to notify observer
theLog.Log.Error("TcpServer: receive data error!", ex);
}
catch (Exception ex)
{
theLog.Log.Error("TcpServer: receive data error!", ex);
}
}
private void DisposeImpl(bool manualDispose)
{
if (manualDispose)
{
//We should manually close all connected sockets
Exception error = null;
try
{
if (tcpServer != null)
{
disposed = true;
tcpServer.Stop();
}
}
catch (Exception ex)
{
theLog.Log.Error("TcpServer: tcpServer.Stop() failes!", ex);
error = ex;
}
try
{
foreach (var sock in connectedSockets.Values)
{
sock.Close();
}
}
catch (SocketException ex)
{
//During one socket disconnected we can faced exception
theLog.Log.Error("TcpServer: close accepted socket failes!", ex);
error = ex;
}
if ( error != null )
throw error;
}
}
private void OnSocketConnected(IPEndPoint ipEndPoint)
{
var handler = SocketConnected;
if (handler != null)
handler(this, new IPEndPointEventArgs(ipEndPoint));
}
private void OnSocketDisconnected(IPEndPoint ipEndPoint)
{
var handler = SocketDisconnected;
if (handler != null)
handler(this, new IPEndPointEventArgs(ipEndPoint));
}
private void OnDataReceived(byte[] data, IPEndPoint ipEndPoint)
{
var handler = DataReceived;
if ( handler != null )
handler(this, new DataReceivedEventArgs(data, ipEndPoint));
}
#endregion Private Functions
//----------------------------------------------------------------------
//Private Fields
//----------------------------------------------------------------------
#region Private Fields
private const int SocketBufferSize = 1024;
private readonly TcpListener tcpServer;
private bool disposed;
private readonly Dictionary<IPEndPoint, Socket> connectedSockets;
private readonly object connectedSocketsSyncHandle = new object();
#endregion Private Fields
}
}
It is surprisingly simple to make a multi-threaded server. Check out this example.
class Server
{
private Socket socket;
private List<Socket> connections;
private volatile Boolean endAccept;
// glossing over some code.
/// <summary></summary>
public void Accept()
{
EventHandler<SocketAsyncEventArgs> completed = null;
SocketAsyncEventArgs args = null;
completed = new EventHandler<SocketAsyncEventArgs>((s, e) =>
{
if (e.SocketError != SocketError.Success)
{
// handle
}
else
{
connections.Add(e.AcceptSocket);
ThreadPool.QueueUserWorkItem(AcceptNewClient, e.AcceptSocket);
}
e.AcceptSocket = null;
if (endAccept)
{
args.Dispose();
}
else if (!socket.AcceptAsync(args))
{
completed(socket, args);
}
});
args = new SocketAsyncEventArgs();
args.Completed += completed;
if (!socket.AcceptAsync(args))
{
completed(socket, args);
}
}
public void AcceptNewClient(Object state)
{
var socket = (Socket)state;
// proccess
}
}
A bit of advise from the guy who deals mainly with mobile networking: do your homework with regular networking connection, preferably on the localhost. This will save you a lot of time during testing and will keep you sane until you figure out the approach that works for you best.
As for some particular implementation, I always go with synchronized sockets (you will need to configure timeouts to not to get stuck if something will go wrong) and everything runs in separate threads that are synchronized with the help of events. It's much simplier than you think. Here's some useful links to get you started:
http://msdn.microsoft.com/en-us/library/3e8s7xdd.aspx
http://msdn.microsoft.com/en-us/library/ms228969.aspx
I'm writing the same application right now and I use solution like this:
http://clutch-inc.com/blog/?p=4
It's been tested right now and works perfectly. It is important to make this service only for receiving and storing messages (somewhere) without other work. I'm using NServiceBus for saving messages. Other service takes messages from queue and do the rest.
Well, the C# syntax is not fresh in my mind now but I don't think it is to much different from the Posix standard.
What you can may do is when you create your listen socket you can stipulate a value for the backlog (maximum number of simultaneous connections for that server) and create a thread pull with the same size. Thread pools are easier to use than traditional ones. The TCP you queue for you all the connections above the backlog parameter.

Categories