TCP 协议经历了漫长的时间发展到现在,其连接到一个地址时,如果对方地址不存在而且没有 ICMP 协议返回目标不可达消息时,连接就会漫长的等待重试失败。这显然在网络带宽比 TCP 协议产生时高出不知道 N 倍的今天,是一个没有意义的等待,而当我们想要控制这个连接时间,我们会发现我们竟然无从下手(至少我不知道,也许你知道)。
那么,本文尝试利用 QWorker 的延迟作业功能来断开连接(socket 函数是线程安全的)。
首先,我们作业的唯一内容就是关闭这个连接对应的句柄:
procedure TForm1.DoCloseSocket(AJob: PQJob); begin CloseSocket(THandle(AJob.Data)); end;
然后,我们在调用 connect 连接到远程之前,Delay 一下这个作业,这里延迟1秒,也就是说1秒后还连不上,就取消这个连接:
AJobHandle:=Workers.Delay(DoCloseSocket, Q1Second, Pointer(AHandle));
if connect(AHandle, sockaddr_in(Addr), SizeOf(Addr)) <> SOCKET_ERROR then
begin
Workers.ClearSingleJob(AJobHandle);
//连接上了,正常处理
end
else
begin
AErrorCode := GetLastError;
if AErrorCode <> WSAENOTSOCK then//如果是由作业关闭的话,返回的错误代码是WSANOTSOCK
begin
//处理其它错误
end;
end;
OK,使用 QWorker 处理这个问题就这么简单。用 QWorker ,就这么自信!(海飞丝呢?找作者要版权费)
好了,我们来一个不依赖QWorker的标准的实现吧,直接调用ConnectTo(目标IP,目标端口,超时时间)就要以了:
function ConnectTo(const Addr: TInAddr; const APort: Word;
ATimeout: Cardinal): TSocket;
var
ARemote: TSockAddrIn;
AErrorCode: Cardinal;
tm: TTimeVal;
AFlag: Integer;
AConnected: Boolean;
AFd: TFdSet;
begin
AConnected := False;
Result := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if Result <> INVALID_SOCKET then
begin
FillChar(ARemote, SizeOf(TSockAddrIn), 0);
ARemote.sin_family := AF_INET;
ARemote.sin_port := htons(APort);
ARemote.sin_addr := Addr;
AFlag := 1;
if ioctlsocket(Result, FIONBIO, AFlag) <> SOCKET_ERROR then
begin
AConnected := connect(Result, sockaddr_in(ARemote), SizeOf(ARemote)) <>
SOCKET_ERROR;
if not AConnected then
begin
tm.tv_sec := ATimeout div 1000;
tm.tv_usec := (ATimeout mod 1000) * 1000;
FD_Zero(AFd);
FD_Set(Result, AFd);
if select(1, nil, @AFd, nil, @tm) <> SOCKET_ERROR then
begin
AConnected := Fd_IsSet(Result, AFd);
if not AConnected then
begin
CloseSocket(Result);
AErrorCode := WSAETIMEDOUT;
end;
end
else
begin
AErrorCode := GetLastError;
CloseSocket(Result);
end;
end;
end
else
AErrorCode := GetLastError;
end
else
AErrorCode := GetLastError;
if not AConnected then
begin
Result := INVALID_SOCKET;
SetLastError(AErrorCode);
end
else // 还原回同步模式
begin
AFlag := 0;
ioctlsocket(Result, FIONBIO, AFlag);
end;
end;
