TServerSocket 清理死连接

业务场景: 时段性的繁忙,时段性的空闲,用户量小于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;

 

  • 4
    点赞
  • 2
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值