I am writing a small multi-threaded network server. All classical stuff: it listens for incoming connections, accepts them and then serves them in different threads. Also, this server sometimes will have to restart, and to do so it must a) stop listening, b) kick out all connected clients, c) adjust some settings/wait, d) resume listening.
Well, I pretty much don't know a thing about developing multi-threaded programs, so I am looking for help. Here's what I came to (core stuff only):
class Server
{
class MyClient
{
Server server;
TcpClient client;
bool hasToFinish = false;
public MyClient(Server server, TcpClient client)
{
this.server = server;
this.client = client;
}
public void Go()
{
while (!hasToFinish)
{
// do all cool stuff
}
CleanUp();
}
private void CleanUp()
{
// finish all stuff
client.Close();
server.myClients.Remove(this);
}
public void Finish()
{
hasToFinish = true;
}
}
bool running = false;
TcpListener listener;
HashSet<MyClient> myClients = new HashSet<MyClient>();
public void Start()
{
if (running)
return;
myClients.Clear();
listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 1234);
listener.Start();
listener.BeginAcceptTcpClient(AcceptClient, this);
running = true;
}
public void Stop()
{
if (!running)
return;
listener.Stop();
foreach (MyClient client in myClients)
{
client.Finish();
}
myClients.Clear();
running = false;
}
public void AcceptClient(IAsyncResult ar)
{
MyClient client = new MyClient(this, ((TcpListener)ar.AsyncState).EndAcceptTcpClient(ar));
myClients.Add(client);
client.Go();
}
}
It's absolutely unsatisfactory. There is no sychronizing (I just don't know where to put it!), and calling Server.Stop() doesn't make MyClient-s to stop immediately. How do I fix these problems?
The code looks quite clean, we can make it thread-safe with simple modifications.
There are three parts of the problem, the "client", the "server" and the client-server interaction.
Client first, the Go() method is invoked by one thread (let's call it A) and the Finish() method is invoke by another thread (B). When thread B modify hasToFinish field, thread A may not see the modification immediately because the variable may be cached in the CPU cache. We can fix it by making hasToFinish field "volatile", which force thread B to publish the variable change to thread A when update.
Now the server class. I recommend you to synchronise three methods on the "Server" instance like the example below. It makes sure Start and Stop are called sequentially and the variables they changes are published across threads.
The client-server interaction need to be addressed as well. In your code, Client remove its reference from the Server but the server clear all clients references when Finish() any way. It looks redundant to me. If we can remove the part of code in client, we have nothing to worry about. If you choose to keep the logic in the client rather in the server for what ever reason, create a public method call RemoveClient(Client client) in the Server class and synchronise it against the Server instance. Then let the client to invoke this method instead of manipulating the HashSet directly.
I hope this solve your problem.
public void Start()
{
lock(this)
{
if (running)
return;
myClients.Clear();
listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 1234);
listener.Start();
listener.BeginAcceptTcpClient(AcceptClient, this);
running = true;
}
}
public void Stop()
{
lock(this)
{
if (!running)
return;
listener.Stop();
foreach (MyClient client in myClients)
{
client.Finish();
}
myClients.Clear();
running = false;
}
}
public void AcceptClient(IAsyncResult ar)
{
lock(this)
{
MyClient client = new MyClient(this, ((TcpListener)ar.AsyncState).EndAcceptTcpClient(ar));
myClients.Add(client);
client.Go();
}
}
Related
I am working on creating a custom TCP/IP client/server application, and have run into a snag when attempting to stop the server. Originally, my code used one TcpListener to listen on a designated port, and my (simplified for convenience) code to start and stop the server went as follows:
private bool state;
private TcpListener listener;
private CancellationTokenSource tokenSource;
private Dictionary<string, ConnectedClient> clients;
private List<Task> clientTasks;
private ConnectedClient command_client;
public async Task RunServer() {
if (!state) {
state = true;
tokenSource = new CancellationTokenSource();
listener = new TcpListener(IPAddress.Any, 55001);
listener.Start();
while (true) {
try {
TcpClient socketClient = await listener.AcceptTcpClientAsync();
ConnectedClient client = new ConnectedClient(socketClient);
clients.Add(client.id, client);
client.task = ProcessClientAsync(client, tokenSource.Token);
clientTasks.Add(client.task);
}
catch (ObjectDisposedException) {
//Server stopped by user
//exit while
break;
}
}
/* Server has been stopped; close all connections */
CloseAll();
}
else {
/* Stop the server */
tokenSource.Cancel();
listener.Stop();
/* Clean up of currently connected clients is handled in CloseAll, handled upon ObjectDisposedException above */
}
}
ConnectedClient is a class I wrote to hold some info about individual clients for convenience and has a function that handles what happens when data is received. I realize I left some things out to streamline, but this code does exactly what I want it to do: the server waits for connections, creates a ConnectedClient object to handle connections received, and goes back to waiting. When this function is called when the server is already listening, the listener is stopped, which causes the listener to throw an exception, which breaks the loop and closes all connections.
The snag occurs when I attempted to create a server that listens on two different ports, which need to be treated differently.
Here is my code for (attempting) that:
private bool state;
private Dictionary<string, ConnectedClient> clients;
private TcpListener command_listener;
private TcpListener query_listener;
private CancellationTokenSource tokenSource;
private List<Task> clientTasks;
public async Task RunServer() {
if (!state) {
state = true;
tokenSource = new CancellationTokenSource();
command_listener = new TcpListener(IPAddress.Any, 55001);
command_listener.Start();
query_listener = new TcpListener(IPAddress.Any, 55002);
query_listener.Start();
Task prevCommand = null;
Task prevQuery = null;
while (true) {
try {
if (prevCommand == null || prevCommand.IsCompleted) {
prevCommand = waitForConnections(command_listener);
}
if (prevQuery == null || prevQuery.IsCompleted) {
prevQuery = waitForConnections(query_listener);
}
await Task.WhenAny(prevCommand, prevQuery);
}
catch (ObjectDisposedException) {
//Server stopped by user
//exit while
break;
}
}
/* Server has been stopped; close all connections */
CloseAll();
}
else {
/* Stop the server */
tokenSource.Cancel();
command_listener.Stop();
query_listener.Stop();
/* Clean up of currently connected clients is handled in CloseAll, handled upon ObjectDisposedException above */
}
}
The purpose of waitForConnections is to handle connection requests so that waiting for a connection on one port doesn't block connections on the other, and also to ensure that only one connection can be made on port 55001.
public async Task waitForConnections(TcpListener listener) {
TcpClient socketClient = await listener.AcceptTcpClientAsync();
if (((IPEndPoint)listener.LocalEndpoint).Port == 55001 ) {
if (command_client == null) {
command_client = new ConnectedClient(socketClient, onClientUpdate, onResend);
clients.Add(command_client.id, command_client);
command_client.task = ProcessClientAsync(command_client, tokenSource.Token);
clientTasks.Add(command_client.task);
}
else {
//only one client allowed on this port, reject the connection
socketClient.Close();
}
}
else {
ConnectedClient client = new ConnectedClient(socketClient, onClientUpdate, onResend);
clients.Add(client.id, client);
client.task = ProcessClientAsync(client, tokenSource.Token);
clientTasks.Add(client.task);
}
}
With this, I am able to connect clients on the two ports without blocking, but calling this function again and stopping the listeners does not seem to cause a ObjectDisposedException to be thrown as expected, which causes the whole program to hang and not do anything. I suspect this is being caused by some irresponsible use of asynchronous functions, but how can I fix it?
Because the await listener.AcceptTcpClientAsync() call is inside an async task instead of directly inside the loop, the exception that occurs when the listener is stopped causes the task to return with a status of "Faulted". Because the exception isn't caught, the loop continues, and a faulted task is considered a completed task, so it goes right back to trying to listen for connections despite the stopped listener (which in turn most likely causes the task to fault again).
Could have fixed this by checking for faulted tasks instead of catching the exception, but I instead chose to set a flag when I try to stop the server that breaks the loop and allows the program to close connections as intended.
so I have created a server which needs to listen for clients to connect and in the constructor of my server class I have:
class Server
{
private Thread listenerThread;
private TcpListener listener;
public Server()
{
this.listener = new TcpListener(IPAddress.Any, 5000);
listenerThread = new Thread(ListenMethod) { IsBackground = true };
listenerThread.Start();
}
private void ListenMethod()
{
listener.Start();
while (true)
{
TcpClient client = listener.AcceptTcpClient();
}
}
}
but when I created the instance of server in my main like so:
class Program
{
static void Main(string[] args)
{
Server s = new Server();
}
}
The server should stay open cause of the ListenMethod running and the while loop, yet the program seems to complete the constructor and closes the program.
Any ideas?
Thanks :)
Background threads will not keep a console application alive, as is their purpose. The Server class instance is also going out of scope because it isn't used by anything; in a Release build of this application it might not even be compiled into the application and optimised away. You need to do something like this:
static void Main(string[] args)
{
Server server = new Server();
Console.Write("The server is running. Press any key to stop the server.");
Console.ReadKey();
server.Stop();
}
It doesn't look like you are instantiating your listner instance, before you call Start(), so the code is probably crashing on listener.Start() due to a NullReferenceException
I would expect something like this:
private void ListenMethod()
{
listener = new TcpListener();
listener.Start();
while (true)
{
TcpClient client = listener.AcceptTcpClient();
}
}
I have one main form class and another class. In the second class, I have a thread loop:
public void StartListening()
{
listening = true;
listener = new Thread(new ThreadStart(DoListening));
listener.Start();
}
// Listening for udp datagrams thread loop
/*=====================================================*/
private void DoListening()
{
while (listening)
{
IPEndPoint remoteIPEndPoint = new IPEndPoint(IPAddress.Any, port);
byte[] content = udpClient.Receive(ref remoteIPEndPoint);
if (content.Length > 0)
{
string message = Encoding.ASCII.GetString(content);
delegMessage(message);
}
}
}
// Stop listening for udp datagrams
/*=====================================================*/
public void StopListening()
{
lock (locker)
{
listening = false;
}
}
In main form class, I start this listening in class constructor
udp.StartListening();
And than, in this main form class, I have key hook event, too. In this event, I wan to stop thread running in the second class.
private void hook_KeyPressed(int key)
{
if (key == (int)Keys.LMenu)
altPressed = true;
if (key == (int)Keys.F4 && altPressed == true)
udp.StopListening();
}
Unfortunetely, the thread is still running.
Do you have some ideas about this??
Thank you very much.
Your thread is blocking at the byte[] content = udpClient.Receive(ref remoteIPEndPoint); line. The Receive method blocks until something is received.
You should use the asynchronous version (BeginReceive) instead.
Also, another flaw in your code - you check for the stopping condition without any synchronization. Here:
private void DoListening()
{
while (listening){ //this condition could stuck forever in 'false'
}
Actually, without a memory barrier, there is no guarantee, that a thread, that is running DoListening will ever see the change to listening var from other thread. You should at least use locking here (which provides memory barrier)
As #igelineau pointed out - your code is blocking on the receive call. If you don;t want to go down the async route (which I'd recommend) just send something to the udp port in your stop listening method.
I have a TCP server that continually monitors for new incoming clients asynchronously and adds them to a client list:
public class TcpServer
{
public List<TcpClient> ClientsList = new List<TcpClient>();
protected TcpListener Server = new TcpListener(IPAddress.Any, 3000);
private _isMonitoring = false;
public TcpServer()
{
Server.Start();
Server.StartMonitoring();
}
public void StartMonitoring()
{
_isMonitoring = true;
Server.BeginAcceptTcpClient(HandleNewClient, null);
}
public void StopMonitoring()
{
_isMonitoring = false;
}
protected void HandleNewClient(IAsyncResult result)
{
if (_isMonitoring)
{
var client = Server.EndAcceptTcpClient(result);
ClientsList.Add(client);
StartMonitoring(); // repeats the monitoring
}
}
}
However, I'm having two issues with this code.
The first is the StartMonitoring() call in HandleNewClient(). Without it, the server will accept only one incoming connection and ignore any additional connections. What I'd like to do is have it continually monitor for new clients, but something rubs me wrong about the way I'm doing it now. Is there a better way of doing this?
The second is the _isMonitoring flag. I don't know how else to stop the async callback from activating and stop it from looping. Any advice on how this can be improved? I'd like to stick to using asynchronous callbacks and avoid having to manually create new threads running methods that have while (true) loops in them.
Basically, your StartMonitoring function, needs to loop - you'll only accept a single client at a time, and then you'd typically pass the request off to a worker thread, and then resume accepting new connections. The way its written, as you've stated, it will only accept a single client.
You'll want to expand on this to suit your startup/shutdown/terminate needs, but basically, what you're looking for is StartMonitoring to be more like this:
public void StartMonitoring()
{
_isMonitoring = true;
while (_isMonitoring)
Server.BeginAcceptTcpClient(HandleNewClient, null);
}
Note that if _isMonitoring is going to be set by another thread, you'd better mark it as volatile, or you'll likely never terminate the loops.
We're evaluating db4o (an OO-DBMS from http://www.db4o.com). We've put together a performance test for client/server mode, where we spin up a server, then hammer it with several clients at once. It seems like the server can only process one client's query at a time.
Have we missed a configuration switch somewhere that allows for this scenario? Server implementation is below. The client connects, queries (read-only), and disconnects per operation, and operations run one immediately after the other from several worker threads in the client process. We see same behaviour if we spin up one client process with one worker each against the same server.
Any suggestions?
Edit: We've now discovered, and tried out, the Lazy and Snapshot QueryModes, and although this alleviates the blocking server problem (partially), we still see significant concurrency problems when our clients (we run 40 concurrent test-clients that wait 1-300ms before issuing a random operation-request) hammer on the server. There appear to be exceptions emanating from the LINQ provider and from the IO internals :-(
public class Db4oServer : ServerConfiguration, IMessageRecipient
{
private bool stop;
#region IMessageRecipient Members
public void ProcessMessage(IMessageContext con, object message)
{
if (message is StopDb4oServer)
{
Close();
}
}
#endregion
public static void Main(string[] args)
{
//Ingestion.Do();
new Db4oServer().Run(true, true);
}
public void Run(bool shouldIndex, bool shouldOptimizeNativeQueries)
{
lock (this)
{
var cfg = Db4oFactory.NewConfiguration();
if (shouldIndex)
{
cfg.ObjectClass(typeof (Sequence))
.ObjectField("<ChannelID>k__BackingField")
.Indexed(true);
cfg.ObjectClass(typeof (Vlip))
.ObjectField("<ChannelID>k__BackingField")
.Indexed(true);
}
if (shouldOptimizeNativeQueries)
{
cfg.OptimizeNativeQueries(true);
}
var server = Db4oFactory.OpenServer(cfg, FILE, PORT);
server.GrantAccess("0", "kieran");
server.GrantAccess("1", "kieran");
server.GrantAccess("2", "kieran");
server.GrantAccess("3", "kieran");
//server.Ext().Configure().ClientServer().SingleThreadedClient(false);
server.Ext().Configure().MessageLevel(3);
server.Ext().Configure().Diagnostic().AddListener(new DiagnosticToConsole());
server.Ext().Configure().ClientServer().SetMessageRecipient(this);
try
{
if (!stop)
{
Monitor.Wait(this);
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
server.Close();
}
}
public void Close()
{
lock (this)
{
stop = true;
Monitor.PulseAll(this);
}
}
}
Well, there is something on the db40 servers that doesn't allow too many clients on at a time since it is too much for some to handle. You also locked it which did nothing to help in this case.