设为首页 收藏本站
查看: 661|回复: 0

[经验分享] apache mina第二发-自定义协议通信(上)

[复制链接]

尚未签到

发表于 2017-1-10 08:38:20 | 显示全部楼层 |阅读模式
  上一篇中有一行代码

//设定这个过滤器将一行一行地读取数据
chain.addLast("myChain", new ProtocolCodecFilter(new TextLineCodecFactory()));
  这句代码其实并不简单,这句代码的意思是设置编码码工厂(就是一个能得到编码器和解码器的东西),这里我们用的是Mina自己提供的字符行编解码工厂,就是把一行文本认为是一条消息。
  如果我们想要实现自己的通信协议,就需要自己编写编解码器了。这里我们定义这样一个协议
M sip:wap.fetion.com.cn SIP-C/2.0
S: 1580101xxxx
R: 1889020xxxx
L: 21
Hello World!
第一行代表协议类型,表示是一个飞信信息
第二行代表飞信的发送者
第三行代表飞信的接收者
第四行代表文本信息的长度
第五行开始代表实际的文本信息(注意这里不能用换行作为分隔符,因为实际的飞信内容里面也是有换行符的)
  首先,我们定义一个实体类代表一条信息

package mina.common;

/**
* 协议的实体类
* @author 尹定宇
* @Email 768166775@qq.com
* @version 2013-5-1 上午9:54:44
* @info
*/
public class SmsObject {
private String sender;// 短信发送者
private String receiver;// 短信接受者
private String message;// 短信内容
/***getters & setters***/
}

  编解码工厂,必须实现 ProtocolCodecFactory接口,这个工厂的作用就是得到具体的编码器和解码器对象

package mina.common;
import java.nio.charset.Charset;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolEncoder;
/**
* 编解码工厂
* @author 尹定宇
* @Email 768166775@qq.com
* @version 2013-5-2 上午10:22:51
* @info
*/
public class CmccSipcCodecFactory implements ProtocolCodecFactory {
private final CmccSipcEncoder encoder;
private final CmccSipcDecoder decoder;
public CmccSipcCodecFactory() {
this(Charset.defaultCharset());
}
public CmccSipcCodecFactory(Charset charSet) {
this.encoder = new CmccSipcEncoder(charSet);
this.decoder = new CmccSipcDecoder(charSet);
}
@Override
public ProtocolDecoder getDecoder(IoSession session) throws Exception {
return decoder;
}
@Override
public ProtocolEncoder getEncoder(IoSession session) throws Exception {
return encoder;
}
}

  编码器类,编码器类必须实现ProtocolEncoder接口,或者继承ProtocolEncoderAdapter接口,而且必须重写encode方法,它的第一个参数表示一个会话(还是和Java Web中的session有点像,可以存取上下文,有妙用,在下一篇介绍)第二个参数表示消息实体,第三个参数表示它要输出的信息

package mina.common;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderAdapter;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
/**
* 继承ProtocolEncoderAdapter,
* 而此类已经实现ProtocolEncoder其他不太需要我们关注的方法
* 我们只需重写编码方法
* @author 尹定宇
* @Email 768166775@qq.com
* @version 2013-5-1 上午10:00:35
* @info
*/
public class CmccSipcEncoder extends ProtocolEncoderAdapter {
private final Charset charset;
public CmccSipcEncoder(Charset charset) {
this.charset = charset;
}
/**
* 必须实现的编码方法
*/
@Override
public void encode(IoSession arg0, Object message, ProtocolEncoderOutput out)
throws Exception {
SmsObject sms = (SmsObject) message;
CharsetEncoder ce = charset.newEncoder();
IoBuffer buffer = IoBuffer.allocate(100).setAutoExpand(true);
String statusLine = "M sip:wap.fetion.com.cn SIP-C/2.0";
String sender = sms.getSender();
String receiver = sms.getReceiver();
String smsContent = sms.getMessage();
buffer.putString(statusLine + '\n', ce);
buffer.putString("S: " + sender + '\n', ce);
buffer.putString("R: " + receiver + '\n', ce);
buffer
.putString("L: " + (smsContent.getBytes(charset).length)
+ "\n",
ce);
buffer.putString(smsContent, ce);
buffer.flip();
out.write(buffer);

}
}

  编码器的逻辑很清晰,就是拿到一个smsObject对象,然后按照协议规定那么一行一行地输出。
  之后是解码器

package mina.common;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
/**
* 解码器类
* @author 尹定宇
* @Email 768166775@qq.com
* @version 2013-5-2 上午10:10:03
* @info
*/
public class CmccSipcDecoder extends CumulativeProtocolDecoder {
private final Charset charset;
public CmccSipcDecoder(Charset charset) {
this.charset = charset;
}
@Override
protected boolean doDecode(IoSession session, IoBuffer in,
ProtocolDecoderOutput out) throws Exception {
IoBuffer buffer = IoBuffer.allocate(100).setAutoExpand(true);
CharsetDecoder cd = charset.newDecoder();
int matchCount = 0;
String statusLine = "", sender = "", receiver = "", length = "",
sms = "";
int i = 1;
while (in.hasRemaining()) {
byte b = in.get();
buffer.put(b);
if (b == 10 && i < 5) {
matchCount++;
if (i == 1) {
buffer.flip();
statusLine = buffer.getString(matchCount, cd);
statusLine = statusLine.substring(0,
statusLine.length() - 1);
matchCount = 0;
buffer.clear();
}
if (i == 2) {
buffer.flip();
sender = buffer.getString(matchCount, cd);
sender = sender.substring(0, sender.length() - 1);
matchCount = 0;
buffer.clear();
}
if (i == 3) {
buffer.flip();
receiver = buffer.getString(matchCount, cd);
receiver = receiver.substring(0, receiver.length()-1);
matchCount = 0;
buffer.clear();
}
if (i == 4) {
buffer.flip();
length = buffer.getString(matchCount, cd);
length = length.substring(0, length.length() - 1);
matchCount = 0;
buffer.clear();
}
i++;
} else if (i == 5) {
matchCount++;
if (matchCount == Long.parseLong(length.split(": ")[1]))
{
buffer.flip();
sms = buffer.getString(matchCount, cd);
i++;
break;
}
} else {
matchCount++;
}
}
SmsObject smsObject = new SmsObject();
smsObject.setSender(sender.split(": ")[1]);
smsObject.setReceiver(receiver.split(": ")[1]);
smsObject.setMessage(sms);
out.write(smsObject);
return false;
}
}

  解码器的逻辑就不如编码器那么清晰,这里专门介绍一下。解码器里有个Byte b,表示当前从输入中读到的字节,int i,用来表示当时是在信息的第几行,变量matchCount表示是一行中的第几个字节。我们从输入中一个字节一个字节的读,根据i判断当前如果不是第五行,而且不是换行符(10),那么只将matchCount++,如果是换行符,就降这一行解码出来,设置为一个smsObject对象的对应属性;对于第五行之后则是不同的逻辑,因为实际信息内容中可能包括换行符,第五行之后是根据第四行中解码出的信息长度来截取的。
  然后是服务器端的IoHandler示例了,这个很容易看懂

package mina.server;
import mina.common.SmsObject;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyServerHandler extends IoHandlerAdapter{
//日志
private final static Logger log = LoggerFactory.getLogger(MyServerHandler.class);
/**
* 当一个客户端连接进入时
*/
public void sessionOpened(IoSession session) throws Exception{
System.out.println("新接入的客户端"+session.getRemoteAddress());
}
/**
* 当一个客户端断开连接时
*/
public void sessionClosed(IoSession session){
System.out.println("客户端断开连接");
}
/**
* 当有信息发来时
*/
public void messageReceived(IoSession session,Object message){
SmsObject sms = (SmsObject)message;
log.info("收到短信,内容是:【"+sms.getMessage()+"】");
}
private int count = 0;
}

  客户端的IoHandler示例

package mina.client;
import mina.common.SmsObject;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
public class MyClientHandler extends IoHandlerAdapter {
public void sessionOpened(IoSession session) throws Exception{
System.out.println("连接到服务器");
SmsObject sms = new SmsObject();
sms.setSender("15580046143");
sms.setReceiver("10010");
sms.setMessage("今天天气不错,适合开例会");
session.write(sms);
}
public void sessionClosed(IoSession session){
System.out.println("与服务器断开连接");
}
public void messageReceived(IoSession session,Object message){
}
}

  两边的主函数在这边就省略了,只需要将本篇开头中的编解码工厂换成我们自己编写的编解码工厂就好了。
  最后讨论一个细节,编码器实现的是ProtocolEecoder接口,而解码器实现的是CumulativeProtocolDecoder接口,它为什么要使用这个接口呢?我们可以试着分析一下。在实际使用过程中,信息并不是一条一条发来的,很可能某一刻很多信息累积在一起,如果我们用传统的解码器,调用一次decode方法,解码出第一条信息之后,后面的信息就被我们废弃了,这是一个令我们不能容忍的问题。好在Mina早想到了这个问题,提供了CumulativeProtocolDecoder接口,它的函数dodecode工作原理是解码完成后如果后面还有后续的信息就把它作为这个方法的输入再调用一次自己,这样就很好的解决了上面所描述的问题。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-326274-1-1.html 上篇帖子: 使用Apache搭建Sticky模式的Tomcat集群 下篇帖子: Java日期格式转换,使用apache commons 包 DateUtils
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表