如何获取dbExpress连接错误的Winsock错误代码?

时间:2012-10-12 07:01:18

标签: delphi error-handling winsock dbexpress

如果出现连接问题,dbExpress驱动程序会抛出TDBXError,但不包含套接字错误代码。消息很简单:

  

无法完成托管“exampledb.local”的网络请求。   无法建立连接

有没有办法在发生此类异常时检索底层套接字错误?

堆栈跟踪是:

main thread ($5934):
0061aa59 +051 example.exe DBXCommon    447  +0 TDBXContext.Error
00817f14 +10c example.exe DBXDynalink  796 +21 TDBXMethodTable.RaiseError
00818553 +013 example.exe DBXDynalink  949  +1 TDBXDynalinkConnection.CheckResult
00818744 +050 example.exe DBXDynalink 1048  +4 TDBXDynalinkConnection.DerivedOpen
0061750f +007 example.exe DBXCommon    447  +0 TDBXConnection.Open
00612fed +0f5 example.exe DBXCommon    447  +0 TDBXConnectionFactory.GetConnection
00612ef1 +005 example.exe DBXCommon    447  +0 TDBXConnectionFactory.GetConnection
0062c08f +26f example.exe SqlExpr              TSQLConnection.DoConnect
005d9019 +039 example.exe DB                   TCustomConnection.SetConnected
005d8fd4 +004 example.exe DB                   TCustomConnection.Open
0062b98f +01b example.exe SqlExpr              TSQLConnection.CheckConnection
0062ebdf +01f example.exe SqlExpr              TCustomSQLDataSet.CheckConnection
0062efe4 +04c example.exe SqlExpr              TCustomSQLDataSet.OpenCursor
005e91d5 +055 example.exe DB                   TDataSet.SetActive
005e901c +004 example.exe DB                   TDataSet.Open

1 个答案:

答案 0 :(得分:0)

(此答案中使用了Delphi XE2和'gds32.dll'10.0.1.335(ansi))

dbExpress是一个高级框架,它将提供程序特定的操作委托给其驱动程序。所有这些提供程序特定的驱动程序都公开相同的基本公共功能,因此无法检索超出此常用功能的特定信息。

关于错误报告,驱动程序导出两个函数。无论是mysql还是mssql或任何其他驱动程序,这些函数都是DBXBase_GetErrorMessageLengthDBXBase_GetErrorMessage。驱动程序从客户端库中获取大部分错误,并通过这些函数将该信息报告给框架。除了已经报告的内容之外,无法获得有关连接到数据库服务器或任何其他操作的错误的更具体细节。

因此,客户端库是否包含winsock错误信息。对于问题中包含的错误的interbase驱动程序,此信息已包含在内(当存在时)。

以下是一个示例案例,尝试连接到现有服务器,以及侦听但未被数据库服务器侦听的端口。

  Connection := TSQLConnection.Create(nil);
  Connection.DriverName := 'Interbase';
  Connection.Params.Values['Database'] := 'existingserver/80:database';
  try
    Connection.Open;
  except
    on E: TDBxError do begin
      writeln(E.Message + sLineBreak);

这是输出:

  

无法完成托管“existingserver:80”的网络请求   无法建立连接。

正如您所注意到的那样,问题中的错误信息完全相同。请注意,文本中没有winsock错误。这是因为没有winsock错误,只要涉​​及网络协议,连接实际上是成功的。


这是对现有服务器的尝试,但是对非侦听端口的尝试。

  Connection := TSQLConnection.Create(nil);
  Connection.DriverName := 'Interbase';
  Connection.Params.Values['Database'] := 'existingserver/81:database';
  try
    Connection.Open;
  except
    on E: TDBxError do begin
      writeln(E.Message + sLineBreak);

以下是返回的错误:

  

无法完成托管“existingserver:81”的网络请求   无法建立连接   未知的Win32错误10060

这次连接失败,因为没有特定端口的侦听器。我不知道为什么客户端库无法解析10060,但这是你的winsock错误。


localhost,非侦听端口的案例:

  

无法完成托管“localhost:3051”的网络请求   无法建立连接   无法建立连接,因为目标计算机主动拒绝它。

这里我们有一个10061。


当尝试连接到不可解析的主机时,gds32.dll不会报告任何api错误。我不知道库是如何解析主机的,或者为什么它不包含错误代码,但错误消息是详细的:

  

无法完成托管“不存在的服务器”的网络请求   无法找到主机。
  在hosts文件或域名服务中找不到指定的名称。


<小时/>

如果我们直接使用客户端库,我们只能得到api错误。检查以下程序,其中尝试连接到现有服务器和关闭端口。

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  system.sysutils,
  data.dbxcommon,
  data.sqlexpr,
  data.dbxinterbase;

function isc_attach_database(status_vector: PLongint; db_name_length: Smallint;
    db_name: PAnsiChar; db_handle: PPointer; parm_buffer_length: Smallint;
    parm_buffer: PAnsiChar): Longint; stdcall; external 'gds32.dll';

function isc_interprete(buffer: PAnsiChar; var status_vector: Pointer): Longint;
    stdcall; external 'gds32.dll';

var
  Connection: TSQLConnection;

  StatusVector: array[0..19] of Longint;
  Handle: PPointer;
  Error: array[0..255] of AnsiChar;
  IntrStatus: Pointer;
  s: string;
begin
  try
    Connection := TSQLConnection.Create(nil);
    Connection.DriverName := 'Interbase';
    Connection.Params.Values['Database'] := 'server/3051:database'; // closed port
    try
      Connection.Open;
    except
      on E: TDBxError do begin
        writeln(E.Message + sLineBreak);

        if E.ErrorCode = TDBXErrorCodes.ConnectionFailed then begin

          Handle := nil;
          if isc_attach_database(@StatusVector, 0,
                      PAnsiChar(AnsiString(Connection.Params.Values['Database'])),
                      @Handle, 0, nil) <> 0 then begin

            IntrStatus := @StatusVector;
            s := '';
            while isc_interprete(Error, IntrStatus) <> 0 do begin
              s := s + AnsiString(Error) + sLineBreak;
              if PLongint(IntrStatus)^ = 17 then  // isc_arg_win32
                s := s + ' --below is an api error--' + sLineBreak +
                     Format('%d: %s', [PLongint(Longint(IntrStatus) + 4)^,
                     SysErrorMessage(PLongint(Longint(IntrStatus) + 4)^)]) +
                     sLineBreak;
            end;
            Writeln(s);
          end;
        end else
          raise;
      end;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  readln;
end.

哪个输出:

  

无法完成主机“服务器:3051”的网络请求   无法建立连接   未知的Win32错误10060
  
  无法完成主机“server:3051”的网络请求   无法建立连接     - 下面是api错误 -
  10060:连接尝试失败,因为连接方未正确连接   一段时间后响应,或建立连接失败,因为连接
  主持人没有回复   未知Win32错误10060

第一段是dbx报告的内容。第二段是我们从'gds32.dll'获得的,包括注入api错误代码和文本,否则它们是相同的。

以上是一个粗略的演示,正确的打字使用'interbase.h'。有关选择可能的api错误的详细信息,请参阅“Parsing the Status Vector”或一般“Handling Error Conditions”。


<小时/>

在任何情况下,可以看出,能够完全获取特定信息取决于dbx用于连接数据库服务器的客户端库。

对于一般情况,要独立于正在使用的数据库服务器获取winsock错误信息,您可以做的是尝试在尝试打开数据库连接之前将套接字连接到服务器,并且只有在成功关闭时您的测试连接,然后继续附加到数据库。您可以使用任何库或裸api来执行此操作。

这是一个简单的例子:

function TestConnect(server: string; port: Integer): Boolean;

  procedure WinsockError(step: string);
  begin
    raise Exception.Create(Format('"%s" fail. %d: %s:',
        [step, WSAGetLastError, SysErrorMessage(WSAGetLastError)]));  
  end;

var
  Error: Integer;
  Data: TWSAData;
  Socket: TSocket;
  SockAddr: TSockAddrIn;
  Host: PHostEnt;
begin
  Result := False;
  Error := WSAStartup(MakeWord(1, 1), Data);
  if Error = 0 then begin
    try
      Socket := winsock.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
      if Socket <> INVALID_SOCKET then begin
        try
          Host := gethostbyname(PAnsiChar(AnsiString(server)));
          if Host <> nil then begin
            SockAddr.sin_family := AF_INET;
            SockAddr.sin_addr.S_addr := Longint(PLongint(Host^.h_addr_list^)^);
            SockAddr.sin_port := htons(port);
            if connect(Socket, SockAddr,  SizeOf(SockAddr)) <> 0 then
              WinsockError('connect')
            else
              Result := True;
          end else
            WinsockError('gethostbyname');
        finally
          closesocket(Socket);
        end;  
      end else
        WinsockError('socket');
    finally  
      WSACleanup;
    end;  
  end else
    raise Exception.Create('winsock initialization fail');
end;

你可以使用类似这样的东西:

if TestConnect('server', 3050) then
  //