(이글은 RAD 스튜디오 10 시애틀 기준으로 테스트하고, 작성되었습니다. 다른 버전 사용자들은 이슈 발생여부를 먼저 확인하고 아래 내용을 참고하시기 바랍니다.)
이 글은 데이터스냅 클라이언트에서 TCP/IP로 접속 시 Timeout이 적용되지 않는 이슈에 대해 원인을 확인하고 회피하는 내용을 소개합니다.
데이터스냅 클라이언트 프로그램에서 서버에 접속하기 위해 SQLConnection을 사용합니다.
연결방식(CommunicationProtocol)은 tcp/ip, http, https가 있습니다.
Timeout 속성은 CommnunicationTimeout(송수신 시간제한)과 ConnectTimeout(연결 시간제한) 두가지가 있습니다.
결론적으로 Timeout 속성이 적용되지 않는 경우는 tcp/ip 연결방식으로 연결한 경우 두가지 Timeout 속성이 모두 적용되지 않습니다.(http, https 연결방식에서는 Timeout이 정상 적용됩니다.)
이미 엠바카데로 QC에 이슈가 등록되었지만 적용되지 않는다는 답변으로 아쉽게 마무리되었습니다.
http://qc.embarcadero.com/wc/qcmain.aspx?d=80954#sthash.9Apdxn5i.dpuf
제일 손쉬운 조치방법은 CommunicationProtocol을 http로 변경하는 것입니다.(물론 데이터스냅 서버에서 http를 지원해야 합니다.)
만약, TCP/IP로 연결하실 분들은 아래 내용을 참고하세요.
(RAD 스튜디오 10 시애틀에서 진행했습니다. 다른 버전 사용자분들은 참고해서 수정하시기 바랍니다.)
총 2개의 Unit을 수정하면됩니다.
아래 2개 파일을 프로젝트 파일 경로로 복사 후 프로젝트에 추가(Add) 합니다.(즉, 해당 프로젝트에서만 수정된 내용이 반영됩니다.)
- Data.DbxSocketChannelNative.pas(C:\Program Files (x86)\Embarcadero\Studio\17.0\source\data\dbx)
- IPPeerClient.pas(C:\Program Files (x86)\Embarcadero\Studio\17.0\source\indy\implementation)
Data.DbxSocketChannelNative.pas(C:\Program Files (x86)\Embarcadero\Studio\17.0\source\data\dbx)
procedure TDBXIdTCPLayer.Open(const DBXProperties: TDBXProperties); var timeout: string; commTimeout: string; LIPVersionStr: string; LIPVersion: Integer; begin Close; FIPImplementationID := DbxProperties[TDBXPropertyNames.IPImplementationID]; if FIdSocket = nil then FIdSocket := CreateClientSocket; FIdSocket.Host := DbxProperties[TDBXPropertyNames.HostName]; FIdSocket.Port := DbxProperties.GetInteger(TDBXPropertyNames.Port); LIPVersionStr := DbxProperties[TDBXPropertyNames.CommunicationIPVersion].Trim; LIPVersion := GetEnumValue(TypeInfo(TIPVersionPeer), LIPVersionStr); if LIPVersion > -1 then FIdSocket.IPVersion := TIPVersionPeer(LIPVersion) else FIdSocket.IPVersion := TIPVersionPeer.IP_IPv4; timeout := DbxProperties[TDBXPropertyNames.ConnectTimeout]; if timeout = '' then ConnectTimeout := 0 else ConnectTimeout := StrToInt(timeout); commTimeout := DbxProperties[TDBXPropertyNames.CommunicationTimeout]; if commTimeout = '' then CommunicationTimeout := 0 else CommunicationTimeout := StrToInt(commTimeout); { Added Humphrey} // ConnectTimeout과 CommunicationTimeout을 FIdSocket에 전달 if Supports(FIdSocket, IIPPeerClientSetTimeout) then begin (FIdSocket as IIPPeerClientSetTimeout).SetConnectTimeout(ConnectTimeout); (FIdSocket as IIPPeerClientSetTimeout).SetReadTimeout(CommunicationTimeout); end; { Added Humphrey} FIdSocket.UseNagle := false; FIdSocket.Connect; FConnected := false; end;
위 메소드는 tcp/ip로 데이터스냅 클라이언트가 접속하는 일부 소스코드입니다.
주의깊게 볼 부분은 ConnectTimeout과 CommunicationTimeout 값을 가져오고 FIdSocket에 전달하는 내용이 누락되었습니다.
그래서 "Added Humphery" 주석으로 감싸진 내용을 추가했습니다.
uses 절에 IPPeerClient 추가해야 합니다.
IPPeerClient.pas(C:\Program Files (x86)\Embarcadero\Studio\17.0\source\indy\implementation)
interface { Added Humphrey} type IIPPeerClientSetTimeout = interface ['{9924134C-9C7D-464F-8ABE-F3E1E408C566}'] procedure SetConnectTimeout(const ATimeout: Integer); procedure SetReadTimeout(const ATimeout: Integer); end; { Added Humphrey}
상단 interface 아래에 IIPPeerClientSettimeout 인터페이스 추가
TIdTCPClientPeerIP = class(TIdClassIP, IIPTCPClient, IIPObject{ Added Humphrey}, IIPPeerClientSetTimeout{ Added Humphrey}) private FTCPClient: TIdTCPClientIP; FIOHandler: IIPIOHandler; FSocket: IIPIOHandlerSocket; protected function Connected: Boolean; function GetSocket: IIPIOHandlerSocket; procedure SetIOHandler(Handler: IIPIOHandler); function GetIOHandler: IIPIOHandler; function GetBoundIP: string; procedure SetBoundIP(IP: string); function GetHost: string; procedure SetHost(LHost: string); function GetPort: TIPPortPeer; procedure SetPort(LPort: TIPPortPeer); procedure SetIPVersion(const AValue: TIPVersionPeer); function GetIPVersion: TIPVersionPeer; function GetUseNagle: Boolean; procedure SetUseNagle(Use: Boolean); procedure Connect; procedure Disconnect; function GetManagedIOHandler: Boolean; procedure SetManagedIOHandler(AManagedIOHandler: Boolean); public function GetObject: TObject; function GetIPImplementationID: string; constructor Create(AOwner: TComponent); destructor Destroy; override; { Added Humphrey} procedure SetConnectTimeout(const ATimeout: Integer); procedure SetReadTimeout(const ATimeout: Integer); { Added Humphrey} end;
TIdTCPClientPeerIP 클래스 선언부에 위 IIPPeerClientSetTimeout 인터페이스 위임, 인터페이스 메소드 추가
구현부에 타임아웃 적용하도록 구현
{ Added Humphrey} procedure TIdTCPClientPeerIP.SetConnectTimeout(const ATimeout: Integer); begin FTCPClient.ConnectTimeout := ATimeout; end; procedure TIdTCPClientPeerIP.SetReadTimeout(const ATimeout: Integer); begin FTCPClient.ReadTimeout := ATimeout; end; { Added Humphrey}
위와 같이 조치하면 TSQLConnection으로 DataSnap을 TCP/IP로 연결하는 경우에도 Timeout 속성이 모두 적용됩니다.
완성된 소스코드는 저작권 문제(?)로 올려놓지 못하니 양해부탁드리며 필요하신 분들은 개인적으로 요청 해주세요.