# jfire-socket **Repository Path**: jeesd/jfire-socket ## Basic Information - **Project Name**: jfire-socket - **Description**: 基于aio实现的通用socket服务器。可直接继承实现类来开发业务插件 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2016-04-05 - **Last Updated**: 2021-11-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Jfire-socket开发文档 [TOC] ##Jfire-socket是什么 Jfire-socket是一个服务端的socket框架。底层基于AIO提供强大的网络能力。可以实现在单机上数个线程(个位数)管理万级别的长连接。并且编程简单,实现的目的类似mina和netty。都是为了屏蔽socket服务端开发的复杂性,让开发人员只需要关注业务逻辑的实现即可。所有数据的收发完全由框架来保证。Jfire-socket的优点包含 + 网络io能力强大: 框架基于AIO实现,异步的网络IO由操作系统提供强大性能保证 + 接口简单: 业务逻辑只需要实现一个接口,接口中封装了每一次tcp读取到的数据。所以开发业务逻辑,开发人员只需要知道一个业务接口即可。 + 吞吐量能力强: 基于Disruptor设计驱动模型。10线程各自并发1w信息请求,服务端500毫秒处理完毕。 + 内嵌的加密功能: 使用RSA1024位进行握手加密,使用AES256位进行通讯加密。使得rpc可以在公网上可靠的传输。 ##快速入门 下面我们来看一个Jfire-socket的例子。 首先是一个实现了接口的业务方法 ```java @Resource//有Resource注解并且实现了EntryHandler注解就被识别为业务处理器 public class Utf8EchoHandler implements EntryHandler { private Logger logger = ConsoleLogFactory.getLogger(); private Charset charset = Charset.forName("utf-8"); //表示该业务处理器感兴趣的命令编号,含义请看下文 @Override public byte interestedDataPacketType() { return BusinessCommand.ECHO; } //Entry代表依次tcp请求中收到的完整的报文。该对象包含tcp通道信息,本次请求的完整报文数据(框架已经处理好粘包拆包的问题) @Override public void handler(Entry entry) { //获取到了报文数据。 ByteCache cache = entry.getBusinessData(); logger.trace("收到的消息是{},消息长度{}", cache.toString(charset), cache.startRead(0).remaining()); //该方法结束后,cache中的内容就会被发送回客户端。由于这里没有对数据处理,所以这个方法实际上是将请求发送的数据原样返回。 } } public static void main(String args[]) { //服务器配置对象 ServerConfig serverConfig = new ServerConfig(); //设置监听端口为80 serverConfig.setPort(80); //设置需要扫描识别的包 serverConfig.setPackageNames("com.socket.listeners"); //设置通信处理线程数。不设置默认为cpu核数的一半 serverConfig.setSocketThreadSize(4); //ServerMain是服务器类。使用上面的配置对象初始化服务器对象 ServerMain serverMain = new ServerMain(serverConfig); //启动服务器,开始按照配置的端口开启监听 serverMain.start(); } ``` 从上面的例子可以看到,使用Jfire-socket开发服务端和业务是非常方便的。 ##启动服务器/配置ServerConfig 启动服务器需要的代码数量其实很少,因为配置类中包含了很多开箱即用的默认选项。最简单的情况下,只需要设置端口和扫描的包路径即可。如下 ```java //服务器配置对象 ServerConfig serverConfig = new ServerConfig(); //设置监听端口为80 serverConfig.setPort(80); //设置需要扫描识别的包 serverConfig.setPackageNames("com.socket.listeners"); //启动服务器,开始按照配置的端口开启监听 serverMain.start(); ``` Jfire-socket基于Jfire-core完成对类对象的管理工作。基于注解的方式使得开发十分的容易。只需要业务类实现了对应的接口并且打上`@resource`注解,并且在ServerConfig类实例中指明需要扫描的包即可。下面会逐步解释ServerConfig的配置项信息。 + 设置端口:setPort(int) + 设置扫描包路径:setPackageNames(String...) + 设置连接超时等待时间:setWaitTimeout(long),单位是毫秒,不设置默认是30秒 + 设置单次读取超时等待时间:setReadTiemout(long),单位是毫秒。不设置的情况下默认是3秒。一旦读取超时发生,服务器会切断该tcp通道。 + 开启安全模式:activeAuth(),该方法调用后服务器启动后处于安全模式。连接和消息发送都是加密的 + 设置服务器密钥,setPrivateKey(String)。服务器采用RSA1024位加密。所以提供的私钥得是1024位的 + 设置服务区队列长度,setRingArraySize(int).服务器采用Disruptor思想设计存储队列结构。所以有一个总的存储长度限制。一旦存储到达了极限,就无法继续接受新的消息,直到消息被消费后,有新的存储空间。 + 设置存储队列类型,setRingArrayTpe(int)。取值存在两种情况。1和2.可以在开发环境下比较各自性能的不同选择合适的类型。具体的区别请看下文。 + 设置io消息处理线程数,setSocketThreadSize(int)。默认为cpu核数的一半。该线程数表明处理底层io请求的线程数。越多并发越好。但是太多的话,cpu切换反而导致性能下降。经过测试。为cpu核数的一半表现较为平均和良好。 + 设置业务处理线程数,setHandlerThreadSize(int)。该线程数是表明处理业务数据的线程数。数量越多并发越好,但是太多又会导致和io处理进行资源争夺。经过测试,为cpu核数的一半表现较为平均和良好。 + 设置头部验证数据工厂,setHeadFactory(HeadFactory)。该方法用于设置报文协议头部4个字节的生产工厂。系统本身提供了复杂和默认的两种形式。默认情况下使用简单即可。因为安全不能依靠复杂的头部验证字节还是需要加密手段才可以。 上面是全部的设置项。不过一般情况,只需要使用设置端口和设置扫描包路径就可以使用。其他的配置都有默认信息,不需要额外的填写。 ##报文格式 要进行tcp通信,双方必然要就tcp报文的格式进行确定。一般而言,tcp报文有两种形式。报文中带有长度信息,或者采用特殊字节作为结尾。Jfire-socket采用的前一种形式。Jfire-socket约定的报文格式如下 |1|2|3|4|5|6|7|8|9|10|11|...| |-| |验证字节1|验证字节2|验证字节3|验证字节4|报文发送方|业务编号|处理结果|报文体长度|报文体长度|报文体长度|报文体长度|报文体内容| + **报文头**:一共4个字节用来表示报文头,报文头需要和客户端协商确认。Jfire-scoket中提供了两种报文头的工厂类。分别是简单和复杂的两种生成和验证规则。具体可以参考代码说明 + **报文发送方**:该字节表达该报文的发送方。0xa1表明这是一个客户端发送的报文或者服务端响应客户端请求的报文。0xa2表示这是一个服务端主动推送的报文。 + **业务编号**:每一个不同业务编号均由服务端特定的业务处理器进行处理。所以这里的业务编号需要由客户端和服务端协商好来指定。业务编号是不可以重复的。Jfire-socket中默认包含了几个业务编号,所以用户自己协商的业务编号不能与之冲突。默认有 ```java //服务端状态查询 public static final byte SERVER_STATUS = (byte) 0xd1; //清除缓存池 public static final byte CLEAR_BUFFER = (byte) 0xd2; //通信测试 public static final byte TEST_SERVER = (byte) 0xd3; //echo消息测试 public static final byte ECHO = (byte) 0xd5; //权限验证请求 public static final byte AUTH = (byte) 0xd6; //发送通讯加密aes密钥 public static final byte SENDKEY = (byte) 0xd7; //服务端信息 public static final byte SERVER_INFO = (byte) 0xd8; ``` + **处理结果**:用来说明服务端对业务的处理情况。该信息默认是成功,可以由方法`link.jfire.socket.socketserver.storage.Entry.failRequest()`来更为为失败。该方法为用户调用。0x80代表成功,0x81代表失败。这个字节只是一个指向性信息。具体作用仍然需要客户端和服务端协商确定。或者一般默认0x80就可以,让报文体来说明具体的业务。 + **报文体长度**:一共4个字节来表达报文体长度。也就是int在内存中的表示方式,采用大端序来表达。 + **报文体内容**:不定长字节的内容,长度为前面报文体长度所指定。 依靠这样的报文格式,服务端可以使用报文长度信息处理拆包和粘包问题。不使用特定的结尾字符的原因是因为根据长度读取可以提高性能。在实际的开发中,**服务端业务开发人员只需要实现业务即可,报文格式处理已经由框架完成了。框架也提供了默认的客户端实现,如果都在java层面,可以屏蔽所有的底层细节** ##业务逻辑开发 在Jfire-socket体系下开发业务逻辑非常的简单。只需要实现一个业务逻辑接口并且该类打上`@Resource`注解即可。下面来看下业务接口 ```java public interface EntryHandler { //表明该业务处理器需要处理的业务编号。当接收到的报文的业务编号与之匹配时就会调用该业务编号的接口实现 public byte interestedDataPacketType(); //具体的业务处理逻辑。entry包含看了本次报文体内容以及相关的通道信息 public void handler(Entry entry) throws Exception; } ``` 我们来看下默认的一个业务处理器,echo业务处理器 ```java @Resource public class Utf8EchoHandler implements EntryHandler { private Logger logger = ConsoleLogFactory.getLogger(); private Charset charset = Charset.forName("utf-8"); @Override public byte interestedDataPacketType() { return BusinessCommand.ECHO; } @Override public void handler(Entry entry) { //ByteCache是一个类似ByteBuffer的byte数组包装类。但是提供自动变长等一些方便的功能。 ByteCache cache = entry.getBusinessData(); logger.trace("收到的消息是{},消息长度{}", cache.toString(charset), cache.startRead(0).remaining()); //cache就是本次报文体的内容。消息的收和发的载体都是ByteCache。将cache中的内容清空,为业务消息生成的数据腾出空间 cache.clear(); //放入本次需要返回的业务数据 cache.putArray(new byte[1024]); //这个方法执行完毕,框架会自动将cache中的字节内容发送回客户端。由框架保证io读写可靠性。 } } ``` 从上面的示例代码可以看到,开发一个业务处理器是非常简单的,只需要关注数据本身,完全感受不到网络io的读写情况存在。