Ответ 1
Как говорили другие в ответ на ваш вопрос, TCP не является ориентированным на сообщения протоколом, а потоковым. Я покажу вам, как писать и читать очень простой сервер эха (это немного измененная версия сервера, который я сделал на этой неделе, чтобы ответить на другой вопрос):
Метод OnExecute сервера выглядит следующим образом:
procedure TForm2.IdTCPServer1Execute(AContext: TIdContext);
var
aByte: Byte;
begin
AContext.Connection.IOHandler.Writeln('Write anything, but A to exit');
repeat
aByte := AContext.Connection.IOHandler.ReadByte;
AContext.Connection.IOHandler.Write(aByte);
until aByte = 65;
AContext.Connection.IOHandler.Writeln('Good Bye');
AContext.Connection.Disconnect;
end;
Этот сервер начинается с приветственного сообщения, а затем просто считывает байты подключения на каждый байт. Сервер отвечает на тот же байт, пока принятый байт не станет 65 (команда отключения) 65 = 0x41 или $41. Затем сервер заканчивается хорошим сообщением.
Вы можете сделать это в клиенте:
procedure TForm3.Button1Click(Sender: TObject);
var
AByte: Byte;
begin
IdTCPClient1.Connect;
Memo1.Lines.Add(IdTCPClient1.IOHandler.ReadLn); //we know there must be a welcome message!
Memo1.Lines.Add('');// a new line to write in!
AByte := 0;
while (IdTCPClient1.Connected) and (AByte <> 65) do
begin
AByte := NextByte;
IdTCPClient1.IOHandler.Write(AByte);
AByte := IdTCPClient1.IOHandler.ReadByte;
Memo1.Lines[Memo1.Lines.Count - 1] := Memo1.Lines[Memo1.Lines.Count - 1] + Chr(AByte);
end;
Memo1.Lines.Add(IdTCPClient1.IOHandler.ReadLn); //we know there must be a goodbye message!
IdTCPClient1.Disconnect;
end;
Следующая процедура байта может быть любым, что вы хотите предоставить байт. Например, чтобы получить вход от пользователя, вы можете превратить KeyPreview вашей формы в true и написать обработчик событий OnKeyPress и функцию NextByte следующим образом:
procedure TForm3.FormKeyPress(Sender: TObject; var Key: Char);
begin
FCharBuffer := FCharBuffer + Key;
end;
function TForm3.NextByte: Byte;
begin
Application.ProcessMessages;
while FCharBuffer = '' do //if there is no input pending, just waint until the user adds input
begin
Sleep(10);
//this will allow the user to write the next char and the application to notice that
Application.ProcessMessages;
end;
Result := Byte(AnsiString(FCharBuffer[1])[1]); //just a byte, no UnicodeChars support
Delete(FCharBuffer, 1, 1);
end;
Все, что пользователь записывает в форме, будет отправлено на сервер, а затем прочитано оттуда и добавлено в memo1. Если фокус ввода уже находится в Memo1, вы увидите каждый символ дважды, один с клавиатуры, а другой - на сервере.
Итак, чтобы написать простой клиент, который получает информацию с сервера, вы должны знать, чего ожидать от сервера. Это строка? несколько строк? Integer? массив? двоичный файл? закодированный файл? Есть ли отметка для конца соединения? Обычно это определяется протоколом или вами, если вы создаете пользовательскую пару "клиент/сервер".
Чтобы написать общий TCP без предварительного уведомления о том, что можно получить с сервера, возможно, но сложный из-за того, что на этом уровне в протоколе нет общей абстракции сообщения.
Не путайте с тем, что есть транспортные сообщения, но один ответ сервера можно разделить на несколько транспортных сообщений, а затем повторно собрать клиентскую сторону, ваше приложение не контролирует это. С точки зрения приложения сокет представляет собой поток (поток) входящих байтов. То, как вы интерпретируете это как сообщение, команду или любой ответ от сервера, зависит от вас. То же самое относится к серверной стороне... например, событие onExecute - это белый лист, в котором у вас также нет абстракции сообщения.
Возможно, вы смешиваете абстракцию сообщений с помощью абстракции команды... в протоколе на основе команды клиент отправляет строки, содержащие команды, а сервер отвечает строками, содержащими ответы (тогда, вероятно, больше данных). Взгляните на компоненты TIdCmdTCPServer/Client.
ИЗМЕНИТЬ
В комментариях OP утверждает, что он/она хочет сделать эту работу в потоке, я не уверен, что проблема с этим связана с этим, но я добавляю пример потока. Сервер такой же, как и ранее, только часть клиента для этого простого сервера:
Во-первых, класс потока, который я использую:
type
TCommThread = class(TThread)
private
FText: string;
protected
procedure Execute; override;
//this will hold the result of the communication
property Text: string read FText;
end;
procedure TCommThread.Execute;
const
//this is the message to be sent. I removed the A because the server will close
//the connection on the first A sent. I'm adding a final A to close the channel.
Str: AnsiString = 'HELLO, THIS IS _ THRE_DED CLIENT!A';
var
AByte: Byte;
I: Integer;
Client: TIdTCPClient;
Txt: TStringList;
begin
try
Client := TIdTCPClient.Create(nil);
try
Client.Host := 'localhost';
Client.Port := 1025;
Client.Connect;
Txt := TStringList.Create;
try
Txt.Add(Client.IOHandler.ReadLn); //we know there must be a welcome message!
Txt.Add('');// a new line to write in!
AByte := 0;
I := 0;
while (Client.Connected) and (AByte <> 65) do
begin
Inc(I);
AByte := Ord(Str[I]);
Client.IOHandler.Write(AByte);
AByte := Client.IOHandler.ReadByte;
Txt[Txt.Count - 1] := Txt[Txt.Count - 1] + Chr(AByte);
end;
Txt.Add(Client.IOHandler.ReadLn); //we know there must be a goodbye message!
FText := Txt.Text;
finally
Txt.Free;
end;
Client.Disconnect;
finally
Client.Free;
end;
except
on E:Exception do
FText := 'Error! ' + E.ClassName + '||' + E.Message;
end;
end;
Затем я добавляю эти два метода к форме:
//this will collect the result of the thread execution on the Memo1 component.
procedure TForm3.AThreadTerminate(Sender: TObject);
begin
Memo1.Lines.Text := (Sender as TCommThread).Text;
end;
//this will spawn a new thread on a Create and forget basis.
//The OnTerminate event will fire the result collect.
procedure TForm3.Button2Click(Sender: TObject);
var
AThread: TCommThread;
begin
AThread := TCommThread.Create(True);
AThread.FreeOnTerminate := True;
AThread.OnTerminate := AThreadTerminate;
AThread.Start;
end;