基于NIO实现的一个高性能通信框架

源码地址

http://git.oschina.net/smartdms/smart-socket?from=20150830

README.md8.2 KB

输入图片说明

框架结构

  • 传输层
    smart-quickly:NIO的具体实现,并提供协议解析接口
  • 协议层
    smart-protocol-p2p:以二进制流进行点对点通信的协议,
  • 应用层
    smart-p2p-server:基于smart-protocol-p2p开发的服务端应用
    smart-p2p-client:基于smart-protocol-p2p开发的客户端应用

未来规划

  • 消息体支持加密通信
  • 提升传输层的安全级别,支持防御类似DDOS之类的网络攻击
  • 完善视频教程
  • 待补充...

快速启动

服务端: P2PServer.java客户端: P2PClient.java

Smart-Socket简介

采用NIO实现的通信框架,目前在此基础上提供了P2P协议的交互支持。

特点:
1. 长连接:由于Socket连接的建立和关闭很耗性能,Smart-Socket的定位为采用了长连接通信;
2. 自修复:采用长连接方式的一个弊端为可能出现网络断连,因此对于客户端,提供了链路断连自动修复支持;
3. 服务端支持集群:各服务端节点都可提供业务服务的同时,充当路由网关进行消息分发。也可通过实现接口ClusterTriggerStrategy自定义消息分发策略;
4. 自定义负载均衡:服务端开启集群功能后,Smart-Socket目前默认提供了轮循算法RoundRobinLoad用于实现负载均衡,也可通过继承AbstractLoadBalancing自定义负载均衡算法;
5. 自定义消息过滤器:通过实现接口SmartFilter,可以对通信的消息进行自定义监控或预处理;

P2P协议简介

P2P协议采用TCP协议承载,二进制编码格式,其消息结构分为P2P协议头部和业务消息体两部分。

  1. P2P消息头定义为定长字段,字段定义固定总长度为(32个字节)。包括:
    • MAGIC_NUMBER:幻数(4字节)
    • Length:消息总长度(4个字节)
    • MessageType:消息类型(4个字节)
    • SequenceID:由发送方填写,请求和响应消息必须保持一致(4个字节)
    • 预留16字节
  2. P2P消息体定义:
    用于存储特定业务数据的区域,支持的数据类型包括boolean、short、int、String、Object(序列化对象,不建议频繁使用)

为什么定义P2P协议?出于两方面考虑:1、效率,P2P专注于业务数据,协议本身除了头部的32字节,没有其余特殊的格式要求。单一的结构使数据更加紧凑而灵活,所有的业务数据都存储于协议的消息体部分,可以保障只传递有价值的数据。从而大幅降低数据传递所需的流量消耗,并且有效的提升的数据的可识别度。2、安全,这是一个一直以来被关注的焦点。采用P2P协议,甚至可以在不依赖于SSL通信的前提下确保数据的安全性。无论何种安全方案都无法有效做到数据的防篡改,因此都为了防窃听而在增加安全级别。而P2P协议的编解码规则完全交由业务消息的提供者控制,第三方很难对其进行破解。

开发教程

定义P2P消息

基于P2P协议进行消息通信,定义的消息体需要继承BaseMessage,例如public class HelloWorldReq extends BaseMessage

继承该抽象类需要实现三个方法:

  • encodeBody:编码
  • decodeBody:解码
  • getMessageType:定义消息类型
定义业务字段

消息通信是业务数据的传递,因此需要将包含业务意义的字段定义于消息类中。以HelloWorldReq为例,该消息需要发送姓名(String name)、年龄(int age)、性别(boolean male)给服务端:

private String name;
private int age;
private boolean male;

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}

public boolean isMale() {
    return male;
}

public void setMale(boolean male) {
    this.male = male;
}
encodeBody

为了能够把本地的数据传递至网络的对端,需要对数据进行编码,转换成P2P协议的消息格式。继承BaseMessage之后便可直接使用父类提供的编码方法,根据不同的数据类型选择不同的编码方法。

protected void encodeBody() throws ProtocolException {
    writeString(name);
    writeInt(age);
    writeBoolean(male);
}
decodeBody

解码是对P2P数据报文的反向解析,还原其业务含义。解码字段的顺序需要与编码保持一致,否则将破坏原真是数据,甚至导致异常的发生。

protected void decodeBody() throws DecodeException {
    name = readString();
    age = readInt();
    male = readBoolen();
}
getMessageType

消息分为请求消息与响应消息两种:MessageType.REQUEST_MESSAGE = 0x10000000MessageType.RESPONSE_MESSAGE = 0x11000000

定义消息类型时可从0x01~0x111111中任选一个数值与MessageType进行位运算得到消息类型值。例如HelloWorldReq:

public int getMessageType() {
    return MessageType.REQUEST_MESSAGE|0x01;
}

假如HelloWorldResp为与其对应的响应消息,则HelloWorldRespgetMessageType必须如下:

public int getMessageType() {
    return MessageType.RESPONSE_MESSAGE|0x01;
}
完整代码

请求消息:

public class HelloWorldReq extends BaseMessage {

    private String name;
    private int age;
    private boolean male;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public boolean isMale() {
        return male;
    }

    public void setMale(boolean male) {
        this.male = male;
    }

    @Override
    protected void encodeBody() throws ProtocolException {
        writeString(name);
        writeInt(age);
        writeBoolean(male);
    }

    @Override
    protected void decodeBody() throws DecodeException {
        name = readString();
        age = readInt();
        male = readBoolen();
    }

    @Override
    public int getMessageType() {
        return MessageType.REQUEST_MESSAGE|0x01;
    }
}

响应消息:

public class HelloWorldResp extends BaseMessage {

    public HelloWorldResp() {
        super();
    }

    public HelloWorldResp(HeadMessage head) {
        super(head);
    }

    private String say;

    @Override
    protected void encodeBody() throws ProtocolException {
        writeString(say);
    }

    @Override
    protected void decodeBody() throws DecodeException {
        say = readString();
    }

    @Override
    public int getMessageType() {
        return MessageType.RESPONSE_MESSAGE | 0x01;
    }
}

注意:1、所有继承BaseMessage的对象必须保留不带参数的默认构造方法;2、各消息的getMessageType值必须保证唯一,P2P实现中已经默认占用了一些数值,使用时应注意避免冲突

消息处理器

对于服务器来说,接受到请求消息后需要进行处理,并给予客户端相应的响应信息。处理器的实现需要继承类AbstractServiceMessageProcessor,以下的代码为接受客户端的HelloWorldReq并响应HelloWorldResp消息。构造响应消息时,必须将请求消息中的消息头赋值到响应消息对象中,否则客户端无法识别请求/响应消息的关系。

public class HelloWorldProcessor extends AbstractServiceMessageProcessor {

    @Override
    public void processor(Session session, DataEntry message) throws Exception {
        HelloWorldReq request = (HelloWorldReq) message;
        HelloWorldResp resp = new HelloWorldResp(request.getHead());
        resp.setSay(request.getName() + " say: Hello World,I'm "
                + request.getAge() + " years old. I'm a "
                + (request.isMale() ? "boy" : "girl"));
        session.sendWithoutResponse(resp);
    }
}

服务端开发

服务器的开发主要包括两部分:1. 注册服务器接受的消息类型,以及各消息对应的处理器2. 设置服务器的相关配置

以下为简化的服务器实现:

public class HelloWorldServer {
    public static void main(String[] args) throws ClassNotFoundException {
        // 注册消息以及对应的处理器
        Properties properties = new Properties();
        properties.put(HelloWorldReq.class.getName(),
                HelloWorldProcessor.class.getName());
        BaseMessageFactory.getInstance().loadFromProperties(properties);

        // 启动服务
        QuicklyConfig config = new QuicklyConfig(true);
        P2PProtocolFactory factory = new P2PProtocolFactory();
        config.setProtocolFactory(factory);
        ProtocolDataProcessor processor = new P2PServerMessageProcessor();
        config.setProcessor(processor);
        NioQuickServer server = new NioQuickServer(config);
        try {
            server.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行之后,控制台打印:

[2015-08-24 15:54:17.754] [SEVERE] [Thread-10] [ServiceProcessorManager(regist:48)]load Service Processor Class[com.test.message.HelloWorldProcessor] for com.test.message.HelloWorldReq
[2015-08-24 15:54:17.851] [SEVERE] [Thread-10] [BaseMessageFactory(regiestMessage:49)]load Message Class[com.test.message.HelloWorldReq]
[2015-08-24 15:54:17.866] [SEVERE] [Thread-10] [ServiceProcessorManager(regist:48)]load Service Processor Class[net.vinote.smart.socket.protocol.p2p.processor.InvalidMessageProcessor] for net.vinote.smart.socket.protocol.p2p.InvalidMessageReq
[2015-08-24 15:54:17.866] [SEVERE] [Thread-10] [BaseMessageFactory(regiestMessage:49)]load Message Class[net.vinote.smart.socket.protocol.p2p.InvalidMessageReq]
[2015-08-24 15:54:17.867] [SEVERE] [Thread-10] [AbstractChannelService(<init>:49)]Registe MessageServer Processor[net.vinote.smart.socket.protocol.p2p.server.P2PServerMessageProcessor] success
[2015-08-24 15:54:18.09] [SEVERE] [Thread-11] [NioQuickServer(notifyWhenUpdateStatus:175)]Running with 8888 port

客户端开发

public class HelloWorldClient {
    public static void main(String[] args) throws Exception {
        Properties properties = new Properties();
        properties.put(HelloWorldResp.class.getName(), "");
        BaseMessageFactory.getInstance().loadFromProperties(properties);

        QuicklyConfig config = new QuicklyConfig(false);
        P2PProtocolFactory factory = new P2PProtocolFactory();
        config.setProtocolFactory(factory);
        P2PClientMessageProcessor processor = new P2PClientMessageProcessor();
        config.setProcessor(processor);
        config.setHost("127.0.0.1");
        config.setTimeout(1000);
        NioQuickClient client = new NioQuickClient(config);
        client.start();

        int num = 10;
        while (num-- > 0) {
            HelloWorldReq req = new HelloWorldReq();
            req.setName("seer" + num);
            req.setAge(num);
            req.setMale(num % 2 == 0);
            DataEntry data = processor.getSession().sendWithResponse(req);
            RunLogger.getLogger().log(Level.FINE,
                    StringUtils.toHexString(data.getData()));
        }
        client.shutdown();
    }
}

关于作者

Edit By Seer
E-mail:zhengjunweimail@163.com
QQ:504166636

Update Date: 2015-09-04


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值