@ailhc/enet

General front-end Socket network management module, with high scalability, development does not need to care about the implementation of the underlying socket, communication protocols, developers can customize the implementation of different underlying so

Usage no npm install needed!

<script type="module">
  import ailhcEnet from 'https://cdn.skypack.dev/@ailhc/enet';
</script>

README

@ailhc/enet

介绍

enet 是一个基于TypeScript的零依赖、跨平台、灵活、高可扩展的网络库

可以轻松集成到任意js/ts项目中

可以根据项目需要进行多层次定制(socket层,协议层,网络通信反馈层)

🧰有用工具和库

😆Demo

基于CocosCreator2.4.2的聊天室demo(含服务端实现):egf-net-ws

灵感来源

  1. pinus:一个基于Node.js的分布式水平扩展游戏服务器轻量级强大框架

  2. 宝爷的Cocos Creator 通用框架设计 —— 网络

特性

  1. 跨平台:适用于任意ts/js项目
  2. 灵活、高可扩展:可以根据项目需要进行多层次定制
  3. 零依赖
  4. 强类型:基于TypeScript
  5. 功能强大:提供完整的基本实现:握手、心跳、重连
  6. 可靠:完善的单元测试

如何使用?

安装

  1. 通过npm安装

     npm i @ailhc/enet   
    
  2. 本地link npm包

    a. clone 项目仓库

     git clone https://github.com/AILHC/EasyGameFrameworkOpen
    

    b. 在packages/enet文件夹下

     npm link
    

    c. 在项目游戏项目目录下

     npm link @ailhc/enet    
    
  3. 使用源码

    a. clone 项目仓库

     git clone https://github.com/AILHC/EasyGameFrameworkOpen
    

    b. 直接复制packages/enet/src下的ts源码到项目文件夹下使用(排除index.ts)

使用

  1. 基础使用(三步)
//三行代码初始化
import { NetNode } from "@ailhc/enet";
const net = new NetNode<string>();
net.init();

//一行代码连接服务器
net.connect("wss://echo.websocket.org/");

//两行代码发收个消息
net.onPush<string>("Msg", (dpkg) => { console.log(dpkg.data) });
net.notify<string>("Msg", msg);

//一步两步,就这么简单~ 如丝般
  1. 进阶使用(不改源码定制属于你的网络层)

    1. 定制网络通信的反馈和交互逻辑

       每个项目都有自己的网络通信的反馈和交互逻辑和定制需求,而且希望能够通用且能可控。
      
      //两步轻松定制
      //实现网络事件处理器接口enet.INetEventHandler 
      //这些方法大部分是可选的,按需求自定义
      
      export class ProtobufNetTest implements enet.INetEventHandler {
          init(){
              const netMgr = new NetNode<string>();
              //注入处理器
              netMgr.init({
                  netEventHandler: this
              })
          }
      
          onStartConnenct?(connectOpt: enet.IConnectOptions): void {
              console.log(`start connect:${connectOpt.url}`)
          }
          
          onConnectEnd?(connectOpt: enet.IConnectOptions): void {
              console.log(`connect end:${connectOpt.url}`);
          }
          onError?(event: any, connectOpt: enet.IConnectOptions): void {
              console.error(`socket error`);
              console.error(event);
          }
          onClosed?(event: any, connectOpt: enet.IConnectOptions): void {
              console.error(`socket close`);
              console.error(event);
          }
          onStartReconnect(reConnectCfg: enet.IReconnectConfig, connectOpt: enet.IConnectOptions): void {
              console.log(`start reconnect:${connectOpt.url}`);
          }
          onReconnecting?(curCount: number, reConnectCfg: enet.IReconnectConfig, connectOpt: enet.IConnectOptions): void {
              console.log(`url:${connectOpt.url} reconnect count:${curCount},less count:${reConnectCfg.reconnectCount}`);
          }
          onReconnectEnd?(isOk: boolean, reConnectCfg: enet.IReconnectConfig, connectOpt: enet.IConnectOptions): void {
              console.log(`url:${connectOpt.url}reconnect end ${isOk ? "ok" : "fail"} `);
          }
          onStartRequest?(reqCfg: enet.IRequestConfig, connectOpt: enet.IConnectOptions): void {
              console.log(`start request:${reqCfg.protoKey},id:${reqCfg.reqId}`);
          }
          onData?(dpkg: enet.IDecodePackage<any>, connectOpt: enet.IConnectOptions): void {
              console.log(`data :${dpkg.key}`);
          }
          onRequestTimeout?(reqCfg: enet.IRequestConfig, connectOpt: enet.IConnectOptions): void {
              console.warn(`request timeout:${reqCfg.protoKey}`);
          }
          onCustomError?(dpkg: enet.IDecodePackage<any>, connectOpt: enet.IConnectOptions): void {
              console.error(`proto key:${dpkg.key},reqId:${dpkg.reqId},code:${dpkg.code},errorMsg:${dpkg.errorMsg}`);
          }
          onKick(dpkg: enet.IDecodePackage<any>, copt: enet.IConnectOptions) {
              console.log(`be kick`);
          }
      }
      
      
    2. 定制协议处理层

       可能很多都会用protobuf作为通信协议处理,但它只是编解码数据的一个库,一些特殊和业务向的通信则需要自己处理
      
       这个网络库提供这个协议处理扩展的能力
      
          //两步轻松定制
          //默认的协议处理是将数据序列化成字符串,然后发送出去
      
          //第一步实现协议处理接口,可以将字符串转成二进制数据
          import { Byte } from "./byte";
          //ByteProtoHandler.ts
          //#region 
          class ByteProtoHandler<ProtoKeyType> implements enet.IProtoHandler<ProtoKeyType> {
              private _heartbeatCfg: enet.IHeartBeatConfig;
              protected _byteUtil: Byte = new Byte();//这个二进制处理库可以在egf-pbws库里拿源码(这个是复制Laya的二进制处理库)
              public get heartbeatConfig(): enet.IHeartBeatConfig {
                  return this._heartbeatCfg;
              }
              encodePkg(pkg: enet.IPackage<any>, useCrypto?: boolean): enet.NetData {
                  const byteUtil = this._byteUtil;
                  byteUtil.clear();
                  byteUtil.endian = Byte.LITTLE_ENDIAN;
                  const str = JSON.stringify(pkg);
                  byteUtil.writeUTFString(str);
                  const buffer = byteUtil.buffer;
                  byteUtil.clear();
                  return buffer;
              }
              protoKey2Key(protoKey: ProtoKeyType): string {
                  return protoKey as any;
              }
              encodeMsg<T>(msg: enet.IMessage<T, ProtoKeyType>, useCrypto?: boolean): enet.NetData {
                  return this.encodePkg({ type: PackageType.DATA, data: msg } as enet.IPackage);
              }
              decodePkg(data: enet.NetData): enet.IDecodePackage<any> {
                  const byteUtil = this._byteUtil;
                  byteUtil.clear();
                  byteUtil.endian = Byte.LITTLE_ENDIAN;
                  byteUtil.writeArrayBuffer(data);
                  byteUtil.pos = 0;
                  const dataStr = byteUtil.readUTFString();
      
                  const parsedData: enet.IDecodePackage = JSON.parse(dataStr);
                  const pkgType = parsedData.type;
                  if (parsedData.type === PackageType.DATA) {
                      const msg: enet.IMessage = parsedData.data;
                      return {
                          key: msg && msg.key, type: pkgType,
                          data: msg.data, reqId: parsedData.data && parsedData.data.reqId
                      } as enet.IDecodePackage;
                  } else {
                      if (pkgType === PackageType.HANDSHAKE) {
                          this._heartbeatCfg = parsedData.data;
                      }
                      return {
                          type: pkgType,
                          data: parsedData.data
                      } as enet.IDecodePackage;
                  }
      
              }
      
          }
          //#endregion
          //第二步 注入协议处理器
          //...省略代码
          const netMgr = new NetNode<string>();
          const protoHandler = new ByteProtoHandler(); 
          netMgr.init({
              protoHandler: protoHandler
          })
          //...
      
    3. 定制socket层

       使用ts/js开发项目的,大部分是使用websocket进行socket网络通信,当然也有可能使用socket.io,现在越来越少了。
       也可能有的项目会自己基于tcp、udp进行socket通信
       同样这个socket层也是可以定制的
      
      //两步轻松定制
      //socket的定制实现可以参考enet/src/wsocket.ts
      //比如
      export class IOSocket implements enet.ISocket {
          //...省略
      }
      //
      //...省略
      //注入socket
      const netMgr = new NetNode<string>();
      const ioSocket = new IOSocket(); 
      netMgr.init({
          socket: ioSocket
      })
      //...
      
  2. 其他使用(重连、心跳)

    提供丰富的配置项进行配置

    /**
     * 重连配置接口
     */
    interface IReconnectConfig {
        /**
         * 重连次数
         * 默认:4
         */
        reconnectCount?: number;
        /**
         * 连接超时时间,单位毫秒
         * 默认: 120000 2分钟
         */
        connectTimeout?: number;
    }
    interface INodeConfig {
        /**
         * 底层socket实现
         */
        socket?: ISocket;
        /**
         * 网络事件处理器
         * 默认:使用log输出方式
         */
        netEventHandler?: INetEventHandler;
        /**
         * 协议编码,解码处理器
         * 默认: 使用字符串协议处理器
         */
        protoHandler?: IProtoHandler;
        /**
         * 重连配置,有默认值
         */
        reConnectCfg?: IReconnectConfig;
        /**心跳间隔阈值 ,默认100*/
        heartbeatGapThreashold?: number;
        /**使用加密 */
        useCrypto?: boolean;
    }
    

CHANGELOG