Программа чата TCP / IP отправляет мне смешанные сообщения - PullRequest
0 голосов
/ 12 января 2011

Я создаю простую программу чата tcp / ip, и мне трудно отправлять сообщения отдельно.Если, например, я отправляю два сообщения, и оба имеют содержимое, превышающее размер буфера, который может содержать 20 символов, отправляется 20 символов первого сообщения, затем отправляется 20 символов следующего сообщения, а затем оставшаяся часть первого сообщения, а затемостальная часть последнего сообщения.Поэтому, когда я анализирую и объединяю строки, я получаю два сообщения: начало первого сообщения и начало второго, конец первого и конец второго соответственно.Я хочу знать, как отправить сообщение, и ставить в очередь следующие сообщения, пока первое сообщение не будет отправлено.В качестве примечания я использую асинхронные вызовы методов, а не потоки.

Мой код:

Клиент:

protected virtual void Write(string mymessage)
{


               var buffer = Encoding.ASCII.GetBytes(mymessage);
               MySocket.BeginSend(buffer, 0, buffer.Length, 
SocketFlags.None,EndSendCallBack, null);

               if (OnWrite != null)
               {
                   var target = (Control) OnWrite.Target;
                   if (target != null && target.InvokeRequired)
                   {
                       target.Invoke(OnWrite, this, new EventArgs());
                   }
                   else
                   {
                       OnWrite(this, new EventArgs());
                   }
               }
        }

и два смешанных вызова:

 client.SendMessage("CONNECT",Parser<Connect>.TextSerialize(connect));
  client.SendMessage("BUDDYLIST","");

и, наконец, функция чтения (Iиспользуйте номер в начале каждого сообщения, чтобы узнать, когда оно заканчивается в квадратных скобках):

private void Read(IAsyncResult ar)
        {

            string content;
            var buffer = ((byte[]) ar.AsyncState);
            int len = MySocket.EndReceive(ar);
            if (len > 0)
            {
                string cleanMessage;
                content = Encoding.ASCII.GetString(buffer, 0, len);
                if (MessageLength == 0)
                {
                    MessageLength = int.Parse(content.Substring(1, content.IndexOf("]", 1) - 1));
                    cleanMessage = content.Replace(content.Substring(0, content.IndexOf("]", 0) + 1), "");
                }
                else
                    cleanMessage = content;

                if(cleanMessage.Length <1)
                {
                    if(MySocket.Connected)
                        MySocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Read), buffer);
                    return;
                }

                MessageLength = MessageLength > cleanMessage.Length? MessageLength - cleanMessage.Length : 0;
                amessage += cleanMessage;

                if(MessageLength == 0)
                {
                    if (OnRead != null)
                    {
                        var e = new CommandEventArgs(this, amessage);
                        Control target = null;
                        if (OnRead.Target is Control)
                            target = (Control)OnRead.Target;
                        if (target != null && target.InvokeRequired)
                            target.Invoke(OnRead, this, e);
                        else
                            OnRead(this, e);
                    }
                    amessage = String.Empty;
                }
                MySocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Read), buffer);
                    return;
            }
        }

Ответы [ 2 ]

1 голос
/ 12 января 2011

Если вы отправите сообщение целиком за один вызов, оно придет в последовательности. Вам нужен разделитель для разделения ваших сообщений. На принимающей стороне необходимо буферизовать сообщения в буфер. Если вы не отправляете большие объемы данных, вы можете позволить себе немного небрежно, но легко кодировать для обработки входящих сообщений:

// Note: Untested pseudocode
buffer += stringDataRead;
while (buffer.Contains("\r\n")) {
  // You may want to compensate for \r\n by doing a -2 here and +2 on next line
  line = buffer.Substring(0, buffer.IndexOf("\r\n"));
  buffer = buffer.Remove(0, line.Length);

  DoSomehingWithThisLine(line);
}

Никакой магии не требуется. :)

1 голос
/ 12 января 2011

TCP do not гарантирует, что вы получите сообщение целиком за одно чтение. Поэтому вы должны быть в состоянии определить, где начинается сообщение и где оно заканчивается.

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

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

Обновление

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

Другими словами, это не будет работать:

_socket.BeginSend(Encoding.ASCII.GetBytes("[" + message.Length + "]"))
_socket.BeginSend(Encoding.ASCII.GetBytes(message));

Вы должны отправить все одним отправлением.

Обновление 2

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

Если вы отправите:

[11]Hello world
[5]Something else

Они могут прибыть как:

[11]Hello World[5]Some
thing else

Другими словами, часть второго сообщения может прибыть в первое BeginRead. Вы должны всегда создавать буфер со всем полученным содержимым (используйте StringBuilder) и удалять обработанные части.

Псевдокод:

method OnRead
    myStringBuilder.Append(receivedData);
    do while gotPacket(myStringBuilder)
        var length = myStringBuilder.Get(2, 5)
        if (myStringBuilder.Length < 7 + length)
           break;

        var myMessage = myStringBuilder.Get(7, length);
        handle(myMessage);

        myStringBuilder.Remove(0, 7+length);
    loop
end method

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

...