У меня есть Indy TCP Server, работающий на Windows 8.1 с RAD Studio 10.3 Delphi. Клиент приложения для телефона подключается к серверу и после получения приветствия от сервера приложение для телефона отправляет запрос на указание c данных с сервера - все хорошо до этого момента.
В OnExecute
Процедуру на сервере я называю процедурой (LogInFromPhone
), которая получает некоторые данные из базы данных, а также из некоторых других файлов. Эти данные отображаются в памятке в приложении «Сервер», а затем отправляются обратно клиенту, который их запросил. То, как я кодировал сервер OnExecute
, похоже, работает нормально, но я знаю, что это неправильный способ - не поточнобезопасный.
Я потратил много часов на поиск форумов и c, пытаясь чтобы узнать, как именно это должно быть сделано. Большинство более продвинутых примеров недостаточно подробны, чтобы новичок, как я, мог полностью понять. Я был бы очень признателен за подробный пример с описаниями, с помощью которых я могу получить это право.
Это код сервера OnExecute
, который у меня есть:
procedure TMainForm.IdTCPServer1Execute(AContext: TIdContext);
var DataRequestFromClient : string;
begin
DataRequestFromClient := AContext.Connection.IOHandler.ReadLn; //receive request from client
StringIn:= DataRequestFromClient;
LogInFromPhone; //Gets Data from DataBase and ini Files and returns with Data in 'PhoneString'
if PhoneString <> '' then
AContext.Connection.IOHandler.WriteLn(PhoneString); //send back data in PhoneString to client
end;
procedure TMainForm.LogInFromPhone;
{Entry: Received UserName, Password & Devices required to be sent back to Phone is in 'StringIn' e.g Nu_Smith,Wp_2222,_1,*
Exit: String to send back to Phone is in 'PhoneString'}
var found: boolean; TempString: string;
i, CPos, UPos, AstrasPos: integer;
begin
Memo1.Lines.Add(StringIn); //view most recent Log Ins
While Memo1.Lines.Count > 100 do Memo1.Lines.Delete(0); //limit Memo1 lines to 100
//Pick out UserName & Password values. Username_Smith,Password_2222,
TempString:= StringIn;
UPos:= pos('_',TempString);
delete(TempString,1,UPos);
CPos:= pos(',',TempString);
UserName:= copy (TempString,1,CPos-1); //UserName
delete(TempString,1,CPos);
UPos:= pos('_',TempString);
delete(TempString,1,UPos);
CPos:= pos(',',TempString);
Password:= copy (TempString,1,CPos-1); //Password
delete(TempString,1,CPos);
UPos:= pos('_',TempString);
delete(TempString,1,UPos);
AstrasPos:= pos('*',TempString);
DeviceString:= copy (TempString,1,AstrasPos); //DeviceString
delete(TempString,1,CPos);
Edit5.Text:= DeviceString;
Edit3.Text:= UserName; //show who logged in
Edit4.Text:= Password;
//Lookup Client Data Base to get ESP No and Site Address
found := false;
ADOTable1.First(); //start at top of table
for i := 1 to ADOTable1.RecordCount do
begin
if (ADOTable1.FindField('User Name').AsString = UserName) AND
(ADOTable1.FindField('Password').AsString = Password) then
begin
//correct LogIn
ESPNumber:= ADOTable1.FindField('ESP No').AsString; //get value
SiteAddress:= ADOTable1.FindField('Site Address').AsString;
ConvertLogInData; //convert ESPNo(00002) &SiteAddress(1Fh_31)
Edit1.Text:= ESPNumber;
Edit2.Text:= SiteAddress;
CreatePathName; //Create PathName
GetSystemGridData; //load System Grid
GetTagdata; //get data from ini File
Exit;
end;
ADOTable1.Next();
end;
if not found = true then
PhoneString:= 'LogInFail*'
end;
procedure TMainForm.CreatePathName;
{Load existing or create new directory.
Exit: 'MainPathName' contains directory e.g C:\FarmTrenz\ESP_00079\SiteAdd_0Ch_12\TagDB\2018\July\}
var CurrentYear, CurrentMonth: string;
begin
CurrentYear:= FormatDateTime('yyyy', (now)); //format = DataDate: 18/10/17
CurrentMonth:= FormatDateTime('mmmm', (now)); //DataTime: 17:28:32
MainPathname:= 'C:\FarmTrenz\' + 'ESP_' + ESPNumber + '\';
if not DirectoryExists(MainPathname) then
if not CreateDir(MainPathname) then //make ESP Number folder
raise Exception.Create(MainPathname);
MainPathname:= MainPathname + 'SiteAdd_' + SiteAddress + '\';
if not DirectoryExists(MainPathname) then
if not CreateDir(MainPathname) then //make Site Address folder
raise Exception.Create(MainPathname);
MainPathname:= MainPathname + 'TagDB' + '\';
if not DirectoryExists(MainPathname) then
if not CreateDir(MainPathname) then //make Tag DataBase folder
raise Exception.Create(MainPathname);
TagDBPath:= MainPathName;
MainPathname:= MainPathname + CurrentYear + '\';
if not DirectoryExists(MainPathname) then
if not CreateDir(MainPathname) then //make year folder
raise Exception.Create(MainPathname);
MainPathname:= MainPathname + CurrentMonth + '\';
if not DirectoryExists(MainPathname) then
if not CreateDir(MainPathname) then //make month folder
raise Exception.Create(MainPathname);
end;
procedure TMainForm.GetSystemGridData;
{Get Device Signal Types to be sent back to Phone.
Load CheckListBox settings from existing file. If no file exists then check all CheckListBoxes for all Signals}
var i: integer;
begin
IniFile:= TIniFile.Create(MainForm.TagDBPath + 'PhoneSystemsList.ini'); //read TagDB file
try
if inifile.SectionExists('PhoneSystemsList') then
begin
IniFile.ReadSection('PhoneSystemsList', Form2.Checklistbox1.Items); //does 'section' exist
for i := 0 to Form2.Checklistbox1.Items.Count - 1 do //get complete System List
Form2.CheckListbox1.Checked[i] := IniFile.ReadBool('PhoneSystemsList', Form2.Checklistbox1.Items[i], False);
end
else
Form2.CheckListBox1.CheckAll(cbChecked, false, true); //No file so check all boxes
finally
IniFile.Free;
end;
end;
procedure TMainForm.GetTagData;
{Get Data for Selected Device Types to send back to Phone.
Entry: 'DeviceString' contains the Device numbers to get Data from to send back to Phone.
Exit: Selected Data is in 'PhoneString'}
var CPos: integer;
TempString: string;
begin
// Username_Smith,Password_2222,_1,3,27,17,* DeviceString = 1,2,27,17,*
PhoneString:= ''; //clear for starting a new Phone String.
TempString:= DeviceString;
while TempString <> '*' do
begin
CPos:= pos(',',TempString);
DeviceType:= copy(TempString,1,CPos-1); //DeviceString
delete(TempString,1,CPos);
if DeviceType <> '' then
begin
case StrToInt(DeviceType) of //process each selected Device Type
0: ;
1: GetTIMTagData;
2: GetTIMTagData;
3: GetKIMTagData;
4: GetKIMTagData;
5: GetPIMTagData;
6: GetPIMTagData;
7: GetPLinkTagData;
8: ;
9: GetSumpTagData;
10: GetPondTagData;
11: GetBoreTagData;
12: GetTankTagData;
13: GetFenceTagData;
14: GetFlowTagData;
15: GetHydrantTagData;
16: GetSiloTagData;
17: GetVatTagData;
18: GetMovmentTagData;
19: ;
20: GetSoilTagData;
21: GetMagFlowTagData;
22: ;
23: GetLevelTagData;
24: GetPWeatherTagData;
25: GetTWeatherTagData;
26: GetTankLinkTagData;
27: GetDPLinkTagData;
28: GetHubTagData;
29: GetFencePulseTagData;
30: GetLarallTagData;
31: GetVatBasicTagData;
end;
end;
end;
end;
////////////////////////////////////Start of TIM Tx Data 01 \\\\\\\\\\\\\\\\\
procedure TMainForm.GetTIMTagData;
{Open TagDB.ini file for this Client. Pick out all selected signal types and add to 'PhoneString for sending back to Client Phone.
Entry: 'TagDBPath' contains directory of where to find current Data for this Device.
Exit: Phone Message to send back is in 'PhoneString'}
var TempInt, BitIndex, TimIDCode: integer;
TIMValue: string;
begin
FoundaDevice:= false;
for TimIDCode := 1 to 5 do //do for all 5 TIM Tx Units
begin
IniFile:= TIniFile.Create(TagDBPath+'TagDB.ini'); //read Tag ini file
try
ValueString:= IniFile.ReadString('TimTx'+IntToStr(TimIDCode)+'_Speed','Value','999'); //Speed
if ValueString <> '999' then
begin
PhoneString:= PhoneString + '01,'; //signify start of TIM Messages for phone
FoundaDevice:= true;
TagLastUpDate:= IniFile.ReadString('TimTx'+IntToStr(TimIDCode)+'_Speed','LastUpdate','0');
CheckTagAge; //check if Tag Value isn't too old
If TagValid = True then TIMValue:= IntToStr(HexToDec(ValueString))+ ' spr' else TIMValue:= 'N/A';
if Form2.CheckListBox1.Checked[0] then
PhoneString:= PhoneString + 'TIM '+IntToStr(TimIDCode)+' Speed: ' + TIMValue + ','; //create phone string
ValueString:= IniFile.ReadString('TimTx'+IntToStr(TimIDCode)+'_Pressure','Value','0'); //Pressure
If TagValid = True then TIMValue:= IntToStr(HexToDec(ValueString))+ ' psi' else TIMValue:= 'N/A';
if Form2.CheckListBox1.Checked[1] then
PhoneString:= PhoneString + 'TIM '+IntToStr(TimIDCode)+' Press: ' + TIMValue + ','; //add to phone string
ValueString:= IniFile.ReadString('TimTx'+IntToStr(TimIDCode)+'_BatVolts','Value','0'); //Bat Volts
ConvertToOneDecimalPlace; //convert 'ValueString' to 12.8 Volts
If TagValid = True then TIMValue:= ValueString + ' Volts' else TIMValue:= 'N/A';
if Form2.CheckListBox1.Checked[2] then
PhoneString:= PhoneString + 'TIM '+IntToStr(TimIDCode)+' BatV: ' + TIMValue + ','; //add to phone string
ValueString:= IniFile.ReadString('TimTx'+IntToStr(TimIDCode)+'_Status1', 'Value','0'); //Status1
If TagValid = True then TIMStatus1:= ValueString;
end
else TagValid:= false;
finally
IniFile.Free;
end;
{Process Status1 Bits}
if TagValid <> false then
begin
TempInt:= StrToInt('$' + TIMStatus1); {value to extract is a Hex value saved as a string in 'Status1'}
for BitIndex:= 0 to 7 do
if TempInt and (1 shl BitIndex) <> 0 then
begin
case BitIndex of
0: RStop:= '1';
1: RStart:= '1';
2: PhoneString:= PhoneString + 'TIM '+IntToStr(TimIDCode)+' Moving 2 Slow Alm,'; //add to phone string
3: Start_B:= '1';
4: PhoneString:= PhoneString + 'TIM '+IntToStr(TimIDCode)+' Not Moving Alm,'; //add to phone string
5: PhoneString:= PhoneString + 'TIM '+IntToStr(TimIDCode)+' Low Bat Alm,'; //add to phone string
6: PhoneString:= PhoneString + 'TIM '+IntToStr(TimIDCode)+' EOT Alm,'; //add to phone string
7: GSPFail:= '1';
end;
end;
end;
If FoundaDevice = True then
begin
FoundaDevice:= false;
PhoneString:= PhoneString + '*';
end;
end;
Edit22.Text:= PhoneString;
end;
////////////////////////////////////End of TIM Tx Data \\\\\\\\\\\\\\\\\\\\\\\
Привет, Реми - Спасибо за ваш ответ. Я добавил код в соответствии с просьбой. Вы увидите, что (LogInFromPhone) является общим c для всех клиентов и местами использует элементы управления пользовательским интерфейсом. Как мне вызвать и обработать это потокобезопасным способом?