![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Проблема с SendText в ctBlocking сокете [D7, WinXP]
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.