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;