2016-03-26 2 views
1

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

В клиенте я пишу псевдоним и подключаюсь к серверу. Сервер получает имя от клиента. Я добавляю всех клиентов, которые подключаются к серверу в списке словарей с именем (строка) и (TcpClient) Socket. После этого я хочу отправить каждому клиенту список клиентов.

При отладке на сервере сокеты отображаются с Ошибка DualMode, EnableBroadcast. В клиенте, когда я должен получить список, он останавливается и ничего не делает.

Сервер

namespace MyServer 
{ 
    class MyServer 
    { 
     public Dictionary<string, TcpClient> clientList = new Dictionary<string, TcpClient>(); 
    TcpListener server = null; 
    NetworkStream stream = null; 
    StreamReader streamReader = null; 
    StreamWriter streamWriter = null; 
    TcpClient clientSocket; 

    String messageReceived; 
    int number_clients = 0; 
    public MyServer(TcpClient clientSocket_connect) 
    { 
     stream = clientSocket_connect.GetStream(); 
     streamReader = new StreamReader(stream); 
     streamWriter = new StreamWriter(stream); 

     receiveMessage(clientSocket_connect); // receive messages 
     } 
     public MyServer() 
     { 
      Thread thread = new Thread(new ThreadStart(run)); 
      thread.Start(); 
     }   
     public void receiveMessage(TcpClient client_Socket) 
     { 
      messageReceived = streamReader.ReadLine();    

      if (messageReceived.Substring(messageReceived.Length - 4) == "user") 
      { 
       String name = messageReceived.Substring(0, messageReceived.Length - 4); 
       bool found = false; 
       foreach (var namefound in clientList.Keys) 
       { 
        if (namefound == name) 
        { 
         found = true; 
         streamWriter.WriteLine("The user already exists"); 
         streamWriter.Flush(); 
        } 
       } 
       if (!found) 
       { 
        //show who's connected 
        Console.WriteLine(name + " is online"); 
        number_clients++; 
        clientList.Add(name, client_Socket); 

        //send to client clientlist 
        String send = null; 
        foreach (var key in clientList.Keys) 
        { 
         send += key + "."; 
        } 
        foreach (var value in clientList.Values) 
        { 
         TcpClient trimitereclientSocket = value; 
         if (trimitereclientSocket != null) 
         { 
          NetworkStream networkStream = trimitereclientSocket.GetStream(); 
          StreamWriter networkWriter = new StreamWriter(networkStream); 
          networkWriter.WriteLine(send + "connected"); 
          networkWriter.Flush(); 
         } 
        } 
       } 
      } 

     } 
     void run() 
     { 
      IPAddress ipAddress = IPAddress.Parse("127.0.0.1"); 
      server = new TcpListener(ipAddress, 8000); 
      server.Start(); 
      Console.WriteLine("Server started!"); 
      while (true) 
      { 
       clientSocket = server.AcceptTcpClient();     
       new MyServer(clientSocket); 
      } 
     } 
    } 
static void Main(string[] args) 
     { 
      MyServer server = new MyServer(); 
     } 
} 

Client

namespace MyClient 
    { 
     class MyClient 
     { 
      List<string> clientList = new List<string>(); 

      TcpClient client = null; 
      NetworkStream stream = nul 

l; 
     StreamReader streamReader = null; 
     StreamWriter streamWriter = null; 

     bool connected; 
     String received_message; 
     public MyClient() 
     { 
      client = new TcpClient("127.0.0.1", 8000); 
      stream = client.GetStream(); 
      streamReader = new StreamReader(stream); 
      streamWriter = new StreamWriter(stream);  
     } 
     public void sendClientName(String name) 
     { 
      streamWriter.WriteLine(Convert.ToString(name)); 
      streamWriter.Flush(); 
     } 
     public List<ClientName> receiveClientList() 
     { 
      List<ClientName> val = new List<ClientName>();   
       string name = Convert.ToString(streamReader.ReadLine()); 
       if (name.Substring(0, name.Length - 9) == "connected") 
       { 
        ClientName client = new ClientName(); 
        client.Nume = name; 
        val.Add(client); 
       }   
      return val; 
     } 

    } 
} 

Клиент Форма

public partial class Form1 : Form 
{ 
    MyClient client = new MyClient(); 
    public Form1() 
    { 
     InitializeComponent(); 
     Thread receiveClients = new Thread(new ThreadStart(getMessages)); 
    } 

    private void btnConnect_Click(object sender, EventArgs e) 
    { 
     client.sendClientName(txtNickname.Text + "user"); 
    } 
    public void getMessages() 
    { 
     while (true) 
     { 
      lbClientsConnected.Items.Add(client.receiveClientList()); 
     } 
    } 
} 
+0

В настоящее время, поскольку ваш сервер, похоже, использует только один «StreamReader», сообщения будут получены только от последнего клиента. Вы должны создать пользовательский класс, который добавляется в словарь, который содержит команды «TcpClient», «StreamReader»/«... Writer» и «NetworkStream» клиента. –

+0

Основная проблема заключается в том, что для каждого клиента создается новый экземпляр объекта сервера. Все клиентские сообщения могут быть получены, но a) только один клиент за раз, b) после того, как была получена первая строка текста одного клиента, этот клиент никогда не получает от него, и c) каждый клиент не знает ни о каком другом. Я ничего не вижу в коде, который явно объясняет сообщенную ошибку (которая в любом случае описывается только смутно).Я согласен с предыдущим комментарием, что вам нужно исправить код, чтобы один объект отслеживал всех клиентов; текущий дизайн принципиально нарушен. –

ответ

2

Я не смог воспроизвести любую ошибку при выполнении кода. Я не знаю, что вы подразумеваете под «Сокеты появляются с ошибкой DualMode, EnableBroadcast». Тем не менее, есть ряд исправляемых проблем с кодом, в том числе некоторые, которые относятся непосредственно к вашей проблеме, что «когда я должен получить список, который он останавливает и ничего не делает».

Возможно, самая большая проблема с кодом заключается в том, что вы просто никогда не запускаете поток получателя клиента. Вам нужно вызвать метод Start() на Thread объекта после того, как он был создан:

public Form1() 
{ 
    InitializeComponent(); 
    Thread receiveClients = new Thread(new ThreadStart(getMessages)); 

    // The receiving thread needs to be started 
    receiveClients.Start(); 
} 

Теперь, даже при том, что фиксировано, у вас есть несколько других проблем. Следующая большая проблема заключается в том, что вы неправильно обрабатываете полученный текст. В вашем коде, где вы должны искать текст "connected" в конце строки, вместо этого вы извлекаете другую часть текста (со списком имен клиентов).

Ваш метод receiveClientList() должен вместо этого выглядеть следующим образом:

private const string _kconnected = "connected"; 

public List<string> receiveClientList() 
{ 
    List<string> val = new List<string>(); 
    string name = Convert.ToString(streamReader.ReadLine()); 

    // Need to check the *end* of the string for "connected" text, 
    // not the beginning. 
    if (name.EndsWith(_kconnected)) 
    { 
     name = name.Substring(0, name.Length - _kconnected.Length); 
     val.Add(name); 
    } 
    return val; 
} 

(Вы не разделял ClientName класс в вашем вопросе, а на самом деле пример не нужен, просто string значение достаточно для Цель этого упражнения. Кроме того, я ввел const string с именем _kconnected, чтобы гарантировать, что строковый литерал используется правильно в каждом месте, в котором он необходим, а также для упрощения использования.)

Но даже с этими двумя проблемами исправлено , у вас все еще есть пара i n код Form, где вы фактически обрабатываете возвращаемое значение метода получения. Во-первых, вы передаете объект List<T>, который возвращается из метода получения, к методу ListBox.Items.Add(), что приведет к тому, что ListBox будет отображать имя типа для объекта, а не его элементы.

Во-вторых, потому, что код выполняется в потоке, отличном от потока пользовательского интерфейса, которому принадлежит ListBox объект, вы должны обернуть вызов в вызове Control.Invoke(). В противном случае вы получите исключение операции поперечного потока.

Закрепление эти два вопроса, вы получите это:

public void getMessages() 
{ 
    while (true) 
    { 
     // Need to receive the data, and the call Invoke() to add the 
     // data to the ListBox. Also, if adding a List<T>, need to call 
     // AddRange(), not Add(). 
     string[] receivedClientList = client.receiveClientList().ToArray(); 

     Invoke((MethodInvoker)(() => listBox1.Items.AddRange(receivedClientList))); 
    } 

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

  1. Самая большая проблема в том, что, когда вы принимаете соединение на сервере, вы создаете весь объект новый сервер для обработки этого соединения , Существует несколько причин, по которым это не очень хорошая идея, но главное заключается в том, что в остальной части кода концептуально предполагается, что один серверный объект отслеживает всех клиентов, но каждое соединение приведет к его собственной коллекции клиентских объектов, каждая коллекция имеет только один член (то есть тот клиент).

    Обратите внимание, что как только вы исправили эту проблему, у вас будет несколько потоков, обращающихся к единой структуре данных словаря. Вам необходимо научиться использовать инструкцию lock для обеспечения безопасного совместного использования словаря по нескольким потокам.

  2. Другой серьезной проблемой является то, что вместо того, чтобы использовать streamWriter созданный при первом принял соединение, создать совершенно новый StreamWriter объект (на который ссылается в локальной переменной с именем networkWriter) для записи в сокет. В этом очень простом примере он работает отлично, но между буферизацией и отсутствием безопасности потока, этот неправильно разработанный код может иметь серьезные проблемы с повреждением данных.

  3. Меньше проблем, но стоит исправить, заключается в том, что ваш код сервера полностью не может воспользоваться тем фактом, что вы храните клиентов в словаре, а также что у .NET есть полезные вспомогательные функции для выполнения таких действий, как объединив кучу строк. Я хотел бы написать receiveMessage() метод что-то ваш сервер больше, как это:

private const string _kuser = "user"; 

public void receiveMessage(TcpClient client_Socket) 
{ 
    messageReceived = streamReader.ReadLine(); 

    if (messageReceived.EndsWith(_kuser)) 
    { 
     String name = messageReceived.Substring(0, messageReceived.Length - _kuser.Length); 

     if (clientList.ContainsKey(name)) 
     { 
      streamWriter.WriteLine("The user already exists"); 
      streamWriter.Flush(); 
      return; 
     } 

     //show who's connected 
     Console.WriteLine(name + " is online"); 
     number_clients++; 
     clientList.Add(name, client_Socket); 

     string send = string.Join(".", clientList.Keys); 

     foreach (var value in clientList.Values.Where(v => v != null)) 
     { 
      // NOTE: I didn't change the problem noted in #2 above, instead just 
      // left the code the way you had it, mostly. Of course, in a fully 
      // corrected version of the code, your dictionary would contain not 
      // just `TcpClient` objects, but some client-specific object specific 
      // to your server implementation, in which the `TcpClient` object 
      // is found, along with the `StreamReader` and `StreamWriter` objects 
      // you've already created for that connection (and any other per-client 
      // data that you need to track). Then you would write to that already- 
      // existing `StreamWriter` object instead of creating a new one each 
      // time here. 

      NetworkStream networkStream = value.GetStream(); 
      StreamWriter networkWriter = new StreamWriter(networkStream); 
      networkWriter.WriteLine(send + "connected"); 
      networkWriter.Flush(); 
     } 
    } 
} 

выше, не является исчерпывающим и любыми средствами. Честно говоря, вам, вероятно, стоит больше времени смотреть на существующие примеры сетевого кода, например. на MSDN и Stack Overflow, а также на уроки на веб-сайтах, блогах или в книгах. Даже когда вы пишете сервер по принципу «один поток за соединение», как вы, кажется, пытаетесь сделать здесь, есть много мелких деталей, которые вам действительно нужны, чтобы их правильно исправить, и которых у вас пока нет.

Но я надеюсь, что вышеуказанного достаточно, чтобы прервать ваше текущее препятствие и перейти к следующей большой проблеме (задачам). :)

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