我正在编写一个简单的Windows TCP / IP服务器应用程序,该应用程序一次只需要与一个客户端进行通信。我的应用程序有四个线程:
我在任务管理器中确认我的应用程序确实有四个线程在运行。
我正在使用阻塞TCP / IP套接字,但我的理解是它们仅阻塞调用线程–其他线程仍应被允许执行而不会被阻塞。但是,我遇到了以下问题:
如果ping线程认为连接已终止,则会调用closesocket()。但是,这似乎被接收线程中对recv()的调用所阻止。
当接收线程正在进行对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 }
P.S。由于设置WSA_FLAG_OVERLAPPED属性已解决了该问题,因此出于学术兴趣,我发布了以上内容。
答案 0 :(得分:2)
如果ping线程认为连接已终止,它将调用closesocket()。但是,这似乎被接收线程中对recv()的调用所阻止。
那只是代码中的错误。当一个线程正在使用或可能正在使用另一个线程时,您无法在一个线程中释放资源。您将必须安排一些明智的方法,以确保不会在访问套接字的过程中产生竞争条件。
要清楚,您无法知道这种代码可以做什么。例如,考虑:
recv
,调用recv
大约是,但是调度程序尚未解决。closesocket
。recv
,只有它在库打开的套接字上接收!您有责任避免此类竞争情况,否则您的代码将无法正常运行。您无法知道在随机套接字上执行随机操作的后果。因此,您必须 不在一个线程中释放资源,而另一个线程正在使用,可能正在使用(或者最糟糕)可能正在使用该资源。
最有可能发生的实际情况是,Delphi进行了某种内部同步,试图通过阻止无法安全地前进的线程来使您免于灾难。
答案 1 :(得分:0)
更新:accept()创建具有与用于侦听的套接字相同属性的新套接字。由于我尚未为监听套接字设置WSA_FLAG_OVERLAPPED属性,因此未为新套接字设置此属性,并且诸如接收超时之类的选项也无济于事。
为侦听套接字设置WSA_FLAG_OVERLAPPED属性似乎已解决了该问题。这样,我现在就可以使用接收超时了,如果没有接收到数据,Ping线程就不再需要关闭套接字。
为侦听套接字设置WSA_FLAG_OVERLAPPED属性似乎也解决了阻止其他线程的问题。