
框架结构
- 传输层
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协议头部和业务消息体两部分。
- P2P消息头定义为定长字段,字段定义固定总长度为(32个字节)。包括:
- MAGIC_NUMBER:幻数(4字节)
- Length:消息总长度(4个字节)
- MessageType:消息类型(4个字节)
- SequenceID:由发送方填写,请求和响应消息必须保持一致(4个字节)
- 预留16字节
- 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 = 0x10000000
,MessageType.RESPONSE_MESSAGE = 0x11000000
定义消息类型时可从0x01~0x111111中任选一个数值与MessageType进行位运算得到消息类型值。例如HelloWorldReq
:
public int getMessageType() {
return MessageType.REQUEST_MESSAGE|0x01;
}
假如HelloWorldResp
为与其对应的响应消息,则HelloWorldResp
的getMessageType
必须如下:
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