Конференция "Сети" » Вопрос про сокеты, а точнее про FD_CLOSE и FD_READ [D6, Win95/98, WinME, Win2k, WinXP]
 
  • __Unnamed__ (01.10.07 17:52) [0]
    Сделал сервак на основе сокетов. Использовал WS Api функции. Обработка информации происходит следуюющим образом:

    Да это все в потоке :)

    While not Terminated do
     Begin
        Res:=WaitForMultiPleObjects(1,FSockEvent,False,100,Flase);
        Case Res Of
          0: Begin
                 ...
                  <здесь проверяю флаги>
                  FD_CLOSE
                  FD_READ
                  FD_WRITE
                 ...
              End;
        End;
     End;
    Суть вопроса! :)
    Выявил следующую ошибку.
    Когда передаю большой файл >1Mb по нескольким соединениям(т.е. различные файлы, но соединений более 1-го) иногда не доходит до получателя последний блок. Экспериментальным путем выявил, что  FD_Close приходит раньше, чем FD_READ.
    Т.к. сервер не мой (т.е. не мною писанный). логика сервера заключается в том, что при посылке конечного куска файла он завершает соединение Shutdown + CloseSocket Или просто CloseSocket (Онное мне не известно).
    Как я помню из сокетов на основе оконных функций FD_CLOSE приходит всегда после того как прийдет последний байт из буфера чтения.
    Еще прочитал про критические секции, возможно в этом решение. Т.к. при изучении модуля ScktComp обнаружил, что  любая операция I/O заключена в Enter и Leave.
    Скажите не потеряется ли быстродействие и независимость самого потока при использовании критических секций.
    Или же моя ошибка заключается в другом? :)
  • __Unnamed__ (01.10.07 18:16) [1]
    Да когда делал, через оконные функции, то все доходило нормально, но как-то хотелось бы, чтобы все было в потоке :)
    Заранее спасибо!
  • __Unnamed__ (01.10.07 18:44) [2]
    Может конечно вопрос звучит глупо, но после FD_CLOSE приходит FD_READ в котором доходит оставшийся кусок!!!!
    Но после FD_CLOSE как положенно можно выходить из цикла и потока, поэтому я и написал сюда, может быть кто-нибудь подскажет.
    Может нужно использовать критические секции?
    Режим у сокетов неблокирующий.
    Просто из описания критических секций я понял, что остальные потоки приостанавливаются для ожидания выполнения самой критической секции.
    Т.о. независимость каждого потока нарушается, как я понимаю. Все ждут одного????!!!!!!
    Если я не прав пожалуйста поправте :)
    И почему Borland в ScktComp везде на операцияx чтения/записи использует CriticalSection?
  • __Unnamed__ (01.10.07 21:59) [3]
    Может кто-нибудь подскажет, что это может быть?
    Или все будут морозиться...
  • __Unnamed__ (01.10.07 22:13) [4]
    Ответьте на вопрос.
    Может ли прийти FD_CLOSE, если буфер приема еще не пуст?
  • Slym © (02.10.07 04:32) [5]
    FD_CLOSE и FD_READ могут приходить одновременно...
    WSAEnumNetworkEvents выдает все текущие флаги и проверять надо так
    if (lNetworkEvents and FD_READ)<>0 then
    begin
    //reading
    end;
    if (lNetworkEvents and FD_CLOSE)<>0 then
    begin
    //Closing
    end;


    http://www.sources.ru/cpp/cpp_network_evets_winsock2.shtml
  • Сергей М. © (02.10.07 09:06) [6]

    > Может ли прийти FD_CLOSE, если буфер приема еще не пуст?


    Может.


    > почему Borland в ScktComp везде на операцияx чтения/записи
    > использует CriticalSection?


    Видимо, просто перестраховался, поскольку в Winsock send/recv-вызовы сами по себе потокобезопасны (по кр.мере на NT-платформе).

    С другой стороны, нужно было как-то защитить данные TCustomWinSocket-объектов от потенциальной одновременой их модификации со стороны более чем одного потока при обращении к Send/Receive-методам этих объектов.
  • __Unnamed__ (02.10.07 21:54) [7]
    >>Сергей М. ©

    А что можеш предложить, когда у меня иногда не досылает до 100К и более.

    Кострукция типа:

    If FD_READ Then
     ...
    Else
      If FD_CLOSE Then
        ...
    Очень спасает, но я не получаю тогда извещения о дисконнекте.

    Вот лог:

    GET http://localhost/ HTTP/1.0
    Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
    Accept-Language: en-us
    User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.1.4322)
    Host: localhost
    Proxy-Connection: Keep-Alive

    HTTP/1.1 200 OK
    Date: Tue, 02 Oct 2007 11:33:44 GMT
    Server: Apache/1.3.33 (Win32) PHP/4.4.4
    Last-Modified: Sun, 27 Aug 2006 13:03:10 GMT
    ETag: "0-36fb-44f1980e"
    Accept-Ranges: bytes
    Content-Length: 14075
    Connection: close
    Content-Type: text/html
    X-Pad: avoid browser bug

    Это лог соединений:

    Server(236) connected!
    Server(236) write: 331
    Server(236) read: 7300
    Server(236) read: 1175
    Server(236) disconnect
    //------------------------------
    Это после FD_CLOSE еще два цикла
    Server(236) read: 4380
    Server(236) read: 1503

    Полученных данных 7300+1175=8475, а должно быть 14075+283(размер заголовка ответа)=14358.
  • __Unnamed__ (02.10.07 22:00) [8]
    Два цикла - в смысле два такта входа в цикл с
    WaitForMultipleObjects
  • __Unnamed__ (02.10.07 22:12) [9]
    >>Slym ©

    Function GetEvent(NetEvent:TWSANetworkEvents;Flag:Integer;Var Error:Integer):Boolean;
    Begin
     Result:=NetEvent.lNetworkEvents And Flag<>0;
     If Result Then
       Begin
         Case Flag of
           FD_READ:Error:=NetEvent.iErrorCode[FD_READ_BIT];
           FD_WRITE:Error:=NetEvent.iErrorCode[FD_WRITE_BIT];
           FD_CLOSE:Error:=NetEvent.iErrorCode[FD_CLOSE_BIT];
           FD_CONNECT:Error:=NetEvent.iErrorCode[FD_CONNECT_BIT];
           FD_ACCEPT:Error:=NetEvent.iErrorCode[FD_ACCEPT_BIT];
           FD_OOB:Error:=NetEvent.iErrorCode[FD_OOB_BIT];
         End;
       End;
    End;

    Это обработка События:
    WAIT_OBJECT_0: //Server Socket Event
             Begin
               FError:=WSAEnumNetworkEvents(FServerS,FEvent,@NetEvent);
               If FError<>0 Then
                 Begin
                   FError:=WSAGetLastError;
                   MakeError(True);
                   Break;
                 End;
               If GetEvent(NetEvent,FD_CONNECT,FError) Then
                 Begin
                   If FError<>0 Then
                     Begin
                       MakeError(True);
                       DisconnectServer;
                       Log.Add('Server('+IntToStr(FServerS)+') connect error: '+IntToStr(FError));
                     End
                   Else
                     Begin
                       FServerStatus:=ST_CONNECTED;
                       Log.Add('Server('+IntToStr(FServerS)+') connected!');
                     End;
                 End;
               //FD_READ Chtenie dannix ot Apache
               If GetEvent(NetEvent,FD_READ,FError) Then
                 Begin
                   If FError<>0 Then Break;
                   L:=SockRead(FServerS,ClientMem);
                   If L=Socket_Error Then
                     Begin
                       MakeError(True);
                       Log.Add('Server('+IntToStr(FServerS)+') read error: '+IntToStr(FError));
                       Break;
                     End;
                   Inc(FCountReceive,L);
                   Log.Add('Server('+IntToStr(FServerS)+') read: '+IntToStr(L));
                 End
               
               //FD_CLOSE Zakritie soedinenie s Apache
               If GetEvent(NetEvent,FD_CLOSE,FError) Then
                 Begin
                   If FError<>0 Then
                     MakeError(True);
                   DisconnectServer;
                   Log.Add('Server('+IntToStr(FServerS)+') disconnect');
                 End;

               //FD_WRITE Zapis dannix k Apache
               If GetEvent(NetEvent,FD_WRITE,FError) Then
                 Begin
                   If FError<>0 Then Break;
                   L:=SockWrite(FServerS,ServerMem);
                   If L=Socket_Error Then
                     Begin
                       MakeError(True);
                       Log.Add('Server('+IntToStr(FServerS)+') write error: '+IntToStr(FError));
                       Break;
                     End;
                   Log.Add('Server('+IntToStr(FServerS)+') write: '+IntToStr(L));
                 End;
               //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
             End;
  • __Unnamed__ (02.10.07 22:23) [10]
    >>Slym ©
    Я хотел показать, что я уже перепробовал все, что возможно и порядок обработки сообщений я знаю, но вот как можно правильно обработать ситуацию описанную выше, я просто теряюсь. :)

    Поэтому просто КРИЧУУУУУУУ о помощи.......... :)
  • __Unnamed__ (02.10.07 22:48) [11]
    >>Slym ©
    Да извиняюсь, я сверху правда не так написал как в исходнике, у меня вчера при себе кода не было :)
  • Slym © (03.10.07 05:01) [12]
    а в чем кстати принципиальность использования неблокирующего режима? Всеравно в потоке и с блокировкой (WaitFor)... и без использования готовых компонент.
    И как ты обрабатываешь FD_WRITE/FD_READ - ведь операция записи/чтения может оборваться WSAEWOULDBLOCK и при следующем FD_WRITE/FD_READ  нужно дописывать/дочитывать оставшиеся данные а не все. может в этом затык?
  • __Unnamed__ (03.10.07 17:08) [13]
    Function  SockRead;
    Var
     TempBuff:TBuffType;
     CountR  :Integer;
    Begin
     Result:=0;
       Result:=IOCTLSocket(Sock,FIONREAD,Cardinal(CountR));
     If Result=Socket_Error Then
       Begin
         FError:=WSAGetLastError;
         Exit;
       End;
     Try
       SetLength(TempBuff,CountR);
       Result:=Recv(Sock,TempBuff[0],CountR,0);
       If Result=SOCKET_ERROR Then
         Begin
           FError:=WSAGetLastError;
           SetLength(TempBuff,0);
           Exit;
         End;
       Buff.Position:=Buff.Size;
       Buff.Write(TempBuff[0],Result);
     Finally
       SetLength(TempBuff,0);
     End;
    End;

    Function  SockWrite;
    Var
     CountW:Integer;
     CountS:Integer;
    Begin
     Result:=0;
     IF Buff.Size<=0 Then Exit;
     CountW:=Buff.Size;
     While CountW>0 do
       Begin
         CountS:=CountW;
         If CountS>MaxSend Then CountS:=MaxSend;
         CountS:=Send(Sock,Buff.MemoryAddr^,CountS,0);
         If CountS=Socket_Error Then
           Begin
             FError:=WSAGetLastError;
             If FError=WSAEWOULDBLOCK Then FError:=0
               Else Result:=SOCKET_ERROR;
             Exit;
           End;
         Inc(Result,CountS);
         Buff.Position:=0;
         Buff.Delete(CountS);
         Dec(CountW,CountS);
       End;
    End;
  • DVM © (03.10.07 17:16) [14]

    > __Unnamed__  

    Если все равно создал доп поток то не проще ли использовать блокирующие сокеты, а не асинхронные сокеты на событиях?
  • Сергей М. © (03.10.07 17:16) [15]

    > что можеш предложить


    А какое отношение к этому имеют CriticalSections ?

    И где у тебя реакция на WSAGetLastError = WSAEWOULDBLOCK ?
  • __Unnamed__ (03.10.07 17:37) [16]
    Извиняюсь но в чтении разве может быть WSAEWOULDBLOCK?
    И если оно есть, то что оно означает?
  • DVM © (03.10.07 17:43) [17]

    > Извиняюсь но в чтении разве может быть WSAEWOULDBLOCK?

    Может. Ты хотя бы справку то по recv() открыл бы.
  • __Unnamed__ (03.10.07 17:53) [18]
    Ща открываю :)

    Вот цитирую:

    If no incoming data is available at the socket, the recv call waits for data to arrive 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 calls can be used to determine when more data arrives.

    А теперь смотрим топик 13

    Result:=IOCTLSocket(Sock,FIONREAD,Cardinal(CountR));
    If Result=Socket_Error Then
      Begin
        FError:=WSAGetLastError;
        Exit;
      End;



    В этом куске кода я проверяю кол-во пришедших байт и не пытаюсь закачать больше!
  • __Unnamed__ (03.10.07 18:04) [19]
    Единственное я не проверял  CountR>8192 (Max Receive)
    Возможно там больше, но как показывает практика еще ниразу не было такого, что описанная в 18 топике конструкция выдавала бы блоьше 8Кб :\
  • __Unnamed__ (03.10.07 18:16) [20]
    Я извиняюсь за назойливость, коментарии по 18 и 19 будут? :)
  • Сергей М. © (04.10.07 08:33) [21]

    > как показывает практика еще ниразу не было такого, что описанная
    > в 18 топике конструкция выдавала бы блоьше 8Кб


    Все правильно.
    Этот дифолт определен опциями SO_SNDBUF и SO_RCVBUF на сторонах передатчика и приемника соответственно.

    см. Get/SetSockOpt
  • __Unnamed__ (04.10.07 12:14) [22]
    >>Сергей М.

    Я просто уже не знаю что делать, как мне кажеться, WSAEWOULDBLOCK у меня в чтении произойти не может. И в тоже время приходит раньше FD_CLOSE чем опустеет буфер приемника :/.

    >>DVM
    Просто поймите меня правильно, логика основанная на Асинх/Синхр сокетах должна одинаково получать данные от передающего. Если только вы не намекаете на RECV(...)=0 ==FD_CLOSE, но тут возникает вопрос, почему приходит FD_CLOSE раньше, чем освободится буфер.

    Я уже наверное всех достал :) Ну не могу я понять этого...!!!!??????
  • Сергей М. © (04.10.07 12:28) [23]

    > как мне кажеться, WSAEWOULDBLOCK у меня в чтении произойти
    > не может.


    Ну да, не может, ведь ты вызываешь ф-цию recv() как реакцию на событие FD_READ.
    Но тем не менее, если бы эта "ошибка" фигурировала, в лог ты ее записал бы, хотя ошибкой такая ситуация на самом деле не является.


    > почему приходит FD_CLOSE раньше, чем освободится буфер


    > сервер не мой


    Тогда откуда ты знаешь, что сервер вызывает shutdown ?
    И тем более откуда ты знаешь, с какаим how-параметром сервер вызывает эту функцию ?

    Кстати, рекомендацией Slym ©   (02.10.07 04:32) [5] ты воспользовался ?
  • Сергей М. © (04.10.07 12:38) [24]
    Цитата из справки:

    If how is SD_SEND, subsequent sends are disallowed. For TCP sockets, a FIN will be sent

    Из этого следует, что если партнер по соединению запустил асинхронный send, после чего немедленно выполнил shutdown(SD_SEND), вместо ожидаемого тобой SYN ты запросто можешь получить FIN, что как раз и означает потерю того финального шматка потока, передаваемого тебе партнером.
  • Сергей М. © (04.10.07 12:40) [25]

    > асинхронный send


    Не только асинхронный - и синхронный тоже.
  • __Unnamed__ (04.10.07 12:42) [26]
    1. Сервер Apache но локально(хотя это не имеет значения)
    2. Рекомендации Slym-а смотрел см [9], я так и делал просто когда писал вопрос, первое что в голову пришло это было FD_CLOSE :)

    Как можно проверить с каким пар-ром ShutDown был выполнен с серверной стороны? Я про это еще нигде не видел примеров и статей :)
    Я со своей стороны выполняю всегда ShutDown(FServerS, ??_BOTH);
  • Сергей М. © (04.10.07 12:42) [27]
    Короче, сервер вполне м.б. "кривым".

    Ну а твоя "кривизна" как клиента исправляется задействованием WSAEnumNetWorkEvents
  • __Unnamed__ (04.10.07 12:42) [28]
    Ну и CloseSocket после ShutDown соотвественно :)
  • Сергей М. © (04.10.07 12:44) [29]

    > Как можно проверить с каким пар-ром ShutDown был выполнен
    > с серверной стороны?


    Кроме анализа текста сервера - никак.
    Кстати, Апач - опенсурсный продукт.


    > Я со своей стороны выполняю всегда ShutDown(FServerS, ??
    > _BOTH);


    Нафига ?
  • Сергей М. © (04.10.07 12:46) [30]

    > я так и делал


    Ну так первым дело м при этом ты должен был искать в списке событий, возвращенных вызовом этой ф-ции, событие FD_READ и только потом FD_CLOSE, но не наоборот.
  • __Unnamed__ (04.10.07 12:47) [31]
    Я столкнулся с такой проблемой, когда я не выполняю ShutDown сам Апаче сервер не закрывает со своей стороны Гнезда и ждет непонятно чего??!!!!!
    А с ShutDown все нормально :)
  • Сергей М. © (04.10.07 12:48) [32]

    > когда я не выполняю ShutDown сам Апаче сервер не закрывает
    > со своей стороны Гнезда и ждет непонятно чего


    Быть того не может.

    Если ты выполнил без ошибок closesocket, то рано или поздно партнер получит FIN.
  • __Unnamed__ (04.10.07 12:49) [33]
    >>Сергей М. ©

    Ну так первым дело м при этом ты должен был искать в списке событий, возвращенных вызовом этой ф-ции, событие FD_READ и только потом FD_CLOSE, но не наоборот.


    См. топик [9]!!!
  • Сергей М. © (04.10.07 12:51) [34]
    Весьма полезным будет также изучить поведение гнезда при его закрытии при взведенной и сброшенной опции SO_LINGER
  • __Unnamed__ (04.10.07 13:01) [35]
    Т.е. при SO_LINGER = TRUE с моей стороны, все данные будут приняты?
    Или это только для посылки?
  • Сергей М. © (04.10.07 13:12) [36]
    Это, в свою очередь, зависит от Interval.
  • Сергей М. © (04.10.07 13:16) [37]
    Подозреваю, что где-то что-то ты недопонимаешь.

    Вряд ли Апач будет рвать соединение не передав полностью запрошенные у него данные, если на то нет веских причин.

    Пробуй ту же логику в блокирующем режиме. Если все будет ок, то неправ ты, иначе Апач.
  • __Unnamed__ (04.10.07 20:28) [38]
    Скажи, а если у меня происходит скажем задержка потока на 1-5 секунды в связи с записью на устройство. И чтение из буфера приемника задерживается, то по логике TCP FIN прийдет только тогда, когда Апач полностью передаст данные, ну или у него SO_LINGER=False и прийдет дисконнект но в буфере приемника может быть где-то до 8-64Кб информации как я понимаю.

    И мне кажеться в моем случае, мне прийдется анализировать
    <Content-length:?????> + FD_CLOSE и его ошибки. Если при дисконнекте произошли ошибки, то выходить иначе читать до тех пор пока не будет равно ожидаемого.
  • Сергей М. © (05.10.07 08:58) [39]
    Даже если поступил FIN, данные в буфере приема твоего гнезда, если они там действительно имеются, никуда не денутся, и ты сможешь их прочитать в любой момент до закрытия своего гнезда.


    > мне прийдется анализировать
    > <Content-length:?????>


    Да, придется.
  • имя (05.02.08 19:35) [40]
    Удалено модератором
  • ага (06.02.08 13:22) [41]
    Просмотрел бегло, может уже сказано, но не заметил.

    Нормально это, FIN пришел вместе с последним пакетом. Сервер закончил запись и вызвал shutdown на свой передающий конец - типа больше ниче передавать не буду. Сокет отправил все из буфера и с последним пакетом - FIN. Получив его, нужно вычерпать все из буфера и закрыть свой приемный конец.
  • effingtet (04.02.09 17:32) [42]
    Соберем для Вас по сети интернет
    базу данных потенциальных клиентов для Вашего Бизнеса!!!
    Соберем данных Много!!! Быстро!!! Недорого!!!
    Название телефон факс e-mail www адрес имена итд
    Узнайте подробности по телефону: +79133913837
    ICQ: 6288862
    Email: rassilka.agent@gmail.com
    Skype: prodawez
  • FireMan_Alexey (04.02.09 18:29) [43]
    Сделай так:

    While True do
    Begin
     Err:=Recv(...);
     IF err=SOCKET_ERROR and WSAGetLastError=WSAEWOULDBLOCK Then
       Break;
    End;
    Event - сигнализирует о приходе данных, а IOCTLSocket - покажет всего 8192 макс хотя там может быть и больше :) (проверенно экспериментально)
    А вообще лучше использовать Select в потоках :) тоже экспериментально :)
    Я тоже пытался прокси на Эвентах написать, но столкнулся с такой же ситуацией и с не закрытыми соединениями со стороны апачей :).
    Спасло только то что весь код переделал под синхронные сокеты.
  • !"№;%:?* (12.03.09 07:30) [44]
    При известном количестве данных - читай сколько указано в заголовке, recv вернет ошибку если соединение будет закныто раньше.

    В отдельном потоке, можно и блокирующими сокетами обойтись.
  • Palladin © (12.03.09 08:28) [45]

    > FireMan_Alexey   (04.02.09 18:29) [43]

    А ничего что год прошел? :)
 
Конференция "Сети" » Вопрос про сокеты, а точнее про FD_CLOSE и FD_READ [D6, Win95/98, WinME, Win2k, WinXP]
Есть новые Нет новых   [134435   +33][b:0][p:0.002]