Reputation: 3022
I want a connection where both terminals wait reading for commands, and from time to time, one send some data to the other. Of course, I know how to implement my protocol, but if I do a Socket.ReadUInt32
it will block my execution path and I can't send commands when needed. So I thought, what if I send commands from another thread, using the same socket ? I write a minimal example... and it seems it is working. But I don't know for sure if this is correct way to do it, or it was jus luck this time... Is this technique threadsafe ? The extra thread will use the socket just for sending a UInt32 value, to wake the other side, and then, the connection will take place in the main threads.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IdTCPConnection,
IdTCPClient, IdBaseComponent, IdComponent, IdCustomTCPServer, IdTCPServer,
IdContext, IdThreadComponent;
const
WM_LOGEVENT = WM_USER + 1;
type
TForm1 = class(TForm)
Server: TIdTCPServer;
Client: TIdTCPClient;
BStartServer: TButton;
BConnectClient: TButton;
ClientWriteThread: TIdThreadComponent;
Memo: TMemo;
BSendCommand: TButton;
ClientReadThread: TIdThreadComponent;
procedure BStartServerClick(Sender: TObject);
procedure ServerExecute(AContext: TIdContext);
procedure BConnectClientClick(Sender: TObject);
procedure ClientWriteThreadRun(Sender: TIdThreadComponent);
procedure BSendCommandClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ClientReadThreadRun(Sender: TIdThreadComponent);
private
procedure LogEvent(var Msg: TMessage); message WM_LOGEVENT;
procedure SendEvent(const Msg: String);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.LogEvent(var Msg: TMessage);
var PStr: PString;
begin
PStr:= PString(Msg.WParam);
Memo.Lines.Add(Copy(PStr^, 1, Length(PStr^)));
end;
procedure TForm1.SendEvent(const Msg: String);
begin
SendMessage(Handle, WM_LOGEVENT, WPARAM(@Msg), 0);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Server.Bindings.Clear;
Server.Bindings.Add.SetBinding('192.168.0.3', 60200);
Client.Host:= '192.168.0.3';
Client.Port:= 60200;
end;
procedure TForm1.BStartServerClick(Sender: TObject);
begin
Server.Active:= True;
Caption:= IntToStr(Byte(Server.Active));
end;
procedure TForm1.BConnectClientClick(Sender: TObject);
begin
Client.Connect;
ClientReadThread.Start;
end;
procedure TForm1.BSendCommandClick(Sender: TObject);
begin
ClientWriteThread.Start;
end;
procedure TForm1.ClientWriteThreadRun(Sender: TIdThreadComponent);
var Data: Cardinal;
begin
Data:= 1234;
SendEvent('Client send: '+ IntToStr(Data));
Client.Socket.Write(Data);
Sender.Terminate;
end;
procedure TForm1.ClientReadThreadRun(Sender: TIdThreadComponent);
var Data: Cardinal;
begin
SendEvent('Client listening...');
Data:= Client.Socket.ReadUInt32;
SendEvent('Client received: '+ IntToStr(Data));
Client.Disconnect;
SendEvent('Client stopped.');
Sender.Terminate;
end;
procedure TForm1.ServerExecute(AContext: TIdContext);
var Data: Cardinal;
Srv: String;
begin
Srv:= 'Server '+IntToStr(Random(100));
SendEvent(Srv+' listening...');
Data:= AContext.Connection.Socket.ReadUInt32;
SendEvent(Srv+' received: '+IntToStr(Data));
Data:= 9999;
SendEvent(Srv+' Send: '+IntToStr(Data));
AContext.Connection.Socket.Write(Data);
AContext.Connection.Disconnect;
SendEvent(Srv+' stopped.');
end;
end.
Upvotes: 2
Views: 364
Reputation: 596332
Yes, it is safe to send on a connection in one thread while reading from the same connection in another thread. The socket has separate internal buffers for inbound and outbound data.
Just make sure that you don't try to send from both threads at the same time, or read from both threads at the same time, without synchronizing access to the connection.
Upvotes: 4