Delphi TCP客户端从TCPServer读取字符串
我需要写一个简单的聊天程序,将由一些客户使用。基本上,有很多客户端连接到服务器,它们一起聊天。服务器工作: 如果需要,请在此输入代码:Delphi TCP客户端从TCPServer读取字符串,delphi,Delphi,我需要写一个简单的聊天程序,将由一些客户使用。基本上,有很多客户端连接到服务器,它们一起聊天。服务器工作: 如果需要,请在此输入代码: //CONNECT TO THE SERVER procedure TFormServer.ButtonStartClick(Sender: TObject); begin if not TCPServer.Active then begin try TCPServer.DefaultPort := 8002;
//CONNECT TO THE SERVER
procedure TFormServer.ButtonStartClick(Sender: TObject);
begin
if not TCPServer.Active then
begin
try
TCPServer.DefaultPort := 8002;
TCPServer.Bindings[0].IP := LIP.Text;
TCPServer.Bindings[0].Port := StrToInt(LPort.Text);
TCPServer.MaxConnections := 5;
TCPServer.Active := true;
Memo1.Lines.Add(TimeNow + 'Server started.');
except
on E: Exception do
Memo1.Lines.Add(sLineBreak + ' ====== INTERNAL ERROR ====== ' +
sLineBreak + ' > ' + E.Message + sLineBreak);
end;
end;
end;
//DISCONNECT
procedure TFormServer.ButtonStopClick(Sender: TObject);
begin
if TCPServer.Active then
begin
TCPServer.Active := false;
Memo1.Lines.Add(TimeNow + 'Server stopped.');
end;
end;
//IF CLOSE THE APP DONT FORGET TO CLOSE SERVER!!
procedure TFormServer.FormClose(Sender: TObject; var Action: TCloseAction);
begin
ButtonStopClick(Self);
end;
procedure TFormServer.FormCreate(Sender: TObject);
begin
FClients := 0;
end;
//When a client connects I write a log
procedure TFormServer.TCPServerConnect(AContext: TIdContext);
begin
Inc(FClients);
TThread.Synchronize(nil, procedure
begin
LabelCount.Text := 'Connected sockets: ' + FClients.ToString;
Memo1.Lines.Add(TimeNow + ' Client connected @ ' + AContext.Binding.IP + ':' + AContext.Binding.Port.ToString);
end);
end;
//Same, when a client disconnects I log it
procedure TFormServer.TCPServerDisconnect(AContext: TIdContext);
begin
Dec(FClients);
TThread.Synchronize(nil, procedure
begin
LabelCount.Text := 'Connected sockets: ' + FClients.ToString;
Memo1.Lines.Add(TimeNow + ' Client disconnected');
end);
end;
//WHAT I DO HERE:
//I receive a message from the client and then I send this message to EVERYONE that is connected here. It is a global chat
procedure TFormServer.TCPServerExecute(AContext: TIdContext);
var
txt: string;
begin
txt := AContext.Connection.IOHandler.ReadLn();
AContext.Connection.IOHandler.WriteLn(txt);
TThread.Synchronize(nil, procedure
begin
Memo1.Lines.Add(TimeNow + txt);
end);
end;
服务器代码非常简单,但它满足了我的需要。这是客户端:
下面是代码,非常简单:
//CONNECT TO THE SERVER
procedure TFormClient.ConnectClick(Sender: TObject);
begin
if Length(Username.Text) < 4 then
begin
Memo1.Lines.Clear;
Memo1.Lines.Add('ERROR: Username must contain at least 4 characters');
Exit;
end;
if not TCPClient.Connected then
begin
try
Username.Enabled := false;
Memo1.Lines.Clear;
TCPClient.Host := '127.0.0.1';
TCPClient.Port := 8002;
TCPClient.ConnectTimeout := 5000;
TCPClient.Connect;
Connect.Text := 'Disconnect';
except
on E: Exception do
Memo1.Lines.Add(' ====== ERROR ======' + sLineBreak +
' > ' + E.Message + sLineBreak);
end;
end
else
begin
TCPClient.Disconnect;
Username.Enabled := true;
Connect.Text := 'Connect';
end;
end;
//IF YOU FORGET TO DISCONNECT WHEN APP IS CLOSED
procedure TFormClient.FormDestroy(Sender: TObject);
begin
if TCPClient.Connected then
TCPClient.Disconnect;
end;
//Here I send a string to the server and it's good
procedure TFormClient.SendClick(Sender: TObject);
begin
if TCPClient.Connected then
begin
TCPClient.IOHandler.WriteLn(Username.Text + ': ' + EditMessage.Text);
EditMessage.Text := '';
end
else
begin
Memo1.Lines.Add('ERROR: You aren''t connected!');
end;
end;
//Problems here
procedure TFormClient.Timer1Timer(Sender: TObject);
begin
Memo1.Lines.Add(TCPClient.IOHandler.ReadLn());
end;
如您所见,当服务器收到字符串时,他会立即将其发送回所有客户端。我尝试在此处获取字符串:
procedure TFormClient.Timer1Timer(Sender: TObject);
begin
Memo1.Lines.Add(TCPClient.IOHandler.ReadLn());
end;
怎么了?我在这里看到了一个类似的问题,答案是我必须使用计时器和IOHandler.ReadLn()
,这就是我正在做的。我认为问题就在这里。如何修复
计时器的间隔也是200,是不是太短了
我已经阅读了Remy Lebeau在回答中所说的内容,并产生了以下简单代码:
procedure TFormClient.Timer1Timer(Sender: TObject);
begin
if not(TCPClient.Connected) then
Exit;
if TCPClient.IOHandler.InputBufferIsEmpty then
Exit;
Memo1.Lines.Add(TCPClient.IOHandler.InputBufferAsString());
end;
表单中有一个
Timer1
组件。这正如我所期望的那样,但它仍然可以锁定UI或不锁定?为您的TCP客户端创建一个线程并将消息同步回UI。为您的TCP客户端创建一个线程并将消息同步回UI
服务器工作正常
仅供参考,您根本不需要FClients
变量,尤其是因为您没有真正安全地访问它。至少,使用TInterlocked
安全访问它。或者切换到tidthreadsafeinger
。尽管确实如此,您使用它的唯一位置是在LabelCount
中,您可以从TIdTCPServers.Contexts
属性获取当前客户端计数
这是客户端:
问题从最后一个过程开始Timer1Timer
这是因为您使用的是基于UI的TTimer
,并且(像Indy中的大多数东西一样,IOHandler.ReadLn()
方法会一直阻塞直到完成。您正在UI线程的上下文中调用它,因此它会阻塞UI消息循环,直到套接字到达一整行
解决阻塞UI的一种方法是在表单上放置一个IndyTID防冻剂组件。当ReadLn()
阻塞时,UI将保持响应。但是,与TTimer
一起使用会有点危险,因为您最终会遇到OnTimer
重入问题,可能会损坏IOHandler
的数据
实际上,最好的解决方案是根本不在UI线程中调用IOHandler.ReadLn()
。改为在工作线程中调用它。成功地Connect()
连接到服务器后启动线程,并在断开连接时终止线程。或者甚至将Connect()
本身移动到线程中。无论哪种方式,您都可以使用Indy的TIdThreadComponent
,或者编写自己的T(Id)Thread
派生类
相反,TCPClient不使用线程,我必须手动检查服务器
没错,但你的做法是错误的
如果您不想使用工作线程(您应该这样做),那么至少更改OnTimer
事件处理程序,使其不再阻塞UI,如下所示:
或:
或者:
procedure TFormClient.Timer1Timer(Sender: TObject);
begin
// Connected() performs a read operation and will buffer
// any pending bytes that happen to be received...
if not TCPClient.Connected then Exit;
while TCPClient.IOHandler.InputBuffer.IndexOf(Byte($0A)) <> -1 do
Memo1.Lines.Add(TCPClient.IOHandler.ReadLn());
end;
过程TFormClient.Timer1Timer(发送方:TObject);
开始
//Connected()执行读取操作并将缓存
//碰巧收到的任何挂起字节。。。
如果TCP客户端未连接,则退出;
而TCPClient.IOHandler.InputBuffer.IndexOf(字节($0A))-1执行
Memo1.Lines.Add(TCPClient.IOHandler.ReadLn());
终止
我在这里看到了一个类似的问题,答案是我必须使用计时器和IOHandler.ReadLn()
,这就是我正在做的
无论谁说的都是错的,或者你误解了要求。如果使用正确,在UI中使用计时器是一种可能的解决方案,但这不是一个很好的解决方案
服务器工作正常
仅供参考,您根本不需要FClients
变量,尤其是因为您没有真正安全地访问它。至少,使用TInterlocked
安全访问它。或者切换到tidthreadsafeinger
。尽管确实如此,您使用它的唯一位置是在LabelCount
中,您可以从TIdTCPServers.Contexts
属性获取当前客户端计数
这是客户端:
问题从最后一个过程开始Timer1Timer
这是因为您使用的是基于UI的TTimer
,并且(像Indy中的大多数东西一样,IOHandler.ReadLn()
方法会一直阻塞直到完成。您正在UI线程的上下文中调用它,因此它会阻塞UI消息循环,直到套接字到达一整行
解决阻塞UI的一种方法是在表单上放置一个IndyTID防冻剂组件。当ReadLn()
阻塞时,UI将保持响应。但是,与TTimer
一起使用会有点危险,因为您最终会遇到OnTimer
重入问题,可能会损坏IOHandler
的数据
实际上,最好的解决方案是根本不在UI线程中调用IOHandler.ReadLn()
。改为在工作线程中调用它。成功地Connect()
连接到服务器后启动线程,并在断开连接时终止线程。或者甚至将Connect()
本身移动到线程中。无论哪种方式,您都可以使用Indy的TIdThreadComponent
,或者编写自己的T(Id)Thread
派生类
相反,TCPClient不使用线程,我必须手动检查服务器
没错,但你的做法是错误的
如果您不想使用工作线程(您应该这样做),那么至少要更改OnTimer
事件处理程序,使其不会阻止t
procedure TFormClient.Timer1Timer(Sender: TObject);
begin
if TCPClient.IOHandler.InputBufferIsEmpty then
begin
TCPClient.IOHandler.CheckForDataOnSource(0);
TCPClient.IOHandler.CheckForDisconnect(False);
if TCPClient.IOHandler.InputBufferIsEmpty then Exit;
end;
// may still block if the EOL hasn't been received yet...
Memo1.Lines.Add(TCPClient.IOHandler.ReadLn);
end;
procedure TFormClient.Timer1Timer(Sender: TObject);
begin
// Connected() performs a read operation and will buffer
// any pending bytes that happen to be received...
if not TCPClient.Connected then Exit;
while TCPClient.IOHandler.InputBuffer.IndexOf(Byte($0A)) <> -1 do
Memo1.Lines.Add(TCPClient.IOHandler.ReadLn());
end;