Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Проблема с SendText в ctBlocking сокете [D7, WinXP]


Страницы:   1 2 3

Alex_C ©   (2009-05-12 22:39:00) 

Некоторое время назад я спрашивал о проблемах создания Socket'а в треде. Блягодаря примеру, взятому от сюда
http://www.delphimaster.ru/cgi-bin/forum.pl?id=1227604925&n=4
все прекрасно работает. На прием.
Проблема возникает когда надо сделать SendText:

В методе Execute потока у меня:

 while (not Terminated) and TelnetSocket.Socket.Connected do
 begin
    DataLength := TelnetSocket.Socket.ReceiveBuf(Buf[0], 64000);
    if DataLength


FireMan_Alexey   (2009-05-21 15:43:00) [21]

Убери INFINITE поставь 50-1000 и висеть не будет :)


Сергей М.   (2009-05-21 15:39:00) [22]


> Получаем неприятный эффект: Сначала выполняется событие
> FD_READ, потом FD_CONNECT, что есть неправильно


Способы обработки и диспетчеризации сообщенийникак не касаются winsock-событий и никоим образом не влияют на порядок их возбуждения.


> Иначе так и висит


Если "висит", значит либо в очереди сообщений нет ни единого сообщения либо не происходит ни одно из FD-событий, о которых должен просигналить ивент. Третьего не дано.

Я уже об этом говорил.


Alex_C   (2009-05-21 15:20:00) [23]

И еще одно замечание:
если делать Didconnect как Terminate, то в MsgWaitForMultipleObjects надо INFINITE заменить на конкретное время, допустим 50. Иначе так и висит...

P.S. Замечания пишу для таких же как я, кто будет эту ветку читать)))


Alex_C   (2009-05-21 15:14:00) [24]


> Сергей М.


По поводу [22] и [25] ("Можно и так" отменяется). По ходу все же верно именно [22], а не [25]!
При замене


while PeekMessage(msg, 0, 0, 0, PM_REMOVE) do
         DispatchMessage(msg);


на


while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
  if Msg.Message = WM_QUIT then
    Terminate
  else
     Dispatch(Msg.Message);


Получаем неприятный эффект: Сначала выполняется событие FD_READ, потом FD_CONNECT, что есть неправильно. А вот если как в [22] - то все верно!


FireMan_Alexey   (2009-05-21 15:12:00) [25]

>Alex_C
Ничего, скоро тебя еще грабли ждут, но потом, как разберешься когда приходят события FD_READ, FD_CLOSE :)


Сергей М.   (2009-05-21 11:03:00) [26]


> Alex_C   (21.05.09 10:36) [26]


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


> как правильно сделать Disconnect?


Можно и так.

Ну а спасибы лучше складывать в  WM-авоськи: R288065700395, Z246966205434,  E435338015555 )


Alex_C   (2009-05-21 10:36:00) [27]


> Сергей М.

Так и хочется как в "Дне радио" сказать:
- Вам за это Нобелевскую премию надо вручить!
- Вот приплывем, купишь ее в ларьке и торжественно вручишь! )))

Вообще просто огромное спасибо за помощь!
Вообще модераторам в этом разделе эту тему как Faq надо указывать - потому как когда начинал работать с сокетами - вопросы практически у всех одни и теже, а вот таких подробных и главное с мелкими нюансами объяснений я ни в инете, ни в книгах не нашел.
Так что еще раз спасибо!

Для информации - попутно разобрался, если кому интересно, как по UDP протоколу работать - это если кому в своей программе захочется время синхронизировать.


 if UpperCase(Proto) = 'UDP' then
 begin
   FProto := IPPROTO_UDP;
   FType  := SOCK_DGRAM;
 end
 else
 begin
   FProto := IPPROTO_IP;
   FType  := SOCK_STREAM;
 end;

...

FSock := Socket(AF_INET, FType, FProto);


Кстати тоже практически вообще не документированный момент.

Теперь, думаю уже полностью завершающий момент:
как правильно сделать Disconnect?
Как самый простой вариант - для треда сделать Terminate, в результате при выходе из цикла


   while not Terminated do
   begin


попадаем


 finally
   WSACloseEvent(Event);
   CloseSocket(FSock);
   WSACleanup;
   SetLength(Buff, 0);
 end;


Думаю это самый верный вариант.


Сергей М.   (2009-05-21 09:46:00) [28]

"Можно и так" [см. 23] отменяется.

Если твой поток не создает окон и ты хочешь получать сообщения, в т.ч. и п.о. WM_QUIT, то так нельзя.

WAIT_OBJECT_0 + 1:
   while PeekMessage(msg, 0, 0, 0, PM_REMOVE) do
       if Msg.Message = WM_QUIT then
          Terminate
       else
          Dispatch(Msg.Message);

Если создает (планируешь расширять функц-ть потока), тогда возвращаем на место DispatchMessage :

WAIT_OBJECT_0 + 1:
   while PeekMessage(msg, 0, 0, 0, PM_REMOVE) do
       if Msg.hWnd = 0 then
         if Msg.Message = WM_QUIT then
           Terminate
         else
           Dispatch(Msg.Message)
       else begin
          // TranslateMessage(Msg);
          DispatchMessage(Msg);
       end;


Теперь по поводу [24]:


> про 2 параметр написано pHandle s- вроде как по названию,
>  что это указатель на хендл


Не на хендл, а на хендл ы.

Points to an array of object handles= указывает на массив хэндлов.

А количество элементов этого массива указывается 1-м параметром.

Точно те же параметры того же назначения фигурируют и в WSAWaitFor-функции, сравни сам - разница лишь в уточнении, хендлы каких объектов допускаются в этом массиве:

Points to an array of eventobject handles

Так что в док-ции написано все правильно.


> MsgWaitForMultipleObjects при попытке указать @Event - выдает ошибку и работает именно
> при Event - указании самого хендла евента


И не мудрено.

Ведь 2-й форм.параметр в windows.pas объявлен как var pHandles, в то время как в Winsock2.pas (если именно он тобой используется) он объявлен как lphEvents : PWSAEVENT

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

Вариантов тут несколько - выбирай сам наиболее подходящий:

1.  Не пользовать winsock2.pas

В этом случае придется делать все декларации самостоятельно, в частности

function WSAWaitForMultipleEvents( cEvents : DWORD; var EventArray; fWaitAll : LongBool;
 dwTimeout : DWORD; fAlertable : LongBool ): DWORD; stdcall;
..
var
 hWSAEvent: THandle; // хендл ивент-объекта

..
// корректные конструкции вызовов
 case WSAWaitForMultipleEvents(1, hWSAEvent, ..) of
 case MsgWaitForMultipleObjects(1, hWSAEvent, ..) of


2. Использовать декларации winsock2.pas как есть или сделать эквивалентные декларации в собственном коде

Тогда:

var
 hWSAEvent: THandle; // хендл ивент-объекта
 pWSAEventArray: Pointer absolute hWSAEvent; // указатель на hWSAEvent
..
// корректные конструкции вызовов
 case WSAWaitForMultipleEvents(1, pWSAEventArray, ..) of
 case MsgWaitForMultipleObjects(1, hWSAEvent, ..) of
 case MsgWaitForMultipleObjects(1, pWSAEventArray^, ..) of



> на счет send: возврашаемое значение - количество переданный
> байт


Открываем талмут, читаем:

If no error occurs, send returns the total number of bytes sent. (Note that this can be less than the number indicated by len.) Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError

Error Codes
..
WSAEWOULDBLOCKThe socket is marked as nonblockingand the requested operation would block


Как раз твой случай: сокет у тебя неблокирующий, значит отказ с кодом WSAEWOULDBLOCK вполне может произойти.

О чем он говорит ?

Говорит он о том, что ф-ция send() отказала в запросе на передачу указанного тобой объема данных по причине недостатка места во внутреннем технологическом буфере очереди передачи [см. Get/SetSockopt(..SO_SNDBUF ..)] .. Попросту - внутренний буфер занят ранее принятыми от тебя для передачи данными и не может на сей момент принять очередные len байт данных.
Об успешной или неуспешной передаче и освобождении внутреннего буфера winsock-подсистема известит тебя событием FD_WRITE, НЕследует повторять повторные попытки передачи этих очередных len байт раньше чем возникнет это событие.
Кр.того, событие FD_WRITE возникает однократно одновременно с событием FD_CONNECT, извещая о готовности сокета к передаче данных .. Попросту - внутренний буфер пуст.

Вот такой вот опус)


Alex_C   (2009-05-20 22:31:00) [29]

Ок! Тогда так.

переделываем


 ProcessMessages;
 case WSAWaitForMultipleEvents(1,@Event,False,{WSA_INFINITE}50,True) of


на


  case MsgWaitForMultipleObjects(1, Event, False, INFINITE, QS_ALLINPUT) of


Кстати! В MsgWaitForMultipleObjects про 2 параметр написано pHandles - вроде как по названию, что это указатель на хендл. Однако при попытке указать @Event - выдает ошибку и работает именно при Event - указании самого хендла евента.

Event := WSACreateEvent;

Неправильно в докуменации написано или я что-то неверно пинимаю?

Теперь на счет send: возврашаемое значение - количество переданный байт.
Посмотрел примеры в инете - или оно вообще не обрабатывается, или же что то типа такого

 SendByte := send(FSock, Str[1], Length(Str), 0);
 if SendByte = 0 then
   Disconnect;


Сергей М.   (2009-05-20 21:53:00) [30]


> Я так понимаю на это:


Можно и так.


> надо еще и обрабатывать возвращаемое значение


Именно !

В неблок. режиме с нотификациями это вдвойне важно.
Ибо ждать или не ждать FD_WRITE напрямую зависит от того что возвратила функция.


Alex_C   (2009-05-20 21:48:00) [31]


> Сергей М.


Спасибо! Сейчас сам разобрался немного далее! И как раз сделал именно так, как ты и написал!

На счет [15] - намек понял, что надо еще и обрабатывать возвращаемое значение!

Теперь по выше написанному: с WAIT_TIMEOUT и WAIT_OBJECT_0 все ясно = это как у меня в программе, все так и оставляем. А вот если WAIT_OBJECT_0 + 1 ?
Я так понимаю на это:

WAIT_OBJECT_0 + 1:
    while PeekMessage(msg, 0, 0, 0, PM_REMOVE) do
        DispatchMessage(msg);


Сергей М.   (2009-05-20 21:28:00) [32]

case MsgWaitForMultipleObjects(..) of
WAIT_OBJECT_0: .. произошло одно или более FD_событий ..
WAIT_OBJECT_0 + 1: .. очередь сообщений потоку не пуста ..
WAIT_TIMEOUT: .. за время, равное указанному при вызове ф-ции таймауту, ничего не произошло
end;

Обрати внимание на [15] !


Alex_C   (2009-05-20 21:15:00) [33]


> "Приличный" поток обязан реагировать немедленно на все сообщения-
> события, на которые ему положено реагировать.
> Для этого существует MsgWaitForMultipleObjects


Сергей! К сожалению документации на данную тему очень мало, вот и приходится "домысливать"... Вообще все делалось на основании [4].
Я так понимаю, в данном случае ProcessMessages следует заменть на MsgWaitForMultipleObjects?
Чет перелопатил столько документции, везде все по разному. Не подскажите, как правильно тут исправить?


Сергей М.   (2009-05-20 20:21:00) [34]


> Alex_C


"Приличный" поток обязан реагировать немедленно на все сообщения-события, на которые ему положено реагировать.
Для этого существует MsgWaitForMultipleObjects


Сергей М.   (2009-05-20 20:18:00) [35]


>  while True do
>    begin
>      ProcessMessages;
>      case WSAWaitForMultipleEvents


Вот не будут происходить никакие FD-события - и будет поток висеть на Wait-вызове до морковкиного заговения) ..

А в это время другой поток пыжится - шлет депеши "висящему" потоку, мол, сделай то-се, мол, закругляйся немедля по WM_QUIT.. А тот и в ус не дует - ждет себе преспокойненько FD-события)


Empleado   (2009-05-20 18:41:00) [36]

Вообще вот эта композиция интересная:

> procedure THXClientSocetThread.ProcessMessages;
> begin
>  while Self.ProcessMessage do { loop };
> end;
>
> function THXClientSocetThread.ProcessMessage: Boolean;
> var
>  Msg : TMsg;
> begin
>  Result := False;
>  if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
>  begin
>    Result := True; .......


Empleado   (2009-05-20 18:19:00) [37]


> begin
>      TranslateMessage(Msg);
>      DispatchMessage(Msg);
>    end;

А это зачем?


Сергей М.   (2009-05-20 17:03:00) [38]

send() - это функция!!!


Alex_C   (2009-05-20 16:49:00) [39]

Все работает, кроме некоторый моментов.
1. Почему при вызове THXClientSocetThread.SendData  не отрабатывает событие FD_WRITE? Сюда программа не попадает. Или я что то не правильно понял?
2. Как правильно сделать Disconnect? Если как у меня - closesocket(FSock), то опять же не срабатывает событие FD_CLOSE.
В остальнов все работает. Единственное замечание для тех, кто будет делать нечто подобое: в основной программе на события On...Event  естественно нужно использовать критические секции.


Alex_C   (2009-05-20 16:44:00) [40]


function GetEvent(L,L1:Integer):Boolean;
begin
 Result:=(L and L1)<>0;
end;

function IsIPAddress(const Str: string): boolean;
var
 i, p, d: Integer;
 tmp1, tmp2: String;
begin
 // Длина IP адреса не может быть более 15 символов:
 // 255.255.255.255
 Result:= (Length(Str) <= 15);
 if not Result then
   Exit;
 tmp1:= Str;
 // В IP-адресе всего 3 точки (0..2)
 for i:= 0 to 2 do
 begin
   // Ищем точку
   p:= Pos('.', tmp1);
   // Если точек не хватает (т.е. < 3, то это не IP)
   Result:= Result and (p > 0);
   if not Result then
     Exit;
   tmp2:= Copy(tmp1, 1, Pred(p));
   Delete(tmp1, 1, p);
   d:= StrToIntDef(tmp2, -1);
   Result:= (d >= 0) and (d <= 255);
 end;
 d:= StrToIntDef(tmp1, -1);
 Result:= Result and (d >= 0) and (d <= 255);
end;

function GetIPFromHost(const HostName: string): string;
type
 TaPInAddr = array[0..10] of PInAddr;
 PaPInAddr = ^TaPInAddr;
var
 phe: PHostEnt;
 pptr: PaPInAddr;
 i: Integer;
 GInitData: TWSAData;
begin
 WSAStartup($101, GInitData);
 Result := '';
 phe := GetHostByName(PChar(HostName));
 if phe = nil then Exit;
 pPtr := PaPInAddr(phe^.h_addr_list);
 i := 0;
 while pPtr^[i] <> nil do
 begin
   Result := inet_ntoa(pptr^[i]^);
   Inc(i);
 end;
 WSACleanup;
end;

function IPAddrToName(IPAddr: string): string;
var
 SockAddrIn: TSockAddrIn;
 HostEnt: PHostEnt;
 WSAData: TWSAData;
begin
 WSAStartup($101, WSAData);
 SockAddrIn.sin_addr.s_addr := inet_addr(PChar(IPAddr));
 HostEnt := gethostbyaddr(@SockAddrIn.sin_addr.S_addr, 4, AF_INET);
 if HostEnt <> nil then
   Result := StrPas(Hostent^.h_name)
 else
   Result := '';
end;

//******************************************************************************

constructor THXClientSocetThread.Create(iAddr: string;  iPort: Cardinal);
begin
 FreeOnTerminate := True;
 Host := iAddr;
 Port := iPort;
 FSocketState := wsNone;
 inherited Create(True);
end;

destructor THXClientSocetThread.Destroy;
begin
 if Assigned(FOnDestroyEvent) then
   FOnDestroyEvent(Self);
 inherited;
end;

procedure THXClientSocetThread.Execute;
var
 Addr: TSockAddr;
 EResult: Integer;
 WSAD: TWSAData;
 Event, Size: Cardinal;
 Buff: array of Byte;
 ev: TWSANetworkEvents;
begin
 fillchar(Addr,sizeof(TSockAddr),0);
 Addr.sin_family      := AF_INET;
 Addr.sin_port        := HtoNS(Port);
 if IsIPAddress(Host) then
   Addr.sin_addr.S_addr := inet_addr(PChar(Host))
 else
   Addr.sin_addr.S_addr := inet_addr(PChar(GetIPFromHost(Host)));

 EResult := WSAStartup($202, WSAD);
 if EResult<>0 then Exit;
 FSock := Socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
 if FSock = Invalid_Socket then Exit;
 try
   Event := WSACreateEvent;
   WSAEventSelect(FSock,  Event,  FD_CONNECT or FD_READ or FD_WRITE or FD_CLOSE); //описана в Winsock2
   Connect(FSock, @Addr,  Sizeof(Addr));
   while True do
   begin
     ProcessMessages;
     case WSAWaitForMultipleEvents(1,@Event,False,{WSA_INFINITE}50,True) of
     WSA_WAIT_TIMEOUT:;
     0:
       begin
         //Ev.lNetworkEvents:=FD_CONNECT or FD_READ or FD_WRITE or FD_CLOSE;
         EResult := WSAEnumNetworkEvents(FSock, Event, @Ev);
         if EResult <> 0 then Break;
         if GetEvent(Ev.lNetworkEvents,FD_READ)  then
         begin
           if Ev.iErrorCode[FD_READ_BIT]<>0 Then
           begin
             if Assigned(FOnErrorEvent) then
               FOnErrorEvent(Self, Ev.iErrorCode[FD_READ_BIT]);
             Break;
           end
           else
           begin
             ioctlsocket(FSock, FIONREAD, Size);
             SetLength(Buff, Size);
             Recv(FSock,  Buff[0], Size, 0);
             if Assigned(FOnReadEvent) then
               FOnReadEvent(Self, Buff, Size);
           end;
         end;
         if GetEvent(Ev.lNetworkEvents,FD_WRITE) then
         begin
           if Ev.iErrorCode[FD_WRITE_BIT] <> 0 then
           begin
             if Assigned(FOnErrorEvent) then
               FOnErrorEvent(Self, Ev.iErrorCode[FD_WRITE_BIT]);
             Break;
           end
           else
           begin
             if Assigned(FOnWriteEvent) then
               FOnWriteEvent(Self);
           end;
         end;
         if GetEvent(Ev.lNetworkEvents,FD_CONNECT) then
         begin
           if Ev.iErrorCode[FD_CONNECT_BIT]<>0 Then
           begin
             if Assigned(FOnErrorEvent) then
               FOnErrorEvent(Self, Ev.iErrorCode[FD_CONNECT_BIT]);
             Break;
           end
           else
           begin
             FSocketState := wsConnected;
             if Assigned(FOnConnectEvent) then
               FOnConnectEvent(Self);
           end;
         end;
         if GetEvent(Ev.lNetworkEvents,FD_CLOSE)Then
         begin
           if Ev.iErrorCode[FD_CLOSE_BIT]<>0 Then
           begin
             if Assigned(FOnErrorEvent) then
               FOnErrorEvent(Self, Ev.iErrorCode[FD_CLOSE_BIT]);
             Break;
           end
           else
           begin
             FSocketState := wsClosed;
             if Assigned(FOnDisconnectEvent) then
               FOnDisconnectEvent(Self);
             Break;
           end;
         end;
       end;
     end;
   end;
 finally
   WSACloseEvent(Event);
   CloseSocket(FSock);
   WSACleanup;
   SetLength(Buff, 0);
 end;
end;

procedure THXClientSocetThread.SendData(Data: array of byte; Len: Integer);
begin
 if FSocketState <> wsConnected then
 begin
   if Assigned(FOnErrorEvent) then
     FOnErrorEvent(Self, WSAENOTCONN);
   Exit;
 end;
 send(FSock, Data, Len, 0);
end;

procedure THXClientSocetThread.SendStr(Str: String);
begin
 if (Length(Str) = 0) and (FSocketState <> wsConnected) then
 begin
   if Assigned(FOnErrorEvent) then
     FOnErrorEvent(Self, WSAENOTCONN);
   Exit;
 end;
 send(FSock, Str[1], Length(Str), 0);
end;

procedure THXClientSocetThread.Disconnect;
begin
 closesocket(FSock);
end;

procedure THXClientSocetThread.ProcessMessages;
begin
 while Self.ProcessMessage do { loop };
end;

function THXClientSocetThread.ProcessMessage: Boolean;
var
 Msg : TMsg;
begin
 Result := False;
 if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
 begin
   Result := True;
   if Msg.Message = WM_QUIT then
     Terminate
   else
   begin
     TranslateMessage(Msg);
     DispatchMessage(Msg);
   end;
 end;
end;

end.


Страницы:   1 2 3

Наверх

Титульная страница Поиск, карта сайта Написать письмо