如果出现连接问题,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
答案 0 :(得分:0)
(此答案中使用了Delphi XE2和'gds32.dll'10.0.1.335(ansi))
dbExpress是一个高级框架,它将提供程序特定的操作委托给其驱动程序。所有这些提供程序特定的驱动程序都公开相同的基本公共功能,因此无法检索超出此常用功能的特定信息。
关于错误报告,驱动程序导出两个函数。无论是mysql还是mssql或任何其他驱动程序,这些函数都是DBXBase_GetErrorMessageLength
和DBXBase_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
//