2

mORMot 框架 ( www.synopse.info ) 添加了对 WebSockets 的支持,包装上还有一个关于 WebSockets 的演示(示例 31)。在此示例中,客户端向服务器发送一条消息,然后向客户端回复一条新消息。我想使用这个库来做到这一点:

  1. 客户端向服务器发送消息并发送IP地址(无需等待来自服务器的消息。);
  2. 服务器可以通过IP地址向单个客户端发送消息;

注意:IP 地址仅用于识别客户端。我也可以使用唯一的名称。

类似于客户端和服务器之间的 LAN 聊天。我不明白如何编辑样本 n。31 做到这一点。此示例基于接口。

4

1 回答 1

8

No need to store the IP, or whatever low-level implementation parameter (by the way, the IP would not be able to identify a connection in an unique manner: several clients may share the same IP).

In the mORMot framework, asynchronous callbacks are implemented via interface parameters. On the server side, each instance of this parameter would be in fact a "fake" class instance, linked to the input connection, able to call back the client via its interface methods.

This is a pretty straightforward way of implementing callbacks - in fact this is a good way of implementing SOLID callbacks on the server side, and the mORMot framework allows to publish this mechanism in a client/server way, by using WebSockets.

So you first define the callback interface, and the service interface:

  IChatCallback = interface(IInvokable)
    ['{EA7EFE51-3EBA-4047-A356-253374518D1D}']
    procedure BlaBla(const pseudo, msg: string);
  end;

  IChatService = interface(IInvokable)
    ['{C92DCBEA-C680-40BD-8D9C-3E6F2ED9C9CF}']
    procedure Join(const pseudo: string; const callback: IChatCallback);
    procedure BlaBla(const pseudo,msg: string);
    procedure CallbackReleased(const callback: IInvokable);
  end;

Then, on the server side, each call to IChatService.Join() would subscribe to an internal list of connections:

  TChatService = class(TInterfacedObject,IChatService)
  protected
    fConnected: array of IChatCallback;
  public
    procedure Join(const pseudo: string; const callback: IChatCallback);
    procedure BlaBla(const pseudo,msg: string);
    procedure CallbackReleased(const callback: IInvokable);
  end;

procedure TChatService.Join(const pseudo: string;
  const callback: IChatCallback);
begin
  InterfaceArrayAdd(fConnected,callback);
end;

Then a remote call to the IChatService.BlaBla() method should be broadcasted to all connected clients, just by calling the IChatCallback.BlaBla() method:

procedure TChatService.BlaBla(const pseudo,msg: string);
var i: integer;
begin
  for i := 0 to high(fConnected) do
    fConnected[i].BlaBla(pseudo,msg);
end;

Note that all the loop calls to IChatCallback.BlaBla() would be made via WebSockets, in an asynchronous and non blocking way, so that even in case of huge number of clients, the IChatService.BlaBla() method won't block. In case of high numbers of messages, the framework is even able to gather push notification messages into a single message, to reduce the resource use.

The following method will be called by the server, when a client callback instance is released (either explicitly, or if the connection is broken), so could be used to unsubscribe to the notification:

procedure TChatService.CallbackReleased(const callback: IInvokable);
begin
  InterfaceArrayDelete(fConnected,callback);
end;

On the server side, you define the service as such:

  Server.ServiceDefine(TChatService,[IChatService],sicShared).
    SetOptions([],[optExecLockedPerInterface]);

Here, the optExecLockedPerInterface option has been set, so that all method calls would be made thread-safe, so that concurrent access to the internal fConnected[] list would be safe.

On the client side, you implement the IChatCallback callback interface:

type
  TChatCallback = class(TInterfacedCallback,IChatCallback)
  protected
    procedure BlaBla(const pseudo, msg: string);
  end;

procedure TChatCallback.BlaBla(const pseudo, msg: string);
begin
  writeln(#13'@',pseudo,' ',msg);
end;

Then you subscribe to your remote service as such:

var Service: IChatService;
    callback: IChatCallback;
...
    Client.ServiceDefine([IChatService],sicShared);
    if not Client.Services.Resolve(IChatService,Service) then
      raise EServiceException.Create('Service IChatService unavailable');
...
      callback := TChatCallback.Create(Client,IChatCallback);
      Service.Join(pseudo,callback);
...
    try
      repeat
        TextColor(ccLightGray);
        readln(msg);
        if msg='' then
          break;
        Service.BlaBla(pseudo,msg);
      until false;
    finally
      callback := nil;
      Service := nil; // release the service local instance BEFORE Client.Free
    end;

If you compare with existing client/server SOA solutions (in Delphi, Java, C# or even in Go or other frameworks), this interface-based callback mechanism sounds pretty unique and easy to work with. Of course, this work from Delphi 6 up to XE7, and also under Linux thanks to FPC or CrossKylix.

I've uploaded the full sample source code in our repository, both the server side application and the client side.

于 2015-04-06T11:19:18.407 回答