Winsock recv () функция блокирует другие потоки - PullRequest
0 голосов
/ 02 января 2019

Я пишу простое приложение для Windows TCP / IP-сервера, которое одновременно должно взаимодействовать только с одним клиентом.Мое приложение имеет четыре потока:

  1. Основная программа, которая также обрабатывает передачу данных по мере необходимости.
  2. Получение входящего потока данных.
  3. Поток прослушивания для приема запросов на подключение отклиент.
  4. Поток ping, который отслеживает все остальное и при необходимости передает сообщения пульса.Я понимаю, что последнее не должно быть действительно необходимым с TCP / IP, но клиентское приложение (над которым я не имею никакого контроля) требует этого.

Я подтвердил в диспетчере задач, что мое приложениедействительно работает четыре потока.

Я использую блокировку сокетов TCP / IP, но, насколько я понимаю, они блокируют только вызывающий поток - другим потокам все равно следует разрешить выполнение без блокировки.Однако я столкнулся со следующими проблемами:

  1. Если поток проверки связи считает, что соединение прервано, он вызывает closesocket ().Однако, похоже, что он заблокирован вызовом recv () в получающем потоке.

  2. Основное приложение не может передавать данные, в то время как принимающий поток имеет вызов recv () выполняется.

Сокет создается с помощью функции accept ().На данном этапе я не устанавливаю никаких опций сокетов.

Я сейчас создал простую двухпоточную программу, которая иллюстрирует проблему.Без флага WSA_FLAG_OVERLAPPED второй поток блокируется первым потоком, даже если это будет противоречить тому, что должно произойти.Если установлен флаг WSA_FLAG_OVERLAPPED, то все работает так, как я и ожидал.

PROJECT SOURCE FILE:
====================

program Blocking;

uses
  Forms,
  Blocking_Test in 'Blocking_Test.pas' {Form1},
  Close_Test in 'Close_Test.pas';

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end. { Blocking }

UNIT 1 SOURCE FILE:
===================

unit Blocking_Test;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, WinSock2;

type
  TForm1 = class(TForm)
    procedure FormShow(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  Test_Socket: TSocket;
  Test_Addr: TSockAddr;
  wsda: TWSADATA; { used to store info returned from WSAStartup }

implementation

{$R *.dfm}

uses
  Debugger, Close_Test;

procedure TForm1.FormShow(Sender: TObject);
const
  Test_Port: word = 3804;
var
  Buffer: array [0..127] of byte;
  Bytes_Read: integer;
begin { TForm1.FormShow }
  Debug('Main thread started');
  assert(WSAStartup(MAKEWORD(2,2), wsda) = 0); { WinSock load version 2.2 }
  Test_Socket := WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, nil, 0, 0{WSA_FLAG_OVERLAPPED});
  assert(Test_Socket <> INVALID_SOCKET);
  with Test_Addr do
  begin
    sin_family := AF_INET;
    sin_port := htons(Test_Port);
    sin_addr.s_addr := 0; { this will be filled in by bind }
  end; { with This_PC_Address }
  assert(bind(Test_Socket, @Test_Addr, SizeOf(Test_Addr)) = 0);
  Close_Thread := TClose_Thread.Create(false); { start thread immediately }
  Debug('B4 Rx');
  Bytes_Read := recv(Test_Socket, Buffer, SizeOf(Buffer), 0);
  Debug('After Rx');
end; { TForm1.FormShow }

end. { Blocking_Test }

UNIT 2 SOURCE FILE:
===================

unit Close_Test;

interface

uses
  Classes;

type
  TClose_Thread = class(TThread)
  protected
    procedure Execute; override;
  end; { TClose_Thread }

var
  Close_Thread: TClose_Thread;

implementation

uses
  Blocking_Test, Debugger, Windows, WinSock2;

type
  TThreadNameInfo = record
    FType: LongWord;     // must be 0x1000
    FName: PChar;        // pointer to name (in user address space)
    FThreadID: LongWord; // thread ID (-1 indicates caller thread)
    FFlags: LongWord;    // reserved for future use, must be zero
  end; { TThreadNameInfo }

var
  ThreadNameInfo: TThreadNameInfo;

procedure TClose_Thread.Execute;

  procedure SetName;
  begin { SetName }
    ThreadNameInfo.FType := $1000;
    ThreadNameInfo.FName := 'Ping_Thread';
    ThreadNameInfo.FThreadID := $FFFFFFFF;
    ThreadNameInfo.FFlags := 0;
    try
      RaiseException( $406D1388, 0, sizeof(ThreadNameInfo) div sizeof(LongWord), @ThreadNameInfo );
    except
    end; { try }
  end; { SetName }

begin { TClose_Thread.Execute }
  Debug('Close thread started');
  SetName;
  sleep(10000); { wait 10 seconds }
  Debug('B4 Close');
  closesocket(Test_Socket);
  Debug('After Close');
end; { TClose_Thread.Execute }

end. { Close_Test }

PS Поскольку установка атрибута WSA_FLAG_OVERLAPPED устранила проблему, я опубликовал приведенное выше для академического интереса.

Ответы [ 2 ]

0 голосов
/ 03 января 2019

UPDATE: accept () создает новый сокет с теми же атрибутами, что и сокет, используемый для прослушивания. Поскольку я не установил атрибут WSA_FLAG_OVERLAPPED для сокета прослушивания, этот атрибут не был установлен для нового сокета, а такие параметры, как время ожидания приема, ничего не сделали.

Установка атрибута WSA_FLAG_OVERLAPPED для сокета прослушивания, похоже, устранила проблему. Таким образом, теперь я могу использовать тайм-аут приема, и потоку Ping больше не нужно закрывать сокет, если данные не были получены.

Установка атрибута WSA_FLAG_OVERLAPPED для сокета прослушивания также, похоже, решает проблему блокировки других потоков.

0 голосов
/ 02 января 2019

Если поток проверки связи считает, что соединение прервано, он вызывает closesocket ().Однако, похоже, что он заблокирован вызовом recv () в потоке получения.

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

Для ясности, вы никак не можете знать, что этот код может сделать.Например, рассмотрим:

  1. Поток на самом деле еще не вызвал recv, это о , чтобы вызвать recv, но планировщик еще не дошел до него.
  2. Другой поток вызывает closesocket.
  3. Поток, являющийся частью системной библиотеки, открывает новый сокет и получает тот же дескриптор сокета, который вы только что закрыли.
  4. Ваш поток теперь получает вызов recv, только он получает в сокете открытую библиотеку!

Вы несете ответственность за то, чтобы избежать подобных условий гонки, иначе ваш код будет вести себя непредсказуемо.Нет никакого способа узнать, каковы могут быть последствия выполнения случайных операций со случайными сокетами.Таким образом, вы должны не освобождать ресурс в одном потоке, в то время как другой поток, может быть, или (что хуже всего) может его использовать.

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

...