我有一个Timer
,想要向每个客户端发送一条消息ListView
,例如确定ping时间。然后我有以下代码:
procedure TMainForm.Timer1Timer(Sender: TObject); var i: Integer; begin try for i := 0 to ListView1.Items.count - 1 do begin ListView1.Items.Item[i].SubItems.Objects[2] := TObject(GetTickCount); ServerSocket1.Socket.Connections[i].SendText('ping' + #13#10); end; except exit; end; end;
在发送之前,可以更适当地检查客户端是否已真正连接或类似的连接。怎么做?提前致谢。
无需检查连接。如果客户端实际上已断开连接,则在触发处理程序Connections[]
时,该客户端将不再位于服务器列表中OnTimer
。您应该为OnClientDisconnect
分配了处理程序,以TServerSocket
从中删除客户端TListView
。
如果由于某种原因,客户端仍在Connections[]
列表中(即,由于基础连接已丢失但TServerSocket
尚未检测到),则套接字将仅缓存所有传出数据,直到其出站缓冲区填满为止,然后将开始WSAWOULDBLOCK
为每个发送返回错误。最终,操作系统将使死连接超时并TServerSocket
从Connections[]
列表中将其删除,从而触发OnClientDisconnect
事件。
至少,在显示的代码中,应该将send循环更新为Close()
实际上无法发送的任何套接字,从而触发OnClientDisconnect
事件以从中删除该客户端TListView
,例如:
procedure TMainForm.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket); var Item: TListItem; begin Item := ListView1.Items.Add; Item.Data := Socket; ... end; procedure TMainForm.ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket); var Item: TListItem; begin Item := ListView1.FindData(0, Socket, True, False); if Item <> nil then Item.Delete; end; procedure TMainForm.Timer1Timer(Sender: TObject); const s: AnsiString = 'ping' + #13#10; var Item: TListItem; Socket: TCustomWinSocket; p: PAnsiChar; i, len, sent: Integer; begin for i := 0 to ListView1.Items.Count - 1 do begin Item := ListView1.Items[i]; Item.SubItems.Objects[2] := TObject(GetTickCount); Socket := TCustomWinSocket(Item.Data); try // SendText() does not handle partial sends, or Unicode strings... //Socket.SendText('ping' + #13#10); p := PAnsiChar(s); len := Length(s); repeat sent := Socket.SendBuf(p^, len); if sent = -1 then being if WSAGetLastError() <> WSAEWOULDBLOCK then Break; // TODO: stop trying after several attempts fail... Continue; end; Inc(p, sent); Dec(len, sent); until len = 0; if len = 0 then Continue; except end; Socket.Close; end; end;