werehamster-
asked on
Properly Coded Indy Reader Threads - Part 2
This is basically a continuation of a privious question...
https://www.experts-exchange.com/questions/21116285/Properly-Formatted-and-Stable-Indy-TCP-Client-Thread-w-Reader-Thread.html
I've got a decent system of reading the packets and using methods of handling the data, but now I need a good way of handling disconnects from the server end. (abnormal or whatever)
I cannot modify the server in anyway, but every packet begins with...
Record
StartPacket : Byte; //always $FF
PacketID : Byte; //function
PacketLength : Word; // including header
PacketData : array[PacketLength-4] of byte
I am now, with good suggestions for jacco, using a consts file that contains all the packet ID's and a variable that holds the names of methods that can be searched searched for and run...
const
SID_NULL = $00;
SID_CLIENTID = $05;
[...]
published
[...]
procedure Process_SID_NULL;
procedure Process_SID_CLIENTID;
[...]
var
BNET_HeaderNames: array[Byte] of string;
initialization
BNET_HeaderNames[SID_NULL] := 'SID_NULL';
BNET_HeaderNames[SID_CLIEN TID] := 'SID_CLIENTID';
It currently has threadsafe methods of writing packets from outside the thread. I am basically looking for more ideas and suggestions.
What I really need is a good way to terminate the connection. Should I just .free the reader thread and indy client or is there a better way?
PS, I don't like just links to commonly known sites. I like examples and detailed suggestions.
I will be awarding points appropriately. Some points are owed to a contributor in "Part 1", but I will still give to others who contribute. Soon as there is enough useful information in this topic, I will make a "Part 3" and continue on from there.
https://www.experts-exchange.com/questions/21116285/Properly-Formatted-and-Stable-Indy-TCP-Client-Thread-w-Reader-Thread.html
I've got a decent system of reading the packets and using methods of handling the data, but now I need a good way of handling disconnects from the server end. (abnormal or whatever)
I cannot modify the server in anyway, but every packet begins with...
Record
StartPacket : Byte; //always $FF
PacketID : Byte; //function
PacketLength : Word; // including header
PacketData : array[PacketLength-4] of byte
I am now, with good suggestions for jacco, using a consts file that contains all the packet ID's and a variable that holds the names of methods that can be searched searched for and run...
const
SID_NULL = $00;
SID_CLIENTID = $05;
[...]
published
[...]
procedure Process_SID_NULL;
procedure Process_SID_CLIENTID;
[...]
var
BNET_HeaderNames: array[Byte] of string;
initialization
BNET_HeaderNames[SID_NULL]
BNET_HeaderNames[SID_CLIEN
It currently has threadsafe methods of writing packets from outside the thread. I am basically looking for more ideas and suggestions.
What I really need is a good way to terminate the connection. Should I just .free the reader thread and indy client or is there a better way?
PS, I don't like just links to commonly known sites. I like examples and detailed suggestions.
I will be awarding points appropriately. Some points are owed to a contributor in "Part 1", but I will still give to others who contribute. Soon as there is enough useful information in this topic, I will make a "Part 3" and continue on from there.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Ok. So what if I want to disconnect from the client side manually instead of waiting for the server to disconnect? Do I just use MyReaderThread.free?
ASKER
>> String variables can contain nulls (#0 characters as well) the only thing you have to take care of is when using string functions that expect a PChar with these strings
I don't get what you mean. Do you mean function that convert strings to PChar?
Anyway, do I have to change any of the sample code you origonally gave me at all?
This is what I have re-written so far...
unit BotFrm;
interface
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs, IdTCPClient, SyncObjs,
QStdCtrls, QExtCtrls, QMenus, QTypes, QComCtrls, MainFrm, inifiles, PacketConsts;
type
TBNETReader = class;
TBotForm = class(TForm)
OutputMemo: TMemo;
ListBox1: TListBox;
InputMemo: TMemo;
Splitter1: TSplitter;
fReader : TBNETReader;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
public
INI : TIniFile;
end;
TClientState = (csDisconnected, csConnected);
TMessageHandler = procedure of object;
TBNETReader = class(TThread)
fClient: TIdTCPClient;
fForm : TBotForm;
fState: TClientState;
fInPacket : TBNETPacket;
fOutPacket : TBNETPacket;
fQueueOutSection: TCriticalSection;
fReadPos : Word;
constructor Create(AForm : TBotForm);
destructor Destroy; override;
procedure Execute; override;
procedure DoMessage;
procedure DoCaption;
published
//procedure Process_SID_PING;
procedure Process_SID_OTHER;
end;
implementation
{$R *.xfm}
constructor TBNETReader.Create(AForm : TBotForm);
begin
inherited Create(False);
fClient := TIdTCPClient.Create(nil);
fForm := AForm;
fReadPos := 0;
fQueueOutSection := TCriticalSection.Create;
synchronize(DoCaption);
end;
destructor TBNETReader.Destroy;
begin
fClient.Free;
fQueueOutSection.Free;
inherited Destroy;
end;
procedure TBNETReader.Execute;
begin
while not Terminated do
begin
// fClient.ReadFromStack(Fals e,0,False) ;
Sleep(10);
end;
end;
procedure TBNETReader.DoMessage;
begin
end;
procedure TBNETREader.DoCaption;
const
cConnected : array[Boolean] of string = ('Not Connected','Connected');
begin
FForm.Caption := FForm.INI.ReadString('Main ','Uername ','Werebot ')
+' - '+cConnected[fClient.Conne cted];
end;
procedure TBNETReader.Process_SID_OT HER;
begin
end;
procedure TBotForm.FormCreate(Sender : TObject);
begin
If not assigned(INI) then
INI := TIniFile.Create(ExtractFil ePath(Appl ication.Ex eName)+'co nfig.ini') ;
If INI.ReadString('Main','Con nectOnStar tup','N') = 'Y' then
Begin
OutputMemo.Lines.Add('Conn ecting...' );
End;
end;
procedure TBotForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
end.
-------------------------- ---------- ---------- ---------- --------
unit MainFrm;
interface
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs,
QExtCtrls, QComCtrls, QTypes, QMenus, QStdCtrls, QImgList, QActnList,
QStdActns,inifiles;
type
TMainForm = class(TForm)
StatusBar1: TStatusBar;
MainMenu: TMainMenu;
SaveDialog: TSaveDialog;
OpenDialog: TOpenDialog;
ActionList: TActionList;
actnWinClose: TWindowClose;
actnWinCascade: TWindowCascade;
actnWinTile: TWindowTile;
actnWinMinimizeAll: TWindowMinimizeAll;
actnExit: TAction;
actnConnect: TAction;
ImageList: TImageList;
Connect1: TMenuItem;
Timer1: TTimer;
procedure actnConnectExecute(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
public
INI : TIniFile;
procedure CreateMDIChild(const Name: string);
end;
var
MainForm: TMainForm;
implementation
{$R *.xfm}
uses
BotFrm;
resourcestring
sUntitled = 'Untitled';
procedure TMainForm.CreateMDIChild(c onst Name: string);
var
Child: TBotForm;
begin
{ create a new MDI child window }
Child := TBotForm.Create(Applicatio n);
Child.Caption := Name;
Child.INI := TIniFile.Create(Name)
end;
procedure TMainForm.actnConnectExecu te(Sender: TObject);
var
ABotForm : TBotForm;
begin
if not Assigned(ActiveMDIChild) then
Begin
ABotForm := TBotForm.Create(Self);
ABotForm.WindowState := wsMaximized;
ABotForm.INI := TIniFile.Create(ExtractFil ePath(Appl ication.Ex eName)+'co nfig.ini') ;
ABotForm.Caption := ABotForm.INI.ReadString('M ain','User name','Wer ebot');
End
else if ActiveMDIChild is TBotForm then
Begin
TBotForm(ActiveMDIChild).O utPutMemo. Lines.Add( 'Connect Click')
End;
end;
procedure TMainForm.FormCreate(Sende r: TObject);
begin
Ini := TIniFile.Create( ExtractFilePath(Applicatio n.ExeName) +'config.i ni' );
Top := Ini.ReadInteger( 'Position', 'Top', 50 );
Left := Ini.ReadInteger( 'Position', 'Left', 100 );
Height := Ini.ReadInteger( 'Position', 'Height', 400 );
Width := Ini.ReadInteger( 'Position', 'Width', 540 );
Timer1.Interval := 100;
Timer1.Enabled := True;
end;
procedure TMainForm.FormClose(Sender : TObject; var Action: TCloseAction);
begin
ini.WriteInteger('Position ','Top',To p);
ini.WriteInteger('Position ','Left',L eft);
ini.WriteInteger('Position ','Height' ,Height);
ini.WriteInteger('Position ','Width', Width);
ini.Free;
end;
procedure TMainForm.Timer1Timer(Send er: TObject);
begin
If Ini.ReadString('Main','Con nectOnStar tup','N') = 'Y' then
Begin
MainForm.actnConnectExecut e(Self);
End;
Timer1.Enabled := False;
end;
end.
-------------------------- ---------- ---------- -----
unit PacketConsts;
interface
const
SID_NULL = $00;
SID_CLIENTID = $05;
SID_STARTVERSIONING = $06;
SID_REPORTVERSION = $07;
SID_GETADVLISTEX = $09;
SID_ENTERCHAT = $0A;
SID_GETCHANNELLIST = $0B;
SID_JOINCHANNEL = $0C;
SID_CHATCOMMAND = $0E;
SID_CHATEVENT = $0F;
SID_FLOODDETECTED = $13;
SID_UDPPINGRESPONSE = $14;
SID_MESSAGEBOX = $19;
SID_PING = $25;
SID_READUSERDATA = $26;
SID_WRITEUSERDATA = $27;
SID_LOGONCHALLENGE = $28;
SID_LOGONRESPONSE = $29;
SID_CREATEACCOUNT = $2A;
SID_CHANGEPASSWORD = $31;
SID_CDKEY2 = $36;
SID_LOGONRESPONSE2 = $3A;
SID_CREATEACCOUNT2 = $3D;
SID_LOGONREALMEX = $3E;
SID_AUTH_INFO = $50;
SID_AUTH_CHECK = $51;
SID_FRIENDLIST = $65;
SID_FRIENDUPDATE = $66;
SID_FRIENDADDED = $67;
SID_FRIENDREMOVED = $68;
SID_FRIENDMOVED = $69;
SID_FINDCLANCANDIDATES = $70;
SID_INVITEMULTIPLEUSERS = $71;
SID_DISBANDCLAN = $73;
SID_CLANINFO = $75;
SID_CLANREQUEST = $77;
SID_CLANINVITE = $79;
SID_CLANMOTD = $7C;
SID_CLANMEMBERLIST = $7D;
SID_CLANMEMBERUPDATE = $7F;
SID_CLANPROMOTION = $81;
var
SID_HeaderNames: array[Byte] of string;
const
MCP_STARTUP = $01;
MCP_CHARCREATE = $02;
MCP_CHARLOGON = $07;
MCP_CHARDELETE = $0A;
MCP_MOTD = $12;
MCP_CHARLIST = $17;
MCP_CHARUPGRADE = $18;
const
PACKET_IDLE = $00;
PACKET_LOGON = $01;
PACKET_STATSUPDATE = $02;
PACKET_DATABASE = $03;
PACKET_MESSAGE = $04;
PACKET_CYCLE = $05;
PACKET_USERINFO = $06;
PACKET_USERLOGGINGOFF = $07;
PACKET_COMMAND = $08;
PACKET_CHANGEDBPASSWORD = $09;
PACKET_BOTNETVERSION = $0A;
PACKET_BOTNETCHAT = $0B;
PACKET_ACCOUNT = $0D;
const
PKT_SERVERPING = $05;
PKT_KEEPALIVE = $07;
PKT_CONNTEST2 = $09;
const
EID_SHOWUSER = $01;
EID_JOIN = $02;
EID_LEAVE = $03;
EID_WHISPER = $04;
EID_TALK = $05;
EID_BROADCAST = $06;
EID_CHANNEL = $07;
EID_USERFLAGS = $09;
EID_WHISPERSENT = $0A;
EID_CHANNELFULL = $0D;
EID_CHANNELDOESNOTEXIST = $0E;
EID_ERROR = $13;
EID_INFO = $12;
EID_EMOTE = $17;
MAX_EVENT = $18;
const
BNLS_NULL = $00;
BNLS_CDKEY = $01;
BNLS_LOGONCHALLENGE = $02;
BNLS_LOGONPROOF = $03;
BNLS_CREATEACCOUNT = $04;
BNLS_CHANGECHALLENGE = $05;
BNLS_CHANGEPROOF = $06;
BNLS_UPGRADECHALLENGE = $07;
BNLS_UPGRADEPROOF = $08;
BNLS_VERSIONCHECK = $09;
BNLS_CONFIRMLOGON = $0A;
BNLS_HASHDATA = $0B;
BNLS_CDKEY_EX = $0C;
BNLS_CHOOSENLSREVISION = $0D;
BNLS_AUTHORIZE = $0E;
BNLS_AUTHORIZEPROOF = $0F;
BNLS_REQUESTVERSIONBYTE = $10;
BNLS_VERIFYSERVER = $11;
BNLS_RESERVESERVERSLOTS = $12;
BNLS_SERVERLOGONCHALLENGE= $13;
BNLS_SERVERLOGONPROOF = $14;
const
PRODUCT_STARCRAFT = $01;
PRODUCT_BROODWAR = $02;
PRODUCT_WAR2BNE = $03;
PRODUCT_DIABLO2 = $04;
PRODUCT_LORDOFDESTRUCTION= $05;
PRODUCT_JAPANSTARCRAFT = $06;
PRODUCT_WARCRAFT3 = $07;
PRODUCT_THEFROZENTHRONE = $08;
const
SID_AUTH_INFO_Data : array[$00..$39] of byte = (
$FF,$50,$3A,$00,$00,$00,$0 0,$00,$36, $38,$58,$4 9,$50,$58, $33,$57, // .P:.....68XIPX3W
$10,$00,$00,$00,$53,$55,$6 E,$65,$18, $BA,$00,$A 6,$F0,$00, $00,$00, // ....SUne........
$09,$04,$00,$00,$09,$04,$0 0,$00,$55, $53,$41,$0 0,$55,$6E, $69,$74, // ........USA.Unit
$65,$64,$20,$53,$74,$61,$7 4,$65,$73, $00 // ed States.
);
type
TByteArray = array[Word] of Byte;
TBNETPacket = Record
StartPacket : Byte; // Always $FF
PacketID : Byte;
PacketLength : Word; // Including Header
PacketData : TByteArray;
End;
implementation
initialization
SID_HeaderNames[SID_NULL] := 'SID_NULL';
SID_HeaderNames[SID_CLIENT ID] := 'SID_CLIENTID';
SID_HeaderNames[SID_STARTV ERSIONING] := 'SID_STARTVERSIONING';
SID_HeaderNames[SID_REPORT VERSION] := 'SID_REPORTVERSION';
SID_HeaderNames[SID_GETADV LISTEX] := 'SID_GETADVLISTEX';
SID_HeaderNames[SID_ENTERC HAT] := 'SID_ENTERCHAT';
SID_HeaderNames[SID_GETCHA NNELLIST] := 'SID_GETCHANNELLIST';
SID_HeaderNames[SID_JOINCH ANNEL] := 'SID_JOINCHANNEL';
SID_HeaderNames[SID_CHATCO MMAND] := 'SID_CHATCOMMAND';
SID_HeaderNames[SID_CHATEV ENT] := 'SID_CHATEVENT';
SID_HeaderNames[SID_FLOODD ETECTED] := 'SID_FLOODDETECTED';
SID_HeaderNames[SID_UDPPIN GRESPONSE] := 'SID_UDPPINGRESPONSE';
SID_HeaderNames[SID_MESSAG EBOX] := 'SID_MESSAGEBOX';
SID_HeaderNames[SID_PING] := 'SID_PING';
SID_HeaderNames[SID_READUS ERDATA] := 'SID_READUSERDATA';
SID_HeaderNames[SID_WRITEU SERDATA] := 'SID_WRITEUSERDATA';
SID_HeaderNames[SID_LOGONC HALLENGE] := 'SID_LOGONCHALLENGE';
SID_HeaderNames[SID_LOGONR ESPONSE] := 'SID_LOGONRESPONSE';
SID_HeaderNames[SID_CREATE ACCOUNT] := 'SID_CREATEACCOUNT';
SID_HeaderNames[SID_CHANGE PASSWORD] := 'SID_CHANGEPASSWORD';
SID_HeaderNames[SID_CDKEY2 ] := 'SID_CDKEY2';
SID_HeaderNames[SID_LOGONR ESPONSE2] := 'SID_LOGONRESPONSE2';
SID_HeaderNames[SID_CREATE ACCOUNT2] := 'SID_CREATEACCOUNT2';
SID_HeaderNames[SID_LOGONR EALMEX] := 'SID_LOGONREALMEX';
SID_HeaderNames[SID_AUTH_I NFO] := 'SID_AUTH_INFO';
SID_HeaderNames[SID_AUTH_C HECK] := 'SID_AUTH_CHECK';
SID_HeaderNames[SID_FRIEND LIST] := 'SID_FRIENDLIST';
SID_HeaderNames[SID_FRIEND UPDATE] := 'SID_FRIENDUPDATE';
SID_HeaderNames[SID_FRIEND ADDED] := 'SID_FRIENDADDED';
SID_HeaderNames[SID_FRIEND REMOVED] := 'SID_FRIENDREMOVED';
SID_HeaderNames[SID_FRIEND MOVED] := 'SID_FRIENDMOVED';
SID_HeaderNames[SID_FINDCL ANCANDIDAT ES] := 'SID_FINDCLANCANDIDATES';
SID_HeaderNames[SID_INVITE MULTIPLEUS ERS] := 'SID_INVITEMULTIPLEUSERS';
SID_HeaderNames[SID_DISBAN DCLAN] := 'SID_DISBANDCLAN';
SID_HeaderNames[SID_CLANIN FO] := 'SID_CLANINFO';
SID_HeaderNames[SID_CLANRE QUEST] := 'SID_CLANREQUEST';
SID_HeaderNames[SID_CLANIN VITE] := 'SID_CLANINVITE';
SID_HeaderNames[SID_CLANMO TD] := 'SID_CLANMOTD';
SID_HeaderNames[SID_CLANME MBERLIST] := 'SID_CLANMEMBERLIST';
SID_HeaderNames[SID_CLANME MBERUPDATE ] := 'SID_CLANMEMBERUPDATE';
SID_HeaderNames[SID_CLANPR OMOTION] := 'SID_CLANPROMOTION';
end.
-------------------------- ---------- ---------- --------
You can download my project folder via...
www.datazap.net/ftp/werehamster/MyBot/MyBot.zip
also contains 2 semi-functional prototypes called project1 and project2. current one is werebot.
I don't get what you mean. Do you mean function that convert strings to PChar?
Anyway, do I have to change any of the sample code you origonally gave me at all?
This is what I have re-written so far...
unit BotFrm;
interface
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs, IdTCPClient, SyncObjs,
QStdCtrls, QExtCtrls, QMenus, QTypes, QComCtrls, MainFrm, inifiles, PacketConsts;
type
TBNETReader = class;
TBotForm = class(TForm)
OutputMemo: TMemo;
ListBox1: TListBox;
InputMemo: TMemo;
Splitter1: TSplitter;
fReader : TBNETReader;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
public
INI : TIniFile;
end;
TClientState = (csDisconnected, csConnected);
TMessageHandler = procedure of object;
TBNETReader = class(TThread)
fClient: TIdTCPClient;
fForm : TBotForm;
fState: TClientState;
fInPacket : TBNETPacket;
fOutPacket : TBNETPacket;
fQueueOutSection: TCriticalSection;
fReadPos : Word;
constructor Create(AForm : TBotForm);
destructor Destroy; override;
procedure Execute; override;
procedure DoMessage;
procedure DoCaption;
published
//procedure Process_SID_PING;
procedure Process_SID_OTHER;
end;
implementation
{$R *.xfm}
constructor TBNETReader.Create(AForm : TBotForm);
begin
inherited Create(False);
fClient := TIdTCPClient.Create(nil);
fForm := AForm;
fReadPos := 0;
fQueueOutSection := TCriticalSection.Create;
synchronize(DoCaption);
end;
destructor TBNETReader.Destroy;
begin
fClient.Free;
fQueueOutSection.Free;
inherited Destroy;
end;
procedure TBNETReader.Execute;
begin
while not Terminated do
begin
// fClient.ReadFromStack(Fals
Sleep(10);
end;
end;
procedure TBNETReader.DoMessage;
begin
end;
procedure TBNETREader.DoCaption;
const
cConnected : array[Boolean] of string = ('Not Connected','Connected');
begin
FForm.Caption := FForm.INI.ReadString('Main
+' - '+cConnected[fClient.Conne
end;
procedure TBNETReader.Process_SID_OT
begin
end;
procedure TBotForm.FormCreate(Sender
begin
If not assigned(INI) then
INI := TIniFile.Create(ExtractFil
If INI.ReadString('Main','Con
Begin
OutputMemo.Lines.Add('Conn
End;
end;
procedure TBotForm.FormClose(Sender:
begin
Action := caFree;
end;
end.
--------------------------
unit MainFrm;
interface
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs,
QExtCtrls, QComCtrls, QTypes, QMenus, QStdCtrls, QImgList, QActnList,
QStdActns,inifiles;
type
TMainForm = class(TForm)
StatusBar1: TStatusBar;
MainMenu: TMainMenu;
SaveDialog: TSaveDialog;
OpenDialog: TOpenDialog;
ActionList: TActionList;
actnWinClose: TWindowClose;
actnWinCascade: TWindowCascade;
actnWinTile: TWindowTile;
actnWinMinimizeAll: TWindowMinimizeAll;
actnExit: TAction;
actnConnect: TAction;
ImageList: TImageList;
Connect1: TMenuItem;
Timer1: TTimer;
procedure actnConnectExecute(Sender:
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
public
INI : TIniFile;
procedure CreateMDIChild(const Name: string);
end;
var
MainForm: TMainForm;
implementation
{$R *.xfm}
uses
BotFrm;
resourcestring
sUntitled = 'Untitled';
procedure TMainForm.CreateMDIChild(c
var
Child: TBotForm;
begin
{ create a new MDI child window }
Child := TBotForm.Create(Applicatio
Child.Caption := Name;
Child.INI := TIniFile.Create(Name)
end;
procedure TMainForm.actnConnectExecu
var
ABotForm : TBotForm;
begin
if not Assigned(ActiveMDIChild) then
Begin
ABotForm := TBotForm.Create(Self);
ABotForm.WindowState := wsMaximized;
ABotForm.INI := TIniFile.Create(ExtractFil
ABotForm.Caption := ABotForm.INI.ReadString('M
End
else if ActiveMDIChild is TBotForm then
Begin
TBotForm(ActiveMDIChild).O
End;
end;
procedure TMainForm.FormCreate(Sende
begin
Ini := TIniFile.Create( ExtractFilePath(Applicatio
Top := Ini.ReadInteger( 'Position', 'Top', 50 );
Left := Ini.ReadInteger( 'Position', 'Left', 100 );
Height := Ini.ReadInteger( 'Position', 'Height', 400 );
Width := Ini.ReadInteger( 'Position', 'Width', 540 );
Timer1.Interval := 100;
Timer1.Enabled := True;
end;
procedure TMainForm.FormClose(Sender
begin
ini.WriteInteger('Position
ini.WriteInteger('Position
ini.WriteInteger('Position
ini.WriteInteger('Position
ini.Free;
end;
procedure TMainForm.Timer1Timer(Send
begin
If Ini.ReadString('Main','Con
Begin
MainForm.actnConnectExecut
End;
Timer1.Enabled := False;
end;
end.
--------------------------
unit PacketConsts;
interface
const
SID_NULL = $00;
SID_CLIENTID = $05;
SID_STARTVERSIONING = $06;
SID_REPORTVERSION = $07;
SID_GETADVLISTEX = $09;
SID_ENTERCHAT = $0A;
SID_GETCHANNELLIST = $0B;
SID_JOINCHANNEL = $0C;
SID_CHATCOMMAND = $0E;
SID_CHATEVENT = $0F;
SID_FLOODDETECTED = $13;
SID_UDPPINGRESPONSE = $14;
SID_MESSAGEBOX = $19;
SID_PING = $25;
SID_READUSERDATA = $26;
SID_WRITEUSERDATA = $27;
SID_LOGONCHALLENGE = $28;
SID_LOGONRESPONSE = $29;
SID_CREATEACCOUNT = $2A;
SID_CHANGEPASSWORD = $31;
SID_CDKEY2 = $36;
SID_LOGONRESPONSE2 = $3A;
SID_CREATEACCOUNT2 = $3D;
SID_LOGONREALMEX = $3E;
SID_AUTH_INFO = $50;
SID_AUTH_CHECK = $51;
SID_FRIENDLIST = $65;
SID_FRIENDUPDATE = $66;
SID_FRIENDADDED = $67;
SID_FRIENDREMOVED = $68;
SID_FRIENDMOVED = $69;
SID_FINDCLANCANDIDATES = $70;
SID_INVITEMULTIPLEUSERS = $71;
SID_DISBANDCLAN = $73;
SID_CLANINFO = $75;
SID_CLANREQUEST = $77;
SID_CLANINVITE = $79;
SID_CLANMOTD = $7C;
SID_CLANMEMBERLIST = $7D;
SID_CLANMEMBERUPDATE = $7F;
SID_CLANPROMOTION = $81;
var
SID_HeaderNames: array[Byte] of string;
const
MCP_STARTUP = $01;
MCP_CHARCREATE = $02;
MCP_CHARLOGON = $07;
MCP_CHARDELETE = $0A;
MCP_MOTD = $12;
MCP_CHARLIST = $17;
MCP_CHARUPGRADE = $18;
const
PACKET_IDLE = $00;
PACKET_LOGON = $01;
PACKET_STATSUPDATE = $02;
PACKET_DATABASE = $03;
PACKET_MESSAGE = $04;
PACKET_CYCLE = $05;
PACKET_USERINFO = $06;
PACKET_USERLOGGINGOFF = $07;
PACKET_COMMAND = $08;
PACKET_CHANGEDBPASSWORD = $09;
PACKET_BOTNETVERSION = $0A;
PACKET_BOTNETCHAT = $0B;
PACKET_ACCOUNT = $0D;
const
PKT_SERVERPING = $05;
PKT_KEEPALIVE = $07;
PKT_CONNTEST2 = $09;
const
EID_SHOWUSER = $01;
EID_JOIN = $02;
EID_LEAVE = $03;
EID_WHISPER = $04;
EID_TALK = $05;
EID_BROADCAST = $06;
EID_CHANNEL = $07;
EID_USERFLAGS = $09;
EID_WHISPERSENT = $0A;
EID_CHANNELFULL = $0D;
EID_CHANNELDOESNOTEXIST = $0E;
EID_ERROR = $13;
EID_INFO = $12;
EID_EMOTE = $17;
MAX_EVENT = $18;
const
BNLS_NULL = $00;
BNLS_CDKEY = $01;
BNLS_LOGONCHALLENGE = $02;
BNLS_LOGONPROOF = $03;
BNLS_CREATEACCOUNT = $04;
BNLS_CHANGECHALLENGE = $05;
BNLS_CHANGEPROOF = $06;
BNLS_UPGRADECHALLENGE = $07;
BNLS_UPGRADEPROOF = $08;
BNLS_VERSIONCHECK = $09;
BNLS_CONFIRMLOGON = $0A;
BNLS_HASHDATA = $0B;
BNLS_CDKEY_EX = $0C;
BNLS_CHOOSENLSREVISION = $0D;
BNLS_AUTHORIZE = $0E;
BNLS_AUTHORIZEPROOF = $0F;
BNLS_REQUESTVERSIONBYTE = $10;
BNLS_VERIFYSERVER = $11;
BNLS_RESERVESERVERSLOTS = $12;
BNLS_SERVERLOGONCHALLENGE=
BNLS_SERVERLOGONPROOF = $14;
const
PRODUCT_STARCRAFT = $01;
PRODUCT_BROODWAR = $02;
PRODUCT_WAR2BNE = $03;
PRODUCT_DIABLO2 = $04;
PRODUCT_LORDOFDESTRUCTION=
PRODUCT_JAPANSTARCRAFT = $06;
PRODUCT_WARCRAFT3 = $07;
PRODUCT_THEFROZENTHRONE = $08;
const
SID_AUTH_INFO_Data : array[$00..$39] of byte = (
$FF,$50,$3A,$00,$00,$00,$0
$10,$00,$00,$00,$53,$55,$6
$09,$04,$00,$00,$09,$04,$0
$65,$64,$20,$53,$74,$61,$7
);
type
TByteArray = array[Word] of Byte;
TBNETPacket = Record
StartPacket : Byte; // Always $FF
PacketID : Byte;
PacketLength : Word; // Including Header
PacketData : TByteArray;
End;
implementation
initialization
SID_HeaderNames[SID_NULL] := 'SID_NULL';
SID_HeaderNames[SID_CLIENT
SID_HeaderNames[SID_STARTV
SID_HeaderNames[SID_REPORT
SID_HeaderNames[SID_GETADV
SID_HeaderNames[SID_ENTERC
SID_HeaderNames[SID_GETCHA
SID_HeaderNames[SID_JOINCH
SID_HeaderNames[SID_CHATCO
SID_HeaderNames[SID_CHATEV
SID_HeaderNames[SID_FLOODD
SID_HeaderNames[SID_UDPPIN
SID_HeaderNames[SID_MESSAG
SID_HeaderNames[SID_PING] := 'SID_PING';
SID_HeaderNames[SID_READUS
SID_HeaderNames[SID_WRITEU
SID_HeaderNames[SID_LOGONC
SID_HeaderNames[SID_LOGONR
SID_HeaderNames[SID_CREATE
SID_HeaderNames[SID_CHANGE
SID_HeaderNames[SID_CDKEY2
SID_HeaderNames[SID_LOGONR
SID_HeaderNames[SID_CREATE
SID_HeaderNames[SID_LOGONR
SID_HeaderNames[SID_AUTH_I
SID_HeaderNames[SID_AUTH_C
SID_HeaderNames[SID_FRIEND
SID_HeaderNames[SID_FRIEND
SID_HeaderNames[SID_FRIEND
SID_HeaderNames[SID_FRIEND
SID_HeaderNames[SID_FRIEND
SID_HeaderNames[SID_FINDCL
SID_HeaderNames[SID_INVITE
SID_HeaderNames[SID_DISBAN
SID_HeaderNames[SID_CLANIN
SID_HeaderNames[SID_CLANRE
SID_HeaderNames[SID_CLANIN
SID_HeaderNames[SID_CLANMO
SID_HeaderNames[SID_CLANME
SID_HeaderNames[SID_CLANME
SID_HeaderNames[SID_CLANPR
end.
--------------------------
You can download my project folder via...
www.datazap.net/ftp/werehamster/MyBot/MyBot.zip
also contains 2 semi-functional prototypes called project1 and project2. current one is werebot.
ASKER
Anyway, I don't have a need for any other process to read the packets outside the thread so I took out the critical section for the reading part.
Basically what I want to do is that if I don't have a process for it, to call process_other which will display a packet dump of the data to the screen. I have some code to this already (though a little buggy).
Unhandled Packet: Header = 0000, Length = 0020, Name = Unknown
0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Would put Name = SID_Something if there is a const for it, but no process handler. Anyway, no real question involved here, just explaining my next goal so you have a better understanding what I am doing.
Heres a couple of questions...
If I call each process_xxx, do I have to pass parameters for any particular reason? I figured I would just use the fData and fDataLength and stuff instead. Calling these process procedures holds up the rest of the thread anyway right?
Also, I've been messing with something in the past unsuccessfully. Can I use something like a TMemoryStream on memory that has already been allocated without writing my own TCustomStream? I suppose I could make fData a TMemoryStream itlsef and work with it from there, but would it be safe to treat String(fData.memory) as a string?
Actually, if there was a component that allowed using an existing pointer/string, to be allowed to do things like ReadInt, ReadByte, ReadBoolean, etc, it would be extremely useful for this. I've written my own one in the past for a customized TCP client I made about 5 years ago that I could dig up I guess if I can find it. Any thoughts on this? Basically in each process_XXX I'd like to ideally just do something like this...
procedure TBNETReader.Process_SID_XX X
var
X : Integer;
B : Byte;
begin
//X := fInPacket.ReadInteger;
//B := fInPacket.ReadByte;
X := ReadInteger;
B := ReadByte;
//fOutPacket.Header := SID_Something
//fOutPacket.Data.WriteByt e(B+1)
//fOutPacket.Send;
PacketStart(SID_Something)
WriteByte(B+1);
PacketSend;
end;
Which could be simplified via WriteByte(ReadByte+1) if I wanted to.
Any suggestions? I looked up and found TReader and TWriter and stuff, but looks too confusing for me.
Basically what I want to do is that if I don't have a process for it, to call process_other which will display a packet dump of the data to the screen. I have some code to this already (though a little buggy).
Unhandled Packet: Header = 0000, Length = 0020, Name = Unknown
0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Would put Name = SID_Something if there is a const for it, but no process handler. Anyway, no real question involved here, just explaining my next goal so you have a better understanding what I am doing.
Heres a couple of questions...
If I call each process_xxx, do I have to pass parameters for any particular reason? I figured I would just use the fData and fDataLength and stuff instead. Calling these process procedures holds up the rest of the thread anyway right?
Also, I've been messing with something in the past unsuccessfully. Can I use something like a TMemoryStream on memory that has already been allocated without writing my own TCustomStream? I suppose I could make fData a TMemoryStream itlsef and work with it from there, but would it be safe to treat String(fData.memory) as a string?
Actually, if there was a component that allowed using an existing pointer/string, to be allowed to do things like ReadInt, ReadByte, ReadBoolean, etc, it would be extremely useful for this. I've written my own one in the past for a customized TCP client I made about 5 years ago that I could dig up I guess if I can find it. Any thoughts on this? Basically in each process_XXX I'd like to ideally just do something like this...
procedure TBNETReader.Process_SID_XX
var
X : Integer;
B : Byte;
begin
//X := fInPacket.ReadInteger;
//B := fInPacket.ReadByte;
X := ReadInteger;
B := ReadByte;
//fOutPacket.Header := SID_Something
//fOutPacket.Data.WriteByt
//fOutPacket.Send;
PacketStart(SID_Something)
WriteByte(B+1);
PacketSend;
end;
Which could be simplified via WriteByte(ReadByte+1) if I wanted to.
Any suggestions? I looked up and found TReader and TWriter and stuff, but looks too confusing for me.
ASKER
Here is a link to a hex dump question I put up asking for source incase you have any.
https://www.experts-exchange.com/questions/21134147/Simple-Hex-Dump-Example-0000-00-49-50-51-123.html
https://www.experts-exchange.com/questions/21134147/Simple-Hex-Dump-Example-0000-00-49-50-51-123.html
>>I don't get what you mean. Do you mean function that convert strings to PChar?
Yep, they exist. Here is a sample:
function AnsiPos(const Substr, S: string): Integer;
var
P: PChar;
begin
Result := 0;
P := AnsiStrPos(PChar(S), PChar(SubStr));
if P <> nil then
Result := Integer(P) - Integer(PChar(S)) + 1;
end;
AnsiPos is also used by:
function StringReplace(const S, OldPattern, NewPattern: string; Flags: TReplaceFlags): string;
Which I use a lot. That why I said you have to take care :)
>>Ok. So what if I want to disconnect from the client side manually instead of waiting for the server to disconnect? Do I just use MyReaderThread.free?
You could. Free while call Terminate (set fTerminate to True) Execute will break from the while loop (in a short time if you use the ReadFromStack) and if you want you can that call fClient.Disconnect before exiting the Execute method (would be clean).
The thing is, this scheme will not work if you wait in any of the ReadXXX functions (except ReadFromStack).
>>If I call each process_xxx, do I have to pass parameters for any particular reason? I figured I would just use the fData and fDataLength and stuff instead. Calling these process procedures holds up the rest of the thread anyway right?
If you use private packet members you do not need to pass anything. And yep, calling the procedures hold up the thread.
>>Also, I've been messing with something in the past unsuccessfully. Can I use something like a TMemoryStream on memory that has already been allocated without writing my own TCustomStream? I suppose I could make fData a TMemoryStream itlsef and work with it from there, but would it be safe to treat String(fData.memory) as a string?
What should work is the following:
Add this empty decalration:
type
TAllocatedMemoryStream = class(TMemoryStream);
With it you can call protected functions so in you code you can create and use an ordinary TMemoryStream and assign it you memory just by using a typecast:
...
TAllocatedMemoryStream(MS) .SetPointe r(@fData[0 ], 65536);
...
I am not sure about the [0] maybe leave it away. And you have to make sure you don't call Write, change the Size or anything, just read.
I still think it would be easier to have fData as a string, you could use is as a stream as simple as a call to TStringStream.Create(fData ).
>>Actually, if there was a component that allowed using an existing pointer/string, to be allowed to do things like ReadInt, ReadByte, ReadBoolean, etc, it would be extremely useful for this. I've written my own one in the past for a customized TCP client I made about 5 years ago that I could dig up I guess if I can find it. Any thoughts on this? Basically in each process_XXX I'd like to ideally just do something like this...
When I can determine the format myself I always use TReader/TWriter from the classes unit for that purpose. You can hook them to a stream and read all kinds of data types from it.
You can easily make a descendant of TStringStream that supports reading Integers/Bytes from it:
type
TMyStringStream = class(TStringStream)
public
function ReadByte: Byte;
end;
function TMyStringStream.ReadByte: Byte;
begin
Read(@Result, 1);
end;
I have downloaded the project, can look at it tonight.
Regards Jacco
Yep, they exist. Here is a sample:
function AnsiPos(const Substr, S: string): Integer;
var
P: PChar;
begin
Result := 0;
P := AnsiStrPos(PChar(S), PChar(SubStr));
if P <> nil then
Result := Integer(P) - Integer(PChar(S)) + 1;
end;
AnsiPos is also used by:
function StringReplace(const S, OldPattern, NewPattern: string; Flags: TReplaceFlags): string;
Which I use a lot. That why I said you have to take care :)
>>Ok. So what if I want to disconnect from the client side manually instead of waiting for the server to disconnect? Do I just use MyReaderThread.free?
You could. Free while call Terminate (set fTerminate to True) Execute will break from the while loop (in a short time if you use the ReadFromStack) and if you want you can that call fClient.Disconnect before exiting the Execute method (would be clean).
The thing is, this scheme will not work if you wait in any of the ReadXXX functions (except ReadFromStack).
>>If I call each process_xxx, do I have to pass parameters for any particular reason? I figured I would just use the fData and fDataLength and stuff instead. Calling these process procedures holds up the rest of the thread anyway right?
If you use private packet members you do not need to pass anything. And yep, calling the procedures hold up the thread.
>>Also, I've been messing with something in the past unsuccessfully. Can I use something like a TMemoryStream on memory that has already been allocated without writing my own TCustomStream? I suppose I could make fData a TMemoryStream itlsef and work with it from there, but would it be safe to treat String(fData.memory) as a string?
What should work is the following:
Add this empty decalration:
type
TAllocatedMemoryStream = class(TMemoryStream);
With it you can call protected functions so in you code you can create and use an ordinary TMemoryStream and assign it you memory just by using a typecast:
...
TAllocatedMemoryStream(MS)
...
I am not sure about the [0] maybe leave it away. And you have to make sure you don't call Write, change the Size or anything, just read.
I still think it would be easier to have fData as a string, you could use is as a stream as simple as a call to TStringStream.Create(fData
>>Actually, if there was a component that allowed using an existing pointer/string, to be allowed to do things like ReadInt, ReadByte, ReadBoolean, etc, it would be extremely useful for this. I've written my own one in the past for a customized TCP client I made about 5 years ago that I could dig up I guess if I can find it. Any thoughts on this? Basically in each process_XXX I'd like to ideally just do something like this...
When I can determine the format myself I always use TReader/TWriter from the classes unit for that purpose. You can hook them to a stream and read all kinds of data types from it.
You can easily make a descendant of TStringStream that supports reading Integers/Bytes from it:
type
TMyStringStream = class(TStringStream)
public
function ReadByte: Byte;
end;
function TMyStringStream.ReadByte: Byte;
begin
Read(@Result, 1);
end;
I have downloaded the project, can look at it tonight.
Regards Jacco
ASKER
I suppose I could just use it as a string. Just going to require a little reconditioning on my part. I'm still in the mentality of getting the length of a string from checking astring[0] and stuff from back in turbo pascal. Never really developed my string manipulating skills, but now isn't the worst time to force myself into it.
Actually, what I am thinking of is making a couple of helper functions that do something like...
function getByteAt(var String; Pos: Word) : Byte;
function getWordAt(var String; Pos: Word) : Word;
...and of course checking the string length and stuff so it isn't out of bounds and stuff.
Actually, what I am thinking of is making a couple of helper functions that do something like...
function getByteAt(var String; Pos: Word) : Byte;
function getWordAt(var String; Pos: Word) : Word;
...and of course checking the string length and stuff so it isn't out of bounds and stuff.
ASKER
As far as the packet dump thing, I got that pretty much figured out with the help of a couple other fellows. Nothing I coudn't do on my own, just like to see how others do things and very frequently find better ways of doing things. The second guy gave me the idea of converting to a string instead of directly to a memo file which would let me reused the doMessage procedure. Anyway, enough with that. :)
ASKER
Anyway, I did some work and got something functional now...
www.datazap.net/ftp/werehamster/MyBot/MyBot2.zip
I just can't get my disconnect button working without getting errors.
www.datazap.net/ftp/werehamster/MyBot/MyBot2.zip
I just can't get my disconnect button working without getting errors.
Reverse the destructor order:
destructor TBNETReader.Destroy;
begin
inherited Destroy;
fClient.Free;
fQueueOutSection.Free;
fQueueOut.Free;
end;
This way the thread will first be terminated and then you resources will be freed.
I have no server so for me it stuck at the Connect with does much longer that 500 milliseconds...
I have IdSocketError added in Tools\Debugger Options\Language Exceptions to ignore it. This measure might be to extreme for you but Indy developers indicate in the sources to at least add EIdSilentException to the ignored ones.
Regards Jacco
destructor TBNETReader.Destroy;
begin
inherited Destroy;
fClient.Free;
fQueueOutSection.Free;
fQueueOut.Free;
end;
This way the thread will first be terminated and then you resources will be freed.
I have no server so for me it stuck at the Connect with does much longer that 500 milliseconds...
I have IdSocketError added in Tools\Debugger Options\Language Exceptions to ignore it. This measure might be to extreme for you but Indy developers indicate in the sources to at least add EIdSilentException to the ignored ones.
Regards Jacco
ASKER
well if you have config.ini in the folder, you have a server to connect to. It is a server that is up now. Just have SID_PING coded right now. I'll give it a shot when I get home.
I have tested you app here and I never get an error (even without my change).
I changed a one small thing:
procedure TMainForm.actnConnectExecu te(Sender: TObject);
var
ABotForm : TBotForm;
begin
if not Assigned(ActiveMDIChild) then Begin
ABotForm := TBotForm.Create(Self);
ABotForm.WindowState := wsMaximized;
ABotForm.INI := TIniFile.Create(ExtractFil ePath(Appl ication.Ex eName)+'co nfig.ini') ;
ABotForm.Caption := ABotForm.INI.ReadString('M ain','User name','Wer ebot');
ABotForm.fReader := TBNETReader.Create(TBotFor m(ActiveMD IChild));
End
else if ActiveMDIChild is TBotForm then
if not Assigned(TBotForm(ActiveMD IChild).fR eader) then begin
TBotForm(ActiveMDIChild).f Reader := TBNETReader.Create(TBotFor m(ActiveMD IChild));
MainForm.actnConnect.Capti on := 'Disconnect';
end
else begin
FreeAndNil(TBotForm(Active MDIChild). fReader);
MainForm.actnConnect.Capti on := 'Connect';
end;
end;
FreeAndNil the thread in stead of terminating. This action is in balance the construction fo a thread when connecting.
Regards Jacco
I changed a one small thing:
procedure TMainForm.actnConnectExecu
var
ABotForm : TBotForm;
begin
if not Assigned(ActiveMDIChild) then Begin
ABotForm := TBotForm.Create(Self);
ABotForm.WindowState := wsMaximized;
ABotForm.INI := TIniFile.Create(ExtractFil
ABotForm.Caption := ABotForm.INI.ReadString('M
ABotForm.fReader := TBNETReader.Create(TBotFor
End
else if ActiveMDIChild is TBotForm then
if not Assigned(TBotForm(ActiveMD
TBotForm(ActiveMDIChild).f
MainForm.actnConnect.Capti
end
else begin
FreeAndNil(TBotForm(Active
MainForm.actnConnect.Capti
end;
end;
FreeAndNil the thread in stead of terminating. This action is in balance the construction fo a thread when connecting.
Regards Jacco
ASKER
www.datazap.net/ftp/werehamster/MyBot/MyBot3.zip
has been expanded to include a 2nd thread to a second server that has a different header. I used the same techniques.
Only thing is this, how do I stop the thing from contantly trying to reconnect a million times if the server refuses the connection (temp IP ban) ?
If your curious what I am doing, goto...
http://bnetdocs.valhallalegends.com/
Anyway, I am going to close this topic soon and give ya these points and start a new topic again.
So far things seem to work fine, but like to implement a reconnection timer or somehow informing the main form that one of the clients disconnected.
Also, I need to slow down the outgoing packets some how so that it transmits packets at a delay between them. The server I am connecting to has flood control that IP bans the client if it detects spam. Maybe some way of guaging the chars per second for the last 10 seconds and adjust the delay between packets accordingly or something. Is this something I would use a timer for and have the timer insert a string into the queue on an interval?
Anyway, I'm just babbling. It's 4am right now and I should be in bed. I always lose track of time when I am writing a program. Small flaw in me I guess.
Thanks for the help so far.
-Steve
has been expanded to include a 2nd thread to a second server that has a different header. I used the same techniques.
Only thing is this, how do I stop the thing from contantly trying to reconnect a million times if the server refuses the connection (temp IP ban) ?
If your curious what I am doing, goto...
http://bnetdocs.valhallalegends.com/
Anyway, I am going to close this topic soon and give ya these points and start a new topic again.
So far things seem to work fine, but like to implement a reconnection timer or somehow informing the main form that one of the clients disconnected.
Also, I need to slow down the outgoing packets some how so that it transmits packets at a delay between them. The server I am connecting to has flood control that IP bans the client if it detects spam. Maybe some way of guaging the chars per second for the last 10 seconds and adjust the delay between packets accordingly or something. Is this something I would use a timer for and have the timer insert a string into the queue on an interval?
Anyway, I'm just babbling. It's 4am right now and I should be in bed. I always lose track of time when I am writing a program. Small flaw in me I guess.
Thanks for the help so far.
-Steve
Maybe you can see in the Exception that occurs in the connect what the reason is. The code in the EIdSocketError might be of interest.
In the exception handler of the connect part you could check for a code in the exception:
on E: EIdSocketError do
begin
case LastError of
10041: ...
else raise;
end;
end;
You could for example add an extra state to the state machine csConnectionTimeOut which just sleeps a timeout and goes to csDisconnected again.
>>Also, I need to slow down the outgoing packets some how so that it transmits packets at a delay between them.
Maybe you can use one of Indy IOHandlers which does a speed limit: TIdIOHandlerThrottle. You can limit the speed to a number of bytes/bits per second for a connection. You could create one in your thread. I haven't experimented with them but I think you can just connect is to the TIdTCPClient.IOHandler before opening it.
>>Anyway, I am going to close this topic soon and give ya these points and start a new topic again.
Thanks! I think this one will push me over the 100.000 points overall. Do they still give out T-Shirts?
>>Anyway, I'm just babbling. It's 4am right now and I should be in bed. I always lose track of time when I am writing a program. Small flaw in me I guess.
I know the problem. Come with the hobby I guess.
Regards Jacco
In the exception handler of the connect part you could check for a code in the exception:
on E: EIdSocketError do
begin
case LastError of
10041: ...
else raise;
end;
end;
You could for example add an extra state to the state machine csConnectionTimeOut which just sleeps a timeout and goes to csDisconnected again.
>>Also, I need to slow down the outgoing packets some how so that it transmits packets at a delay between them.
Maybe you can use one of Indy IOHandlers which does a speed limit: TIdIOHandlerThrottle. You can limit the speed to a number of bytes/bits per second for a connection. You could create one in your thread. I haven't experimented with them but I think you can just connect is to the TIdTCPClient.IOHandler before opening it.
>>Anyway, I am going to close this topic soon and give ya these points and start a new topic again.
Thanks! I think this one will push me over the 100.000 points overall. Do they still give out T-Shirts?
>>Anyway, I'm just babbling. It's 4am right now and I should be in bed. I always lose track of time when I am writing a program. Small flaw in me I guess.
I know the problem. Come with the hobby I guess.
Regards Jacco
ASKER
Continued on to Part 3...
https://www.experts-exchange.com/questions/21136526/Properly-Coded-Indy-Reader-Threads-Part-3.html
https://www.experts-exchange.com/questions/21136526/Properly-Coded-Indy-Reader-Threads-Part-3.html
ASKER
Anyway, I did a re-write again, getting away from CLX as I think it is quite buggy and I don't have any immediate plans to use Kylix and I wanted to use a TRichEdit component to get some color.
fReader.Terminate and then fReader.Free. The Terminate would brake from the while loop in Execute this is done after one buffer read and one message write (if available).
But as you can see from the destructor of TThread Terminate is also called automatically:
destructor TThread.Destroy;
begin
if (FThreadID <> 0) and not FFinished then
begin
Terminate;
if FCreateSuspended then
Resume;
WaitFor;
end;
if FHandle <> 0 then CloseHandle(FHandle);
inherited Destroy;
FFatalException.Free;
RemoveThread;
end;
Now one pitfall is when you use fClient.Read(....) because there the thread will wait for IP input infinitly. That is why I used ReadLn with a timeout in the past and this time I found an even better way of reading using the ReadFromStack/Extract where there is no wait.
Regards Jacco