目前使用的办法是在服务器端维护所有在线用户(客户端)的TCustomWinSocket数组
然后根据其Handle进行循环确认要转发的用户进行转发不知道有没有更简单高效的办法

解决方案 »

  1.   

    如果维护一个在线用户数据库表tb_OnLineUser,又要处理出现的SocketError造成的断线情况
    结果是需要反复操作数据库
      

  2.   

    记得我以前就是这么用的,大概代码:var ClientList:TStringList;//全局变量,客户表接收S:=Socket.ReceiveText;
      ClientList.AddObject('客户标识',Socket);
    转发TCustomWinSocket(ClientList.Objects[i]).SendText(S);个人感觉思路没什么问题,当然了,我对此研究比较少,还请高人指点
      

  3.   

    其实我的做法跟楼上的出发点是基本一致的因为我考虑到一台主机可以同时有几个终端的,不同用户在共享一个网卡的情况
    所以不同用户间的通讯主要是根据唯一用户编号来识别
    (当然从理论上可以说Socket.Handle是唯一的,但是消息发送方不可能在发送消息之前就需要预先知道对方的Socket.Handle)
    所以我维护了这么一个数组
    type
      PInfoClient=^TInfoClient;
      TInfoClient=record
        Handle: integer; //客户端套接字句柄
        Socket:TCustomWinSocket; //客户端套接字
        Name:string; //客户端计算机名称
        Address:string; //客户端计算机IP地址
        UserNO:string;//用户编号
        Used: boolean; //客户端联机标志
    end;
    type TInfoClientList=array of TInfoClient;访问该数组的时候使用独立线程来操着,用TMultiReadExclusiveWriteSynchronizer来实现线程同步就会收到高效简单的效果其中属性UserNO是通过在TClientSocket.OnConnect中发过去,TServerSocket.OnClientRead中写入的发一个今天封装的类供大家探讨
    GetInfo为从传输文本从截取相关命令字符窜的函数unit SvrClt;interface
    uses
      Communication,D7ScktComp,ExtCtrls,SysUtils,Classes,Forms,Define;
    type
      TSvrClt=class(TServerSocket)
      private
        FMaxConnectCount:integer; //最大连接数
        FCurrConnectCount:integer;//当前连接数目
        //FClients:TList;使用该方法只用使用ip进行转发不适合多用户共享一网卡的情况
        procedure ClientSocketConnect(Sender: TObject; Socket: TCustomWinSocket);
        procedure ClientSocketDisconnect(Sender: TObject; Socket: TCustomWinSocket);
        procedure ClientSocketError(Sender: TObject; Socket: TCustomWinSocket;
            ErrorEvent: TErrorEvent; var ErrorCode: Integer);
        procedure ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket);//virtual;abstract;
      protected
      public
        constructor create(const ServerPort:integer; MaxConnectCount:integer;AOwner: TComponent);
        destructor destroy;override;
        function SendMsg(const pktStr:string):boolean;
        //property Clients: TList read GetClients;
      end;
    implementation
    constructor TSvrClt.create(const ServerPort:integer; MaxConnectCount:integer;AOwner: TComponent);
    begin
      inherited create(AOwner);
      FMaxConnectCount:=MaxConnectCount;
      Port:=ServerPort;  OnClientConnect:=ClientSocketConnect;
      OnClientRead:=ClientSocketRead;
      OnClientError:=ClientSocketError;
      OnClientDisconnect:=ClientSocketDisconnect;  Active:=true;
    end;destructor TSvrClt.destroy;
    begin
      Active:=False;
      Close;
      inherited destroy;
    end;procedure TSvrClt.ClientSocketConnect(Sender: TObject;
      Socket: TCustomWinSocket);
    var
      s:string;
      i:integer;
    begin
      for i:=0 to FMaxConnectCount-1 do
      begin
        if not m_infosys[i].Used then    //looking for the idle TCustomWinSocket ,and put down this TCustomWinSocket
        begin
          m_infosys[i].Socket:=Socket;
          m_infosys[i].Used:=True;
          m_infosys[i].Name:='';
          m_infosys[i].Address:=Socket.RemoteAddress;
          m_infosys[i].UserNO:='';
          exit;
        end;//end if
      end;//end for iend;procedure TSvrClt.ClientSocketDisconnect(Sender: TObject;
      Socket: TCustomWinSocket);
    var
      i:integer;
    begin
      //release the TCustomWinSocket in array m_infosys of the Logout user`s TCustomWinSocket on server side
      for i:=0 to FMaxConnectCount-1 do
      begin
        if Socket.Handle=m_infosys[i].Socket.Handle then
        begin
          m_infosys[i].UserNO:='';
          m_infosys[i].Address:='';
          m_infosys[i].Handle:=-1;
          m_infosys[i].Name:='';
          m_infosys[i].Address:='';
          m_infosys[i].Used:=false;
        end;//end if
      end;//end for
    end;procedure TSvrClt.ClientSocketError(Sender: TObject;
      Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
      var ErrorCode: Integer);
    begin
      if not active then active:=true;
    end;
    function TSvrClt.SendMsg(const pktStr:string):boolean;
    begin
      result:=false;  if (not Active)and(Tag=0) then  //before sending msg check the active property
      begin
        Tag:=1;
        Active:=true;
      end;  if Active then
      begin
        result:=Socket.SendText(pktStr)=Length(pktStr);
        //write the data packet`s just been sent
        WriteLog(2,datetimetostr(now) + ' ' + pktStr);
      end;
    end;
    procedure TSvrClt.ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket);
    var
      s,Msg:string;
      UniqueKey,uid:string;
      CommAction:integer;
      handle,i:integer;
      newip:string;//if the user has logined on other computer
    begin
      newip:=Socket.RemoteAddress;
      s:=trim(Socket.ReceiveText);
      s:=GetInfo(s,';');
      while s<>'' do
      begin
        WriteLog(1,datetimetostr(now) + ' ' + Msg);//write data packet log
        UniqueKey:=GetInfo(Msg,',');
        CommAction:=strtoint(GetInfo(Msg,','));
        case CommAction of
          ord(caLogin):  //client request to bind its UserNO 客户端请求账号跟Socket绑定
          begin
            ExecClientCaLogin(Msg,newip);
          end;      ord(caTransmit)://转发消息
          begin
            //client-2-client  message from user to another user
            //ShowTransmitMsg(Msg);
          end;      ord(caForceLogOff)://接收到该命令,新连接迫已登陆使同一账号自动离线(这里可能逻辑还没理顺,或者是作为服务器端管理使用)
          begin
            // unforturnate,this client is forced to logoff  `cause same userno login 
            //ShowForceLogOff(Msg);
          end;      ord(caJump)://令消息接受方从托盘跳到屏幕,这个有点像QQ的窗口抖动提醒对方的意味.(QQ牛,本人不自量力)
          begin
           // Jump2Screen;
          end;
        end;//end case
        //hand the message to the end user;
        Msg:=GetInfo(s,';');
      end;//end while
    end;end.根据该思路,简单测试了一下给出一个比较保守的结果:
    500万之内的在线用户数,单纯在服务器端的处理不会超过1秒就能从服务器发出去
    内存占用1000用户的TCustomWinSocket数组为2M
    用户   需求内存
    1w     20M
    10w    200M
    100w   2000M=2G
    1000w  20G
    带宽需求还得请教牛人希望能找到更高效的处理办法