powered by simpleCommunicator - 2.0.36     © 2025 Programmizd 02
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Алгоритм вызова shutdown сокета
19 сообщений из 19, страница 1 из 1
Алгоритм вызова shutdown сокета
    #40118632
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Расскажите, как правильно и что я делаю не так.

Читаем здесь
1. Call WSAEventSelect to register for FD_CLOSE notification.
2. Call shutdown with how=SD_SEND.
3. When FD_CLOSE received, call the recv or WSARecv until the function completes with success and indicates that zero bytes were received. If SOCKET_ERROR is returned, then the graceful disconnect is not possible.
4. Call closesocket.Т.е. после вызова shutdown, к нам приходит FD_CLOSE, на него нужно прочитать все что осталось, а потом закрыть сокет. Делаю так
Инициализация
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
function SockCheck(ARes: Integer; ASkipNonBlock: Boolean): Integer;
var
  LError: Integer;
begin
  if ARes = SOCKET_ERROR then begin
    LError := WSAGetLastError;
    if not ASkipNonBlock or (LError <> WSAEWOULDBLOCK) then
      RaiseSocketError(LError);
  end;
  Result := ARes;
end;

function TForm1.TryConnect: TSocket;
begin
  Result := SockCheck(Winapi.WinSock2.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
  SockCheck(WSAAsyncSelect(Result, Handle, TM_SOCKET, FD_CONNECT or FD_CLOSE));
  SockCheck(Winapi.WinSock2.connect(Result, TSockAddr(LAddr), SizeOf(LAddr)), True);
end;


Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
procedure TForm1.TMConnect(var AMsg: TMessage);
var
  LSocket: TSocket;
  LError: Word;
  LEvent: Word;
  LRead: Integer;
  LBuf: array[0..511] of AnsiChar;
begin
  LSocket := AMsg.WParam;
  LError := WSAGETSELECTERROR(AMsg.LParam);
  LEvent := WSAGETSELECTEVENT(AMsg.LParam);
  case LEvent of
    FD_CONNECT: begin
      if LError <> 0 then begin
        Win32Check(closesocket(LSocket));
        TryConnect;
      end else begin
        SockCheck(shutdown(LSocket, SD_SEND));
      end;
    end;
    FD_CLOSE: begin
      repeat
        LRead := SockCheck(recv(LSocket, LBuf[0], Length(LBuf), 0));
      until LRead <= 0;
      SockCheck(closesocket(LSocket));
    end;
  end;
end;

Но FD_CLOSE ко мне уже приходит с LError = WSAECONNABORTED и recv возвращает WSAECONNRESET. Значит ли это, что код должен быть таким
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
FD_CLOSE: begin
  if LError <> 0 then begin
    repeat
      LRead := SockCheck(recv(LSocket, LBuf[0], Length(LBuf), 0));
    until LRead <= 0;
  end;
  SockCheck(closesocket(LSocket));
end;

?

И второй вопрос. Вот здесь алгоритм немного другой
Client sideServer side(1) Invokes shutdown(s, SD_SEND) to signal end of session and that client has no more data to send.(2) Receives FD_CLOSE, indicating graceful shutdown in progress and that all data has been received.(3) Sends any remaining response data.(local timing significance only) Gets FD_READ and calls recv to get any response data sent by server .(4) Invokes shutdown(s, SD_SEND) to indicate server has no more data to send.(5) Receives FD_CLOSE indication.(local timing significance only) Invokes closesocket .(6) Invokes closesocket.Т.е. здесь написано, что после shutdown() мы должны штатно обработать FD_READ, а потом когда придет FD_CLOSE просто закрыть сокет.

Если делаю так
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
  SockCheck(WSAAsyncSelect(Result, Handle, TM_SOCKET, FD_CONNECT or FD_CLOSE or FD_READ));
procedure TForm1.TMConnect(var AMsg: TMessage);
var
  LSocket: TSocket;
  LError: Word;
  LEvent: Word;
  LRead: Integer;
  LBuf: array[0..511] of AnsiChar;
begin
  LSocket := AMsg.WParam;
  LError := WSAGETSELECTERROR(AMsg.LParam);
  LEvent := WSAGETSELECTEVENT(AMsg.LParam);
  case LEvent of
    FD_CONNECT: begin
      if LError <> 0 then begin
        Win32Check(closesocket(LSocket));
        TryConnect;
      end else begin
        SockCheck(shutdown(LSocket, SD_SEND));
      end;
    end;
    FD_READ: begin
      repeat
        LRead := SockCheck(recv(LSocket, LBuf[0], Length(LBuf), 0));
      until LRead <= 0;
    end;
    FD_CLOSE:
      SockCheck(closesocket(LSocket));
  end;
end;

Тогда на FD_READ LError = 0, а сам recv возвращает WSAECONNRESET и после этого приходит FD_CLOSE с LError = WSAECONNABORTED

Так все таки, по какому алгоритму работать и как реагировать на ошибки?


С уважением, Vasilisk
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118636
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Vasilisk_Так все таки, по какому алгоритму работать и как реагировать на ошибки?

Я работаю по второму, поскольку первый подразумевает использование подписок и
прочую виндомуть, а у меня чисто BSD сокеты. Никаких ошибок не возникает, ты
закрываешь свой конец с помощью shutdown и когда другой конец согласится и
точно так же закроет свой - можно закрыть сокет.

То есть когда ты хочешь порвать соединение - делаешь shutdown(SD_SEND) и больше
ничего.

Когда получаешь 0 из recv() - делаешь shutdown(SD_SEND) (если не делал его
раньше и это тот конец инициализировал разрыв) + closesocket().

Всё. Судя по ошибкам - ты упустил из виду необходимость делать shutdown и на
другой стороне.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118656
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dimitry Sibiryakov
Судя по ошибкам - ты упустил из виду необходимость делать shutdown и на другой стороне.
Возможно. Другая сторона не моя.
Dimitry Sibiryakov
То есть когда ты хочешь порвать соединение - делаешь shutdown(SD_SEND) и больше ничего.
А closesocket когда?
Dimitry Sibiryakov
Когда получаешь 0 из recv() - делаешь shutdown(SD_SEND) (если не делал его раньше и это тот конец инициализировал разрыв) + closesocket().
Но сказано, что после shutdown нужно еще прочитать данные. Или это не обязательно?

И еще, этот сервер отсылает пакет сразу после коннекта. Вот этого пакета я не вижу, даже если перед shutdown поставить Sleep

Ну и еще вопрос - а зачем этот shutdown нужен? Или вызов closesocket может не дойти до удаленной стороны?
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118662
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Vasilisk_А closesocket когда?

После получения 0 из recv().

_Vasilisk_Но сказано, что после shutdown нужно еще прочитать данные.

После другого shutdown. У сокета есть дырка в которую ты пихаешь данные и дырка
из которой ты их читаешь. shutdown (по-хорошему) надо вызывать для каждой из
дырок в отдельности.

То есть:
1) shutdown для send-дырки - SD_SEND;
2) Читаешь из recv-дырки (вот тут должен прийти твой "пакет сразу после коннекта");
3) shutdown для recv-дырки (ну или сразу для обоих дырок - SD_BOTH - чтобы не
париться).
Теперь сокет официально закрыт и его можно подчистить вызовом closesocket().

_Vasilisk_Или это не обязательно?
В экстренных случаях, когда "бросай мешки, вокзал отходит", ты просто вызываешь
shutdown(SD_BOTH) + closesocket(). При этом ты теряешь недочитанные данные с той
стороны. Тот самый "пакет сразу после коннекта".

_Vasilisk_Ну и еще вопрос - а зачем этот shutdown нужен? Или вызов closesocket может не
дойти до удаленной стороны?

closesocket - чисто локальный вызов чтобы освободить хэндл, буфера и т.п., он не
уходит на удалённую сторону. Точнее уходит, но в виде RST-пакета если перед ним
не был отправлен FIN-пакет. Что и вызывает получение WSAЕCONNRESET на другой
стороне.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118674
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dimitry Sibiryakov
После получения 0 из recv().
А получаю WSAЕCONNRESET
Dimitry Sibiryakov
2) Читаешь из recv-дырки (вот тут должен прийти твой "пакет сразу после коннекта");
Так не вижу я его. После shutdown recv возвращает WSAECONNRESET
Dimitry Sibiryakov
Что и вызывает получение WSAЕCONNRESET на другой стороне.

Еще раз. Все происходит на клиенте.
1. После успешного коннекта я сразу (или после Sleep(1000)) я вызываю shutdown
2. Ко мне приходит FD_READ, но при попытке сделать recv() я получаю WSAЕCONNRESET
3. Когда ко мне приходит FD_CLOSE, оно уже приходит с ошибкой WSAECONNABORTED
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118678
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Все. Разобрался. Чуть позже распишу
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118681
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Еще вопрос:
Код: pascal
1.
shutdown(LSocket, SD_RECEIVE)

что делает?
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118695
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Vasilisk_что делает?

Закрывает дырку из которой читаешь. Дропает принятые, но непрочитанные данные.
При нормальных условиях никогда не используется.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118722
Barlone
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dimitry Sibiryakov

closesocket - чисто локальный вызов чтобы освободить хэндл, буфера и т.п., он не
уходит на удалённую сторону. Точнее уходит, но в виде RST-пакета если перед ним
не был отправлен FIN-пакет. Что и вызывает получение WSAЕCONNRESET на другой
стороне.
не обязательно https://docs.microsoft.com/en-us/windows/win32/winsock/graceful-shutdown-linger-options-and-socket-closure-2
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118742
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Vasilisk_
Разобрался.
Итак, что было. Сервер сам закрывал соединение при неактивности 10 секунд. В итоге, пока я смотрел отладчиком значения переменных, он сам закрывал сокет.

Если убрать отладчик, то вот такая схема работает

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
SockCheck(WSAAsyncSelect(Result, Handle, TM_SOCKET, FD_CONNECT or FD_CLOSE or FD_READ));

procedure TForm1.TMConnect(var AMsg: TMessage);
var
  LSocket: TSocket;
  LError: Word;
  LEvent: Word;
  LRead: Integer;
  LBuf: array[0..511] of AnsiChar;
begin
  LSocket := AMsg.WParam;
  LError := WSAGETSELECTERROR(AMsg.LParam);
  LEvent := WSAGETSELECTEVENT(AMsg.LParam);
  case LEvent of
    FD_CONNECT: begin
      Sleep(1000);
      SockCheck(shutdown(LSocket, SD_SEND));
    end;
    FD_READ: begin
      repeat
        LRead := recv(LSocket, LBuf[0], Length(LBuf), 0);
      until LRead <= 0;
    end;
    FD_CLOSE: begin
      SockCheck(closesocket(LSocket));
    end;
  end;
end;

Т.е. в чистом виде второй алгоритм. При этом первый recv читает данные, а второй возвращает WSAEWOULDBLOCK, что логично. А потом прилетает FD_CLOSE с ошибкой WSAECONNABORTED

Если же работать по первому алгоритму
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
  SockCheck(WSAAsyncSelect(Result, Handle, TM_SOCKET, FD_CONNECT or FD_CLOSE));

FD_CLOSE: begin
  repeat
    LRead := recv(LSocket, LBuf[0], Length(LBuf), 0);
  until LRead <= 0;
  SockCheck(closesocket(LSocket));
end;

То recv сразу возвращает WSAЕCONNRESET
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118748
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Vasilisk_
Код: sql
1.
2.
3.
      FD_CLOSE:begin
        SockCheck(closesocket(LSocket));
      end;


Вот тут перед closesocket должен вызываться shutdown(SD_BOTH) именно на случай,
если сервер решил закрыть коннект по собственной инициативе.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118764
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dimitry Sibiryakov
Вот тут перед closesocket должен вызываться shutdown(SD_BOTH) именно на случай, если сервер решил закрыть коннект по собственной инициативе.
Спасибо.

А теперь последний и очень дурацкий вопрос
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
function TForm1.TryConnect: TSocket;
var
  LAddr: TSockAddrIn;
  LBuf: array[0..255] of Byte;
begin
  FillChar(LAddr, SizeOf(LAddr), 0);
  LAddr.sin_family := AF_INET;
  LAddr.sin_addr.S_addr := inet_addr('192.168.1.100');  // Такого хоста в сети нет
  LAddr.sin_port := htons(21);

  Result := SockCheck(Winapi.WinSock2.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
  SockCheck(Winapi.WinSock2.connect(Result, TSockAddr(LAddr), SizeOf(LAddr)));
  SockCheck(recv(Result, LBuf[0], Length(LBuf), 0));
end;

connect возвращает 0, а recv - WSAЕCONNRESET.

Это как такое может быть? Причем это наблюдается для любого несуществующего хоста, но строго для 21 порта. Для остальных портов connect честно возвращает WSAETIMEDOUT. Что это за барабашка? На реальный сервер - коннект без проблем

Update: похоже это наблюдается только в 192.168.1.ХХХ сети, к которой я подключаюсь через VPN
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118771
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Vasilisk_Что это за барабашка?

WireShark в руки и смотреть с каким TTL приходит RST пакет от этого барабашки.
По результатам - прицельный дихлофос.

https://portal.nutanix.com/page/documents/kbs/details?targetId=kA00e000000LTA6CAO
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118805
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Vasilisk_
Код: sql
1.
2.
3.
4.
      FD_CONNECT:begin
        Sleep(1000);
        SockCheck(shutdown(LSocket, SD_SEND));
      end;


И ещё на всякий случай: спать именно здесь - очень плохая идея, поскольку
события сокета в его собственной очереди стакаются и ивенты приходят
упакованными, а ты их проверяешь на равенство вместо битовых операций, так что
можешь потерять некоторые (а то и все).

PS: CASE здесь использовать вообще нельзя. Надо проверять каждый отдельный бит,
причём FD_READ лучше проверять перед FD_CLOSE, ибо они часто приходят парой.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118872
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dimitry Sibiryakov
И ещё на всякий случай: спать именно здесь - очень плохая идея,
Это тестовое приложение
Dimitry Sibiryakov
PS: CASE здесь использовать вообще нельзя. Надо проверять каждый отдельный бит,
Если не ошибаюсь, то это верно для WSAEventSelect/WSAEnumNetworkEvents. Для WSAAsyncSelect The low word of lParam specifies the network event that has occurred.Единственное число. Т.е. события таки приходят по одному.

И коды ошибок в WSAEnumNetworkEvents приходят массивом, а здесь один код в старшем слове
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118920
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dimitry Sibiryakov
WireShark в руки и смотреть с каким TTL приходит RST пакет от этого барабашки.
В обоих пакетах туда и обратно стоит 128. Отличий от реального сервера я не вижу
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118935
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Чудит OpenVPN сервер. На сервере на VPN интерфейсе я эти пакеты вижу, а на внутреннем уже нет
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118953
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Vasilisk_В обоих пакетах туда и обратно стоит 128.

То есть вредитель прямо на первом хопе если вообще не локальный.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
Алгоритм вызова shutdown сокета
    #40118958
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dimitry Sibiryakov
если вообще не локальный.
Не локальный. Я же говорю, что до OpenVPN сервера пакеті доходят

А вообще для успешного соединения TTL тоже 128, хотя они проходят один шлюз.
...
Рейтинг: 0 / 0
19 сообщений из 19, страница 1 из 1
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Алгоритм вызова shutdown сокета
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]