Newsgroups : Borland : borland.public.delphi.internet.winsock : 2006 Jun : Re: IdTCPServer
| Subject: | Re: IdTCPServer |
| Posted by: | "Jamie Dale" (j.da..@turboz.net) |
| Date: | Thu, 8 Jun 2006 23:24:47 |
Ah, the very source code I also learnt from lol.
Remy will no doubt have some advice for you though - IE about them threads
etc.
In my own server I use an array to keep track of users and threads. Until
recently I thought all was good but recently I've been told that the server
threads could mess up my arrays - now hunting for a way of dealing with it
lol
Gotta give Jeremy Darling credit though, he wrote a good basic demo to get
me started - shame he didn't show how to deal with the thread issue :(
"brandon" <someone@microsoft.com> wrote in message
news:44888e88$1@newsgroups.borland.com...
>I have copied the code for the server below. The error is raised by the
>client when issuing IdTCPClient1.Connect(10)
>
> ^=============================================================
>
> unit MainForm;
>
> interface
>
> uses
> Windows, Messages, Graphics, Controls, Forms, Dialogs, ComCtrls,
> StdCtrls,
> ExtCtrls, ToolWin, ImgList, Spin, Menus, SysUtils, Classes,
> IdBaseComponent,
> IdComponent, IdTCPServer, IdThreadMgr, IdThreadMgrDefault, ShellAPI,
> StrUtils,
> IdAntiFreezeBase, IdAntiFreeze;
>
> const
> WM_APPSERVERICON = WM_USER + 1;
> UI_INITIALIZE = WM_APPSERVERICON + 1;
>
>
> type
> TSimpleClient = class(TObject)
> DNS,
> Name : String;
> ListLink : Integer;
> Thread : Pointer;
> IP : String;
> FullName : String;
> MachineName : String;
> GUID : String;
> ClientNickName: String;
> end;
>
> TfrmMain = class(TForm)
> StatusBar1: TStatusBar;
> Panel1: TPanel;
> Panel2: TPanel;
> lbClients: TListBox;
> PageControl1: TPageControl;
> TabSheet2: TTabSheet;
> TabSheet3: TTabSheet;
> ImageList1: TImageList;
> Label3: TLabel;
> lblDNS: TLabel;
> tcpServer: TIdTCPServer;
> lblSocketVer: TLabel;
> Label5: TLabel;
> Label4: TLabel;
> seBinding: TSpinEdit;
> IdThreadMgrDefault1: TIdThreadMgrDefault;
> Label6: TLabel;
> memEntry: TMemo;
> Label7: TLabel;
> memEMotes: TMemo;
> Label8: TLabel;
> Label9: TLabel;
> lblClientName: TLabel;
> lblClientDNS: TLabel;
> puMemoMenu: TPopupMenu;
> Savetofile1: TMenuItem;
> Loadfromfile1: TMenuItem;
> ToolBar1: TToolBar;
> btnServerUp: TToolButton;
> ToolButton1: TToolButton;
> btnKillClient: TToolButton;
> btnClients: TToolButton;
> btnPM: TToolButton;
> Label12: TLabel;
> edSyopName: TEdit;
> Label1: TLabel;
> lblClientIP: TLabel;
> Label2: TLabel;
> lblClientFullName: TLabel;
> Label10: TLabel;
> lblClientMachineName: TLabel;
> Label11: TLabel;
> lblClientGUID: TLabel;
> Label13: TLabel;
> lblClientNickName: TLabel;
> pmIcon: TPopupMenu;
> Show2: TMenuItem;
> Show1: TMenuItem;
> Shutdown1: TMenuItem;
> Timer1: TTimer;
> IdAntiFreeze1: TIdAntiFreeze;
> procedure btnServerUpClick(Sender: TObject);
> procedure FormCreate(Sender: TObject);
> procedure seBindingChange(Sender: TObject);
> procedure tcpServerConnect(AThread: TIdPeerThread);
> procedure tcpServerDisconnect(AThread: TIdPeerThread);
> procedure FormClose(Sender: TObject; var Action: TCloseAction);
> procedure tcpServerExecute(AThread: TIdPeerThread);
> procedure btnClientsClick(Sender: TObject);
> procedure btnPMClick(Sender: TObject);
> procedure btnKillClientClick(Sender: TObject);
> procedure lbClientsClick(Sender: TObject);
> procedure Timer1Timer(Sender: TObject);
> procedure Show2Click(Sender: TObject);
> procedure Shutdown1Click(Sender: TObject);
> procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
> procedure FormDestroy(Sender: TObject);
> private
> { Private declarations }
> FTaskMessage: DWord;
> FIconData: TNotifyIconData;
> public
> { Public declarations }
> Clients : TList;
> procedure UpdateBindings;
> procedure UpdateClientList;
> procedure BroadcastMessage( WhoFrom, TheMessage : String );
> procedure Initialize(FromService: boolean);
> protected
> procedure AddIcon;
> procedure UIInitialize(var Message: TMessage); message UI_INITIALIZE;
> procedure WMAPPSERVERIcon(var Message: TMessage); message
> WM_APPSERVERICON;
> procedure WndProc(var Message: TMessage); override;
> end;
>
> var
> frmMain: TfrmMain;
>
> implementation
>
> {$R *.DFM}
>
> uses
> IdSocketHandle; // This is where the IdSocketHandle class is defined.
>
> procedure TfrmMain.UpdateBindings;
> var
> Binding : TIdSocketHandle;
> begin
> { Set the TIdTCPServer's port to the chosen value }
> tcpServer.DefaultPort := seBinding.Value;
> { Remove all bindings that currently exist }
> tcpServer.Bindings.Clear;
> { Create a new binding }
> Binding := tcpServer.Bindings.Add;
> { Assign that bindings port to our new port }
> Binding.Port := seBinding.Value;
> end;
>
> procedure TfrmMain.btnServerUpClick(Sender: TObject);
> begin
> try
> { Check to see if the server is online or offline }
> tcpServer.Active := not tcpServer.Active;
> btnServerUp.Down := tcpServer.Active;
> if btnServerUp.Down then
> begin
> { Server is online }
> btnServerUp.ImageIndex := 1;
> btnServerUp.Hint := 'Shut down server';
> end
> else
> begin
> { Server is offline }
> btnServerUp.ImageIndex := 0;
> btnServerUp.Hint := 'Start up server';
> end;
> { Setup GUI buttons }
> btnClients.Enabled:= btnServerUp.Down;
> seBinding.Enabled := not btnServerUp.Down;
> edSyopName.Enabled:= not btnServerUp.Down;
> except
> { If we have a problem then rest things }
> btnServerUp.Down := false;
> seBinding.Enabled := not btnServerUp.Down;
> btnClients.Enabled:= btnServerUp.Down;
> edSyopName.Enabled:= not btnServerUp.Down;
> end;
> end;
>
> procedure TfrmMain.FormCreate(Sender: TObject);
> begin
> { Initalize our clients list }
> Clients := TList.Create;
> { Call updatebindings so that the servers bindings are correct }
> UpdateBindings;
> { Get the local DNS entry for this computer }
> lblDNS.Caption := tcpServer.LocalName;
> { Display the current version of indy running on the system }
> lblSocketVer.Caption := tcpServer.Version;
>
> btnServerUp.Click;
> end;
>
> procedure TfrmMain.seBindingChange(Sender: TObject);
> begin
> UpdateBindings;
> end;
>
> procedure TfrmMain.tcpServerConnect(AThread: TIdPeerThread);
> var
> Client : TSimpleClient;
> begin
> { Send a welcome message, and prompt for the users name }
> AThread.Connection.WriteLn('ISD Connection Established...');
> // AThread.Connection.WriteLn('Please send valid login sequence...');
> // AThread.Connection.WriteLn('Your Name:');
> { Create a client object }
> Client := TSimpleClient.Create;
> { Assign its default values }
> Client.DNS := AThread.Connection.LocalName;
> Client.Name := 'Logging In';
> Client.ListLink := lbClients.Items.Count;
> { Assign the thread to it for ease in finding }
> Client.Thread := AThread;
> { Add to our clients list box }
> lbClients.Items.Add(Client.Name);
> { Assign it to the thread so we can identify it later }
> AThread.Data := Client;
> { Add it to the clients list }
> Clients.Add(Client);
> end;
>
> procedure TfrmMain.tcpServerDisconnect(AThread: TIdPeerThread);
> var
> Client : TSimpleClient;
> begin
> { Retrieve Client Record from Data pointer }
> Client := Pointer(AThread.Data);
> { Remove Client from the Clients TList }
> Clients.Delete(Client.ListLink);
> { Remove Client from the Clients List Box }
> lbClients.Items.Delete(lbClients.Items.IndexOf(Client.Name));
> BroadcastMessage('System', Client.Name + ' has logged out.');
> { Free the Client object }
> Client.Free;
> AThread.Data := nil;
> end;
>
> procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
> begin
> if (Clients.Count > 0) and (tcpServer.Active) then
> begin
> Action := caNone;
> ShowMessage('Can''t close CBServ while server is online.');
> end
> else Clients.Free;
> end;
>
> procedure TfrmMain.UpdateClientList;
> var
> Count : Integer;
> begin
> { Loop through all the clients connected to the system and set their
> names }
> for Count := 0 to lbClients.Items.Count -1 do
> if Count < Clients.Count then
> lbClients.Items.Strings[Count] :=
> TSimpleClient(Clients.Items[Count]).Name;
> end;
>
> procedure TfrmMain.tcpServerExecute(AThread: TIdPeerThread);
> var
> Client: TSimpleClient;
> Com, Msg, sToWho: String;
> sl, slRecord: TStringList;
> Count: Integer;
> begin
> { Get the text sent from the client }
> Msg := AThread.Connection.ReadLn;
> { Get the clients package info }
> Client := Pointer(AThread.Data);
> { Check to see if the clients name has been assigned yet }
> if Client.Name = 'Logging In' then
> begin
> { if not, assign the name and announce the client }
> //parse the message here!!!
> sl := TStringList.Create;
> sl.Delimiter := '|';
> sl.DelimitedText := Msg;
> Client.Name := sl.Values['NAME'];
> Client.IP := sl.Values['IP'];
> Client.FullName := sl.Values['FullName'];
> Client.MachineName := sl.Values['MachineName'];
> Client.GUID := sl.Values['GUID'];
> Client.ClientNickName := sl.Values['ClientNickName'];
> sl.Clear;
> FreeAndNil( sl );
> //Client.Name := Msg;
> UpdateClientList;
> BroadcastMessage('System', Client.Name + ':(' + Client.FullName + ')'
> + ' has just logged in.');
> AThread.Connection.WriteLn(memEntry.Lines.Text);
> end
> else
> { If name is set, then send the message }
> if ( Msg[1] <> '@' ) and ( Msg[1] <> '#' ) then
> begin
> { Not a system command }
> BroadcastMessage(Client.Name, Msg);
> end
> else
> begin
> { System command }
> Com := UpperCase(Trim(Copy(Msg, 2, Pos(':', Msg) -2)));
> Msg := Trim(Copy(Msg, Pos(':', Msg) +1, Length(Msg)));
> if Com = 'CLIENTS' then
> begin
> //AThread.Connection.WriteLn( '@' + 'clients:' +
> lbClients.Items.CommaText);
> sl := TStringList.Create;
> sl.Delimiter := '|';
> sl.DelimitedText := Msg;
>
> slRecord := TStringList.Create;
> slRecord.Delimiter := '~';
>
> for Count := 0 to Clients.Count -1 do
> begin
> slRecord.Clear;
> slRecord.Add( 'NAME=' +
> TSimpleClient(Clients[Count]).Name );
> slRecord.Add( 'IP=' + TSimpleClient(Clients[Count]).IP );
> slRecord.Add( 'FULLNAME=' +
> TSimpleClient(Clients[Count]).FullName );
> slRecord.Add( 'MACHINENAME=' +
> TSimpleClient(Clients[Count]).MachineName );
> slRecord.Add( 'GUID=' +
> TSimpleClient(Clients[Count]).GUID );
> slRecord.Add( 'ClientNickName=' +
> TSimpleClient(Clients[Count]).ClientNickName );
> sl.Add(slRecord.DelimitedText);
> end;
> AThread.Connection.WriteLn( '@' + 'clients:' +
> sl.DelimitedText);
> sl.Clear;
> FreeAndNil( sl );
> slRecord.Clear;
> FreeAndNil( slRecord );
> end;
>
> if Com = 'PRIVATE' then
> begin
> //get the id and send to that client
> sToWho := MidStr(Msg,1,Pos('~',Msg) - 1 );
> Msg := Trim(MidStr(Msg,Pos('~',Msg) + 1, 9999));
> Msg := 'Private=>' + Msg;
> for Count := 0 to Clients.Count - 1 do
> begin
> Client := TSimpleClient(Clients[Count]);
> if Client.GUID = sToWho then
> TIdPeerThread(Client.Thread).Connection.WriteLn(Msg);
> end;
> end;
>
> if Com = 'SYSMSG' then
> begin
> //get the id and send to that client
> sToWho := MidStr(Msg,1,Pos('~',Msg) - 1 );
> Msg := Trim(MidStr(Msg,Pos('~',Msg) + 1, 9999));
> Msg := '#SYSMSG:' + sToWho + '~' + Msg;
> if sToWho = '*' then
> begin
> for Count := 0 to Clients.Count - 1 do
> begin
> Client := TSimpleClient(Clients[Count]);
>
> TIdPeerThread(Client.Thread).Connection.WriteLn(Msg);
> end;
> end
> else
> begin
> for Count := 0 to Clients.Count - 1 do
> begin
> Client := TSimpleClient(Clients[Count]);
> if Client.GUID = sToWho then
> TIdPeerThread(Client.Thread).Connection.WriteLn(Msg);
> end;
> end;
> end;
>
> if Com = 'SYSCMD' then
> begin
> //get the id and send to that client
> sToWho := MidStr(Msg,1,Pos('~',Msg) - 1 );
> Msg := Trim(MidStr(Msg,Pos('~',Msg) + 1, 9999));
> Msg := '#SYSCMD:' + sToWho + '~' + Msg;
> if sToWho = '*' then
> begin
> for Count := 0 to Clients.Count - 1 do
> begin
> Client := TSimpleClient(Clients[Count]);
> TIdPeerThread(Client.Thread).Connection.WriteLn(Msg);
> end;
> end
> else
> begin
> for Count := 0 to Clients.Count - 1 do
> begin
> Client := TSimpleClient(Clients[Count]);
> if Client.GUID = sToWho then
> TIdPeerThread(Client.Thread).Connection.WriteLn(Msg);
> end;
> end;
> end;
> end;
> end;
>
> procedure TfrmMain.BroadcastMessage( WhoFrom, TheMessage : String );
> var
> Count: Integer;
> List : TList;
> EMote,
> Msg : String;
> begin
> Msg := Trim(TheMessage);
>
> EMote := Trim(memEMotes.Lines.Values[Msg]);
>
> if WhoFrom <> 'System' then Msg := WhoFrom + ': ' + Msg;
>
> if EMote <> '' then Msg := Format(Trim(EMote), [WhoFrom]);
>
> List := tcpServer.Threads.LockList;
> try
> for Count := 0 to List.Count -1 do
> try
> TIdPeerThread(List.Items[Count]).Connection.WriteLn(Msg);
> except
> TIdPeerThread(List.Items[Count]).Stop;
> end;
> finally
> tcpServer.Threads.UnlockList;
> end;
> end;
>
> procedure TfrmMain.btnClientsClick(Sender: TObject);
> begin
> UpdateClientList;
> end;
>
> procedure TfrmMain.btnPMClick(Sender: TObject);
> var
> Msg : String;
> Client : TSimpleClient;
> begin
> Msg := InputBox('Private Message', 'What is the message', '');
> Msg := Trim(Msg);
> Msg := edSyopName.Text + '> ' + Msg;
> if (Msg <> '') and (lbClients.ItemIndex <> -1) then
> begin
> Client := Clients.Items[lbClients.ItemIndex];
> TIdPeerThread(Client.Thread).Connection.WriteLn(Msg);
> end;
> end;
>
> procedure TfrmMain.btnKillClientClick(Sender: TObject);
> var
> Msg : String;
> Client : TSimpleClient;
> begin
> Msg := InputBox('Disconnect message', 'Enter a reason for the
> disconnect', '');
> Msg := Trim(Msg);
> Msg := edSyopName.Text + '> ' + Msg;
> if (Msg <> '') and (lbClients.ItemIndex <> -1) then
> begin
> Client := Clients.Items[lbClients.ItemIndex];
> TIdPeerThread(Client.Thread).Connection.WriteLn(Msg);
> TIdPeerThread(Client.Thread).Connection.Disconnect;
> Clients.Delete(lbClients.ItemIndex);
> lbClients.Items.Delete(lbClients.ItemIndex);
> end;
> end;
>
> procedure TfrmMain.lbClientsClick(Sender: TObject);
> var
> Client : TSimpleClient;
> begin
> btnPM.Enabled := lbClients.ItemIndex <> -1;
> btnKillClient.Enabled := btnPM.Enabled;
>
> if lbClients.ItemIndex = -1 then exit;
>
> Client := Clients.Items[lbClients.ItemIndex];
> lblClientName.Caption := Client.Name;
> lblClientDNS.Caption := Client.DNS;
> lblClientIP.Caption := Client.IP;
> lblClientFullName.Caption := Client.FullName;
> lblClientMachineName.Caption := Client.MachineName;
> lblClientGUID.Caption := Client.GUID;
> lblClientNickName.Caption := Client.ClientNickName;
> end;
>
> procedure TfrmMain.Timer1Timer(Sender: TObject);
> begin
> AddIcon;
> Refresh;
> Application.ProcessMessages;
> end;
>
> procedure TfrmMain.Show2Click(Sender: TObject);
> begin
> self.Show;
> end;
>
> procedure TfrmMain.Shutdown1Click(Sender: TObject);
> begin
> Application.Terminate;
> end;
>
> procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
> begin
> self.Hide;
> CanClose := false;
> end;
>
> procedure TfrmMain.AddIcon;
> begin
> with FIconData do
> begin
> cbSize := SizeOf(FIconData);
> Wnd := Self.Handle;
> uID := $DEDB;
> uFlags := NIF_MESSAGE or NIF_ICON or NIF_TIP;
> hIcon := Forms.Application.Icon.Handle;
> uCallbackMessage := WM_APPSERVERICON;
> StrCopy(szTip, PChar(Caption));
> end;
> Shell_NotifyIcon(NIM_Add, @FIconData);
> end;
>
> procedure TfrmMain.Initialize(FromService: boolean);
> begin
> FTaskMessage := RegisterWindowMessage('LicenseServerCreated');
> end;
>
>
>procedure TfrmMain.UIInitialize(var Message: TMessage);
> begin
> Initialize(Message.WParam <> 0);
> end;
>
> procedure TfrmMain.WMAPPSERVERIcon(var Message: TMessage);
> var
> pt: TPoint;
> begin
> case Message.LParam of
> WM_RBUTTONUP:
> begin
> // if not Visible then
> if true then
> begin
> SetForegroundWindow(Handle);
> GetCursorPos(pt);
> pmIcon.Popup(pt.x, pt.y);
> end
> else SetForegroundWindow(Handle);
> end;
> WM_LBUTTONDBLCLK: Show2Click(nil);
> end;
> end;
>
> procedure TfrmMain.WndProc(var Message: TMessage);
> begin
> if Message.Msg = FTaskMessage then
> begin
> AddIcon;
> Refresh;
> end;
> inherited WndProc(Message);
> end;
>
> procedure TfrmMain.FormDestroy(Sender: TObject);
> begin
> Shell_NotifyIcon(NIM_DELETE, @FIconData);
> end;
>
>
> "Remy Lebeau (TeamB)" <no.spam@no.spam.com> wrote in message
> news:44888c5f$1@newsgroups.borland.com...
>>
>> "brandon" <someone@microsoft.com> wrote in message
>> news:448887fc$1@newsgroups.borland.com...
>>
>>> The server is actually the Indy sample that came with Delphi 6. I did
>>> little or no modifiaction to the server sample that was shipped.
>>> The client was also based on the Indy sample. I have made modification
>>> to to it only to handle different message types (Private, Global,
>> Internal...)
>>
>> I don't have the samples onhand. Please just show the offending code.
>>
>>> The actual error appears to be that when a client session terminates in
>>> an "unclean" manner, the server becomes unstatble. By that I mean, it
>>> appears that the next user to attempt to connect gets the "Socket Error
>>> #
>> 0"
>>> message.
>>
>> That is very unlikely. Every client has its own unique socket endpoint
>> in
>> the server. Even if one client did crash, or otherwise got disconnected
>> abnormally, the other clients would not be effected. Unless the network
>> itself is having problems, that is.
>>
>>> Starting at the botom of the call stack and working up, the first line
>>> of
>>> code that belongs to me is line 213:
>>> if not IdTCPClient1.Connected then IdTCPClient1.Connect(10);
>>> As stated above, it appears that the server has become unstable when a
>> user
>>> terminates the session without allowing the application to issue a clean
>>> disconnect. Subsequent attempts by the offending user or other users
>>> attempting to attach to the server raise the connection error at the
>> client
>>> end.
>>
>> That is not what the call stack was showing. But again, you did not show
>> the COMPLETE call stack.
>>
>>> I am only speculating with regard to the server being "unstable", it
>>> is only an observation of a person with limited exposure to Indy.
>>
>> Did you try connecting to the server with any non-Indy clients? The
>> Telnet
>> client that is built into Windows is useful for debugging.
>>
>>> There are no error messages at the server when the clients receive the
>> error.
>>
>> Are any of the server events being triggered?
>>
>>> There is a listbox at the server that shows which users are logged into
>>> the server
>>
>> Updating the UI from inside TIdTCPServer event handlers is not
>> thread-safe,
>> unless TThread.Synchronize() or TIdSync.Synchronize() are being used.
>>
>>
>> Gambit