技术文章 > 服务端 > pomelo 用户指南-connector 实现 

Connector 实现

在与客户端通信的时候,pomelo 目前提供了 hybridconnector 和 sioconnector,其中 hybridconnector 支持 tcp、websocket,sioconnector 支持 socket.io。但是实际编程中,只有这些 connector 可能还无法满足我们的需求,我们可能需要自己定制自己的 connector,pomelo 提供了定制 connector 的接口,在这部分就是要说明这个问题。

内建的 connector 分析


Pomelo 内建的 connector 包括 sioconnector 和 hybridconnector,这里以 sioconnector 为例来分析,说明如何实现一个 connector。
首先 sioconnector 的构造函数里需要三个参数 host、port、opts,(host,port) 是要监听 socket 绑定的,在 sioconnector 的 start 调用中,会开启对应的监听,并使得当有连接事件发生时,能够将连接事件抛出。抛出连接事件的时候,对应的通信 socket 是 SioSocket。SioSocket 的事件中,当客户端主动断开连接时,需要触发 disconnect 事件,当通信出现错误时,触发 error 事件,当有消息获得的时候触发 message 事件。当收到客户端的请求 message 或者需要给客户端发送回应或者推送消息的时候,pomelo 会使用 connector 的 decode 函数对数据进行解码。对于客户端请求的消息,其上报到应用层的格式应为:
{ 
  id : ,
  route: ,
  body:  
}
这里 id 是客户端请求的 requestId,这个数值由客户端产生的,因此不同 session 的请求 id 是互相不干涉的。如果不是请求,只是一个 notify 的话,id 值为空。route 是对应应用服务器的请求位置,格式为 "x.x.x"。body 为具体请求时携带的参数信息,当我们发起 filter-handler 链对请求进行处理的时候,参数 msg 就是这里的 body 值,是在这里产生的。
对于服务器端给客户端的响应或者服务器端的推送消息,会使用 connector 的 encode 进行编码,编码函数的签名为:
encode(reqId, route, msg);
其中,如果是响应的话,reqId 是对应的请求 id,route 为请求携带的 route,实际上可以省略,msg 为相应的具体内容: 经过对 sioconnector 的分析,我们完成了 pomelo 中 connector 的抽象。

connector 抽象


在涉及到客户端与服务器端进行通信的时候,往往都会使用类似与 tcp 服务器客户端通信的模式,我们的 connector 抽象也不例外。在这里,我们假定要实现一个 FooConnector,看看需要实现些什么。
  1. 首先需要一个 FooConnector,FooConnector 的构造函数需要一些自己的参数,这些参数要包括创建一个连接监听所需要的所有信息,当然比较经典的参数信息是 (host,port) 对,但是对于一些比较特殊的 connector 来说,其连接监听所需要的信息可能就不是 (host,port) 对了,比如用于本地通信的 share memory 以及命名管道等;
  2. FooConnector 需要实现一个 start 函数,在这个 start 函数中,会启动对连接的监听,最经典的操作应该是 listen 调用。当然在具体实现一些比较特殊的 connector 的时候,可能不会是 listen 调用。start 中还应该对实现当有连接到来的时候,触发 connector 的 connection 事件,对应经典实现的 accept 返回,当然是异步的触发。在触发 connection 事件的时候,应该传出参数 FooSocket;
  3. FooSocket 是 FooConnector 触发 connection 事件时传出的参数,可以类比经典 tcp 通信中,accept 返回的用于通信的 socket。当 FooSocket 出现通信错误时,触发 error 事件;当遇到客户端主动断开连接时,应该触发事件 disconnect;当收到客户端传来的消息时,应该能够触发 message 事件,message 事件携带的参数为 FooBuffer 类型;
  4. FooBuffer 类型可以类比经典 tcp 通信中的 byte 数组,在 javascript 中,FooBuffer 一般为 string 或者 Buffer 类型,当然用户也可以定制自己的类型。需要注意的每一次 message 事件的触发,携带的都应该是一个完整的包,不能出现半包以及粘包,用户的 connector 实现要保证这一点。当然,只有类似与 tcp 这样的流协议的时候才可能出现半包粘包的问题,一些基于数据报的协议是不会出现这种问题的;
  5. Connector 还需要提供一个 decode 函数,这个 decode 函数的参数为上面 FooSocket 的 message 事件携带的 FooBuffer,这个 decode 函数应该能够把这个 FooBuffer 解码成具体的应用层能够直接使用的请求对象,也就是说 decode 函数的返回值应该为如下格式的对象:
    {
       id: ,
       route: ,
       body: 
    }
  6. 当服务端向用户发起消息推送或者响应的时候,Connector 应该提供 encode 函数,完成具体的打包操作,Connector 提供的 encode 函数签名应该为:
    encode(reqId, route, body);
    encode 函数的返回值类型为 FooBuffer,也就是 FooSocket 可以处理的类型,encode 完成用户的请求数据的打包。如果是服务端推送数据的话,reqId 的值应该为空;
  7. 由于我们自己定义了 encode/decode 函数,因此我们可以设计实现我们自己的线上协议,只要在应用层抛数据的时候保持一致即可;
  8. 当具体发送打包后的数据时,需要 FooSocket 提供一个发送方法 send,send 的参数就是 encode 打包后的 FooBuffer,其签名为:
    send(FooBuffer);
  9. 在具体实现数据往客户端发送的时候,有时候并不是一旦数据产生就往外发出,而是会先缓存,定时发送数据,这样对于大量小包产生的场景,将会带来很大的性能提升。因为产生的很多小包,可以批量发出,因此 FooSocket 还要支持批量发消息,对应的函数签名为:
    sendBatch(FooBufferArray);
    一般来说,对于底层协议是二进制协议的话,在打包的时候由于已经定义了包边界,因此当有多个包的 FooBuffer 要发出的时候,只需要将所有的 Buffer 打包成一个大的 Buffer 就可以了,hybridconnector 中就是这样实现的。而在 sioconnector 中,因为其底层协议使用的 json,因此其将这些消息组成了一个 json 数组,具体可以参考它们对应的实现。我们在实现 FooSocket 的 sendBatch 方法时,可以根据具体的实际情况进行实现,其语义就是一次批量发送多个包;
  10. FooSocket 还应该有主动断开连接的方法 disconnect,用于当服务器想把某个用户 kick 掉的时候调用;
  11. 当最后应用程序关闭的时候,FooConnector 需要提供 stop 方法,用来关闭相应的连接监听,可以类比 tcp 服务器中最后关闭用来监听的 socket;
  12. 综上所述,我们得出与定制Connector相关的类图,如下:


  13. 在 app.js 中,如果我们的 FooConnector 的构造函数使用的绑定地址信息是 (host,port) 对的话,我们通过如下的调用,启用我们自己定制的 connector:
    app.set('connectorConfig', {
        connector: FooConnector,
        encode : , //optional
        decode : , //optional
        others: 
    });
    这里需要指出,通过 opts 配置的 encode/decode 会优先使用,如果没有通过 opts 配置 encode/decode 的话,将会使用 connector 配置的 encode/decode,如果既没有配置 encode/decode,又没有对 connector 实现 decode/encode,将会出现错误。这里的 connector 配置项使用的是 FooConnector 的构造函数,当 pomelo 构造 connector 时,如果发现配置的是一个构造函数的话,会按如下的方式进行构造:
    new FooConnector(host, clientPort, opts);
    
    如果定制的 FooConnector 的构造的时候使用的地址信息不是 (host,port),那么你就不能使用这种方式进行配置,你可以使用如下方式进行配置:
    var conn = new FooConnector(, opts);
    app.set('connectorConfig', {
      connector: conn,
      // ....
    });

小结


在本部分,阐述了如何实现自定义 connector,给出了 connector 的抽象,介绍了当实现一个全新的 connector 的时候应该需要实现的内容。pomelo 对于 connector 的实现是完全开放的,用户可以根据自己的需求定制 connector,定制自己的通信协议,只要自己的客户端与服务端线上协议保持一致就行了。


来源:摘自 https://github.com/NetEase/pomelo/wiki/connector%E5%AE%9E%E7%8E%B0,本站 行痴 整理