I am using Delphi 2007. In my Delphi application I need to send a string via a port and IP and the receiver has to send me an answer.
I have created this code but I am not sure if I am really doing things correctly, because I receive no answer:
procedure TForm1.Button1Click(Sender: TObject);
var port:integer;
begin
port:=StrToInt(edit4.text);
ClientSocket1.Port:=21000;
ClientSocket1.Host:=Edit3.text;
ClientSocket1.ClientType:=ctNonBlocking;
ClientSocket1.Active:=true;
ClientSocket1.Connect.
end;
procedure TForm1.ClientSocket1Connect(Sender: TObject; Socket:CustomWinSocket);
begin
Checkbox1.Checked:=true;
Edit1.Text:=Socket.LocalAddress;
Edit2.Text:=Socket.LocalHost;
Memo2.Lines.Clear;
Edit5.Text:='STX ~ JR | ETX';
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
if ClientSocket1.Active then
ClientSocket1.Socket.SendText(Edit5.text)
else
Memo1.Lines.Add('Not working');
end;
procedure TForm1.ClientSocket1Read(Sender: TObject; Socket: CustomWinSocket);
begin
Memo2.Lines.Add(Socket.ReceiveText);
end;
How can I be sure I am still connected before clicking second button? (The one who sends the data).
Sorry for my bad English and apologies if I am breaking any rule.
How can I be sure I am still connected before clicking second button? (The one who sends the data).
In your code you set CheckBox1.Checked := true when ClientSocket1 is connected.
So you can set the same check box to false when disconected using ClientSocket1.OnDisconnect event.
ClientSocket1Disconnect is fired automatically when connection is broken.
procedure TForm1.ClientSocket1Disconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
Checkbox1.Checked:=false;
end;
Now you may use CheckBox1 as an indicator ifClientSocket1 is connected or not.
Related
Im writing an Indy chat app, and am wondering if there is a way for the server component to tell the client that there is a string waiting, or even a way for the client to have an "OnExecute" like event.
This is what i have now:
server:
procedure TServer.ServerExecute(AContext: TIdContext);
var
sResponse: string;
I: Integer;
list: Tlist;
begin
List := Server.Contexts.LockList;
sResponse:= AContext.Connection.Socket.ReadLn;
try
for I := 0 to List.Count-1 do
begin
try
TIdContext(List[I]).Connection.IOHandler.WriteLn(sResponse);
except
end;
end;
finally
Server.Contexts.UnlockList;
end;
end;
Client:
procedure TForm1.Button1Click(Sender: TObject);
var
sMsg : string;
begin
Client.Socket.WriteLn(edit1.Text);
sMsg := Client.Socket.ReadLn;
Memo1.Lines.Add(sMsg);
end;
The problem is when i have 2 or more clients running the messages keep stacking because the button only processes 1 message a time. I'd like a way for the client to wait for messages and when it is triggered it processes those messages, like it does now under the button procedure. I've tried to put the "readln" part under a timer, but that causes some major problems.
Im Using Delphi 2010 and Indy 10
procedure TForm1.Timer1Timer(Sender: TObject);
var
sMsg : string;
begin
IdIOHandlerStack1.CheckForDataOnSource(0);
sMsg := IdIOHandlerStack1.InputBuffer.AsString;
if not (sMsg = '') then
begin
Memo1.Lines.Add(IdIOHandlerStack1.InputBuffer.AsString);
IdIOHandlerStack1.InputBuffer.Clear;
end;
end;
I'm triyng to delete a item caption from a listview when a client socket disconnect from a server socket. In my code when a client connect to a server the listview add a icon and a caption (client.hostname), but how to delete the item when client disconnect? Listview1.items.delete.caption(socket.host). Any help?
I'll try to explain despite my bad english: All I need is to delete an item from listview so the client socket disconnect. Mr. Ken White gave me a solution to a question almost identical however I'm not managing to fit it in a situation where several clients are connected to the server ie, how can I delete the listview caption belonging to the host who just disconnect?
My serversocket1 code: (i renamed serversocket1 to socket1)
procedure TForm1.socket1ClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
ListView1.AddItem(Socket.RemoteHost, socket);
end;
Code that i using to try delete the item when client disconnect:
procedure TForm1.socket1ClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
var
L : TListItem;
begin
L:=listview1.FindCaption(0,inttostr(socket.Handle),false,true,false);
if L<>nil then
L.Delete;
end;
But the listview item don't delete.
You need to find the index of the added item first, ie Listview1.items.IndexOf(socket.host) and then delete the item at that specific index.
I changed the code:
procedure TForm1.socket1ClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
var
L : TListItem;
begin
L:=listview1.FindCaption(0,inttostr(socket.Handle),false,true,false);
if L<>nil then
L.Delete;
end;
to this:
procedure TForm1.socket1ClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
var
L : TListItem;
begin
L:=listview1.FindCaption(0,socket.remotehost,false,true,false); // <--changed this line socket.remotehost
if L<>nil then
begin
L.Delete;
end;
end;
And it's works,Just not sure if I made the right way. Thanks for all.
I am trying to handle some events when my application is idle so i created this code
procedure TForm1.ApplicationEventIdle(Sender: TObject; var Done: Boolean);
begin
Done := false;
ShowMessage('Hello');
Done := true;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnIdle := ApplicationEventIdle;
end;
The problem is the message box appears infinite times how can i display it only once ?
This behaviour happens precisely because you show the dialog. In order to close the dialog you have to click the OK button. This places an input message on the queue. After that has been processed the OnIdle event fires. And you show the message box again. And so on and so on.
You need to make sure that you don't do anything that requires user input in your OnIdle handler. In your case try outputting to a log rather than showing a dialog, e.g. OutputDebugString.
Create a private boolean field in TForm1 to indicate when the dialog has been shown, so you don't show it again.
procedure TForm1.ApplicationEventIdle(Sender: TObject; var Done: Boolean);
begin
if not FDialogShown then
begin
FDialogShown := True;
ShowMessage('Hello');
end;
end;
When i send a message from TCPClient to a TCPServer it will be handled using OnExecute event in the server . Now i want to handle the received messages in the Client but TCPClient doesn't have any event for this. So i have to make a thread to handle them manually. how can i do it ?
As others said in response to your question, TCP is not a message oriented protocol, but a stream one. I'll show you how to write and read to a very simple echo server (this is a slightly modified version of a server I did this week to answer other question):
The server OnExecute method looks like this:
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;
This server starts with a welcome message, then just reads the connection byte per byte. The server replies the same byte, until the received byte is 65 (the disconnect command) 65 = 0x41 or $41. The server then end with a good bye message.
You can do this in a client:
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;
The next byte procedure can be anything you want to provide a byte. For example, to get input from the user, you can turn the KeyPreview of your form to true and write a OnKeyPress event handler and the NextByte function like this:
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;
Anything the user writes in the form will be sent to the server and then read from there and added to memo1. If the input focus is already in Memo1 you'll see each character twice, one from the keyboard and the other form the server.
So, in order to write a simple client that gets info from a server, you have to know what to expect from the server. Is it a string? multiple strings? Integer? array? a binary file? encoded file? Is there a mark for the end of the connection? This things are usually defined at the protocol or by you, if you're creating a custom server/client pair.
To write a generic TCP without prior known of what to get from the server is possible, but complex due to the fact that there's no generic message abstraction at this level in the protocol.
Don't get confused by the fact there's transport messages, but a single server response can be split into several transport messages, and then re-assembled client side, your application don't control this. From an application point of view, the socket is a flow (stream) of incoming bytes. The way you interpret this as a message, a command or any kind of response from the server is up to you. The same is applicable server side... for example the onExecute event is a white sheet where you don't have a message abstraction too.
Maybe you're mixing the messages abstraction with the command abstraction... on a command based protocol the client sends strings containing commands and the server replies with strings containing responses (then probably more data). Take a look at the TIdCmdTCPServer/Client components.
EDIT
In comments OP states s/he wants to make this work on a thread, I'm not sure about what's the problem s/he is having with this, but I'm adding a thread example. The server is the same as shown before, just the client part for this simple server:
First, the thread class I'm using:
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;
Then, I'm adding this two methods to the form:
//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;
TCP doesn't operate with messages. That is stream-based interface. Consequently don't expect that you will get a "message" on the receiver. Instead you read incoming data stream from the socket and parse it according to your high-level protocol.
Here is my code to Read / Write with Delphi 7. Using the Tcp Event Read.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ScktComp;
type
TForm1 = class(TForm)
ClientSocket1: TClientSocket;
Button1: TButton;
ListBox1: TListBox;
Edit1: TEdit;
Edit2: TEdit;
procedure Button1Click(Sender: TObject);
procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
procedure ClientSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
UsePort: Integer;
UseHost: String;
begin
UseHost := Edit1.Text;
UsePort := STRTOINT(Edit2.Text);
ClientSocket1.Port := UsePort;
ClientSocket1.Host := UseHost;
ClientSocket1.Active := true;
end;
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
begin
ListBox1.Items.Add(ClientSocket1.Socket.ReceiveText);
end;
procedure TForm1.ClientSocket1Error(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
ErrorCode:=0;
ClientSocket1.Active := False;
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
ClientSocket1.Socket.SendText(Edit1.Text);
end;
end.
If you need the Indy client to handle incoming "messages" (definition of "message" depends on the protocol used), I recommend to take a look at the implementation of TIdTelnet in the protocols\IdTelnet unit.
This component uses a receiving thread, based on a TIdThread, which asynchronously receives messages from the Telnet server, and passes them to a message handler routine. If you have a similar protocol, this could be a good starting point.
Update: to be more specific, the procedure TIdTelnetReadThread.Run; in IdTelnet.pas is where the asynchronous client 'magic' happens, as you can see it uses Synchronize to run the data processing in the main thread - but of course your app could also do the data handling in the receiving thread, or pass it to a worker thread to keep the main thread untouched. The procedure does not use a loop, because looping / pausing / restarting is implemented in IdThread.
Add a TTimer.
Set its Interval to 1.
Write in OnTimer Event:
procedure TForm1.Timer1Timer(Sender: TObject);
var
s: string;
begin
if not IdTCPClient1.Connected then Exit;
if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
s := IdTCPClient1.IOHandler.InputBufferAsString;
Memo1.Lines.Add('Received: ' + s);
end;
Don't set Timer.Interval something else 1.
Because, the received data deletes after some milliseconds.
I want to send a broadcast UDP message in my LAN, the application is client/server.
I desire to update the user interface, this way any computer send a message to update the others.
Can I use UDPServer indy, how to use ?
Thanks
Create two applications, one represents the sender and the other the receiver.
Sender
Drop a TIdUDPClient and a TButton component on your form. On the OnClick handler of the button write:
procedure TfrmUDPClient.BroadcastClick(Sender: TObject);
begin
UDPClient.Broadcast('Test', 8090);
end;
Receiver
Drop a TIdUDPServer on your form, define the same port (8090) for it and add this to the OnUDPRead handler:
procedure TfrmUDPServer.UDPServerUDPRead(Sender: TObject; AData: TStream; ABinding: TIdSocketHandle);
var
DataStringStream: TStringStream;
Msg: String;
begin
DataStringStream := TStringStream.Create('');
try
DataStringStream.CopyFrom(AData, AData.Size);
Msg := DataStringStream.DataString;
finally
DataStringStream.Free;
end;
ShowMessage(Msg);
end;
Or, in later versions of Indy:
procedure TfrmUDPServer.UDPServerUDPRead(AThread: TIdUDPListenerThread;
const AData: TIdBytes; ABinding: TIdSocketHandle);
var
Msg: String;
begin
try
{if you actually sent a string encoded in utf-8}
Msg := TEncoding.UTF8.GetString(AData);
except
end;
ShowMessage(Msg);
end;
To test, run both applications and click on the button. To test with two or more "listeners" you have to use another machine. That is, you can't run multiple listeners on the same IP.
Create a TIdUDPServer or TIdUDPClient component. Both have Broadcast methods that should do exactly what you need.