none
Передача сообщения TCPClient RRS feed

  • Вопрос

  • Сервер передает сообщение клиенту длинной 3000 символов. Клиент получает блок байт, перекодирует в строку и выводит на экран. В сообщении все русские буквы и вначале одна английская, буфер чтения 512 байт. Когда принимается сообщение, блок байт обрабатывается и получается, что русская буква делится на 2 байта. 1 байт в следующем блоке байт. При обработке каждые 512 байт теряется буква, заменяясь двумя символами. Как это можно исправить без ответа клиента? Что бы сервер сразу слал все сообщение, а не по 512 байт и ждал ответа клиента. Вариант с записью всех байт в переменную, а потом декодировать ее так же отпадает. Потому что сервер по запросу может слать несколько разных сообщений...
    9 января 2012 г. 19:14

Ответы

  • То, что сервер отправил одно сообщение (500), еще не гарантирует, что клиенту оно придет одним сообщением (т.е. будет вычитано одним вызовом Read). При медленном соединении придет, например, 5 по 100.

    Правильный вариант - добавить в начало сообщения его длину в байтах, и накапливать байты, пока не накопишь ровно N. Делается очень легко - созданием BinaryReader поверх клиентского NetworkStream, и вызовами у него ReadInt32(для длины) и ReadBytes/ReadChars для байт или текста. Последние два метода блокируют выполнение, пока не будет вычитано ровно N байт/символов. Вручную ничего накапливать и перекодировать не придется.

    • Помечено в качестве ответа Siompc 15 января 2012 г. 13:29
    11 января 2012 г. 7:31
  • Как я понял, клиент сейчас делает stream.Read() c буффером на 512 байт, 6 раз. В любом случае, всего 2 решения:

    1. Накапливать автоматом, через BinaryReader. В приложение придет сразу весь текст. Нужно заранее знать длину.

    2. Накапливать вручную. Опять же, нужно заранее знать длину, чтобы знать в какой момент перестать накапливать.

    Вариант "пересылать в ANSI" необходимость накопления убирает. Но добавляет проблем с нерусскими символами. 21-й век все-таки, пора на нормальные кодировки переходить :)

    2 Siompc - если сервер отправит сразу все сообщение, в 3000 символов, то клиент вычитает его как непрерывное сообщение в 3000 символов, хоть все сразу, хоть кусками по 500 байт. Даже если сервер в процессе приема начнет слать что-то еще.

    • Предложено в качестве ответа Abolmasov Dmitry 12 января 2012 г. 6:52
    • Помечено в качестве ответа Siompc 15 января 2012 г. 13:29
    11 января 2012 г. 14:25
  • Вообщем все сделал вручную... не разобрался с BinaryReader'ом. Узнал что асинхронный сервер может поддерживать не более 4000 клиентов. Это из за доступной ОЗУ или с чем это связано? Как можно увеличить число допустимых подключений?

    У BinaryReader только синхронные методы, в вашем случае проще сделать вручную.

    По поводу лимита соединений - посмотрите http://smallvoid.com/article/winnt-tcpip-max-limit.html

    • Помечено в качестве ответа Siompc 15 января 2012 г. 13:28
    15 января 2012 г. 9:08
  • Там в примере начинается асинхронное чтение и в процедуре чтения вызывается еще одна на вторую... Что-то запутался немного.
    У вас точно так же сделано, с комментарием 'И начинаем новое асинхронное чтение. Кстати, блокировка и BeginInvoke при повторном вызове чтения не нужны. Достаточно просто вызова BeginRead.
    • Помечено в качестве ответа Siompc 15 января 2012 г. 13:28
    15 января 2012 г. 9:13

Все ответы

  • Не очень понятен вопрос.

    >Что бы сервер сразу слал все сообщение, а не по 512 байт и ждал ответа клиента.

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

    >Вариант с записью всех байт в переменную, а потом декодировать ее так же отпадает.

    Ну вообще-то это и есть правильное решение.

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

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


    9 января 2012 г. 21:58
    Отвечающий
  • Сейчас сервер работает так, отправляет сообщение до 500 байт, ждет ответа клиента. Клиент обрабатывает, отправляет что принял и шлется вторая часть и т.д. ANSI Не подходит... В сообщении обязательно присутствуют русские символы. Пара идей есть, если что, отпишусь :)
    10 января 2012 г. 19:20
  • То, что сервер отправил одно сообщение (500), еще не гарантирует, что клиенту оно придет одним сообщением (т.е. будет вычитано одним вызовом Read). При медленном соединении придет, например, 5 по 100.

    Правильный вариант - добавить в начало сообщения его длину в байтах, и накапливать байты, пока не накопишь ровно N. Делается очень легко - созданием BinaryReader поверх клиентского NetworkStream, и вызовами у него ReadInt32(для длины) и ReadBytes/ReadChars для байт или текста. Последние два метода блокируют выполнение, пока не будет вычитано ровно N байт/символов. Вручную ничего накапливать и перекодировать не придется.

    • Помечено в качестве ответа Siompc 15 января 2012 г. 13:29
    11 января 2012 г. 7:31
  • PashaPash,

    Как я понял, размер сообщения сервера - 3000. Но он(сервер) просто разбивает его на куски по 500 байт. При этом часть юникода бъется. Отсюда и проблема. Посылка размера блока (или сообщения) не решает проблему. Битый юникод все равно будет.

    >ANSI Не подходит... В сообщении обязательно присутствуют русские символы

    Вообще-то ANSI как раз и поддерживает русский язык. Этот вариант можно использовать, если вы не собираетесь передавать сообщения более чем на двух языках (т.е. только латинский и русский набор символов).

    11 января 2012 г. 13:03
    Отвечающий
  • Как я понял, клиент сейчас делает stream.Read() c буффером на 512 байт, 6 раз. В любом случае, всего 2 решения:

    1. Накапливать автоматом, через BinaryReader. В приложение придет сразу весь текст. Нужно заранее знать длину.

    2. Накапливать вручную. Опять же, нужно заранее знать длину, чтобы знать в какой момент перестать накапливать.

    Вариант "пересылать в ANSI" необходимость накопления убирает. Но добавляет проблем с нерусскими символами. 21-й век все-таки, пора на нормальные кодировки переходить :)

    2 Siompc - если сервер отправит сразу все сообщение, в 3000 символов, то клиент вычитает его как непрерывное сообщение в 3000 символов, хоть все сразу, хоть кусками по 500 байт. Даже если сервер в процессе приема начнет слать что-то еще.

    • Предложено в качестве ответа Abolmasov Dmitry 12 января 2012 г. 6:52
    • Помечено в качестве ответа Siompc 15 января 2012 г. 13:29
    11 января 2012 г. 14:25
  •  

    Всем вам спасибо за помощь :) 

    >При медленном соединении придет, например, 5 по 100.

    Не знал )) Теперь это поможет избежать многих ошибок.

    У меня была идея сделать накопление в массив байт, пока после перекодирования не будет символа chr(0) - разделителя сообщений. Когда он есть, значит пришло целое сообщение, отделяем его и обрабатываем, продолжая накапливать следующие :) А вот по поводу BinaryReader если можно поподробнее :) Клиент принимает следующим образом

     

    Dim BytesRead As Integer
            Dim strMessage As String
    
            Try
                'Заканчиваем асинхронное чтение и считаем кол-во байт
                SyncLock GetDataLock
                    BytesRead = IPlayerCMD.GetStream.EndRead(ar)
                End SyncLock
    
                If BytesRead < 1 Then
                    'Если нет байт для чтения - значит сервер закрыт.  Деактивируем все окна.
                    ServerIsDown("Удаленный хост принудительно разорвал существующее подключение.")
                    Exit Sub
                End If
    
                'Декодируем в текст
                strMessage = Encoding.UTF8.GetString(readBufferCMD, 0, BytesRead)
    
                IDispatcher.BeginInvoke(New Action(Sub() RaiseEvent ProcessCommandsCMD(strMessage))) 'Вызываем обработчик команд
    
                'И начинаем новое асинхронное чтение
                SyncLock GetDataLock
                    IDispatcher.BeginInvoke(New Action(Sub() IPlayerCMD.GetStream.BeginRead(readBufferCMD, 0, READ_BUFFER_SIZE_CMD, AddressOf DoReadCMD, Nothing)))
                End SyncLock
            Catch e As Exception
                ServerIsDown("Исключение при асинхронном чтении. Удаленный хост принудительно разорвал существующее подключение.") 'Сервер "убит", отключаем клиент
            End Try
    

    Необходимо отдельную процедуру или можно здесь добавить ByteRead?

     

     


    • Изменено Siompc 12 января 2012 г. 18:03
    12 января 2012 г. 18:02
  • Посылаем в запросе длинну в байтах, дальше следом сообщение... в клиенте в ByteRead добавить нечто такое? 

    Dim TempAr() As Byte

                Dim binReader As New BinaryReader(IPlayerCMD.GetStream.Read(TempAr, 0, binReader.ReadInt32))

    BinaryReader читает байты с потока... где сам этот поток?

    13 января 2012 г. 14:31
  • Чуть подправлю... Почитал где нашел про ридер... при подключении клиента 

     Public Sub StartReadCMD(ByVal CMD_Client As TcpClient)
            Me.IPlayerCMD = CMD_Client 'Создаем новые подключения к серверу
            NWS_CMD = Me.IPlayerCMD.GetStream 'NetworkStream
            BinaryReader_CMD = New BinaryReader(NWS_CMD) 'Создаем читалку
            'Начинаем асинхронное чтение с записью в readBufferCMD
            Me.IPlayerCMD.GetStream.BeginRead(readBufferCMD, 0, READ_BUFFER_SIZE_CMD, AddressOf DoReadCMD, Nothing)
        End Sub
    


    Дальше выполняется процедура, которую писал выше. Начать читать нужное количество байт необходимо здесь?

    'Заканчиваем асинхронное чтение и считаем кол-во байт
                SyncLock GetDataLock
                    BytesRead = IPlayerCMD.GetStream.EndRead(ar)
                End SyncLock

    13 января 2012 г. 19:24
  • > Начать читать нужное количество байт необходимо здесь?
      
     
    см. пример (многопоточный сервер и асинхронный ввод-вывод)
       
     
    • Изменено Malobukv 13 января 2012 г. 20:49
    13 января 2012 г. 20:48
  • Да, я понимаю как работает клиент-сервер, но там нету ничего про BinaryReader
    14 января 2012 г. 10:34
  • Там в примере начинается асинхронное чтение и в процедуре чтения вызывается еще одна на вторую... Что-то запутался немного.
    14 января 2012 г. 10:52
  • Вообщем все сделал вручную... не разобрался с BinaryReader'ом. Узнал что асинхронный сервер может поддерживать не более 4000 клиентов. Это из за доступной ОЗУ или с чем это связано? Как можно увеличить число допустимых подключений?
    14 января 2012 г. 20:00
  • > Там в примере начинается асинхронное чтение и в процедуре чтения вызывается еще одна на вторую... Что-то запутался немного.


    см. Asynchronous Server Socket Example, Asynchronous Client Socket Example
    и SocketAsyncEventArgs Class

       
     
    • Изменено Malobukv 15 января 2012 г. 7:32
    15 января 2012 г. 7:13
  • Вообщем все сделал вручную... не разобрался с BinaryReader'ом. Узнал что асинхронный сервер может поддерживать не более 4000 клиентов. Это из за доступной ОЗУ или с чем это связано? Как можно увеличить число допустимых подключений?

    У BinaryReader только синхронные методы, в вашем случае проще сделать вручную.

    По поводу лимита соединений - посмотрите http://smallvoid.com/article/winnt-tcpip-max-limit.html

    • Помечено в качестве ответа Siompc 15 января 2012 г. 13:28
    15 января 2012 г. 9:08
  • Там в примере начинается асинхронное чтение и в процедуре чтения вызывается еще одна на вторую... Что-то запутался немного.
    У вас точно так же сделано, с комментарием 'И начинаем новое асинхронное чтение. Кстати, блокировка и BeginInvoke при повторном вызове чтения не нужны. Достаточно просто вызова BeginRead.
    • Помечено в качестве ответа Siompc 15 января 2012 г. 13:28
    15 января 2012 г. 9:13
  • Спасибо :)
    15 января 2012 г. 13:28