[QWorker] 使用QWorker的延迟功能快速断掉一个无效的TCP连接

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;
分享到: