Disclaimer: My C# isn't even close to as good as my C++
I am trying to learn how to do async sockets in C# in order to write a test app for a component of mine. My former attempts using TcpClient ended in failure and you can read the outstanding questions on that here:
TcpClient.NetworkStream Async operations - Canceling / Disconnect
Detect errors with NetworkStream.WriteAsync
Since, I could not get that working, I tried using Socket.BeginX and Socket.EndX instead. I got much further along. My problem now is that in the listing below, when it comes time to disconnect, which in turn calls shutdown and close on the socket, async operations are still outstanding and they will throw an object disposed exception or an object is set to null exception.
I found a similar post about that here:
After disposing async socket (.Net) callbacks still get called
However, I do not accept that answer, because if you are using exceptions for intended behavior, then 1) They are not exceptional 2) You cannot tell if the exception was thrown for your intended case or if it was thrown because you actually used a disposed object or a null reference in your async method, other than the socket.
In C++ with async socket code, I'd keep track of the number of outstanding async operations with Interlocked and when it came time to disconnect, I'd call shutdown, then wait for the interlocked to hit 0, and then close and destroy any members I needed to.
How would I go about waiting in my Disconnect method for all outstanding Async operations to complete in C# in the following listing?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using log4net;
using System.Net.Sockets;
using System.Net;
namespace IntegrationTests
{
public class Client2
{
class ReceiveContext
{
public Socket m_socket;
public const int m_bufferSize = 1024;
public byte[] m_buffer = new byte[m_bufferSize];
}
private static readonly ILog log = LogManager.GetLogger("root");
static private ulong m_lastId = 1;
private ulong m_id;
private string m_host;
private uint m_port;
private uint m_timeoutMilliseconds;
private string m_clientId;
private Socket m_socket;
private uint m_numOutstandingAsyncOps;
public Client2(string host, uint port, string clientId, uint timeoutMilliseconds)
{
m_id = m_lastId++;
m_host = host;
m_port = port;
m_clientId = clientId;
m_timeoutMilliseconds = timeoutMilliseconds;
m_socket = null;
m_numOutstandingAsyncOps = 0;
}
~Client2()
{
Disconnect();
}
public void Connect()
{
IPHostEntry ipHostInfo = Dns.GetHostEntry(m_host);
IPAddress[] ipV4Addresses = ipHostInfo.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetwork).ToArray();
IPAddress[] ipV6Addresses = ipHostInfo.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetworkV6).ToArray();
IPEndPoint endpoint = new IPEndPoint(ipV4Addresses[0], (int)m_port);
m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
m_socket.ReceiveTimeout = (int)m_timeoutMilliseconds;
m_socket.SendTimeout = (int)m_timeoutMilliseconds;
try
{
m_socket.Connect(endpoint);
log.Info(string.Format("Connected to: {0}", m_socket.RemoteEndPoint.ToString()));
// Issue the next async receive
ReceiveContext context = new ReceiveContext();
context.m_socket = m_socket;
m_socket.BeginReceive(context.m_buffer, 0, ReceiveContext.m_bufferSize, SocketFlags.None, new AsyncCallback(OnReceive), context);
}
catch (Exception e)
{
// Error
log.Error(string.Format("Client #{0} Exception caught OnConnect. Exception: {1}"
, m_id, e.ToString()));
}
}
public void Disconnect()
{
if (m_socket != null)
{
m_socket.Shutdown(SocketShutdown.Both);
// TODO - <--- Error here in the callbacks where they try to use the socket and it is disposed
// We need to wait here until all outstanding async operations complete
// Should we use Interlocked to keep track of them and wait on it somehow?
m_socket.Close();
m_socket = null;
}
}
public void Login()
{
string loginRequest = string.Format("loginstuff{0})", m_clientId);
var data = Encoding.ASCII.GetBytes(loginRequest);
m_socket.BeginSend(data, 0, data.Length, 0, new AsyncCallback(OnSend), m_socket);
}
public void MakeRequest(string thingy)
{
string message = string.Format("requeststuff{0}", thingy);
var data = Encoding.ASCII.GetBytes(message);
m_socket.BeginSend(data, 0, data.Length, 0, new AsyncCallback(OnSend), m_socket);
}
void OnReceive(IAsyncResult asyncResult)
{
ReceiveContext context = (ReceiveContext)asyncResult.AsyncState;
string data = null;
try
{
int bytesReceived = context.m_socket.EndReceive(asyncResult);
data = Encoding.ASCII.GetString(context.m_buffer, 0, bytesReceived);
ReceiveContext newContext = new ReceiveContext();
newContext.m_socket = context.m_socket;
m_socket.BeginReceive(newContext.m_buffer, 0, ReceiveContext.m_bufferSize, SocketFlags.None, new AsyncCallback(OnReceive), newContext);
}
catch(SocketException e)
{
if(e.SocketErrorCode == SocketError.ConnectionAborted) // Check if we disconnected on our end
{
return;
}
}
catch (Exception e)
{
// Error
log.Error(string.Format("Client #{0} Exception caught OnReceive. Exception: {1}"
, m_id, e.ToString()));
}
}
void OnSend(IAsyncResult asyncResult)
{
Socket socket = (Socket)asyncResult.AsyncState;
try
{
int bytesSent = socket.EndSend(asyncResult);
}
catch(Exception e)
{
log.Error(string.Format("Client #{0} Exception caught OnSend. Exception: {1}"
, m_id, e.ToString()));
}
}
}
}
Main:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using log4net;
using log4net.Config;
namespace IntegrationTests
{
class Program
{
private static readonly ILog log = LogManager.GetLogger("root");
static void Main(string[] args)
{
try
{
XmlConfigurator.Configure();
log.Info("Starting Component Integration Tests...");
Client2 client = new Client2("127.0.0.1", 24001, "MyClientId", 60000);
client.Connect();
client.Login();
client.MakeRequest("StuffAndPuff");
System.Threading.Thread.Sleep(60000); // Sim work until user shutsdown
client.Disconnect();
}
catch (Exception e)
{
log.Error(string.Format("Caught an exception in main. Exception: {0}"
, e.ToString()));
}
}
}
}
EDIT:
Here is my additional attempt using the answer proposed by Evk, to the best of my ability. It works fine as far as I can tell.
Problem with this is, that I feel like I basically made everything that was asynchronous into a synchronous call, because of the requirements to lock around anything that would change the counter or the state of the socket. Again, I am novice at C# compared to my C++, so please do point out if I completely missed the mark interpreting his answer.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace IntegrationTests
{
public class Client
{
class ReceiveContext
{
public const int _bufferSize = 1024;
public byte[] _buffer = new byte[_bufferSize]; // Contains bytes from one receive
public StringBuilder _stringBuilder = new StringBuilder(); // Contains bytes for multiple receives in order to build message up to delim
}
private static readonly ILog _log = LogManager.GetLogger("root");
static private ulong _lastId = 1;
private ulong _id;
protected string _host;
protected int _port;
protected int _timeoutMilliseconds;
protected string _sessionId;
protected Socket _socket;
protected object _lockNumOutstandingAsyncOps;
protected int _numOutstandingAsyncOps;
private bool _disposed = false;
public Client(string host, int port, string sessionId, int timeoutMilliseconds)
{
_id = _lastId++;
_host = host;
_port = port;
_sessionId = sessionId;
_timeoutMilliseconds = timeoutMilliseconds;
_socket = null;
_numOutstandingAsyncOps = 0;
_lockNumOutstandingAsyncOps = new object();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if(_disposed)
{
return;
}
if (disposing)
{
_socket.Close();
}
_disposed = true;
}
public void Connect()
{
lock (_lockNumOutstandingAsyncOps)
{
IPHostEntry ipHostInfo = Dns.GetHostEntry(_host);
IPAddress[] ipV4Addresses = ipHostInfo.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetwork).ToArray();
IPAddress[] ipV6Addresses = ipHostInfo.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetworkV6).ToArray();
IPEndPoint endpoint = new IPEndPoint(ipV4Addresses[0], _port);
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_socket.ReceiveTimeout = _timeoutMilliseconds;
_socket.SendTimeout = _timeoutMilliseconds;
try
{
_socket.Connect(endpoint);
}
catch (Exception e)
{
// Error
Debug.WriteLine(string.Format("Client #{0} Exception caught OnConnect. Exception: {1}"
, _id, e.ToString()));
return;
}
Debug.WriteLine(string.Format("Client #{0} connected to: {1}", _id, _socket.RemoteEndPoint.ToString()));
// Issue the first async receive
ReceiveContext context = new ReceiveContext();
++_numOutstandingAsyncOps;
_socket.BeginReceive(context._buffer, 0, ReceiveContext._bufferSize, SocketFlags.None, new AsyncCallback(OnReceive), context);
}
}
public void Disconnect()
{
if (_socket != null)
{
// We need to wait here until all outstanding async operations complete
// In order to avoid getting 'Object was disposed' exceptions in those async ops that use the socket
lock(_lockNumOutstandingAsyncOps)
{
Debug.WriteLine(string.Format("Client #{0} Disconnecting...", _id));
_socket.Shutdown(SocketShutdown.Both);
while (_numOutstandingAsyncOps > 0)
{
Monitor.Wait(_lockNumOutstandingAsyncOps);
}
_socket.Close();
_socket = null;
}
}
}
public void Login()
{
lock (_lockNumOutstandingAsyncOps)
{
if (_socket != null && _socket.Connected)
{
string loginRequest = string.Format("loginstuff{0}", _clientId);
var data = Encoding.ASCII.GetBytes(loginRequest);
Debug.WriteLine(string.Format("Client #{0} Sending Login Request: {1}"
, _id, loginRequest));
++_numOutstandingAsyncOps;
_socket.BeginSend(data, 0, data.Length, 0, new AsyncCallback(OnSend), _socket);
}
else
{
Debug.WriteLine(string.Format("Client #{0} Login was called, but Socket is null or no longer connected."
, _id));
}
}
}
public void MakeRequest(string thingy)
{
lock (_lockNumOutstandingAsyncOps)
{
if (_socket != null && _socket.Connected)
{
string message = string.Format("requeststuff{0}", thingy);
var data = Encoding.ASCII.GetBytes(message);
Debug.WriteLine(string.Format("Client #{0} Sending Request: {1}"
, _id, message));
++_numOutstandingAsyncOps;
_socket.BeginSend(data, 0, data.Length, 0, new AsyncCallback(OnSend), _socket);
}
else
{
Debug.WriteLine(string.Format("Client #{0} MakeRequest was called, but Socket is null or no longer connected."
, _id));
}
}
}
protected void OnReceive(IAsyncResult asyncResult)
{
lock (_lockNumOutstandingAsyncOps)
{
ReceiveContext context = (ReceiveContext)asyncResult.AsyncState;
string data = null;
try
{
int bytesReceived = _socket.EndReceive(asyncResult);
data = Encoding.ASCII.GetString(context._buffer, 0, bytesReceived);
// If the remote host shuts down the Socket connection with the Shutdown method, and all available data has been received,
// the EndReceive method will complete immediately and return zero bytes
if (bytesReceived > 0)
{
StringBuilder stringBuilder = context._stringBuilder.Append(data);
int index = -1;
do
{
index = stringBuilder.ToString().IndexOf("#");
if (index != -1)
{
string message = stringBuilder.ToString().Substring(0, index + 1);
stringBuilder.Remove(0, index + 1);
Debug.WriteLine(string.Format("Client #{0} Received Data: {1}"
, _id, message));
}
} while (index != -1);
}
}
catch (SocketException e)
{
// Check if we disconnected on our end
if (e.SocketErrorCode == SocketError.ConnectionAborted)
{
// Ignore
}
else
{
// Error
Debug.WriteLine(string.Format("Client #{0} SocketException caught OnReceive. Exception: {1}"
, _id, e.ToString()));
Disconnect();
}
}
catch (Exception e)
{
// Error
Debug.WriteLine(string.Format("Client #{0} Exception caught OnReceive. Exception: {1}"
, _id, e.ToString()));
Disconnect();
}
finally
{
--_numOutstandingAsyncOps;
Monitor.Pulse(_lockNumOutstandingAsyncOps);
}
}
// Issue the next async receive
lock (_lockNumOutstandingAsyncOps)
{
if (_socket != null && _socket.Connected)
{
++_numOutstandingAsyncOps;
ReceiveContext newContext = new ReceiveContext();
_socket.BeginReceive(newContext._buffer, 0, ReceiveContext._bufferSize, SocketFlags.None, new AsyncCallback(OnReceive), newContext);
}
}
}
protected void OnSend(IAsyncResult asyncResult)
{
lock (_lockNumOutstandingAsyncOps)
{
try
{
int bytesSent = _socket.EndSend(asyncResult);
}
catch (Exception e)
{
Debug.WriteLine(string.Format("Client #{0} Exception caught OnSend. Exception: {1}"
, _id, e.ToString()));
Disconnect();
}
finally
{
--_numOutstandingAsyncOps;
Monitor.Pulse(_lockNumOutstandingAsyncOps);
}
}
}
}
}
You can use Monitor.Wait and Monitor.Pulse for that:
static int _outstandingOperations;
static readonly object _lock = new object();
static void Main() {
for (int i = 0; i < 100; i++) {
var tmp = i;
Task.Run(() =>
{
lock (_lock) {
_outstandingOperations++;
}
// some work
Thread.Sleep(new Random(tmp).Next(0, 5000));
lock (_lock) {
_outstandingOperations--;
// notify condition might have changed
Monitor.Pulse(_lock);
}
});
}
lock (_lock) {
// condition check
while (_outstandingOperations > 0)
// will wait here until pulsed, lock will be released during wait
Monitor.Wait(_lock);
}
}
Related
Currently my android app communicates with a server via tcp socket with synchronous read and write methods
private val addressString : String = "192.168.1.200"
private val port : Int = 33322
private var socketAddress: InetSocketAddress = InetSocketAddress(addressString, port)
private var socket: Socket? = null
fun connect(){
socket = Socket()
socketAddress = InetSocketAddress(addressString, port)
try{
socket!!.connect(socketAddress)
}catch(e : Exception){
socket!!.close()
}
}
fun disconnect(){
try {
socket?.shutdownOutput()
} catch (e: Exception) {}
try {
socket?.shutdownInput()
} catch (e: Exception) {}
try {
socket?.close()
} catch (e: Exception) {}
socket = null
}
fun send(data: ByteArray): Int {
var sentByteCount: Int = -1
try {
socket!!.getOutputStream().write(data)
sentByteCount = data.size
} catch (e: Exception) {
throw e
}
return sentByteCount
}
data class Wrapper<T>(
var value:T
)
fun receive(buffer: Wrapper<ByteArray>): Int {
val size = buffer.value.size
var receivedByteCount: Int = -1
try {
receivedByteCount = socket!!.getInputStream().read(buffer.value)
} catch (e: Exception) {
throw e
}
return receivedByteCount
}
However, the server, written in C#, always communicates with another device via socket but with an asynchronous reading method
public const int BUFFER_SIZE = 4096;
private string addressString = "192.168.1.200"
private int port = 33322
private int timeout = 5000
private byte[] _buffer = new byte[BUFFER_SIZE];
private TcpClient _client;
private SocketError _socketError;
private AsyncCallback _callback = new AsyncCallback(ReceiveCallback);
public void connect()
{
_client = TCpClient()
_client.ConnectAsync(addressString, port).Wait(timeout)
}
public void receive()
{
_client.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, _socketError, _callback, null)
}
private void receiveCallback(IAsyncResult ar)
{
//callback method when it has finished reading asynchronously
}
public void send()
{
_client.GetStream().Write(_buffer, 0, _buffer.Length);
}
public void disconnect()
{
if (_client.Connected)
_client.Close();
}
What I need is to communicate between my app and the device as the Server is already doing.
I tried to find out if even in kotlin there was the possibility of creating an asynchronous connection that would give me the possibility of being able to do an asynchronous reading as it can be done in C # and I found AsynchronousSocketChannel.
From the documentation, however, it has been deduced that the possibility of using this socket is linked to the fact that the server is an AsynchronousServerSocketChannel socket, in my case it is not possible to use it as I can only create the client.
Are there any other possibilities to recreate something similar to the C # code shown above in Kotlin?
I have these two classes that is part of my Server application (desktop) and need send a command back to Client after connection was established.
When i try make this way:
clients[i].Send("info");
the Send() routine (of Listener.cs) is accessible, but i have this following sintaxe error:
How solve this?
Listener.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
class Listener
{
Socket s;
string IP;
public List<Info> clients;
public delegate void ReceivedEventHandler(Listener l, Info i, string received);
public event ReceivedEventHandler Received;
public delegate void DisconnectedEventHandler(Listener l, Info i);
public event DisconnectedEventHandler Disconnected;
bool listening = false;
public Listener()
{
clients = new List<Info>();
s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
public bool Running
{
get { return listening; }
}
public void BeginListen(int port)
{
s.Bind(new IPEndPoint(IPAddress.Any, port));
s.Listen(100);
s.BeginAccept(new AsyncCallback(AcceptCallback), s);
listening = true;
}
public void StopListen()
{
if (listening == true)
{
s.Close();
}
}
void AcceptCallback(IAsyncResult ar)
{
Socket handler = (Socket)ar.AsyncState;
Socket sock = handler.EndAccept(ar);
Info i = new Info(sock);
clients.Add(i);
Console.WriteLine("New Connection: " + i.ID.ToString());
clients[i].Send("info");
sock.BeginReceive(i.buffer, 0, i.buffer.Length, SocketFlags.None, new AsyncCallback(ReadCallback), i);
handler.BeginAccept(new AsyncCallback(AcceptCallback), handler);
}
void ReadCallback(IAsyncResult ar)
{
Info i = (Info)ar.AsyncState;
try
{
int rec = i.sock.EndReceive(ar);
if (rec != 0)
{
string data = Encoding.ASCII.GetString(i.buffer, 0, rec);
Received(this, i, data);
}
else
{
Disconnected(this, i);
return;
}
i.sock.BeginReceive(i.buffer, 0, i.buffer.Length, SocketFlags.None, new AsyncCallback(ReadCallback), i);
}
catch
{
Disconnected(this, i);
i.sock.Close();
clients.Remove(i);
}
}
}
Info.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
public class Info
{
public Socket sock;
public Guid ID;
public string RemoteAddress;
public byte[] buffer = new byte[8192];
public Info(Socket sock)
{
this.sock = sock;
ID = Guid.NewGuid();
RemoteAddress = sock.RemoteEndPoint.ToString();
}
public void Send(string data)
{
byte[] buffer = Encoding.ASCII.GetBytes(data);
sock.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback((ar) =>
{
sock.EndSend(ar);
}), buffer);
}
}
Project of reference
clients[i].Send("info");
This statement is incorrect. The variable 'i' refers to an instance of the Info class. It is not an integer index into the list. The correct call would be as below.
i.Send("info");
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.
For the last 2 weeks I've been trying to solve a problem, apparently unsuccessfully.
I have a server, using TcpListener class, and a client, using TcpClient class.
My tests were using 2 computers in my internal home network (level 1 router).
Trying to connect, client-computer got "connection request timeout" exception.
What I've already tried:
Running the client and the server on the same computer - worked
Listening on 0.0.0.0 port 6666 (check with netstat to make sure that the server is listening)
Disable both client and server firewalls
Trying to connect my remote application with telnet (to reject "client code bug" possibilty)
Installing telnet on both computers, and connecting successfully.
Still my server does not seem to get any connection. Client computer is Windows XP and server computer is Windows 7.
Please help, I'm really stuck on this one.
Edit - Server code ->
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Utils
{
public class ClientConnection
{
public ClientConnection(IPHostEntry hostName, TcpClient client)
{
HostName = hostName;
Client = client;
}
public IPHostEntry HostName { get; set; }
public TcpClient Client { get; set; }
}
public class SocketServer : IDisposable
{
private const int DefaultRecieveSize = 1024;
protected const int ReceiveTimeoutBeforeQuiting = 5000;
private readonly Encoding _defaultEncoding = Encoding.ASCII;
private readonly TcpListener _socket;
protected Dictionary<IPAddress, ClientConnection> Clients;
private static object _locker;
private readonly AutoResetEvent _listeningStopEvent;
private readonly Thread _listeningThread;
private readonly AutoResetEvent _acceptNewClientEvent;
public delegate void ClientsListChangedEvent();
public event ClientsListChangedEvent ClientsChangedEvent;
private Timer _refreshClientsTimer;
private static int _updateClientListInterval;
#region Ctors
protected SocketServer(int port)
{
_socket = new TcpListener(IPAddress.Parse("0.0.0.0"),port);
Clients = new Dictionary<IPAddress, ClientConnection>();
_updateClientListInterval = Convert.ToInt32(ConfigurationManager.AppSettings["UpdateClientListInterval"]);
_listeningThread = new Thread(AcceptNewConnections);
_listeningStopEvent = new AutoResetEvent(false);
_acceptNewClientEvent = new AutoResetEvent(false);
_locker = new object();
_socket.Start();
}
#endregion
#region Send methods
protected void SendString(IPAddress client, string data)
{
// implementation here
}
#endregion
#region Receieve methods
protected MemoryStream GetInputDataAsMemoryStream(IPAddress client)
{
// implementation here
}
protected string GetInputDataAsMemoryString(IPAddress client)
{
// implementation here
}
protected string GetInputDataAsMemoryString(IPAddress client, int timeout)
{
// implementation here
}
#endregion
#region Listening methons
public void StopListening()
{
_listeningStopEvent.Set();
// todo : check if works
_refreshClientsTimer.Dispose();
}
public bool StartListening()
{
if (_listeningThread.ThreadState == ThreadState.Unstarted)
{
_listeningThread.Start();
// start refreshing clients list too. update clients list every 10 seconds
_refreshClientsTimer = new Timer(UpdateList, null, 0, _updateClientListInterval * 1000);
return true;
}
return false;
}
private void AddClientCallback(IAsyncResult ar)
{
// get all needed data before entering lock
if (_socket != null)
{
TcpClient client;
try
{
client = _socket.EndAcceptTcpClient(ar);
}
catch (ObjectDisposedException)
{
// probebly server has been closed. exit
return;
}
var clientEndPoint = client.Client.RemoteEndPoint;
var ip = SocketFunctions.ExtractIpFromEndPoint(clientEndPoint);
IPHostEntry hostName;
try
{
hostName = SocketFunctions.ExtractHostnameFromEndPoint(clientEndPoint);
}
catch(SocketException)
{
// error while parsing from ip to host name. Put default
hostName = new IPHostEntry();
}
// check if given ip already connected
if (!Clients.ContainsKey(ip))
{
lock (_locker)
{
Clients.Add(ip, new ClientConnection(hostName, client));
}
// notify that a new clients has been added
ClientsChangedEvent.Invoke();
}
}
// a new client has accepted
_acceptNewClientEvent.Set();
}
private void AcceptNewConnections(object obj)
{
var shouldStop = false;
while (!shouldStop)
{
_socket.BeginAcceptTcpClient(AddClientCallback, null);
AutoResetEvent[] events = { _listeningStopEvent, _acceptNewClientEvent };
var result = WaitHandle.WaitAny(events);
if (result == 0)
{
shouldStop = true;
}
}
}
#endregion
public List<KeyValuePair<string, string>> GetConnectedClients()
{
var connectedClients = new List<KeyValuePair<string, string>>();
lock (_locker)
{
foreach (var client in Clients)
{
// get only connected clients. Don`t remove - next Timer round will do it anyway and I don`t want to lock _clients
if (client.Value.Client.Connected)
{
connectedClients.Add(new KeyValuePair<string, string>(client.Key.ToString(), client.Value.HostName.HostName));
}
}
}
return connectedClients;
}
private static void CheckConnectionStatus(TcpClient client)
{
if (client.Client.Poll(0, SelectMode.SelectRead))
{
var checkConn = new byte[1];
checkConn[0] = 0;
try
{
if (client.Client.Receive(checkConn, SocketFlags.Peek) == 0)
{
throw new IOException();
}
}
catch (SocketException)
{
// for me, both exceptions mean that there was a conncetion error. Throw IOException for this case too
throw new IOException();
}
}
}
public void UpdateList(object obj)
{
// iterate each client and check its state. Remove if not connected
var tempClients = new Dictionary<IPAddress, ClientConnection>(Clients);
var changedOccurred = false;
lock (_locker)
{
foreach (var client in Clients)
{
try
{
CheckConnectionStatus(client.Value.Client);
}
catch (IOException)
{
// client is not connected anymore
tempClients.Remove(client.Key);
changedOccurred = true;
}
}
}
if (changedOccurred)
{
lock (_locker)
{
Clients = tempClients;
}
// notify that a new clients has been added
ClientsChangedEvent.Invoke();
}
}
#region IDisposble
public void Dispose()
{
if (_socket != null)
{
StopListening();
_socket.Stop();
}
}
#endregion
}
}
I need to implement a TCP client application. The client and the server send messages to each other. I want to make this program scalable enough to handle connections to multiple servers at the same time. It seems like asynchronous sockets is the way to go for this. I'm new to C# so I'm pretty sure I don't know what I'm doing here. I wrote some classes and a simple console program to get started with. Eventually, I want to create a Windows Forms application but I want to start small and simple first. The Client class runs in its own thread. Is this all thread-safe and correctly done? It's a lot of code and I tried to cut out some fat.
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace FastEyeClient
{
class Program
{
static void Main(string[] args)
{
Client client = new Client();
client.ConnectEvent += new ConnectEventHandler(OnConnect);
client.SetLiveStatusEvent += new SetLiveStatusEventHandler(OnSetLiveStatus);
client.Connect("hostname", 1987);
Thread.Sleep(1000);
client.SetLiveStatus("hostname", true);
}
private static void OnConnect(object sender, ConnectEventArgs e)
{
Console.WriteLine(e.Message);
}
private static void OnSetLiveStatus(object sender, SetLiveStatusEventArgs e)
{
Console.WriteLine(e.Message);
}
}
}
Client.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace FastEyeClient
{
public delegate void ConnectEventHandler(object sender, ConnectEventArgs e);
public delegate void SetLiveStatusEventHandler(object sender, SetLiveStatusEventArgs e);
public class Client : IDisposable
{
public event ConnectEventHandler ConnectEvent;
public event SetLiveStatusEventHandler SetLiveStatusEvent;
ServerManager m_Manager;
EventWaitHandle m_WaitHandle;
readonly object m_Locker;
Queue<Event> m_Tasks;
Thread m_Thread;
public Client()
{
m_Manager = new ServerManager(this);
m_WaitHandle = new AutoResetEvent(false);
m_Locker = new object();
m_Tasks = new Queue<Event>();
m_Thread = new Thread(Run);
m_Thread.Start();
}
public void EnqueueTask(Event task)
{
lock (m_Locker)
{
m_Tasks.Enqueue(task);
}
m_WaitHandle.Set();
}
public void Dispose()
{
EnqueueTask(null);
m_Thread.Join();
m_WaitHandle.Close();
}
private void Run()
{
while (true)
{
Event task = null;
lock (m_Locker)
{
if (m_Tasks.Count > 0)
{
task = m_Tasks.Dequeue();
if (task == null)
{
return;
}
}
}
if (task != null)
{
task.DoTask(m_Manager);
}
else
{
m_WaitHandle.WaitOne();
}
}
}
public void Connect(string hostname, int port)
{
EnqueueTask(new ConnectEvent(hostname, port));
}
public void SetLiveStatus(string hostname, bool status)
{
EnqueueTask(new SetLiveEvent(hostname, status));
}
public void OnConnect(bool isConnected, string message)
{
if (ConnectEvent != null)
{
ConnectEvent(this, new ConnectEventArgs(isConnected, message));
}
}
public void OnSetLiveStatus(string hostname, string message)
{
if (SetLiveStatusEvent != null)
{
SetLiveStatusEvent(this, new SetLiveStatusEventArgs(hostname, message));
}
}
}
}
Server.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace FastEyeClient
{
public class Server
{
private ServerManager m_Manager;
private string m_Hostname;
private bool m_IsLive;
private class StateObject
{
public Socket AsyncSocket = null;
public const int BufferSize = 1024;
public byte[] Buffer = new byte[BufferSize];
public StringBuilder Builder = new StringBuilder();
}
public Server(ServerManager manager, Socket socket)
{
try
{
m_Manager = manager;
IPEndPoint endPoint = (IPEndPoint)socket.RemoteEndPoint;
IPAddress ipAddress = endPoint.Address;
IPHostEntry hostEntry = Dns.GetHostEntry(ipAddress);
Hostname = hostEntry.HostName;
IsLive = false;
StateObject state = new StateObject();
state.AsyncSocket = socket;
socket.BeginReceive(state.Buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
}
catch (Exception)
{
}
}
public string Hostname
{
get
{
return m_Hostname;
}
set
{
m_Hostname = value;
}
}
public bool IsLive
{
get
{
return m_IsLive;
}
set
{
m_IsLive = value;
}
}
private void ReceiveCallback(IAsyncResult result)
{
try
{
StateObject state = (StateObject)result.AsyncState;
Socket socket = state.AsyncSocket;
int read = socket.EndReceive(result);
if (read > 0)
{
state.Builder.Append(Encoding.ASCII.GetString(state.Buffer, 0, read));
if (state.Builder.Length > 1)
{
string messages = state.Builder.ToString();
ParseMessages(messages);
}
}
StateObject newState = new StateObject();
newState.AsyncSocket = socket;
socket.BeginReceive(newState.Buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), newState);
}
catch (Exception)
{
}
}
private void ParseMessages(string messages)
{
string[] messagesArray = messages.Split('\n');
foreach (string message in messagesArray)
{
string[] tokens = message.Split(',');
if (tokens[0].Contains("#"))
{
ParseServerMessage(tokens);
}
}
}
private void ParseServerMessage(string[] tokens)
{
tokens[0].Remove(0, 1);
if (tokens[0] == "4")
{
bool status;
if (tokens[1] == "0")
{
status = false;
m_Manager.SetLiveStatus(m_Hostname, status);
}
else if (tokens[1] == "1")
{
status = true;
m_Manager.SetLiveStatus(m_Hostname, status);
}
}
}
}
}
ServerManager.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace FastEyeClient
{
public class ServerManager
{
private Client m_Client;
private Dictionary<string, Server> m_Servers;
private object m_Locker;
public ServerManager(Client client)
{
m_Client = client;
m_Servers = new Dictionary<string, Server>();
m_Locker = new object();
}
public void AddServer(string hostname, int port)
{
try
{
IPAddress[] IPs = Dns.GetHostAddresses(hostname);
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.BeginConnect(IPs, port, new AsyncCallback(ConnectCallback), socket);
}
catch (Exception)
{
bool isConnected = false;
string message = "Could not connect to server.";
m_Client.OnConnect(isConnected, message);
}
}
private void ConnectCallback(IAsyncResult ar)
{
bool isConnected;
string message;
try
{
Socket socket = (Socket)ar.AsyncState;
socket.EndConnect(ar);
IPEndPoint endPoint = (IPEndPoint)socket.RemoteEndPoint;
IPAddress ipAddress = endPoint.Address;
IPHostEntry hostEntry = Dns.GetHostEntry(ipAddress);
string hostname = hostEntry.HostName;
lock (m_Servers)
{
if (m_Servers.ContainsKey(hostname))
{
isConnected = false;
message = "Client is already connected to server";
}
else
{
m_Servers.Add(hostname, new Server(this, socket));
isConnected = true;
message = "Successfully connected.";
}
}
m_Client.OnConnect(isConnected, message);
}
catch (Exception)
{
isConnected = false;
message = "Could not connect to server.";
m_Client.OnConnect(isConnected, message);
}
}
public void SetLiveStatus(string hostname, bool newStatus)
{
string message;
lock (m_Locker)
{
if (m_Servers.ContainsKey(hostname))
{
if (m_Servers[hostname].IsLive == newStatus)
{
message = "Server is already set to this status.";
}
else
{
m_Servers[hostname].IsLive = newStatus;
message = "Successfully set new status.";
}
}
else
{
message = "Server not found.";
}
}
m_Client.OnSetLiveStatus(hostname, message);
}
}
}
Does it Run?
Does it throw an exception(s)?
Pitfall in trying to run server code in multiple threads:
AVOID attempting to manipulate, read or write a socket in different threads. Have one thread accept connections from the server socket and spawn a thread to handle transactions. If you get too many threads going on at once, you're going to have have 1 thread handle several sockets.
No it is not thread safe.
A subscriber can have unsubscribe before between the check and the invocation:
if (ConnectEvent != null)
{
ConnectEvent(this, new ConnectEventArgs(isConnected, message));
}
Define the event as:
public event ConnectEventHandler ConnectEvent = delegate{};
and remove the event to get thread safety.
I would reduce the run loop to:
private void Run()
{
while (true)
{
m_WaitHandle.WaitOne();
Event task = null;
lock (m_Locker)
{
if (m_Tasks.Count == 0)
{
m_WaitHandle.Reset();
continue;
}
task = m_Tasks.Dequeue();
}
task.DoTask(m_Manager);
}
}
The loop will continue to run until the event is reset.
Make sure that no null items are inserted into the queue instead of checking for null.
You could simplify the producer-consumer pattern in the Client class by using BlockingCollection instead of a combination of an AutoResetEvent and plain old Queue.
The EnqueueTask method would look like:
public void EnqueueTask(Event task)
{
m_Queue.Add(task);
}
The Run method would look like:
public void Run()
{
while (true)
{
Event task = m_Queue.Take();
if (task == null)
{
return;
}
task.DoTask();
}
}