业务场景: 时段性的繁忙,时段性的空闲,用户量小于1000;
问题描述: SOCKET服务器因为网络等问题导致会有死连接,死连接过多会导致服务器端口资源耗尽而亡,必须周期性重启服务器,使用KEEPALIVE并不能100%剔除死连接。
改善措施: 加入服务器空闲检测措施(在无接收数据动作后8秒开启定时器发送一个空字符,如果发送异常则会触发ClientError事件,在此事件里面刷新服务器连接状态,并断开与客户端的连接Socket.Close)。
验证结论:服务器稳定运行3个月内存CPU正常,无死连接。
如果你有更好的建议或想法欢迎留言一起探讨,共同进步!
/// <summary>
/// 客户端连接事件
/// </summary>
/// <param name="Sender"></param>
/// <param name="Socket"></param>
procedure TFrm_Main.ServerSocketMainClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
var
S:string;
begin
try
S:=Socket.RemoteAddress+' : '+IntToStr(Socket.RemotePort);
if lst_ClientIP.Items.IndexOf(S)<0 then
begin
lst_ClientIP.Items.Add(S);
end;
RefrshClientConnectCount();
except
end;
end;
/// <summary>
/// 客户端断开事件
/// </summary>
/// <param name="Sender"></param>
/// <param name="Socket"></param>
procedure TFrm_Main.ServerSocketMainClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
var
iIndex:Integer;
S:string;
begin
try
S:=Socket.RemoteAddress+' : '+IntToStr(Socket.RemotePort);
iIndex:=lst_ClientIP.Items.IndexOf(S);
if iIndex>=0 then
begin
lst_ClientIP.Items.Delete(iIndex);
end;
RefrshClientDisconnectCount();
except
end;
end;
/// <summary>
/// 接收客户端数据
/// </summary>
/// <param name="Sender"></param>
/// <param name="Socket"></param>
procedure TFrm_Main.ServerSocketMainClientRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
tmr_CheckActiveConnection.Enabled:=False; //关闭定时器
try
//加入队列
//G_Cls_sfQueue.Push(PRecPack);
finally
tmr_CheckActiveConnection.Interval:=8000;
tmr_CheckActiveConnection.Enabled:=True; //打开定时器,重新计时
end;
end;
/// <summary>
/// 剔除死连接,解决(长时间运行导致服务器资源端口耗尽,只能重启服务器)问题
/// </summary>
/// <param name="Sender"></param>
procedure TFrm_Main.tmr_CheckActiveConnectionTimer(Sender: TObject);
var
i_Index:Integer;
begin
tmr_CheckActiveConnection.Enabled:=False;
if ServerSocketMain.Active then
begin
Randomize;
i_Index:=RandomRange(0,ServerSocketMain.Socket.ActiveConnections); //随机给任意客户端发送空字符,随着时间的推移,死连接将清除干净!(因为是在主线程中实现剔除死连接,每次最多剔除一个死连接,就不会卡主线程造成界面不响应)
try
ServerSocketMain.Socket.Connections[i_Index].SendText(' '); //If error then raise ServerSocketMainClientError event
except
end;
tmr_CheckActiveConnection.Enabled:=(ServerSocketMain.Socket.ActiveConnections>0);
end;
end;
/// <summary>
/// 客户端异常事件
/// </summary>
/// <param name="Sender"></param>
/// <param name="Socket"></param>
/// <param name="ErrorEvent"></param>
/// <param name="ErrorCode"></param>
procedure TFrm_Main.ServerSocketMainClientError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
var
iIndex:Integer;
S:string;
begin
ErrorCode:=0;
try
S:=Socket.RemoteAddress+' : '+IntToStr(Socket.RemotePort);
iIndex:=lst_ClientIP.Items.IndexOf(S);
if iIndex>=0 then
begin
lst_ClientIP.Items.Delete(iIndex); //关闭客户端前刷新面板
end;
RefrshClientDisconnectCount(); //刷新显示
except
end;
Socket.Close; //关闭与客户端的连接
end;
/// <summary>
/// 客户端连接时刷新客户端连接数
/// </summary>
procedure TFrm_Main.RefrshClientConnectCount();
begin
statbarMain.Panels[0].Text:='客户端连接数: '+IntToStr(ServerSocketMain.Socket.ActiveConnections);
statbarMain.Refresh;
end;
/// <summary>
/// 客户端即将断开时刷新客户端连接数
/// </summary>
procedure TFrm_Main.RefrshClientDisconnectCount();
begin
statbarMain.Panels[0].Text:='客户端连接数: '+IntToStr(ServerSocketMain.Socket.ActiveConnections-1);
statbarMain.Refresh;
end;
为什么sendText发送异常就会触发ClientError事件?
答:在delphi的serversocket源码scktcomp.pas中能找到答案,
if Result = SOCKET_ERROR then
begin
ErrorCode := WSAGetLastError;
if (ErrorCode <> WSAEWOULDBLOCK) then
begin
Error(Self, eeSend, ErrorCode); //触发ClientError事件
function TCustomWinSocket.SendText(const s: string): Integer;
begin
Result := SendBuf(Pointer(S)^, Length(S));
end;
function TCustomWinSocket.SendBuf(var Buf; Count: Integer): Integer;
var
ErrorCode: Integer;
begin
Lock;
try
Result := 0;
if not FConnected then Exit;
Result := send(FSocket, Buf, Count, 0);
if Result = SOCKET_ERROR then
begin
ErrorCode := WSAGetLastError;
if (ErrorCode <> WSAEWOULDBLOCK) then
begin
Error(Self, eeSend, ErrorCode);
Disconnect(FSocket);
if ErrorCode <> 0 then
raise ESocketError.CreateResFmt(@sWindowsSocketError,
[SysErrorMessage(ErrorCode), ErrorCode, 'send']);
end;
end;
finally
Unlock;
end;
end;