@eric1989
2016-02-18T10:32:58.000000Z
字数 5046
阅读 626
Jfire-socket是一个服务端的socket框架。底层基于AIO提供强大的网络能力。可以实现在单机上数个线程(个位数)管理万级别的长连接。并且编程简单,实现的目的类似mina和netty。都是为了屏蔽socket服务端开发的复杂性,让开发人员只需要关注业务逻辑的实现即可。所有数据的收发完全由框架来保证。Jfire-socket的优点包含
下面我们来看一个Jfire-socket的例子。
首先是一个实现了接口的业务方法
@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 serverConfig = new ServerConfig();
//设置监听端口为80
serverConfig.setPort(80);
//设置需要扫描识别的包
serverConfig.setPackageNames("com.socket.listeners");
//启动服务器,开始按照配置的端口开启监听
serverMain.start();
Jfire-socket基于Jfire-core完成对类对象的管理工作。基于注解的方式使得开发十分的容易。只需要业务类实现了对应的接口并且打上@resource
注解,并且在ServerConfig类实例中指明需要扫描的包即可。下面会逐步解释ServerConfig的配置项信息。
上面是全部的设置项。不过一般情况,只需要使用设置端口和设置扫描包路径就可以使用。其他的配置都有默认信息,不需要额外的填写。
要进行tcp通信,双方必然要就tcp报文的格式进行确定。一般而言,tcp报文有两种形式。报文中带有长度信息,或者采用特殊字节作为结尾。Jfire-socket采用的前一种形式。Jfire-socket约定的报文格式如下
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ... |
---|---|---|---|---|---|---|---|---|---|---|---|
验证字节1 | 验证字节2 | 验证字节3 | 验证字节4 | 报文发送方 | 业务编号 | 处理结果 | 报文体长度 | 报文体长度 | 报文体长度 | 报文体长度 | 报文体内容 |
//服务端状态查询
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就可以,让报文体来说明具体的业务。依靠这样的报文格式,服务端可以使用报文长度信息处理拆包和粘包问题。不使用特定的结尾字符的原因是因为根据长度读取可以提高性能。在实际的开发中,服务端业务开发人员只需要实现业务即可,报文格式处理已经由框架完成了。框架也提供了默认的客户端实现,如果都在java层面,可以屏蔽所有的底层细节
在Jfire-socket体系下开发业务逻辑非常的简单。只需要实现一个业务逻辑接口并且该类打上@Resource
注解即可。下面来看下业务接口
public interface EntryHandler
{
//表明该业务处理器需要处理的业务编号。当接收到的报文的业务编号与之匹配时就会调用该业务编号的接口实现
public byte interestedDataPacketType();
//具体的业务处理逻辑。entry包含看了本次报文体内容以及相关的通道信息
public void handler(Entry entry) throws Exception;
}
我们来看下默认的一个业务处理器,echo业务处理器
@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的读写情况存在。