Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Сети";
Текущий архив: 2011.10.02;
Скачать: [xml.tar.bz2];

Вниз

Проблема с SendText в ctBlocking сокете   Найти похожие ветки 

 
Alex_C   (2009-05-12 22:39) [0]

Некоторое время назад я спрашивал о проблемах создания Socket"а в треде. Блягодаря примеру, взятому от сюда
http://delphimaster.net/view/6-1227604925/
все прекрасно работает. На прием.
Проблема возникает когда надо сделать SendText:

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

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


А вот куда SendText  вставлять?
пробовал отдельной ф-цией треда:

procedure TTelnetThread.SendText(S: string);
begin
 TelnetSocket.Socket.SendText(S);
end;

И из программы:

if Assigned(TelnetThread) then TTelnetThread.SendText("Строка, которую нужно передать");

Однако при этом вся программа зависала до того момента, пока от сервера не прийдет очередная порция данных. Понятно, что это не верно. А как надо?


 
Сергей М. ©   (2009-05-13 08:15) [1]


> Понятно, что это не верно


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


 
Сергей М. ©   (2009-05-13 08:17) [2]


> куда SendText  вставлять?


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


 
Alex_C   (2009-05-13 08:31) [3]


> Если нужно организовать "одновременные" прием и передачу
> с использованием одного и того же сокета, то блокирующий
> режим не подойдет.


Да Сергей! Вчера перелопатил кучу инфы по сокетам - тоже уже это понял.
Однако получается какой-то замкнутый круг:
1. Если хочешь "одновременно" прием-передача - используй не блокирующий сокет (а значит в отдельном треде его использовать нельзя) - но при этом программа "зависает" при подключении к серверу.
2. Хочешь, чтоб не зависала при подключении - используй сокет в отдельном треде, а значит блокирующий, но при этом нельзя организовать одновременно прием-передача.
Интересно, и как эту проблему в принципе решить? Или ее решить нельзя - или-или?


 
FireMan_Alexey ©   (2009-05-13 08:55) [4]

Select помойму никто не отменял :)


> 1. Если хочешь "одновременно" прием-передача - используй
> не блокирующий сокет (а значит в отдельном треде его использовать
> нельзя) - но при этом программа "зависает" при подключении
> к серверу.
>

Кто тебе сказал, что нельзя использовать? (плюнь ему в лицо)
Вот пример использования неблок. сокета в отдельном потоке
Думаю шапку сам до пишеш :)

...
 //

А если не в отдельном потоке используй оконную функцию WSAAsyncSelect,
а чтобы программа не зависала переводи в асинхронный режим до коннекта, а потом лови сообщение FD_CONNECT!

Сергей если что меня поправит :), просто я уже давно не использую оконные функции.

А конструкция с простым Select-ом тебя чем не устраивает?

 Addr.sin_family:=AF_INET;
 Addr.sin_port:=HtoNS(2000);
 Addr.sin_addr.S_addr:=inet_addr("127.0.0.1");
 EResult:=WSAStartup($202,WSAD);
 If EResult<>0 Then Exit;
 Sock:=Socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
 IF Sock=Invalid_Socket Then Exit;
 Event:=WSACreateEvent;
 WSAEventSelect(Sock,Event,FD_CONNECT or FD_READ or FD_WRITE or FD_CLOSE); //описана в Winsock2
 Connect(Sock,@Addr,Sizeof(addr));
 While True do
 Begin
   Application.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(Sock,Event,@Ev);
         IF EResult<>0 Then Break;
         IF GetEvent(Ev.lNetworkEvents,FD_READ)Then
           Begin
             IF Ev.iErrorCode[FD_READ_BIT]<>0 Then
               Begin
                 Log.Items.Add("READ ERROR: "+IntToStr(Ev.iErrorCode[FD_READ_BIT]));
                 Break;
               End
             Else
               Begin
                 ioctlsocket(Sock,FIONREAD,Size);
                 SetLength(Buff,Size);
                 Recv(Sock,buff[1],Size,0);
                 Log.Items.Add("READ "+IntToStr(Size)+" "+Buff);
               End;  
           End;
         IF GetEvent(Ev.lNetworkEvents,FD_WRITE)Then
           Begin
             IF Ev.iErrorCode[FD_WRITE_BIT]<>0 Then
               Begin
                 Log.Items.Add("WRITE ERROR: "+IntToStr(Ev.iErrorCode[FD_WRITE_BIT]));
                 Break;
               End
             Else
               Log.Items.Add("WRITE");
           End;
         IF GetEvent(Ev.lNetworkEvents,FD_CONNECT)Then
           Begin
             IF Ev.iErrorCode[FD_CONNECT_BIT]<>0 Then
               Begin
                 Log.Items.Add("CONNECT ERROR: "+IntToStr(Ev.iErrorCode[FD_CONNECT_BIT]));
                 Break;
               End
             Else
               Log.Items.Add("CONNECT");
           End;
         IF GetEvent(Ev.lNetworkEvents,FD_CLOSE)Then
           Begin
             IF Ev.iErrorCode[FD_CLOSE_BIT]<>0 Then
               Begin
                 Log.Items.Add("CLOSE ERROR: "+IntToStr(Ev.iErrorCode[FD_CLOSE_BIT]));
                 Break;
               End
             Else
               Begin
                 Log.Items.Add("CLOSE");
                 Break;
               End;
           End;
       End;
   End;
 End;
 WSACloseEvent(Event);
 CloseSocket(Sock);
 WSACleanup;
...


 
FireMan_Alexey ©   (2009-05-13 08:57) [5]

Чего-то я запарился с коментариями, они должны были идти после кода :)


 
FireMan_Alexey ©   (2009-05-13 09:02) [6]

Да
Function GetEvent(L,L1:Integer):Boolean;
Begin
 Result:=(L and L1)<>0;
End;

Сильно не ругайте код, он был написан для Butto1Click просто для демонстрации :)
поэтому присутствуют всякие Application и другие несуразности...
Но после не долгой переработки может стать даже очень работоспособным для потока.


 
Сергей М. ©   (2009-05-13 09:23) [7]


> блокирующий сокет .. в отдельном треде его использовать нельзя


Сокету в любом режиме абсолютно фиолетово, в каком потоке его используют - в любом потоке он будет успешно работать.

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


 
Slym ©   (2009-05-13 12:22) [8]

const
 winsocket = "wsock32.dll";
type PWSAEvent=^TWSAEvent;
TWSAEvent=THandle;
function WSACreateEvent:TWSAEvent;stdcall; external winsocket name "WSACreateEvent";
function WSACloseEvent(hEvent:TWSAEvent):BOOL;stdcall; external winsocket name "WSACloseEvent";
function WSASetEvent(hEvent:TWSAEvent):BOOL;stdcall; external winsocket name "WSASetEvent";
function WSAResetEvent(hEvent:TWSAEvent):BOOL;stdcall; external winsocket name "WSAResetEvent";
function WSAEventSelect(hSocket: TSocket; hEventObject: TWSAEvent; lNetworkEvents: LongInt): integer;stdcall; external winsocket name "WSAEventSelect";

procedure TPortMapClientThread.ClientExecute;
var
 events:array[0..1] of THandle;
 WaitResult:DWORD;
begin
 try
   events[0]:=WSACreateEvent;
   events[1]:=WriteEvent;
   WSAEventSelect(ClientSocket.SocketHandle,events[0],SD_RECEIVE);

   WaitResult:=WaitForMultipleObjects(2,@events,false,INFINITE);
   case WaitResult of
     WAIT_OBJECT_0:  Read;WSAResetEvent(events[0]);
     WAIT_OBJECT_0+1: Write;WSAResetEvent(events[1]);
   else
   end;
..........
end;

procedure TTelnetThread.SendText(S: string);
begin
 FToSend:=FToSend+S;
 SetEvent(WriteEvent);
end;



 
Slym ©   (2009-05-13 12:23) [9]

Slym ©   (13.05.09 12:22) [8]
SD_RECEIVE

FD_READ!!!


 
Slym ©   (2009-05-13 12:26) [10]

или уйти в сторону overlapped операций хотя там теже евенты...


 
Slym ©   (2009-05-13 12:35) [11]

Slym ©   (13.05.09 12:22) [8]
переводит в неблокирующий режим!
после выяснений "кто-почем" перевести обратно в блокирующий
WSAEventSelect(ClientSocket.SocketHandle,nil,0);
ioctlsocket(ClientSocket.SocketHandle,FIONBIO,0);


 
Alex_C   (2009-05-20 16:43) [12]

Здравствуйте!
Всем большое спасибо за советы!
Благодаря им я написал ClientSocketThread.
Приведу текс модуля полностью - думаю он многим будет интересен:


unit HXClientSocetThreadUnit;

interface

uses
 Windows, Messages, SysUtils, Classes, WinSock2, ScktComp;

type
 TSocketState = (wsNone, wsConnected, wsClosed);

 THXOnConnectEvent = procedure(Sender: TObject) of object;
 THXOnDisconnectEvent = procedure(Sender: TObject) of object;
 THXOnReadEvent = procedure(Sender: TObject; Buff: array of byte;
   Count: Integer) of object;
 THXOnWriteEvent = procedure(Sender: TObject) of object;
 THXOnErrorEvent = procedure(Sender: TObject; Error: Integer) of object;
 THXOnDestroyEvent = procedure(Sender: TObject) of object;

 THXClientSocetThread = class(TThread)
   Host: string;
   Port: Cardinal;
 private
   function ProcessMessage : Boolean;
   procedure ProcessMessages;
 protected
   FSock: Cardinal;
   FSocketState: TSocketState;
   FOnConnectEvent   : THXOnConnectEvent;
   FOnDisconnectEvent: THXOnDisconnectEvent;
   FOnReadEvent      : THXOnReadEvent;
   FOnWriteEvent     : THXOnWriteEvent;
   FOnErrorEvent     : THXOnErrorEvent;
   FOnDestroyEvent   : THXOnDestroyEvent;
   procedure Execute; override;
 public
   constructor Create(iAddr: string;  iPort: Cardinal);
   destructor Destroy; override;
   procedure SendData(Data: array of byte; Len: Integer);
   procedure SendStr(Str: String);
   procedure Disconnect;
   property OnConnectEvent   : THXOnConnectEvent read FOnConnectEvent write FOnConnectEvent;
   property OnDisconnectEvent: THXOnDisconnectEvent read FOnDisconnectEvent write FOnDisconnectEvent;
   property OnReadEvent      : THXOnReadEvent read FOnReadEvent write FOnReadEvent;
   property OnWriteEvent     : THXOnWriteEvent read FOnWriteEvent write FOnWriteEvent;
   property OnErrorEvent     : THXOnErrorEvent read FOnErrorEvent write FOnErrorEvent;
   property OnDestroyEvent   : THXOnDestroyEvent read FOnDestroyEvent write FOnDestroyEvent;
 end;

type
 PWSAEvent=^TWSAEvent;
 TWSAEvent=THandle;

const
 fd_max_events = 10;

type
 TWSANetworkEvents = record
   lNetworkEvents: longint;
   iErrorCode: array[0..fd_max_events-1] of integer;
 end;

 PWSANetworkEvents = ^TWSANetworkEvents;

implementation

uses
 Unit1;



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


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.


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

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


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

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


 
Empleado ©   (2009-05-20 18:19) [16]


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

А это зачем?


 
Empleado ©   (2009-05-20 18:41) [17]

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

> 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; .......


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


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


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

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


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


> Alex_C


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


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


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


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


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

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

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


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


> Сергей М.


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

На счет [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:53) [23]


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


Можно и так.


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


Именно !

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


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

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

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


 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-21 09:46) [25]

"Можно и так" [см. 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 параметр написано pHandles - вроде как по названию,
>  что это указатель на хендл


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

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

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

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

Points to an array of event object 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
..
WSAEWOULDBLOCK The socket is marked as nonblocking and the requested operation would block


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

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

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

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


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


> Сергей М.

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

Вообще просто огромное спасибо за помощь!
Вообще модераторам в этом разделе эту тему как 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 11:03) [27]


> Alex_C   (21.05.09 10:36) [26]


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


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


Можно и так.

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


 
FireMan_Alexey ©   (2009-05-21 15:12) [28]

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


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


> Сергей М.


По поводу [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] - то все верно!


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

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

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


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


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


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


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


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

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


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

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


 
Сергей М. ©   (2009-05-21 16:02) [33]


> FireMan_Alexey ©   (21.05.09 15:43) [32]


А смысл ?
MsgWaitForMultipleObjects обязана немедленно вернуть управление вызывающему коду, как только очередь сообщений будет не пуста, вне зависимости от указанного значения таймаута, будь там даже и INFINITE.


 
Alex_C   (2009-05-21 16:18) [34]


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


Да вот как бы и я это понимаю. Только пока не понял, почему такое происходит... Буду разбираться!


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


Уже на эти грабли наступил ))) Никак не могу добиться, чтоб произошло событие FD_CLOSE.


 
Сергей М. ©   (2009-05-21 16:21) [35]


> Alex_C   (21.05.09 16:18) [34]


Кто будет посылать WM_QUIT твоему потоку ?


 
FireMan_Alexey ©   (2009-05-21 16:24) [36]

Хочешь кину тебе исходник моего класса сокета специально для потока?
Он сделан по другому...


 
FireMan_Alexey ©   (2009-05-21 16:25) [37]


> Сергей М.

Смысл в Terminated!


 
FireMan_Alexey ©   (2009-05-21 16:44) [38]


> Сергей М.


Извиняюсь, а Terminated у него нет :)


> Alex_C  


Подумай, что для тебя(Вас) лучше:

While True do
или
While not Terminated do ?


 
Сергей М. ©   (2009-05-21 16:48) [39]


> FireMan_Alexey ©   (21.05.09 16:25) [37]


Чтобы поток имел возможность проверить флаг Terminated, ф-ция должна "отвиснуть")

Чтобы она "отвисла", нужно послать потоку сообщение (или должно просигналить ожидаемое событие), при этом ф-ция "отвиснет" ВНЕ зависимости от значения таймаута.

Если событие не сигналит бесконечно долгое время, вопрос сводится лишь к тому кто пошлет потоку сообщение. Судя по [13] автор ожидает, что это самое WM_QUIT свалится в очередь его потока с какой-то луны.

Потому я и заострил на этом внимание)


 
FireMan_Alexey ©   (2009-05-21 16:55) [40]

Я понял, поэтому и предложил использовать с определенной задержкой, а не бесконечной! :)
У Автора будет еще много вопросов :), хотя в [4] я предлагал использовать Select. Со своего опыта знаю, что с этой функцией гемора меньше :)


 
FireMan_Alexey ©   (2009-05-21 17:20) [41]


> Alex_C

Чтобы первое пришло FD_CONNECT поменяй местами If-ы :)


 
Alex_C   (2009-05-21 21:36) [42]


> FireMan_Alexey ©   (21.05.09 16:24) [36]
> Хочешь кину тебе исходник моего класса сокета специально
> для потока?
> Он сделан по другому...


Очень хочу!
Вообще хочу достаточно досканально разобраться с работой сокетов, т.к. в дальнейшем предстоит решить при их помощи достаточно серьезную задачу.


 
Alex_C   (2009-05-21 21:56) [43]


> Чтобы первое пришло FD_CONNECT поменяй местами If-ы :)


Ну это тут не причем  )))
Последовательность прихода сообщений от последовательности if-ов не зависит. Хотя тут вот тоже пока непонятность - почему меняется последовательность?


 
Сергей М. ©   (2009-05-22 08:39) [44]


> почему меняется последовательность?
>


Ничего она не меняется, не выдумывай.

FD_CONNECT всегда возбуждается первым, вне зависимости будут ли при этом одновременно возбуждаться FD_WRITE и FD_READ.

Поставь строку

if GetEvent(Ev.lNetworkEvents,FD_CONNECT) then

на самый верх цепочки строк-проверок, как тебе сказал FireMan_Alexey ©, и убедись в этом)

FD_WRITE же обычно возбуждается одновременно с FD_CONNECT.

Одновременно взведенные флаги FD_CONNECT | FD_WRITE показывают, что соединение установлено и готово к передаче.

При проверке событийных флагов следует обязателоьно проверять соответствующие событиям коды ошибок. Так, например, попытка коннекта, приведшая к событию FD_CONNECT, еще не говорит об успешном коннекте. Об успешности коннекта можно судить только проанализировав Ev.iErrorCode[FD_CONNECT_BIT]. Тоже самое касается и всех прочих событий без исключения.


 
Alex_C   (2009-05-22 11:26) [45]


> Ничего она не меняется, не выдумывай.


Честно признаюсь - выдумал )))
Сейчас все внимательно проверил и нашел у себя ошибку! Естественно событие коннект приходит первым и никак иначе.


> Об успешности коннекта можно судить только проанализировав
> Ev.iErrorCode[FD_CONNECT_BIT]. Тоже самое касается и всех
> прочих событий без исключения.


Я так и делаю:


      if GetEvent(Ev.lNetworkEvents,FD_CONNECT) then
         begin
           if Ev.iErrorCode[FD_CONNECT_BIT]<>0 Then
           begin
             if Assigned(FOnErrorEvent) then
               Synchronize(OnErrorEventProcedure);
             Break;
           end
           else
           begin
             FSocketState := wsConnected;
             if Assigned(FOnConnectEvent) then
               Synchronize(OnConnectProcedure);
           end;
         end;


 
FireMan_Alexey ©   (2009-05-22 13:41) [46]

За код не ругать делал для себя:

unit NewSock;

interface
Uses WinSock;

CONST
 SOCKET_ERROR=WINSOCK.SOCKET_ERROR;
   
Type
 TNewSock=Class
 Private
   FSock    :Integer;
   FClose   :Boolean;
   FError   :Integer;
   FCanRead :Boolean;
   FCanWrite:Boolean;
   FOnError :Boolean;
   FAddr    :TSockAddr;
   FHost    :String;
   FPort    :Word;
 Public
   Procedure Close;
   Procedure GetStatus(Const TimeOut:Integer=50);
   Function  GetRemoteAddress:Integer;
   Function  GetRemoteHost:String;
   Function  Connect:Boolean;
   Function  Write(Var Buff; Size:Integer):Integer;
   Function  Read (Var Buff; Size:Integer):Integer;
   Function  ReceiveLength:Integer;
   Constructor Create(Const Sock:Integer=Invalid_Socket);
   Destructor  Destroy;Override;
   Property Host:String Read FHost Write FHost;
   Property Port:Word Read FPort Write FPort;
   Property CanRead:Boolean Read FCanRead;
   Property CanWrite:Boolean Read FCanWrite;
   Property Disconnected:Boolean Read FClose;
   Property ErrorPresent:Boolean Read FOnError;
   Property Error:Integer Read FError;
   Property SocketHandle:TSocket Read FSock;
 End;

Function InetAddr(Addr:Integer):String;

implementation

Constructor TNewSock.Create;
Begin
 Inherited Create;
 FClose:=Sock=Invalid_Socket;
 FSock:=Sock;
 {IF Not FClose Then
   Begin
     FCanWrite:=True;
   End
 Else}
 FCanWrite:=False;
 FCanRead:=False;
 FOnError:=False;
 FError:=0;    
End;

Destructor  TNewSock.Destroy;
Begin
//
 Close;
 Inherited Destroy;
End;

Procedure TNewSock.Close;
Begin
 If FSock<>Invalid_Socket Then
   Begin
     If CloseSocket(FSock)<>0 Then
       Begin
         FError:=WSAGetLastError;
         FOnError:=True;
       End;
     FSock:=Invalid_Socket;
     FCanRead:=False;
     FCanWrite:=False;
   End;
 FClose:=True;
End;

Procedure TNewSock.GetStatus;
Var
 FD_READS,FD_WRITES,FD_ERROR:TFDSET;
 T:TTimeVal;
 R:Integer;
Begin
//
 If FClose Then Exit;
 T.tv_usec:=TimeOut Mod 1000;
 T.tv_sec:=TimeOut Div 1000;
 FD_ZERO(FD_READS);
 FD_SET(FSock,FD_READS);
 FD_WRITES:=FD_READS;
 FD_ERROR:=FD_READS;

 //FD_SET(FSock,FD_WRITES);

 R:=Select(1,@FD_READS,@FD_WRITES,@FD_ERROR,@T);
 If R=Socket_Error Then
   Begin
     FError:=WSAGetLastError;
     FOnError:=True;
     Exit;
   End
 Else FError:=0;
 If R>0 Then
   Begin
     If FD_ISSET(FSock,FD_READS) Then FCanRead:=True
       Else FCanRead:=False;
     If FD_ISSET(FSock,FD_WRITES) Then FCanWrite:=True;
     If FD_ISSET(FSock,FD_ERROR) Then
       Begin
         FOnError:=True;
         R:=SizeOf(FError);
         R:=GetSockOpt(FSock,SOL_SOCKET,SO_ERROR,@FError,R);
         {If R=SOCKET_ERROR Then
           FError:=WSAGetLastError;}
       End
     Else
       FOnError:=False;  
   End;
End;

Function  TNewSock.GetRemoteAddress;
Var
 _Addr:TSockAddr;
 _Size:Integer;
Begin
 Result:=-1;
 If Not FClose Then
   Begin
     _Size:=SizeOf(_Addr);
     FError:=GetPeerName(FSock,_Addr,_Size);
     If FError=SOCKET_ERROR Then
       Begin
         FError:=WSAGetLastError;
         FOnError:=True;
         Exit;
       End;
     Result:=_Addr.sin_addr.S_addr;  
   End;
End;

Function  TNewSock.GetRemoteHost;
Var
 _Addr:Integer;
 Host:PHostEnt;
Begin
 _Addr:=GetRemoteAddress;
 Host:=GetHostByAddr(@_Addr,SizeOf(_Addr),AF_INET);
 Result:="";
 If Host<>Nil Then Result:=Host.h_name;
End;

Function  TNewSock.Write;
Var
 R:Integer;
Begin
 R:=Send(FSock,Buff,Size,0);
 If R=SOCKET_ERROR Then
   Begin
     FError:=WSAGetLastError;
     If FError=WSAEWOULDBLOCK Then
       Begin
         FCanWrite:=False;
         R:=0;
         FError:=0;
       End
     Else FOnError:=True;
   End;
 Result:=R;  
End;

Function  TNewSock.Read;
Var
 R:Integer;
Begin
 R:=Recv(FSock,Buff,Size,0);
 If R=0 Then
   Close;
 If R=SOCKET_ERROR Then
   Begin
     FError:=WSAGetLastError;
     If FError=WSAEWOULDBLOCK Then
       Begin
         FCanRead:=False;
         FError:=0;
       End
     Else FOnError:=True;  
     R:=0;
   End;
 Result:=R;  
End;

Function  TNewSock.ReceiveLength;
Begin
 If IOCTLSocket(FSock,FIONREAD,Result)=SOCKET_ERROR Then
   Begin
     FError:=WSAGetLastError;
     FOnError:=True;
   End;
End;

Function  TNewSock.Connect;
Var
 PHost:PHostEnt;
 R:Integer;
Begin
 Result:=False;
 If Not FClose Then Exit;
 If FHost="" Then Exit;
 If FPort=0 Then Exit;
 FSock:=Socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
 If FSock=Invalid_Socket Then
   Begin
     FError:=WSAGetLastError;
     FOnError:=True;
     Close;
     Exit;
   End;
 FAddr.sin_family:=AF_INET;
 FAddr.sin_port:=HToNS(FPort);
 FAddr.sin_addr.S_addr:=Inet_addr(PChar(FHost));
 If FAddr.sin_addr.S_addr=INADDR_NONE Then
   Begin
     PHost:=GetHostByName(PChar(FHost));
     If PHost=Nil Then
       Begin
         FError:=WSAGetLastError;
         FOnError:=True;
         Exit;
       End;
     Move(PHost^.h_addr_list^[0],
          FAddr.sin_addr,
          PHost^.h_length);
   End;
 R:=WinSock.Connect(FSock,FAddr,SizeOF(FAddr));
 If R=SOCKET_ERROR Then
   Begin
     FError:=WSAGetLastError;
     FOnError:=True;
     Exit;
   End;
 FClose:=False;
 Result:=True;
End;

Function InetAddr;
Var
 _Addr:TSockAddr;
Begin
 _Addr.sin_addr.S_addr:=Addr;
 Result:=inet_ntoa(_Addr.sin_addr);
End;

VAR
 WSAD:TWSAData;

Initialization
Begin
 WSAStartup($202,WSAD);
End;

Finalization
Begin
 WSACleanup;
End;

end.


 
FireMan_Alexey ©   (2009-05-22 13:46) [47]

Используем так:
...
TvoySocket:=TNewSock.Create;

TvoySocket.Host:="127.0.0.1";
TvoySocket.Port:=???;
If Not TvoySocket.Connect Then
 Begin
    Error:=TvoySocket.Error;
    TvoySocket.Close;
 End;
While not Terminated Do
 Begin
    TvoySocket.GetStatus;
    If TvoySocket.CanRead Then  //Chitaem ;
    If tvoySocket.CanWrite Then //Pishem;
    If TvoySocket.ErrorPresent Then //Error!;  
 End;
TvoySocket.Free;
...


 
FireMan_Alexey ©   (2009-05-22 13:54) [48]

If Not TvoySocket.Connect Then
Begin
   Error:=TvoySocket.Error;
   TvoySocket.Close;
   Exit;//Забыл :)
End;


 
FireMan_Alexey ©   (2009-05-22 14:00) [49]

Да и:
If TvoySocket.ErrorPresent Then //Error ! ;
Можно первым поставить :)


 
FireMan_Alexey ©   (2009-05-22 14:45) [50]

Да еще забыл, там нужна строчка:

If TvoySocket.Disconnected Then Break;


 
Alex_C   (2009-05-22 21:13) [51]


> FireMan_Alexey


Спасибо! Очень интересная реализация! Буду изучать!
После чего задам вопросы )))


 
FireMan_Alexey ©   (2009-05-23 12:28) [52]

Не забывай, что при CanRead необходимо проверить ReceiveLength(Сколько можно прочитать) и когда CanRead=False читать не нужно, а то будет как у тебя в TelnetSocket!



Страницы: 1 2 вся ветка

Форум: "Сети";
Текущий архив: 2011.10.02;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.69 MB
Время: 0.005 c
1-1267355128
AlexChudd
2010-02-28 14:05
2011.10.02
Выделение ячейки в StringGrid по нажатию правой кнопки мыши


1-1267450439
apic
2010-03-01 16:33
2011.10.02
Как при компиляции автоматически поместить исходный код


2-1307726626
Exterr
2011-06-10 21:23
2011.10.02
Создание кнопок и меню в рантайм


15-1307392194
Юрий
2011-06-07 00:29
2011.10.02
С днем рождения ! 7 июня 2011 вторник


15-1307548488
Rouse_
2011-06-08 19:54
2011.10.02
Может и боян, но все-же хочется поделиться ссылочкой :)





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский