title: Mina服务端客户端通信

date: 2018-09-30 09:00:30

tags:

- [mina]

- [tcp]

categories:

- [编程]

permalink: zxh

前两章节已经完整的介绍了理论部分,今天我们就利用这些理论来实现tcp协议的c/s 通信。首先我们简单回顾下之前的介绍,

在mina中我们的客户端和服务端简直就是一模一样,只是我们用不同适配器。但是他的数据处理流程是一样的。今天我们就重点看看如何建立服务端、客户端

并且处理两者之间的消息通信处理

服务端

服务端和客户端不同的就是我们创建的监听对象不同而已,客户端发送消息到服务端,服务端需要经历过滤器的处理才能到达消息中心,但是在过滤器中我们就需要将消息进行解码,然后才会到消息接收的地方处理我们的业务。正常情况下我们处理完消息需要对客户端进行回应。回应的时候也会经历过滤器中的编码逻辑,进行数据编码然后发送。信息发送到客户端我们可以看成服务端的方向。也是需要进行编解码的。下面看看服务端的创建代码


//创建监听对象
IoAcceptor acceptor = new NioSocketAcceptor();
TextLineCodecFactory textLineCodecFactory =
new TextLineCodecFactory(Charset.forName("utf-8"), LineDelimiter.WINDOWS.getValue(),
LineDelimiter.WINDOWS.getValue());
//添加过滤器
acceptor.getFilterChain().addLast("logger",new LoggingFilter());
acceptor.getFilterChain().addLast("protocal",new ProtocolCodecFilter(
textLineCodecFactory
));
//设置时间处理的handler
acceptor.setHandler(new ServerMessageHandler());
//设置读取数据缓存区的大小
acceptor.getSessionConfig().setReadBufferSize(Constaint.READSIZE);
//设置多久没有消息就进入空闲状态
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE,Constaint.IDLETIME);
//绑定端口
try {
acceptor.bind(new InetSocketAddress(Constaint.REMOTE_PORT));
} catch (IOException e) {
logger.error(String.format("bind %s error",Constaint.REMOTE_PORT));
e.printStackTrace();
}
logger.info(String.format("bind %s success",Constaint.REMOTE_PORT));

客户端


//创建监听对象
IoConnector connector = new NioSocketConnector();
TextLineCodecFactory textLineCodecFactory =
new TextLineCodecFactory(Charset.forName("utf-8"), LineDelimiter.WINDOWS.getValue(),
LineDelimiter.WINDOWS.getValue());
//添加过滤器
//日志过滤器 。 sltf日志设置
connector.getFilterChain().addLast("logger",new LoggingFilter());
//在这个过滤器中提供了编解码,这里的编码是以信息中已\r\n结尾算是一条信息
connector.getFilterChain().addLast("protocal",new ProtocolCodecFilter(
new SocketFactory()
));
//设置时间处理的handler , 提供session生命周期的监听函数,消息接受,发送的函数
connector.setHandler(new ClientMessageHandler());
//设置读取数据缓存区的大小
connector.getSessionConfig().setReadBufferSize(Constaint.READSIZE);
//设置多久没有消息就进入空闲状态
connector.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE,Constaint.IDLETIME);
ConnectFuture future = connector.connect(new InetSocketAddress(Constaint.REMOTE_IP,Constaint.REMOTE_PORT));
//是异步处理,这里不会造成阻塞
future.addListener(new IoFutureListener<IoFuture>() {
@Override
public void operationComplete(IoFuture ioFuture) {
logger.info("连接准备完成");
IoSession session = ioFuture.getSession(); }
});

通信

  • 其实上面服务端,客户端两边创建好就应经在通信了,在上面创建的时候我们发现。创建的时候需要指定消息处理器(IoHandlerAdapter) , 这个在IoService中会排在IoFilter之后执行。在过滤器执行之后我们就会调用我们的消息处理器。

private static Logger logger = LogManager.getLogger(ServerMessageHandler.class);
public void sessionCreated(IoSession session) throws Exception {
super.sessionCreated(session);
logger.info("sessionCreated");
} public void sessionOpened(IoSession session) throws Exception {
super.sessionOpened(session);
try {
IoBuffer buffer = IoBuffer.allocate(30);
buffer.clear();
buffer.putString("quit\r\n", Charset.forName("utf-8").newEncoder());
buffer.flip();
session.write(buffer);
} catch (Exception e) {
logger.error(e.toString());
}
logger.info("sessionOpened");
} public void sessionClosed(IoSession session) throws Exception {
super.sessionClosed(session);
logger.info("sessionClosed");
} public void sessionIdle(IoSession session, IdleStatus idleStatus) throws Exception {
super.sessionIdle(session,idleStatus);
try {
IoBuffer buffer = IoBuffer.allocate(30);
buffer.clear();
buffer.putString("quit\r\n", Charset.forName("utf-8").newEncoder());
buffer.flip();
session.write(buffer);
} catch (Exception e) {
logger.error(e.toString());
}
// logger.info("sessionIdle");
} public void exceptionCaught(IoSession ioSession, Throwable throwable) throws Exception {
logger.info("exceptionCaught");
throwable.printStackTrace();
} public void messageReceived(IoSession session, Object message) throws Exception {
super.messageReceived(session, message);
String info = message.toString();
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
String time = sdf.format(date);
session.write(time);
System.out.println("接收到的消息:"+info);
} public void messageSent(IoSession session, Object message) throws Exception {
super.messageSent(session, message);
logger.info("messageSent");
}
  • 这里消息处理器,提供了几个时刻可以控制,比如session创建、销毁的时候执行的地方。消息接收的地方,消息发送成功的地方。这些控制力度可以根据我们的需要进行适度的复写。

自定义工厂编解码

  • 工厂是提供编解码的方法。这个工厂是加载在ProtocolCodecFilter这个过滤器中的。我们也可以自定义过滤器,在自定义的过滤器中我们也可以加载我们自定义的工厂,实现编解码。我们在编解码的地方,就可以加入我们的业务代码。比如解码通过约定的协议方式读取到内容后通过ProtocolDecoderOutput 将消息写出去就可以在我们的IoHandlerAdapter的messageReceived方法中获取到消息。然后业务书写。这样做到代码的解耦。

public class SocketFactory implements ProtocolCodecFactory {
private MessageDecoder decoder;
private MessageEncoder encoder; public SocketFactory() {
decoder = new MessageDecoder();
encoder = new MessageEncoder();
} public ProtocolDecoder getDecoder(IoSession session) throws Exception {
return this.decoder;
} public ProtocolEncoder getEncoder(IoSession session) throws Exception {
return this.encoder;
}
}

解码器

  • 上面的工厂就是提供编解码的。和我们生活中一样工厂提供功能,但是实际并不是工厂做的,工厂可能只代理功能,仅仅是个加工厂而已。mina通信也是如此。真正编解码的并不是工厂执行的,本节将揭露解码者CumulativeProtocolDecoder

  • 解码器写好之后只需要在上面自定义工厂中创建就好了。至于自定义编码器只需要继承CumulativeProtocolDecoder这个类就好了。而且复写doDecode方法就好了。这个方法的返回值是boolean类型。返回值不同代表意义不一。这里需要重点理清楚

+ true:  返回true表示你已经从CumulativeProtocolDecoder的消息中消费了信息,在编码器中返回true之前也应该调用ProtocolDecoderOutput 的wirte将消息发布到IoHandAdaptor中进行业务处理。但是这里会出现其他情况,应为我们服务端客户端是长连接所以在我们消息中消息是不断发过来的,我们缓存中的消息可能是完整一条消息,也可能不够一整条消息,也可能是一整条多了一点,
1、如果不是一条完整(半包)的那么我们返回falsed等待客户端继续发送
2、如果正好是一整条,那么我们接受到之后返回true的时候我们缓存中就没有数据了,在CumulativeProtocolDecoder会停止对解码中doDecode的调用了,这种情况不会出现意外
3、数据比一条完整信息(粘包)多,那么我们处理到一条信息后也需要返回true,但是CumulativeProtocolDecoder会将剩余的缓存继续拼装,剩余消息就相当于内部进行了第二次解码。如果不过那么相当于上面第一种情况 记住三种情况 半包 、 正常 、 粘包
+ false: 返回false就是缓存中的数据不够我们一整条消息,需要继续等待客户端的消息。CumulativeProtocolDecoder中的缓存机制会不断的将客户端发过来的数据拼接到缓存中

public class MessageDecoder extends CumulativeProtocolDecoder {
/**
* 此方法return true : 表示父类中CumulativeProtocolDecoder会不断的调用此方法进行消息的消费
* return false: 表示消息已经消费完全了,缓存中就算有数据也不会再消费了。等待再次客户端
* 发送消息时会触发消息发送接口,此时会将新旧消息拼接再一起进行处理
* @param ioSession
* @param ioBuffer
* @param protocolDecoderOutput
* @return
* @throws Exception
*/
@Override
protected boolean doDecode(IoSession ioSession, IoBuffer ioBuffer, ProtocolDecoderOutput protocolDecoderOutput) throws Exception {
IoBuffer buffer = IoBuffer.allocate(10);
while (ioBuffer.hasRemaining()) {
if (ioBuffer.remaining()<3) {
//继续接受
return false;
}
//获取三个字节
int oldLimit = ioBuffer.limit();
ioBuffer.limit(ioBuffer.position()+3);
String text = ioBuffer.getString(Charset.forName("UTF-8").newDecoder());
protocolDecoderOutput.write(text);
ioBuffer.limit(oldLimit);
if (ioBuffer.hasRemaining()) {
return true;
}
}
return false;
}
}

编码器

  • 编码器相对解码器简单很多,编码器就是加入我们的协议,正常情况就是我们业务代码中消息是一个Java实体,我们需要做的是将Java实体按照协议转换成IoBuffer进行发送。但是我们mina中发送消息是通过IoSession中write方法发送的。我们查看源码发现在IoSession.write(Object o),发送的如果是IoBuffer那么就不经过我们的编码器,否则会经过我们编码器进行编码最终将转换后的IoBuffer发送出去。


public class MessageEncoder extends ProtocolEncoderAdapter {
@Override
public void encode(IoSession ioSession, Object o, ProtocolEncoderOutput protocolEncoderOutput) throws Exception {
//TODO 根据协议编码
//组装好之后 ioSession.write(IoBuffer)写出
System.out.println(o);
}
}

总结

加入战队

# 加入战队

微信公众号

Mina实现Socket通信完整过程的更多相关文章

  1. TCP Socket通信详细过程

    下面这篇文章是参考"骏马金龙"博客中 不可不知的socket和TCP连接过程 https://www.cnblogs.com/f-ck-need-u/p/7623252.html ...

  2. 简单的Socket通信

    Socket简介 Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求. 服务端步骤: • socket:创建服务器socket ...

  3. AgileEAS.NET SOA 中间件平台.Net Socket通信框架-完整应用例子-在线聊天室系统-下载配置

    一.AgileEAS.NET SOA中间件Socket/Tcp框架介绍 在文章AgileEAS.NET SOA 中间件平台Socket/Tcp通信框架介绍一文之中我们对AgileEAS.NET SOA ...

  4. java netty socket库和自定义C#socket库利用protobuf进行通信完整实例

    之前的文章讲述了socket通信的一些基本知识,已经本人自定义的C#版本的socket.和java netty 库的二次封装,但是没有真正的发表测试用例. 本文只是为了讲解利用protobuf 进行C ...

  5. Linux 下socket通信终极指南(附TCP、UDP完整代码)

    linux下用socket通信,有TCP.UDP两种协议,网上的很多教程把两个混在了一起,或者只讲其中一种.现在我把自己这两天研究的成果汇总下来,写了一个完整的,适合初学者参考,也方便自己以后查阅. ...

  6. Flex通信-与Java实现Socket通信实例

    Flex通信-与Java实现Socket通信实例  转自:http://blessht.iteye.com/blog/1136888 博客分类: Flex 环境准备 [服务器端] JDK1.6,“ja ...

  7. 线程间使用socket通信的计算器

    该程序是处理平时的算数运算,程序也没有什么特别之处,只是将所有运算分开运算,每个函数(线程)处理不同的运算符号里面的运算,然后将所有结果都汇总到main函数中进行最后汇总(相加减)运算,每个函数内都处 ...

  8. 多线程方式实现Socket通信

    一.首先,介绍下两类传输协议:TCP:UDP TCP是Tranfer Control Protocol的 简称,是一种面向连接的保证可靠传输的协议.通过TCP协议传输,得到的是一个顺序的无差错的数据流 ...

  9. Java与C之间的socket通信

    最近正在开发一个基于指纹的音乐检索应用,算法部分已经完成,所以尝试做一个Android App.Android与服务器通信通常采用HTTP通信方式和Socket通信方式.由于对web服务器编程了解较少 ...

随机推荐

  1. vector是序列式容器而set是关联式容器。set包含0个或多个不重复不排序的元素。

    1.vector是序列式容器而set是关联式容器.set包含0个或多个不重复不排序的元素.也就是说set能够保证它里面所有的元素都是不重复的.另外对set容器进行插入时可以指定插入位置或者不指定插入位 ...

  2. UVA514 铁轨 Rails:题解

    题目链接:https://www.luogu.org/problemnew/show/UVA514 分析: 入站序列是1-n,入站后判断如果等于出站序列的当前值,则直接出站.否则就在栈里待着不动.模拟 ...

  3. 庖丁解牛Linux内核分析 0x00:《庖丁解牛》

    庖丁解牛  吾生也有涯,而知也无涯 .以有涯随无涯,殆已!已而为知者,殆而已矣!为善无近名,为恶无近刑.缘督以为经,可以保身,可以全生,可以养亲,可以尽年. 庖丁为文惠君解牛,手之所触,肩之所倚,足之 ...

  4. 简单web网页与SSM后台交互

    简单web网页与SSM后台交互 情况说明 如今,已经搭建好SSM后台开发环境,并且可以经由postman工具测试成功.现在尝试写出web前端网页,通过实现简单的提交.注册.查询功能来加深对前后端数据传 ...

  5. [mysql终极优化]之主从复制与读写分离详细设置教程

    读写分离与主从复制是提升mysql性能的重要及必要手段,大中型管理系统或网站必用之. 一.什么是读写分离与主从复制 先看图 如上图所示,当web server1/2/3要写入数据时,则向mysql d ...

  6. idea新建javaweb工程

    最近尝试了idea的使用,将idea建立javaweb工程的步骤记录下来 1.方框里边是重点 2.next后输入工程文件名点击finish 3.如图看到项目文件夹里边没有WEB-INF文件夹及里边的w ...

  7. Consul和Kong的实践(一)

    Consul和Kong的实践(一) 这一篇先介绍consul集群,以及consul和应用服务的结合使用,下一篇是和kong网关的结合. 一.Consul的集群安装 以其中一台机器为例: mkdir / ...

  8. 【JDK】JDK源码分析-AbstractQueuedSynchronizer(1)

    概述 前文「JDK源码分析-Lock&Condition」简要分析了 Lock 接口,它在 JDK 中的实现类主要是 ReentrantLock (可译为“重入锁”).ReentrantLoc ...

  9. 【Java例题】7.2 线程题2-随机数求和线程

    2.随机数求和线程.设计一个线程子类,产生10000个随机数,并求和,显示和的结果:然后编写主类,在主函数中定义一个线程对象,并启动这个线程. package chapter7; public cla ...

  10. 【游记】NOIP2018复赛

    声明 我的游记是一个完整的体系,如果没有阅读过往届文章,阅读可能会受到障碍. ~~~上一篇游记的传送门~~~ 前言 参加完NOIP2018的初赛过后,我有点自信心爆棚,并比之前更重视了一点(也仅仅是一 ...