#delphi #sockets #tcpclient #indy
#delphi #сокеты #tcpclient #indy
Вопрос:
У меня проблема с IdTelnet (indy 10.1). Я не могу прочитать данные с сервера в режиме Unicode. и теперь я хочу написать терминал telnet с IdTCPClient.
Сервер иногда отправляет одну строку, а иногда все больше и больше строк. Но между отправкой нет фиксированного времени.
Теперь моя проблема в том, что когда я должен считывать данные из InBuffer.
Или когда я должен использовать функцию ReadLn для чтения данных с сервера, сколько раз я должен запускать ReadLn?
Ответ №1:
TIdTelnet
является многопоточным компонентом. У него есть внутренний поток, который непрерывно считывает данные из сокета, вызывая TIdTelnet.OnDataAvailable
событие всякий раз, когда доступен буфер данных.
TIdTelnet
является TIdTCPClient
потомком. Посмотрите в исходном файле IdTelnet.pas, чтобы увидеть, как это реализовано. Вы можете сделать что-то подобное в своем собственном коде, вызывая TIdIOHandler.ReadLn()
свой собственный поток, например:
type
TMyThread = class(TThread)
private
FConn: TIdTCPConnection;
protected
procedure Execute; override;
public
constructor Create(AConn: TIdTCPConnection); reintroduce;
end;
constructor TMyThread.Create(AConn: TIdTCPConnection);
begin
inherited Create(False);
FConn := AConn;
end;
procedure TMyThread.Execute;
var
S: String;
begin
while not Terminated do
begin
S := FConn.IOHandler.ReadLn(...);
...
end;
end;
var
Thread: TMyThread = nil;
procedure TForm1.ConnectButtonClick(Sender: TObject);
begin
IdTCPClient1.Connect;
try
Thread := TMyThread.Create(IdTCPClient1);
except
IdTCPClient1.Disconnect;
raise;
end;
end;
procedure TForm1.DisconnectButtonClick(Sender: TObject);
begin
if Assigned(Thread) then Thread.Terminate;
try
IdTCPClient1.Disconnect;
finally
if Assigned(Thread) then
begin
Thread.WaitFor;
FreeAndNil(Thread);
end;
end;
end;
Если вы не хотите использовать поток, вместо этого вы можете использовать таймер. Чтобы убедиться, что ваш поток таймера (например, основной поток) не заблокирован без необходимости, используйте TIdIOHandler.CheckForDataOnSource()
метод с небольшим таймаутом всякий TIdIOHandler.InputBuffer
раз, когда он пуст, а затем вызывайте TIdIOHandler.ReadLn()
только тогда, когда данные доступны, например:
procedure TForm1.ConnectButtonClick(Sender: TObject);
begin
IdTCPClient1.Connect;
ReadTimer.Enabled := True;
end;
procedure TForm1.DisconnectButtonClick(Sender: TObject);
begin
ReadTimer.Enabled := False;
IdTCPClient1.Disconnect;
end;
procedure TForm1.ReadTimerElapsed(Sender: TObject);
var
S: String;
begin
if IdTCPClient1.IOHandler.InputBufferIsEmpty then
begin
IdTCPClient1.IOHandler.CheckForDataOnSource(10);
if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
end;
S := IdTCPClient1.IOHandler.ReadLn(...);
...
end;
Комментарии:
1. Большое вам спасибо, если я хочу использовать idTelnet, как я могу изменить кодировку буфера? в icTCPClienet я использую ReadLn с TEncoding . UTF8 в качестве аргумента, и он работает хорошо.
2. TIdTelnet предоставляет вам необработанные буферы (после декодирования последовательностей Telnet) по мере их поступления. У буферов нет кодировок, у строк есть. Вы должны скопировать необработанные данные в свой собственный буфер, а затем прочитать из него завершенные строки / строки по мере необходимости после определения того, когда завершенные строки / строки закончили буферизацию. Indy имеет несколько функций для извлечения строк / строк из буферов TIdBuffer, TStream и TidBytes. В противном случае не используйте TIdTelnet, используйте TIdTCPClient с вашим собственным потоком / таймером ReaLn(), как я вам показал.
Ответ №2:
Синхронное решение для IdTCPClient
Я искал решение, которое будет «ждать» ответа, похоже, это работает достаточно хорошо для того, что я делаю, если я играю с настройкой ReadTimeout и отключаю клиент от серверной части после записи с сервера. На основе ReadTimeout это отключится после того, как это будет достигнуто с помощью IOHandler.ReadLn();
function sendTCPCommand(command: string): string;
var
lines, lineInput: String;
tcpClient : TIdTcpClient;
begin
tcpClient := TidTcpClient.Create(nil);
try
with tcpClient do
begin
Port := 80;
Host := 127.0.0.1;
try
ReadTimeOut := 5000; //Adjust according to demands, each readln event uses it
lines := '';
connect;
IoHandler.WriteLn(command);
lineInput := IoHandler.ReadLn();
while (lineInput <> '') do
begin
lines := lines lineInput;
lineInput := IoHandler.ReadLn();
if (lineInput = '') then
begin
Disconnect; //ignores timeout when finished reading or getting nothing
end;
end;
Result := lines;
except
On E:Exception do
begin
Result := E.Message;
end;
end;
end;
finally
tcpClient.Free;
end;
end;