2014-02-06 4 views
3

Может кто-то, пожалуйста, помогите мне с этим ... Я изо всех сил боролся весь день.Проблема с буфером асинхронного чата

Так что я пытаюсь узнать сокеты Async, что-то, что давало мне проблемы.

Вопрос в основном способ, которым я обновляя ListBox с людьми, которые присоединились имена чата в:

Chat Window

В основном то, что я делаю того, чтобы каждый клиент посылает «!! addlist [nickname] ", когда они присоединяются к серверу.

Это не идеальный вариант, поскольку он не проверяет наличие дубликатов и т. Д., Но теперь я просто хочу знать, почему это не сработает. Всякий раз, когда кто-то добавляет имя они не видели раньше, они будут также отправить «!! addlist [ник]»

Таким образом, каждый раз, когда кто-то присоединяется, списки должны быть обновлены для всех. Проблема заключается в том, что все клиенты начинают общаться в одно и то же время, и это мешает буферу.

Я пробовал использовать отдельный буфер для каждого клиента, так что это не проблема. Я пробовал использовать lock(), но это, похоже, не работает.

По существу, происходит то, что буферы, кажется, усекаются; где есть данные от двух разных людей в том же буфере.

Пожалуйста, просто скажи мне, что я делаю неправильно с буферами или на стороне клиента:

Обратите внимание, что сокет асинхронной использует Отправить вместо BeginSend. Я пробовал оба метода, и они сталкиваются с одной проблемой ... так что, вероятно, это клиентская сторона?

public partial class Login : Form 
{ 
    private ChatWindow cw; 
    private Socket serverSocket; 
    private List<Socket> socketList; 
    private byte[] buffer; 
    private bool isHost; 
    private bool isClosing; 

    public void startListening() 
    { 
     try 
     { 
      this.isHost = true;               //We're hosting this server 
      cw.callingForm = this;              //Give ChatForm the login form (this) [that acts as the server] 
      cw.Show();                 //Show ChatForm 
      cw.isHost = true;               //Tell ChatForm it is the host (for display purposes) 
      this.Hide();                //And hide the login form 
      serverSocket.Bind(new IPEndPoint(IPAddress.Any, int.Parse(portBox.Text))); //Bind to our local address 
      serverSocket.Listen(1);              //And start listening 
      serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);   //When someone connects, begin the async callback 
      cw.connectTo("127.0.0.1", int.Parse(portBox.Text), nicknameBox.Text);  //And have ChatForm connect to the server 
     } 
     catch (Exception) { /*MessageBox.Show("Error:\n\n" + e.ToString());*/ }   //Let us know if we ran into any errors 
    } 

    public void AcceptCallback(IAsyncResult AR) 
    { 
     try 
     { 
      Socket s = serverSocket.EndAccept(AR);                //When someone connects, accept the new socket 
      socketList.Add(s);                     //Add it to our list of clients 
      s.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), s); //Begin the async receive method using our buffer 
      serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);         //And start accepting new connections 
     } 
     catch (Exception) {} 
    } 

    public void ReceiveCallback(IAsyncResult AR)    //When a message from a client is received 
    { 
     try 
     { 
      if (isClosing) 
       return; 

      Socket s = (Socket)AR.AsyncState;     //Get the socket from our IAsyncResult 

      int received = s.EndReceive(AR);     //Read the number of bytes received (*need to add locking code here*) 
      byte[] dbuf = new byte[received];     //Create a temporary buffer to store just what was received so we don't have extra data 

      Array.Copy(buffer, dbuf, received);     //Copy the received data from our buffer to our temporary buffer 

      foreach (Socket client in socketList)    //For each client that is connected 
      { 
       try 
       { 
        if (client != (Socket)AR.AsyncState)  //If this isn't the same client that just sent a message (*client handles displaying these*) 
         client.Send(dbuf);      //Send the message to the client 
       } 
       catch (Exception) { } 
      }             //Start receiving new data again 
      s.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), s); 
     } 
     catch (Exception) { /*cw.output("\n\nError:\n\n" + e.ToString());*/ } 
    } 

    public void SendCallback(IAsyncResult AR) 
    { 
     try 
     { 
      Socket s = (Socket)AR.AsyncState; 
      s.EndSend(AR); 
     } 
     catch (Exception) { /*cw.output("\n\nError:\n\n" + e.ToString());*/ } 
    } 

Вот на стороне клиента:

public void getData() 
    { 
     try 
     { 
      byte[] buf = new byte[1024]; 
      string message = ""; 
      while(isConnected) 
      { 
       Array.Clear(buf, 0, buf.Length); 
       message = ""; 
       clientSocket.Receive(buf, buf.Length, SocketFlags.None); 
       message = Encoding.ASCII.GetString(buf); 
       if (message.StartsWith("!!addlist")) 
       { 
        message = message.Replace("!!addlist", ""); 
        string userNick = message.Trim(); 
        if (!namesBox.Items.Contains(userNick)) 
        { 
         addNick(userNick.Trim()); 
        } 
        continue; 
       } 
       else if (message.StartsWith("!!removelist")) 
       { 
        message = message.Replace("!!removelist", ""); 
        string userNick = message.Trim(); 
        removeNick(userNick); 
        output("Someone left the room: " + userNick); 
        continue; 
       } 
       else if (!namesBox.Items.Contains(message.Substring(0, message.IndexOf(":")))) 
       { 
        addNick(message.Substring(0, message.IndexOf(":")).Trim()); //So they at least get added when they send a message 
       } 
       output(message); 
      } 
     } 
     catch (Exception) 
     { 
      output("\n\nConnection to the server lost."); 
      isConnected = false; 
     } 
    } 

Вот моя запаздывающая функция addNick, которая, кажется, чтобы исправить некоторые вещи?

public void addNick(string n) 
    { 
     if (n.Contains(" ")) //No Spaces... such a headache 
      return; 
     if (n.Contains(":")) 
      return; 
     bool shouldAdd = true; 
     n = n.Trim(); 
     for (int x = namesBox.Items.Count - 1; x >= 0; --x) 
      if (namesBox.Items[x].ToString().Contains(n)) 
       shouldAdd = false; 
     if (shouldAdd) 
     { 
      namesBox.Items.Add(n); 
      output("Someone new joined the room: " + n); 
      sendRaw("!!addlist " + nickName); 
     } 
    } 

Я думаю, что проблема в том, что некоторые из пакетов пропуска?

Возможно, есть слишком много кода в клиенте после получения, прежде чем он снова будет вызван?

Должен ли я создать отдельный поток для каждого сообщения, чтобы прием выполнялся постоянно? (Dumb)

Должен ли мой клиент использовать Async, он принимает и отправляет?

У меня есть ощущение, что ответ^

Со всеми проверок, которые я делаю, мне удалось очистить дубликат вопрос имени ... но я регулярно получать сообщения с пробелами и частичными сообщениями от других клиентов кажется.

Not Receiving All The Names

+2

Is getData работает в своей собственной теме? Если это может быть вашей проблемой. Исключение CrossThread, добавляющее материал в ваш список. – Matt

+0

интересный; Я не думал об этом. Да. Должен ли я заблокировать его во время редактирования? У меня регулярно есть несколько потоков, которые записываются в окно сообщения без использования делегатов, хотя и, похоже, он никогда не добавляет лишний текст случайно. – user1274820

+1

@ user1274820 В этом красота неопределенного поведения. Вместо блокировки элемента управления вы должны выполнить доступ в потоке пользовательского интерфейса с помощью 'Control.Invoke()'. – itsme86

ответ

1

Хорошо так, после того, как возиться с этим в течение длительного времени, у меня есть относительно стабильным.

Для начала я добавил следующее состояние объекта:

public class StateObject 
{ 
    public Socket workSocket = null; 
    public const int BufferSize = 1024; 
    public byte[] buffer = new byte[BufferSize]; 
    public StringBuilder sb = new StringBuilder(); 
    public bool newConnection = true; 
} 

Это позволяет легко отслеживать каждое соединение и дает каждому соединение своему собственному буфер.

Вторая вещь, которую я сделал, - это поиск новой строки в каждом сообщении. Я не искал этого в исходном коде, и я считаю, что это корень большинства проблем.

Я также взял на себя ответственность за управление именем пользователя на сервере; то, что я должен был сделать с самого начала, очевидно.

Вот текущий код сервера:

Этот код никоим образом не идеально, и я постоянно находить новые ошибки, тем больше я стараюсь, чтобы разбить его. Я собираюсь продолжать возиться с ним на некоторое время, но на данный момент он работает прилично.

public partial class Login : Form 
{ 
    private ChatWindow cw; 
    private Socket serverSocket; 
    private List<Socket> socketList; 
    private byte[] buffer; 
    private bool isHost; 
    private bool isClosing; 
    private ListBox usernames; 

    public Login() 
    { 
     InitializeComponent(); 
    } 

    private void Login_Load(object sender, EventArgs e) 
    { 
     ipLabel.Text = getLocalIP(); 
     cw = new ChatWindow(); 
     serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 
     socketList = new List<Socket>(); 
     buffer = new byte[1024]; 
     isClosing = false; 
     usernames = new ListBox(); 
    } 

    public string getLocalIP() 
    { 
     return Dns.GetHostEntry(Dns.GetHostName()).AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToString(); 
    } 

    private void joinButton_Click(object sender, EventArgs e) 
    { 
     try 
     { 
      int tryPort = 0; 
      this.isHost = false; 
      cw.callingForm = this; 
      if (ipBox.Text == "" || portBox.Text == "" || nicknameBox.Text == "" || !int.TryParse(portBox.Text.ToString(), out tryPort)) 
      { 
       MessageBox.Show("You must enter an IP Address, Port, and Nickname to connect to a server.", "Missing Info"); 
       return; 
      } 
      this.Hide(); 
      cw.Show(); 
      cw.connectTo(ipBox.Text, int.Parse(portBox.Text), nicknameBox.Text); 
     } 
     catch(Exception otheree) { 
      MessageBox.Show("Error:\n\n" + otheree.ToString(),"Error connecting..."); 
      cw.Hide(); 
      this.Show(); 
     } 
    } 

    private void hostButton_Click(object sender, EventArgs e) 
    { 
     int tryPort = 0; 
     if (portBox.Text == "" || nicknameBox.Text == "" || !int.TryParse(portBox.Text.ToString(), out tryPort)) { 
      MessageBox.Show("You must enter a Port and Nickname to host a server.", "Missing Info"); 
      return; 
     } 
     startListening(); 
    } 

    public void startListening() 
    { 
     try 
     { 
      this.isHost = true;               //We're hosting this server 
      cw.callingForm = this;              //Give ChatForm the login form (this) [that acts as the server] 
      cw.Show();                 //Show ChatForm 
      cw.isHost = true;               //Tell ChatForm it is the host (for display purposes) 
      this.Hide();                //And hide the login form 
      serverSocket.Bind(new IPEndPoint(IPAddress.Any, int.Parse(portBox.Text))); //Bind to our local address 
      serverSocket.Listen(1);              //And start listening 
      serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);   //When someone connects, begin the async callback 
      cw.connectTo("127.0.0.1", int.Parse(portBox.Text), nicknameBox.Text);  //And have ChatForm connect to the server 
     } 
     catch (Exception) {} 
    } 

    public void AcceptCallback(IAsyncResult AR) 
    { 
     try 
     { 
      StateObject state = new StateObject(); 
      state.workSocket = serverSocket.EndAccept(AR); 
      socketList.Add(state.workSocket); 
      state.workSocket.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state); 
      serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null); 
     } 
     catch (Exception) {} 
    } 

    public void ReceiveCallback(IAsyncResult AR) 
    { 
     try 
     { 
      if (isClosing) 
       return; 

      StateObject state = (StateObject)AR.AsyncState; 
      Socket s = state.workSocket; 
      String content = ""; 
      int received = s.EndReceive(AR); 

      if(received > 0) 
       state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, received)); 

      content = state.sb.ToString(); 

      if (content.IndexOf(Environment.NewLine) > -1) //If we've received the end of the message 
      { 

       if (content.StartsWith("!!addlist") && state.newConnection) 
       { 
        state.newConnection = false; 
        content = content.Replace("!!addlist", ""); 
        string userNick = content.Trim(); 
        if (isHost && userNick.StartsWith("!")) 
         userNick = userNick.Replace("!", ""); 
        userNick = userNick.Trim(); 
        if (userNick.StartsWith("!") || userNick == string.Empty || usernames.Items.IndexOf(userNick) > -1) 
        { 
         //Invalid Username :c get dropped 
         s.Send(Encoding.ASCII.GetBytes("Invalid Username/In Use - Sorry :(")); 
         s.Shutdown(SocketShutdown.Both); 
         s.Disconnect(false); 
         s.Close(); 
         socketList.Remove(s); 
         return; 
        } 
        usernames.Items.Add(userNick); 
        foreach (string name in usernames.Items) 
        { 
         if (name.IndexOf(userNick) < 0) 
         { 
          s.Send(Encoding.ASCII.GetBytes("!!addlist " + name + "\r\n")); 
          Thread.Sleep(10); //such a hack... ugh it annoys me that this works 
         } 
        } 
        foreach (Socket client in socketList) 
        { 
         try 
         { 
          if (client != s) 
           client.Send(Encoding.ASCII.GetBytes("!!addlist " + userNick + "\r\n")); 
         } 
         catch (Exception) { } 
        } 
       } 
       else if (content.StartsWith("!!removelist") && !state.newConnection) 
       { 
        content = content.Replace("!!removelist", ""); 
        string userNick = content.Trim(); 
        usernames.Items.Remove(userNick); 
        foreach (Socket client in socketList) 
        { 
         try 
         { 
          if (client != s) 
           client.Send(Encoding.ASCII.GetBytes("!!removelist " + userNick + "\r\n")); 
         } 
         catch (Exception) { } 
        } 
       } 
       else if (state.newConnection) //if they don't give their name and try to send data, just drop. 
       { 
        s.Shutdown(SocketShutdown.Both); 
        s.Disconnect(false); 
        s.Close(); 
        socketList.Remove(s); 
        return; 
       } 
       else 
       { 
        foreach (Socket client in socketList) 
        { 
         try 
         { 
          if (client != s) 
           client.Send(System.Text.Encoding.ASCII.GetBytes(content)); 
         } 
         catch (Exception) { } 
        } 
       } 
      } 
      Array.Clear(state.buffer, 0, StateObject.BufferSize); 
      state.sb.Clear(); 
      s.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state); 
     } 
     catch (Exception) { 
      socketList.Remove(((StateObject)AR.AsyncState).workSocket); 
     } 
    } 
    public void SendCallback(IAsyncResult AR) 
    { 
     try 
     { 
      StateObject state = (StateObject)AR.AsyncState; 
      state.workSocket.EndSend(AR); 
     } 
     catch (Exception) {} 
    } 
    private void Login_FormClosed(object sender, FormClosedEventArgs e) 
    { 
     try 
     { 
      this.isClosing = true; 
      if (this.isHost) 
      { 
       foreach (Socket c in socketList) 
       { 
        if (c.Connected) 
        { 
         c.Close(); 
        } 
       } 
       serverSocket.Shutdown(SocketShutdown.Both); 
       serverSocket.Close(); 
       serverSocket = null; 
       serverSocket.Dispose(); 
      } 
      socketList.Clear(); 
     } 
     catch (Exception) { } 
     finally 
     { 
      Application.Exit(); 
     } 
    } 
} 
public class StateObject 
{ 
    public Socket workSocket = null; 
    public const int BufferSize = 1024; 
    public byte[] buffer = new byte[BufferSize]; 
    public StringBuilder sb = new StringBuilder(); 
    public bool newConnection = true; 
} 

Код клиента (работа в процессе):

public partial class ChatWindow : Form 
{ 
    private Socket clientSocket; 
    private Thread chatThread; 
    private string ipAddress; 
    private int port; 
    private bool isConnected; 
    private string nickName; 
    public bool isHost; 
    public Login callingForm; 

    private static object conLock = new object(); 

    public ChatWindow() 
    { 
     InitializeComponent(); 
     isConnected = false; 
     isHost = false; 
    } 

    public string getIP() { 
     return Dns.GetHostEntry(Dns.GetHostName()).AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToString(); 
    } 

    public void displayError(string err) 
    { 
     output(Environment.NewLine + Environment.NewLine + err + Environment.NewLine); 
    } 

    public void op(string s) 
    { 
     try 
     { 
      lock (conLock) 
      { 
       chatBox.Text += s; 
      } 
     } 
     catch (Exception) { } 
    } 

    public void connectTo(string ip, int p, string n) { 
     try 
     { 
      this.Text = "Trying to connect to " + ip + ":" + p + "..."; 
      this.ipAddress = ip; 
      this.port = p; 
      this.nickName = n; 

      clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 

      if (!isHost) 
      { 
       op("Connecting to " + ipAddress + ":" + port + "..."); 
      } 
      else 
      { 
       output("Listening on " + getIP() + ":" + port + "..."); 
      } 

      clientSocket.Connect(ipAddress, port); 

      isConnected = true; 

      if (!isHost) 
      { 
       this.Text = "Connected to " + ipAddress + ":" + port + " - Nickname: " + nickName; 
       output("Connected!"); 
      } 
      else 
      { 
       this.Text = "Hosting on " + getIP() + ":" + port + " - Nickname: " + nickName; 
      } 

      chatThread = new Thread(new ThreadStart(getData)); 
      chatThread.Start(); 

      nickName = nickName.Replace(" ", ""); 
      nickName = nickName.Replace(":", ""); 
      if(nickName.StartsWith("!")) 
       nickName = nickName.Replace("!", ""); 
      namesBox.Items.Add(nickName); 

      sendRaw("!!addlist " + nickName); 
     } 
     catch (ThreadAbortException) 
     { 
      //do nothing; probably closing chat window 
     } 
     catch (Exception e) 
     { 
      if (!isConnected) 
      { 
       this.Hide(); 
       callingForm.Show(); 
       clearText(); 
       MessageBox.Show("Error:\n\n" + e.ToString(), "Error connecting to remote host"); 
      } 
     } 
    } 

    public void removeNick(string n) 
    { 
     if (namesBox.Items.Count <= 0) 
      return; 
     for (int x = namesBox.Items.Count - 1; x >= 0; --x) 
      if (namesBox.Items[x].ToString().Contains(n)) 
       namesBox.Items.RemoveAt(x); 
    } 

    public void clearText() 
    { 
     try 
     { 
      lock (conLock) 
      { 
       chatBox.Text = ""; 
      } 
     } 
     catch (Exception) { } 
    } 

    public void addNick(string n) 
    { 
     if (n.Contains(" ")) //No Spaces... such a headache 
      return; 
     if (n.Contains(":")) 
      return; 
     bool shouldAdd = true; 
     n = n.Trim(); 
     for (int x = namesBox.Items.Count - 1; x >= 0; --x) 
      if (namesBox.Items[x].ToString().Contains(n)) 
       shouldAdd = false; 
     if (shouldAdd) 
     { 
      namesBox.Items.Add(n); 
      output("Someone new joined the room: " + n); 
      //sendRaw("!!addlist " + nickName); 
     } 
    } 

    public void addNickNoMessage(string n) 
    { 
     if (n.Contains(" ")) //No Spaces... such a headache 
      return; 
     if (n.Contains(":")) 
      return; 
     bool shouldAdd = true; 
     n = n.Trim(); 
     for (int x = namesBox.Items.Count - 1; x >= 0; --x) 
      if (namesBox.Items[x].ToString().Contains(n)) 
       shouldAdd = false; 
     if (shouldAdd) 
     { 
      namesBox.Items.Add(n); 
      //sendRaw("!!addlist " + nickName); 
     } 
    } 

    public void getData() 
    { 
     try 
     { 
      byte[] buf = new byte[1024]; 
      string message = ""; 
      while(isConnected) 
      { 
       Array.Clear(buf, 0, buf.Length); 
       message = ""; 
       int gotData = clientSocket.Receive(buf, buf.Length, SocketFlags.None); 
       if (gotData == 0) 
        throw new Exception("I swear, this was working before but isn't anymore..."); 
       message = Encoding.ASCII.GetString(buf); 
       if (message.StartsWith("!!addlist")) 
       { 
        message = message.Replace("!!addlist", ""); 
        string userNick = message.Trim(); 
        if(!namesBox.Items.Contains(userNick)) 
        { 
         addNick(userNick); 
        } 
        continue; 
       } 
       else if (message.StartsWith("!!removelist")) 
       { 
        message = message.Replace("!!removelist", ""); 
        string userNick = message.Trim(); 
        removeNick(userNick); 
        output("Someone left the room: " + userNick); 
        continue; 
       } 
       output(message); 
      } 
     } 
     catch (Exception) 
     { 
      isConnected = false; 
      output(Environment.NewLine + "Connection to the server lost."); 
     } 
    } 

    public void output(string s) 
    { 
     try 
     { 
      lock (conLock) 
      { 
       chatBox.Text += s + Environment.NewLine; 
      } 
     } 
     catch (Exception) { } 
    } 

    private void ChatWindow_FormClosed(object sender, FormClosedEventArgs e) 
    { 
     try 
     { 
      if(isConnected) 
       sendRaw("!!removelist " + nickName); 
      isConnected = false; 
      clientSocket.Shutdown(SocketShutdown.Receive); 
      if (chatThread.IsAlive) 
       chatThread.Abort(); 
      callingForm.Close(); 
     } 
     catch (Exception) { } 
    } 

    private void sendButton_Click(object sender, EventArgs e) 
    { 
     if(isConnected) 
      send(sendBox.Text); 
    } 

    private void sendBox_KeyUp(object sender, KeyEventArgs e) 
    { 
     if (e.KeyCode == Keys.Enter) 
     { 
      if (isConnected) 
      { 
       if (sendBox.Text != "") 
       { 
        send(sendBox.Text); 
        sendBox.SelectAll(); 
        e.SuppressKeyPress = true; 
        e.Handled = true; 
       } 
      } 
     } 
    } 

    private void send(string t) { 
     try 
     { 
      byte[] data = System.Text.Encoding.ASCII.GetBytes(nickName + ": " + t + "\r\n"); 
      clientSocket.Send(data); 
      output(nickName + ": " + t); 
     } 
     catch (Exception e) 
     { 
      displayError(e.ToString()); 
     } 
    } 

    private void sendRaw(string t) 
    { 
     try 
     { 
      byte[] data = System.Text.Encoding.ASCII.GetBytes(t + "\r\n"); 
      clientSocket.Send(data); 
     } 
     catch (Exception e) 
     { 
      displayError(e.ToString()); 
     } 
    } 

    private void chatBox_TextChanged(object sender, EventArgs e) 
    { 
     chatBox.SelectionStart = chatBox.Text.Length; 
     chatBox.ScrollToCaret(); 
    } 

    private void sendBox_KeyDown(object sender, KeyEventArgs e) 
    { 
     if (e.KeyCode == Keys.Enter) 
      e.SuppressKeyPress = true; 
    } 
} 

Для:

Добавить вызываемые программой, больше делегатов, сделать некоторые QA и выяснить, что разрушает его , Кроме того, я считаю, что по-прежнему существует вероятность потери пакетов из-за того, что функции списка клиентов находятся в цикле чтения. Я считаю, что именно поэтому «дрянная русалка», использующая Thread.Sleep (10) в обратном вызове сервера для популяции имен, является проблемой.

Возможно, было бы лучше либо передать команду в другой поток, продолжая читать, либо клиент сообщит серверу, что он готов к другому имени.

В противном случае при обновлении имени могут быть потери данных.

Другое дело, что, как сказано в комментариях выше, делегаты должны использовать при обновлении объектов пользовательского интерфейса (chatbox и listbox). Я написал код для них, но в конечном итоге удалил его, потому что не было заметных изменений, и я хотел сохранить его простым.

Я все еще использую блокировку объекта при выводе текста в чат, но там нет заметной разницы.

Код должен быть добавлен как, так как не использование делегатов является потенциально проблематичным, но я буквально поймал окно чата в бесконечном цикле обновлений без проблем.

Я попытался сломать его с помощью telnet и был успешным, поэтому я добавил свойство NewConnection в StateObject, чтобы гарантировать, что каждый клиент может только отправить «!! addlist» один раз.

Есть, конечно, другие способы злоупотребления сервером, создавая клиент, который многократно присоединяется и уходит, так что, в конечном счете, я, вероятно, в конечном итоге передам обработчик !! removeelist на сервер, а не оставил его клиенту ,

Смежные вопросы