Я вижу ряд проблем с этим кодом.
Как реализованы addToLog()
и addToConsole()
?Они потокобезопасны?Помните, что TIdTCPServer
является многопоточным компонентом, его события запускаются в контексте рабочих потоков, а не основного потока пользовательского интерфейса, поэтому любой доступ к пользовательскому интерфейсу, общим переменным и т. Д. Должен быть синхронизирован.
Что такое clients
?Это пользовательский интерфейс?Вам необходимо синхронизировать доступ к нему, чтобы не повредить его содержимое, когда несколько потоков пытаются получить к нему доступ одновременно.
Использование свойства TIdTCPServer.Contexts
неадекватнозащищен от исключений.Вам нужен блок try..finally
, чтобы вы могли безопасно звонить Contexts.UnlockList()
.
Что более важно, вы звоните Contexts.LockList()
слишком много раз в вашем serverConnect()
цикл (это основная причина вашей проблемы).LockList()
возвращает объект TIdContextList
.Внутри вашего цикла вы должны получить доступ к свойству Items[]
этого списка вместо того, чтобы снова вызывать LockList()
.Поскольку у вас нет соответствующих UnlockList()
для каждого LockList()
, когда клиент подключается к вашему серверу, список Contexts
блокируется и больше не может быть доступен после выхода из serverConnect()
, который включает в себя, когда клиенты подключаются /отключите, и во время TIdTCPServer
выключения (как в вашем случае).
serverDisconnect()
не удаляет какие-либо элементы из clients
.serverConnect()
вообще не должен сбрасывать clients
.Он должен добавить только вызывающего TIdContext
к clients
, а затем serverDisconnect()
должен удалить тот же TIdContext
из clients
позже.
С этим сказал, попробуйте что-тобольше похоже на это:
procedure TfrmMain.addToConsole(const AMsg: string);
begin
TThread.Queue(nil,
procedure
begin
// add AMsg to console ...
end
);
end;
procedure TfrmMain.addToLog(const AMsg: string);
begin
TThread.Queue(nil,
procedure
begin
// add AMsg to log ...
end
);
end;
procedure TfrmMain.startClick(Sender: TObject);
begin
if server.Active then
stopServer()
else
startServer();
end;
procedure TfrmMain.startServer();
var
binding: TIdSocketHandle;
begin
server.Bindings.Clear();
try
server.DefaultPort := StrToInt(port.Text);
binding := server.Bindings.Add();
binding.IP := ip;
binding.Port := StrToInt(port.Text);
server.Active := True;
addToLog('Server started');
start.Caption := 'Stop';
except
on e: Exception do
addToLog('Error: ' + e.message + '.');
end;
end;
procedure TfrmMain.stopServer();
begin
try
server.Active := False;
server.Bindings.Clear();
addToLog('Server stopped');
start.Caption := 'Start';
except
on e: Exception do
addToLog('Server shutdown error.');
end;
end;
procedure TfrmMain.serverConnect(AContext: TIdContext);
var
PeerIP: string;
begin
PeerIP := AContext.Binding.PeerIP;
addToLog('New client: ' + PeerIP + '.');
TThread.Queue(nil,
procedure
{
var
i: integer;
list: TIdContextList;
}
begin
{
clients.clear();
list := server.Contexts.LockList;
try
for i := 0 to list.count - 1 do begin
clients.Items.Add(TIdContext(list[i]).Binding.PeerIP);
end;
finally
list.UnlockList();
end;
}
// I'm assuming clients is a UI control whose Items property
// is a TStrings object. If not, adjust this code as needed...
clients.Items.AddObject(PeerIP, AContext);
end;
);
end;
procedure TfrmMain.serverDisconnect(AContext: TIdContext);
begin
addToLog('Client ' + AContext.Binding.PeerIP + ' disconnected from the server.');
TThread.Queue(nil,
procedure
var
i: Integer;
begin
// I'm assuming clients is a UI control whose Items property
// is a TStrings object. If not, adjust this code as needed...
i := clients.Items.IndexOfObject(AContext);
if i <> -1 then
clients.Items.Delete(i);
end
);
end;
procedure TfrmMain.clientConnected(Sender: TObject);
begin
addToConsole('You connected to server successfully.');
end;
procedure TfrmMain.clientDisconnected(Sender: TObject);
begin
addToConsole('The connection to the server was interrupted.');
end;