Hi. I am trying to write server side windows forms application that supports multiple clients. What I want is following:
1) I need an infinite while loop to accept new connections. In order to do that I start new thread which executes while loop. I can't execute while loop without new thread because my user interface would be blocked.
2) When new client connects to server I want to dispatch him in another thread, so every user has its own thread.
3) In that thread (one thread per user) I start client-server communication. Client first need to authorize himself with username and password. Server checks database, and if password and username are ok, they continue communication.
4) Server user interface has DataGridView that is updated with every new client that logged with correct username-password pair.
I managed to do this with BackgroundWorker, but I am not sure it is good solution. I will present my soulution, and then I will ask some questions that are bothering me.
Note: I will post just relevant part of code
First in windows forms class I define two event handlers for my background worker inherited class (so I can update form controls), and in Form_Load event I start first background worker:
private void Form1_Load(object sender, EventArgs e)
{
// Doorman is inherited from BackGroundWorker...I will show code later
Doorman doorman = new Doorman();
doorman.ProgressChanged += OnProgresChanged;
doorman.RunWorkerCompleted += OnWorkCompleted;
doorman.RunWorkerAsync();
dataGridView1.DataSource = bList; //blist is BindingList
}
private void OnProgresChanged(object sender, ProgressChangedEventArgs e)
{
List<User> list = new List<User>();
list = e.UserState as List<User>;
// Update BindingList so DataGridView shows all users
lock(locker)
{
bList.Clear();
foreach (User user in list)
{
bList.Add(user);
}
}
}
private void OnWorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// cleanup job here
}
Next I have two classes. Each of them inherits BackgroundWorker. First is called Doorman. Its job is to listen for connections and start new thread (through MyBackgroundWorker class that also inherits BackgroundWorker) for every connected client. Also it keeps track of all connected users.
class Doorman:BackgroundWorker
{
private List<User> userList;
private int numberOfClients;
Object locker;
public Doorman()
{
WorkerReportsProgress = true;
WorkerSupportsCancellation = true;
userList = new List<User>();
numberOfClients = 0;
locker = new object();
}
protected override void OnDoWork(
DoWorkEventArgs e)
{
Socket socket = new
Socket(AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
IPEndPoint localEP = new
IPEndPoint(IPAddress.Any, 50000);
socket.Bind(localEP);
socket.Listen(5);
while (true)
{
//here goes code dealing with cancelation
Socket workerSocket = socket.Accept();
MyBackgroundWorker bgWorker = new
MyBackgroundWorker();
bgWorker.ProgressChanged +=
OnProgressChanged;
bgWorker.RunWorkerCompleted +=
OnWorkCompleted;
bgWorker.RunWorkerAsync(workerSocket);
}
}
private void OnProgressChanged(Object sender, ProgressChangedEventArgs e)
{
lock (locker)
{
if (e.ProgressPercentage == 1)
{
User user = e.UserState as User;
userList.Remove(user);
numberOfClients--;
}
else
{
User user = e.UserState as User;
usertList.Add(user);
numberOfClients++;
}
ReportProgress(1, userList);
}
}
private void OnWorkCompleted(Object sender, RunWorkerCompletedEventArgs e)
{
//cleanup code
}
}
As you can see, I create instance of MyBackgroundWorker inside Doorman code.
MyBackgroundWorker has job to communicate with clients over tcp sockets. If client send correct username and password MyBackgroundWorker calls ReportProgress with User as second parameter and number 1 ad first parameter. I use first parameter as signaling code which is interpreted inside Doorman's OnProgressChanged handler.
Here are some parts of MyBackgroundWorker class:
class MyBackgroundWorker:BackgroundWorker
{
// . . .
protected override void OnDoWork(DoWorkEventArgs e)
{
socket = e.Argument as Socket;
//Here goes communication with client code,
// checking username and password in database,
// responding to client messages in a loop etc.
// If client is logged in with correct username-password,
// ReportProgress(1, user); is called.
// User is litlle helper class that contains
// username, first name, last name, and Socket of that user
}
// . . .
I hope I extracted all relevant parts of code, so you can understand how I "designed" my solution. This is my first multithreading application and I know this is probably not good solution. Thats why I would like to hear from you how would you solve it?
I also have some questions about background worker and above code:
1) Every class that make instance of BackgroundWorker (or inherited class) needs to implement event handlers. Are those handlers always called in thread where BackgroundWorker instance is created? I guess yes, because thats probably why updating windows forms controls is working. If answer is yes, how authors of BackgroundWorker class forced those handlers to execute in specific thread without use of Invoke?
2) In my Doorman class I have private List<User> member. Just to play safe I locked part of code in OnProgressChanged handler where I update that list with new users. Did I have to do this? If that event handler is always called from same thread, I suppose there is no need for lock on private list? Also, I am courious when those handlers are executed? Is there some sort of queue and they are executed one by one as different MyBackgroundWorker instances call ReportProgress()? Because my Doorman.OnDoWork function is actually infinite loop, I dont understand when event handlers are executed?
3) Is it bad idea to use "percentage" argument from ReportProgress in OnProgressChanged handler as some sort of ID for communication between backgroundworker thread and caller? (I used it as signal that tells me if I need to add new user (passed as second argument) in list, or I have to remove it).
Thats everything for now.
Thanks in advance!
p.s. sorry for my bad English, it is not my first language :)