Форум: "Сети";
Текущий архив: 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