作者:老九—技术大黍
社交:知乎
公众号:老九学堂(新人有惊喜)
特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系笔者授权
前言
如果要使用Java语言来开发游戏服务器,那么Mina框架是当之无愧的首先。
什么是Mina
Mina是一个开源的网络应用框架,这种框架帮助应用程序员便捷的开始出高性能、高并发处理的网络应用。Mina框架使用Java NI封装了TCP/IP和UDP/IP协议,封装结果表现基于事件的异步API给应用程序员使用。
因此,Mina又被叫做:NIO框架库、C/S框架库或者网络套接字库。
基于mina框架有扩展的服务器:Asyncweb、FtpServer和SSHd服务器。
Mina的功能
Mina提供了常用如下功能:
- 为各种传输类型的提供统一的API接口
- 使用Java NIO技术的TCP/IP和UDP/IP
- 使用RXTX的串行化通讯(RS232)
- In-VM管道通讯
- 自定义的实现为各种传输类型的提供统一的API接口
- 类似Servlet技术过滤器的Fitler接口
- 低阶和高阶API
- 使用ByteBuffers低阶API
- 使用自定义消息对象的高阶API
- 高级自定义线程模型
- 单线程
- 单个线程池
- 多个线程池
- 通过StreamIoHandler支持基本的IO流
服务端代码演示
package com.mr.game.server.app;
import java.util.Date;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
public class TimeServerHandler extends IoHandlerAdapter{
public void exceptionCaught( IoSession session, Throwable cause ) throws Exception
{
cause.printStackTrace();
}
public void messageReceived( IoSession session, Object message ) throws Exception
{
String str = message.toString();
if( str.trim().equalsIgnoreCase("quit") ) {
session.close();
return;
}
Date date = new Date();
String messge = "Hello(中文), today is " + date.toString();
session.write("Your request: " + str);
System.out.println("Message written..." + str);
}
public void sessionIdle( IoSession session, IdleStatus status ) throws Exception
{
System.out.println( "IDLE " + session.getIdleCount( status ));
}
public void sessionOpened(IoSession session) throws Exception{
//none
}
}
复制代码
package com.mr.game.server.app;
import java.io.IOException;
import java.nio.charset.Charset;
import java.net.InetSocketAddress;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.apache.mina.core.session.IdleStatus;
/**
* 功能:这是第一个Mina服务端应用示例
* 作者:技术大黍
*
*/
public class App
{
private static final int PORT = 9123;
public static void main( String[] args )
{
//定义一个接收器
IoAcceptor acceptor = new NioSocketAcceptor();
//添加日志
acceptor.getFilterChain().addLast("logger",new LoggingFilter());
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));
//处理客户端请求
acceptor.setHandler(new TimeServerHandler());
//设置通讯的字节流量单位
acceptor.getSessionConfig().setReadBufferSize(2048);
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
try{
//绑定端口
acceptor.bind(new InetSocketAddress(PORT));
System.out.println("The mina server has been started.");
}catch(IOException e){
e.printStackTrace();
}
}
}
复制代码
小结
- mina是一个封装Java NIO技术的框架
- mina实现了网络异步通讯的概念
- mina把网络的物理通讯与业务逻辑代码解耦合,方便了应用程序员编程
Mina框架原理
Mina架构
Mina的框架如下图所示:
Mina封装了TCP/UDP/串行化以及虚通道通讯等物理网络通讯机制,应用程序员只关注自己的业务逻辑即可。
- Mina分为三层结构
- I/O服务层–执行实际的I/O操作
- I/O过滤器链路层–过滤和转换字节为期望的数据结构
- I/O句柄处理层–在这层放实际的业务逻辑代码
- 创建一个最基本的Mina应用,必须做以下几步
- 创建I/O服务层–选择一个可以使用服务(Acceptor)或者自己创建服务
- 创建过滤器链–选择一个现存的过滤器,或者自己创建一个过滤器
- 创建一个I/O句柄对象–写业务逻辑,处理不同的消息
服务端架构
服务端架构
服务端在一个端口监听网络请求,处理来自网络的消息,响应这些网络请求。mina使用Session来处理客户端(基于TCP和UDP协议的客户端),IOAcceptor类监听网络连接和数据包。有一个新连接,那么mina会个新的session对象,从session获取的数据包都是穿过指定的过滤器链。因此,过滤器链可修改数据包。比如,把字节信息转换成高阶对象使用,打包和解码特定的数据包等。最后打包/转换对象到IOHandler中,因此IOHandler用来填充业务逻辑代码。
小结
- mina框架是一个封装各种物理底层通讯机制,让应用程序员关注业务逻辑
- mina框架分为三层:IO服务层、过滤器链路层和句柄处理层
- IOAcceptor对象封装了网络物理链接
- 服务对象处理的结果是产生一个Sessions对象
- 继承IoHandlerAdapter实现业务逻辑
IoService服务层
IoService服务
IoService设计用来抽象服务端网络链接,而IoConnector接口用来抽象客户端链接。
IoService接口提供了基本的I/O服务,并且管理I/O的Session功能。它是mina框架中最关键的部分。
IoService提供以下功能
- session管理:创建和删除session,以及侦测闲置连接
- 过滤器链管理:处理过滤器,允许用户任意修改链
- 句柄调用:当有接收到消息时调用处理句柄(基于事件驱动概念)
- 统计管理:实时更新消息发送的数量、字节发送数量等
- 监听管理:监听服务以便客户端可以连接
- 通讯管理:处理通讯双方的数据传输
核心接口方法
IoService详解
IoService对象实现两个最重要的类:IoAcceptor和IoConnector接口。
服务端构建一个服务需要实现一个IoAcceptor接口的实现类。客户端需要实现IoConnector的实现类。
IoAcceptor接口定义一个业务逻辑方法accept()用来负责创建一个新的连接。服务端接收网络连接请求。
mina对象各种类型的物理网络连接,提供了如下的四种IoAcceptor实现类。
IoService对象实现两个最重要的类:IoAcceptor和IoConnector接口
服务端构建一个服务需要实现一个IoAcceptor接口的实现类。客户端需要实现IoConnector的实现类
IoAcceptor接口定义一个业务逻辑方法accept()用来负责创建一个新的连接。服务端接收网络连接请求
mina对象各种类型的物理网络连接,提供了如下的四种IoAcceptor实现类
小结
- IoService是mina的核心API
- IoService封装了各种物理网络连接,提供了四种IoService实现的工具类:NioSocketAcceptor 、NioDatagramAcceptor 、AprSocketAcceptor 、VmPipeSocketAcceptor
- IoService主要提供session管理、过滤器管理、句柄处理(消息/事件处理)和通讯管理等功能
Session、过滤器和事件处理
Session的状态
Session是mina框架的核心。每次一个新客户端连接到服务器,那么会创建一个新的session对象,然后保存在内存到客户端连接。
session有三种状态:
- 连接
- 闲置
- 关闭
session用来保存连接的持久化信息,以及额外的服务器信息。这些信息是服务器处理客户端请求时需要的信息,并且持续整个session的生命周期
session在它的生命周期中有如下状态:
- Connected: 一个session被创建并且可以被使用
- Idle: 一个不处理任何请求
- Idle for read: 持续没有从session读的闲置时间
- Idle for write: 持续没有向session写的闲置时间
- Idle for both: 即没有读也没有写的闲置时间
- Closing: 一个session正在关闭的状态
- Closed: 一个session已经被关闭了的状态
Session配置
使用一个Session时需要配置,对session有两种配置:
- 标准配置
- 接收buffer的大小
- 发送buffer的大小
- 闲置时间
- 写超时时间
- 自定义配置,除了以上标准配置可能还有额外信息需要知道。比如,需要知道已经连接了多少个连接的客户端数量。
session是一个实现Map接口的实现类,使用键/值的方式来保存值。当一个session被创建之后,有一个容器也会自动被创建
不要在session保存过大的信息,或者生命周期很长的数据结构内容。也就是,被保存的对象的生命周期不能大于session的生命周期
每个session会关联一个过滤器链,这些链会在网络连接请求和消息读写过程处理
Session的默认功能
每个session会保存以下记录信息
- 网络发送和接收的字节数量
- 网络发送和接收的消息数量
- 闲置状态
- 吞吐量
- 其它有用信息
句柄(事件)处理,一个session至少会关联一个句柄(事件)处理。这个句柄(事件)主要负责向应用分发消息。同时,句柄也负责通过session发送响应的数据包–使用write()方法即可
session.write(<消息内容>);
了解默认的过滤器
重写事件
我们继承IoAdapter而不是直接实现IoFilter接口时,需要重写相应的事件处理方法。
常用事件
小结
- session是mina框架的核心每个客户端连接会有一个session对象对应
- 与客户端通讯是通过session的read和write方法来实现
- session有三种主要状态:连接、闲置和关闭状态
- 一个session关联一个过滤器链,过滤器实现事件处理机制
- 过滤器有7个主要事件:
- sessionCreate
- sessionOpened
- sessionClosed
- sessionIdle
- exceptionCaught
- messageReceived
- messageSent
Mina核心API讲解
为什么使用IoBuffer
- IoBuffer是Mina应用使用字节buffer对象。它用来替换NIO包中的ByteBuffer类。
- 不使用ByteBuffer有两个理由
- 不提供getter和setter方法
- 对于可变长度的数据很处理
- IoBuffer是一个抽象类,因此不能直接实例化,需要实现allocate()方法来实例化
设置和使用buffer
设置buffer一般有两种方式
- 默认分配为堆buffer– IoBuffer.setUseDirectBuffer(false);
- 返回一个堆buffer — IoBuffer buf = IoBuffer.allocate(1024);
创建自动可扩展buffer
- IoBuffer buffer = IoBuffer.allocate(8);
- buffer.setAutoExpand(true);
- buffer.putString(“12345678”, encoder);
- buffer.put((byte)10); // Add more to this buffer
创建可伸缩buffer
- IoBuffer buffer = IoBuffer.allocate(16);
- buffer.setAutoShrink(true);
- buffer.put((byte)1);
- System.out.println(“Initial Buffer capacity = “+buffer.capacity());
- buffer.shrink();
- System.out.println(“Initial Buffer capacity after shrink = “+buffer.capacity());
- buffer.capacity(32);
- System.out.println(“Buffer capacity after incrementing capacity to 32 = “+buffer.capacity());
- buffer.shrink();
- System.out.println(“Buffer capacity after shrink= “+buffer.capacity());
掌握ProtocolCodecFilter
TCP保证正确的发送数据包,但是不保证发送方的写操作能够在接收方正确的读取。
在Mina术语中,如果没有使用 ProtocolCodecFilter而进行IoSession.write(消息)操作,会导致接收产生多个messageReceive事件发生,以及多次IoSession.write(消息)操作会导致单个messageReceived事件发生。这种如果服务端和客户端在同一台机器上不会发生,但是在不同机器上一定会发生。
大多数和的网络应用都需要当前消息结束之后,才能进行下一个消息的操作。
应用程序员在可以在IoHander类中实现以上所有逻辑,但是如果使用了ProtocolCodecFilter对象,那么可以我们编程更加便捷。使用ProtocolCodecFilter对象,可以实现把网络协议逻辑与我们在IoHandler对象中的业务逻辑解耦合。因为,我们的客户端是针对C++的,所以,传输的消息都文本字符串作为载体,因此,我们在服务端只需要这样使用即可。
小结
- IoBuffer实现了可伸缩的buffer功能
- ProtocolCodecFilter简化了消息编码和解码方式
最后
记得给大黍❤️关注+点赞+收藏+评论+转发❤️
作者:老九学堂—技术大黍
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。