TL; DR: У меня есть сервер C#, который управляет одним клиентом. Каков наилучший способ заставить его обрабатывать несколько клиентов?C# сокеты с несколькими клиентами (подход с несколькими потоками)
Полное объяснение:
Я реализует C# серверное приложение сокета, который нужно обрабатывать несколько клиентов одновременно.
Моя логика такова:
- Множественные клиенты подключаются к серверу, передачи и приема данных (в данном случае простой текст - думать об этом как посланник).
- У сервера есть отдельный поток для каждого клиента, поток, который отправляет ответы.
- Каждый из этих потоков для каждого клиента порождает дополнительный поток, который прослушивает данные соответствующего клиента (я не знаю, хорошо ли этот подход, я могу рассмотреть некоторые асинхронные чтения, но я не понимаю, как они работают как я они никогда не использовали их).
- Сервер различает каждый клиент, используя список, мы можем предположить, что список содержит IP-адрес каждого клиента и уникальный идентификатор для легкого распознавания (предположим, имя пользователя).
код у меня до сих пор управляет только один клиент:
public partial class Form1 : Form
{
TcpListener listener;
Socket clientSock;
Thread listenThread, receiveThread;
string sendMsg, recvMsg;
byte[] sendMsgRaw, recvMsgRaw;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
// Handler for onExit.
AppDomain.CurrentDomain.ProcessExit += new EventHandler(onExit);
}
private void listenButton_Click(object sender, EventArgs e) {
// Launch thread that listens for clients attempting to connect.
listener = new TcpListener(8888); // I know this method is deprecated, will fix.
listener.Start();
listenThread = new Thread(new ThreadStart(listenForClients));
listenThread.Start();
}
private void sendButton_Click(object sender, EventArgs e) {
// Send message to client.
sendMsg = sendTextbox.Text;
sendMsgRaw = Encoding.ASCII.GetBytes(sendMsg + "\r\n");
clientSock.Send(sendMsgRaw);
}
private void listenForClients() {
// Listen for connection attempts.
clientSock = listener.AcceptSocket();
disableButton(listenButton);
receiveThread = new Thread(new ThreadStart(readFromClient));
receiveThread.Start();
}
private void readFromClient() {
// Read response from client.
while(true) {
recvMsg = "";
recvMsgRaw = new byte[1024];
clientSock.Receive(recvMsgRaw);
recvMsg = Encoding.ASCII.GetString(recvMsgRaw);
appendRecvTextbox(recvMsg);
}
}
private void appendRecvTextbox(string message) {
// Append client's reply to a textbox.
if(InvokeRequired) {
this.Invoke(new Action<string>(appendRecvTextbox), new object[] { message });
return;
}
recvTextbox.AppendText(message + Environment.NewLine);
}
private void disableButton(Button btn) {
// Disable a button.
if(InvokeRequired) {
this.Invoke(new Action<Button>(disableButton), new object[] { btn });
return;
}
btn.Enabled = false;
}
private void onExit(object sender, EventArgs e) {
// Executed on program exit.
clientSock.Close();
}
}
Вот скриншот формы я имею в виду: http://prntscr.com/azkaed
В основном, когда элемент ListBox (клиент) является выбранная, область полученных данных изменится в соответствии с тем, что было получено этим клиентом.
И когда выбран тот же элемент списка (клиент) и отправляется сообщение, он будет отправлен этому клиенту и только этому клиенту (без многоадресной рассылки или чего-то подобного).
FYI позже, я реализую передачу файлов, следуя тем же принципам.
Я не прошу вас, смиренные люди, запросить у меня мое заявление, это непродуктивно, и я ничего не узнаю. Я хочу понять и обработать, как все это работает. Очевидно, что образцы кода, которые помогут мне понять, очень приветствуются. Потенциальные проблемы и вопросы:
- Является ли моя логика ошибочной? Если да, то что вы предлагаете? Мне кажется, что я использую слишком много потоков, и я не буду разбираться в потоке, который отправляется клиенту, и потоке, который читается от клиента.
- Когда я создаю поток, что он наследует? Будет ли он наследовать КОПИЮ сокета (например, слушателя) или самого исходного сокета?
- Если я создаю столько потоков, как я могу различать основной поток для клиента (который отправляет данные) и связанный с ним поток чтения?
- Должен ли я пойти на UDP? Протокол без установления соединения имеет смысл в моей голове, я могу просто отправлять и получать на/из IP без активного подключения, которое мне нужно позаботиться. Но я беспокоюсь о ненадежности UDP. Тем более, что после этого я намерен реализовать передачи файлов.
- После того как я закодировал этот сервер, я заметил, что я не привязывал сокет к порту, но он счастливо общается с клиентом cpp.Я помню из своих сетевых классов еще в колледже, которые нам нужно связать, если мы используем протокол TCP. Я делаю вывод, что привязка не нужна. Почему это?
Итак, главная проблема: в текущем состоянии приложение обрабатывает один клиент. Как я могу заставить его обрабатывать несколько клиентов? Обратите внимание, что я выбрал многопоточность, потому что у меня будет ограниченное количество клиентов, подключенных одновременно - до 10 клиентов.
Я знаю, что это непростой вопрос, который я задаю, но каждый ответ будет глубоко оценен.
Если это имеет значение, клиенты будут записаны в амальгаме таких языков, как c/cpp, java, python, я даже рассматриваю PHP. Это для единственной цели обучения. Но я сомневаюсь, что это имеет значение. Клиентам гораздо проще программировать, так как существует меньше логики, поэтому я не требую никакой помощи там.
Благодарим вас за любые предложения.