Конференция "Сети" » winsock, о приеме данных [D7, WinXP]
 
  • Очень злой (24.10.11 23:42) [0]
    1.Если я установлю требование генерации сообщений с помощью функции WSAAsyncSelect, то в какие моменты они будут происходить?
    Только при приходе от сервера новой порции данных или не только?

    2. нужно организовать прием и обработку данных от сервера, который посылает их в виде пакетов такого формата:
    первые 2 байта - длина пакета, остальное - собственно данные.

    При проверке трафика между сервером и стандартным клиентом не замечалось "разрывов" отдельных пакетов (у них максимальная длина не очень большая) и склеек нескольких подряд идущих пакетов, но тем не менее, чтобы подстраховаться от возможных таких случаев, планирую делать примерно так:


    type TPacket: packed record
     case boolean of
     true: (buf: array[0..8191] of byte;);
     false: (len:word;
             typepack:byte;);
     end;

    var
     Packet:TPacket;

    ...
    begin
     while true do
       begin
         if recv(FSocket,Packet,2,MSG_PEEK)<2 then Exit;
         if recv(FSocket,Packet,Packet.len,MSG_PEEK)<packet.len then Exit;
         recv(FSocket,Packet,Packet.len,0);
         Obrabotka(Packet); // функция обработки пакета
       end;
    end;



    Если так делать, то получается как-то "некрасиво" что-ли, из-за многократного дергания функции recv. Это нормально или лучше переписать по другому, если да, то как?
  • Rouse_ © (25.10.11 00:00) [1]
    1. Не только, т.к. будут приходить оконные сообщения, может произойти ситуация что сообщение посланное через HWND_BROADCAST может пойматься твоим ЦВС и если оно будет идентично тем, которые ты обрабатываешь, то...
    2. Такой сервер можно легко "положить" отправив FFFF первым словом. Для разделения пакетов? в случае бинарных данных? можно использовать Base64? используя в качестве разделителя байт, не входящий в последовательность BASE64
  • Очень злой (25.10.11 00:54) [2]
    Не, я пишу не сервер. Это у меня клиент. Т.е. тут я не изобретаю свой протокол обмена, а придерживаюсь того, с которым работает сервер.
    Просто протокол обмена большей частью описан в некоторых статьях в интернете, ну и частично я могу просниферить то, как с сервером общается стандартный клиент.
    Что касается буфера [0..8191], то этого более чем достаточно для приема любого из реальных пакетов посланных сервером. Защищать мой клиент от  FFFF или нечто подобного можно разве что на случай какого-нить сбоя, что вроде бы и невозможно,  но если такой сбой произойдет, то весь дальнейший обмен трафиком потеряет смысл, и можно будет дисконектить соединение. А защитить клиент от этого можно добавивши еще одну проверку.
  • Омлет © (25.10.11 08:03) [3]
    > Если так делать, то получается как-то "некрасиво" что-ли, из-за многократного дергания функции recv.

    Так делать неправильно.
    recv читает данные в буфер и возвращает размер прочитанных данных, либо число меньше нуля (тогда надо вызвать WSAGetLastError, чтобы обработать ошибку). Если recv вернул размер, равный размеру буфера, то надо читать дальше, пока не вернет размер меньше размера буфера или 0. Всё прочитанное последовательно складывается в буфер (а лучше сразу в него и считывать, передавая указатель на текущую позицию), затем там ищутся пакеты. По-хорошему, у пакетов должна быть сигнатура и контрольная сумма.

    > if recv(FSocket,Packet,Packet.len,MSG_PEEK)<packet.len then Exit;

    Прийти может 1 байт - и ты просто потеряешь его.
  • Омлет © (25.10.11 08:09) [4]
    > Омлет ©   (25.10.11 08:03) [3]

    А, пардно, кое в чем не прав. Не заметил флаг MSG_PEEK ))
    Но, всё равно, незачем лишний раз вызывать recv, если можно сразу считать данные.
  • Slym © (25.10.11 09:16) [5]
    я делаю по такому принципу

    function RecvBufFully(Peer:TSocket;var Buf; Count: Integer):boolean;
    var pBuf:PByte;
      s:integer;
    begin
      result:=false;
      pBuf:=@Buf;
      while count>0 do
      begin
        s:=recv(Peer,pBuf,Count,0);
        if s=0 then exit;
        inc(pBuf,s);
        dec(Count,s);
      end;
      result:=true;
    end;

    if RecvBufFully(FSocket,Packet.len,SizeOf(Packet.len)) then
    if not RecvBufFully(FSocket,Packet.typepack,Packet.len) then БитыйПакет;

  • Омлет © (25.10.11 09:38) [6]
    > Slym ©   (25.10.11 09:16) [5]

    У тебя нет проверки на s < 0, огребешь ведь..
    И что будет, если еще не все данные пришли? recv вернет 0, а через секунду придет остаток данных - ты же пакет потеряешь. Или, к примеру, придет сначала часть пакета..
  • Очень Злой (25.10.11 10:48) [7]

    > Омлет ©   (25.10.11 08:09) [4]
    >
    > > Омлет ©   (25.10.11 08:03) [3]
    >
    > А, пардно, кое в чем не прав. Не заметил флаг MSG_PEEK ))
    > Но, всё равно, незачем лишний раз вызывать recv, если можно
    > сразу считать данные.


    Вот и интересует как можно по-другому считать данные, если:

    // нам нужно считать только один пакет, чтобы обработать его,
    // но мы не знаем имеется ли в принятых данных целый пакет.
    // для начала пытаемся считать первых 2 байта (которые хранят длину пакета, включая и эти 2 байта)
    if recv(FSocket,Packet,2,MSG_PEEK)<2 then Exit; // если данных меньше 2-х байт - дальше не читаем.

    // длина пакета известна, проверяем или есть весь пакет целиком, если нет, то прекращаем обработку. будем ждать...
    if recv(FSocket,Packet,Packet.len,MSG_PEEK)<packet.len then Exit;

    // пакет есть целиком и мы его прочитали, однако нужно удалить эти данные в буфере сокета
    recv(FSocket,Packet,Packet.len,0);

    //вроде бы и лишние операции, но и без них не могу обойтись...

  • Сергей М. © (25.10.11 12:36) [8]

    > как можно по-другому считать данные


    Не использовать MSG_PEEK.
  • Очень Злой (25.10.11 12:43) [9]

    > Не использовать MSG_PEEK.


    Т.е. читать в буфер все что получили, а потом работать с буфером и пакеты выделять из буфера? насколько это будет "лучше"?
  • Сергей М. © (25.10.11 13:29) [10]
    Не "все что получили", а не более того сколько ожидается до окончания приема текущего принимаемого пакета.

    > насколько это будет "лучше"?

    Ну ты же сам сказал что

    > "некрасиво" ..из-за многократного дергания функции recv


    И это действительно "некрасиво", потому что ее можно и нужно "дергать" тогда когда нужно получить очередную "порцию" данных из.входного потока.
  • Омлет © (25.10.11 15:37) [11]
    > Очень Злой   (25.10.11 12:43) [9]
    > Т.е. читать в буфер все что получили, а потом работать с буфером и пакеты выделять из буфера?


    Да.

    > насколько это будет "лучше"?

    В чем тебе измерить? ))
  • Slym © (26.10.11 06:44) [12]
    Омлет ©   (25.10.11 9:38) [6]
    У тебя нет проверки на s < 0, огребешь ведь..

    уговорил

    uses RTLConsts;

    function RecvBufFully(Peer:TSocket;var Buf; Count: Integer):boolean;
    var pBuf:PByte;
     s,err:integer;
    begin
     result:=false;
     pBuf:=@Buf;
     while count>0 do
     begin
       s:=recv(Peer,pBuf,Count,0);
       if s=SOCKET_ERROR then
       begin
         err:=WSAGetLastError;
         raise Exception.CreateResFmt(@sWindowsSocketError, [SysErrorMessage(err), err, 'recv']);
       end else
         if s=0 then exit;// сокет клозед грацефули
       inc(pBuf,s);
       dec(Count,s);
     end;
     result:=true;
    end;

    if RecvBufFully(FSocket,Packet.len,SizeOf(Packet.len)) then
    if not RecvBufFully(FSocket,Packet.typepack,Packet.len) then БитыйПакет;

  • Омлет © (26.10.11 08:25) [13]
    > Slym ©   (26.10.11 06:44) [12]

    Так тоже нехорошо делать, т.к. ошибки-то разные бывают.
    Например, WSAEWOULDBLOCK.
  • Сергей М. © (26.10.11 09:06) [14]

    > Например, WSAEWOULDBLOCK


    WSAEWOULDBLOCK никогда не возникнет при вызове recv()
  • han_malign (26.10.11 09:46) [15]

    > WSAEWOULDBLOCK никогда не возникнет при вызове recv()

    - не надо обманывать - в неблокирующем режиме WSAEWOULDBLOCK - это обычное состояние, если перед recv не дожидаться данных с помощью select(0, readfds,...)

    If no incoming data is available at the socket, the recv call blocks and waits for data to arrive according to the blocking rules defined for WSARecv with the MSG_PARTIAL flag not set unless the socket is nonblocking. In this case, a value of SOCKET_ERROR is returned with the error code set to WSAEWOULDBLOCK. The select, WSAAsyncSelect, or WSAEventSelect functions can be used to determine when more data arrives.

    З.Ы. Если что
    recv() = 0

    - означает "элегантное закрытие" соединения...
  • han_malign (26.10.11 09:55) [16]
    а вот MSG_PEEK - это плохо:
    MSG_PEEK:
    Peeks at the incoming data. The data is copied into the buffer, but is not removed from the input queue. The function subsequently returns the amount of data that can be read in a single call to the recv (or recvfrom) function, which may not be the same as the total amount of data queued on the socket. The amount of data that can actually be read in a single call to the recv (or recvfrom) function is limited to the data size written in the send or sendto function call.

    Если пакет разобьется на несколько датаграмм(а это нормальная ситуация на потоковых сокетах) - то "аля-улю - гони госей"...
  • Омлет © (26.10.11 10:00) [17]
    > Сергей М. ©   (26.10.11 09:06) [14]

    олала
    http://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx
  • Slym © (26.10.11 13:07) [18]
    han_malign   (26.10.11 9:46) [15]
    вот нафега новички лезут в неблокирующий режим?
    в блоке все по порядку никаких сообщений все линейно
  • Сергей М. © (26.10.11 13:19) [19]

    > han_malign   (26.10.11 09:46) [15]


    Согласен.
    Ляпнул не освежив в памяти сабж и поленившись приоткрыть справку)
 
Конференция "Сети" » winsock, о приеме данных [D7, WinXP]
Есть новые Нет новых   [134436   +21][b:0][p:0.002]