Reputation: 39
I am using TIdTCPServer in my application. Here hardware acting as tcp client. Client establishes the connection with SYN command (observed in wire shark which is a tool). My application has multiple clients so every client connects to my server. For the first time connection, data sending & receiving is fine. But when hardware power off & on happens, my server not able to send data, to hardware, until application restart. Following are the observations regarding this:
1.When first time client connects SYN with Seq No = 0 received, SYN ACK with Seq No = 1 send to the client from server
2.Data sending & receiving are working fine
3.Hardware power off happened
4.In command prompt by using “netstat” i observed there is connection established for the disconnected IP & port number.
5.I send some data (in wire shark it displayed 6 times retransmission)
6.After this in “command prompt” corresponded connection established data not appeared
7.I send data to client now "Connection closed" exception raised by the IdTCPServer (after this exception, in on except, i closed the connection by using connection.disconnect in code & deleted that particular client from Locklist of IdTCPServer.)
8.Hardware powered on & send SYN with Seq No = 0
9.In wire shark SYN ACK with Seq No like 45678452 sent to hardware
10.After that in command prompt connection establishment was observed
11.I tried to send data to client, but “Locklist” not updated with the client IP& port again so data not sent the client (my code is like if IP not present in “Locklist” then not sending data). Is there any solutions?
Following is my code:
try
for Count := 0 to frmtcpserver.IdTCPServer1.Contexts.LockList.Count - 1
do
begin
if TIdContext(frmtcpserver.IdTCPServer1.Contexts.LockList.Items[Count]).Binding.PeerIP = Destination_IP then
begin
DestinationIPIdx := Count;
end;
end;
frmtcpserver.IdTCPServer1.Contexts.UnlockList;
if DestinationIPIdx > -1 then
begin
// sending data here
TIdContext(frmtcpserver.IdTCPServer1.Contexts.LockList.Items[DestinationIPIdx])
.Connection.IOHandler.Write(TempBuf, NoofBytesToSend,0);
end;
end;
on E: EidException do
begin
TIdContext(frmtcpserver.IdTCPServer1.Contexts.LockList.Items[DestinationIPIdx]).Connection.Disconnect;
frmtcpserver.IdTCPServer1.Contexts.LockList.Delete(DestinationIPIdx);
end;
Upvotes: 0
Views: 601
Reputation: 595349
You are calling Contexts.LockList()
WAY too many times. The contexts list is protected by a critical section. The calls to LockList()
and UnlockList()
MUST be balanced or you will deadlock the server, preventing clients from connecting and disconnecting.
LockList()
returns the actual list. So, you should lock it once, access its items as needed, and then unlock it once.
Try something more like this instead:
list := frmtcpserver.IdTCPServer1.Contexts.LockList;
try
for i := 0 to list.Count - 1 do
begin
ctx := TIdContext(list[i]);
if ctx.Binding.PeerIP = Destination_IP then
begin
// sending data here
try
ctx.Connection.IOHandler.Write(TempBuf, NoofBytesToSend, 0);
except
on E: EIdException do
begin
ctx.Connection.Disconnect;
end;
end;
break;
end;
end;
finally
frmtcpserver.IdTCPServer1.Contexts.UnlockList;
end;
That being said, if the server's OnExecute
event is communicating back and forth with the client then it is generally not safe to directly send data to a client from outside of the client's OnExecute
event, like you are doing. You risk corrupting the communications. It is safer to give each client context its own thread-safe queue of outgoing data, and then use the OnExecute
event to send that data when it is safe to do so. For example:
type
TMyContext = class(TIdServerContext)
public
Queue: TThreadList;
...
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override;
destructor Destroy; override;
end;
PIdBytes := ^TIdBytes;
constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil);
begin
inherited;
Queue := TThreadList.Create;
end;
destructor TMyContext.Destroy;
var
list: TList;
I: integer;
begin
list := Queue.LockList;
try
for i := 0 to list.Count-1 do
begin
PIdBytes(list[i])^ := nil;
Dispose(list[i]);
end;
finally
Queue.UnlockList;
end;
Queue.Free;
inherited;
end;
procedure TFrmTcpServer.FormCreate(Sender: TObject);
begin
IdTCPServer1.ContextClass := TMyContext;
end;
procedure TFrmTcpServer.IdTCPServer1Execute(AContext: TIdContext);
var
Queue: TList;
tmpList: TList;
i: integer;
begin
...
tmpList := nil;
try
Queue := TMyContext(AContext).Queue.LockList;
try
if Queue.Count > 0 then
begin
tmpList := TList.Create;
tmpList.Assign(Queue);
Queue.Clear;
end;
finally
TMyContext(AContext).Queue.UnlockList;
end;
if tmpList <> nil then
begin
for i := 0 to tmpList.Count-1 do
begin
AContext.Connection.IOHandler.Write(PIdBytes(tmpList[i])^);
end;
end;
finally
if tmpList <> nil then
begin
for i := 0 to tmpList.Count-1 do
begin
PIdBytes(tmpList[i])^ := nil;
Dispose(tmpList[i]);
end;
end;
tmpList.Free;
end;
...
end;
var
list: TList;
ctx: TIdContext;
I: integer;
data: PIdBytes;
begin
list := IdTCPServer1.Contexts.LockList;
try
for i := 0 to list.Count - 1 do
begin
ctx := TIdContext(list[i]);
if ctx.Binding.PeerIP = Destination_IP then
begin
New(data);
try
data^ := Copy(TempBuf, 0, NoofBytesToSend);
TMyContext(ctx).Queue.Add(data);
except
data^ := nil;
Dispose(data);
end;
break;
end;
end;
end;
finally
IdTCPServer1.Contexts.UnlockList;
end;
Upvotes: 1