版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/specialshoot/article/details/50684912
上回说道,socket是基本的网络通讯方式,然而,真正的工程考虑到高并发性、高可靠性等要求,基本的socket是肯定无法用到项目上的。好在Apache开发出了一套高性能和高可用性的网络应用程序框架,这个框架就是大名鼎鼎的Mina框架。
废话不多说,首先Mina的jar包大家可以从http://mina.apache.org/downloads-mina.html上下载最新版本,我用的是2.0.9版本,截止到我写这篇博客的时候,最新的已经是2.0.12版本了。Binaries是不带源码的版本,普通项目用的话,下载这个就行,如果大家要看下mina的源码,可以下载sources带源码的版本。下载完后解压,找到dist文件夹,mina-core-版本号.jar这个jar包是我们需要包含到项目中去的。此外,lib文件夹下的slf4j的jar包也是我们这个代码所需要的(用于日志)。至于jar包怎么放到项目中相信不需要我说大家已经了如指掌了,这里就不细说了。
服务器端
使用Mina最基本的功能仅需要四步:
1.创建NioSocketAcceptor对象。
2.设置Handler,传入一个专门处理消息收发的handler,这个handler要继承IoHandlerAdapter。
3.设置过滤器,可以是自定义过滤器,也可以是默认的,这里我们设置默认的文本过滤器,因为我们现在仅仅是传递文本,至于自定义过滤器的写法,后面我会说道。
4.绑定端口号
NioSocketAcceptor对象也可以设置一些属性比如设置服务器空闲时长等,多用于心跳机制,做推送的时候会用到这个。
服务器端代码:
MinaServer.java:
package com.imooc.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
public class MinaServer {
public static void main(String[] args) {
try {
NioSocketAcceptor acceptor = new NioSocketAcceptor();
acceptor.setHandler(new MyServerHandler());
//添加mina过滤器,getFilterChain()可以得到所有过滤器
//addlast添加一个拦截器,第一个参数是拦截器名字,自己起一个,第二个参数是拦截器
//ProtocolCodecFilter是用于二进制数据与对象转换,参数是ProtocolCodecFactory
//可以传入默认的参数比如TextLineFactory,内置好的,专门读取文本的factory
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));
//这里我们传入自定义factory
//acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MyTextLineFactory()));
//setIdleTime(IdleStatus,arg1)设置服务器空闲时长
//IdelStatus可选三种参数,BOTH_IDEL,READER_IDLE,WRITER_IDLE,分别代表发送接收均超过时长没有响应,接收超过时长没有响应,发送超过时长没有响应
//第二个是时长,单位是秒
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 5);
acceptor.bind(new InetSocketAddress(9898));
} catch (IOException e) {
e.printStackTrace();
}
}
}
IoHandlerAdapter是用于处理消息收发的,其中有一些方法需要复写
messageReceived:当收到消息后进入此方法
messageSent:发送消息时进入此方法
sessionClosed:客户端与服务器端断开连接进入此方法
sessionCreated:
客户端与服务器端建立连接进入此方法
sessionIdle:会话进入空闲时进入此方法
sessionOpened:会话打开时进入此方法(这个方法会在sessionCreated执行后执行)
下面代码中,我们仅仅在收到消息后,把消息再发送给客户端,用write方法。
MyServerHandler.java(继承IoHandlerAdapter):
package com.imooc.server;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
public class MyServerHandler extends IoHandlerAdapter {
//网络连接出现异常
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
System.out.println("exceptionCaught");
}
//收到消息进入此方法
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
String s = (String) message;
System.out.println("messageReceived: " + s);
session.write("server reply: " + s); //返回消息给客户端
}
//发送消息会进入此方法
@Override
public void messageSent(IoSession session, Object message) throws Exception {
System.out.println("messageSent");
}
//客户端与服务器会话断开连接时进入此方法
@Override
public void sessionClosed(IoSession session) throws Exception {
System.out.println("sessionClosed");
}
//客户端与服务器会话创建时进入此方法
@Override
public void sessionCreated(IoSession session) throws Exception {
System.out.println("sessionCreated");
}
//会话进入空闲状态进入此方法
@Override
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
System.out.println("sessionIdle");
}
//会话打开进入此方法
@Override
public void sessionOpened(IoSession session) throws Exception {
System.out.println("sessionOpened");
}
}
为了了解过滤器的原理及方便以后自定义比如解析xml,json等文件,下面我们自己写一个自定义过滤器
我们需要创建加解密的对象,MyTextLineDecoder和MyTextLineEncoder,至于MyTextLineCumulativeDecoder是另外一种解码器,我们稍后再说。
继承ProtocolCodecFactory,我们需要实现两个方法,getDecoder和getEncoder分别得到解码器和编码器
我们创建一个MyTextLineFactory.java
package com.imooc.server;
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;
public class MyTextLineFactory implements ProtocolCodecFactory {
private MyTextLineDecoder mDecoder;
private MyTextLineCumulativeDecoder mCumulativeDecoder;
private MyTextLineEncoder mEncoder;
public MyTextLineFactory () {
mDecoder = new MyTextLineDecoder();
mEncoder = new MyTextLineEncoder();
mCumulativeDecoder = new MyTextLineCumulativeDecoder();
}
//解密
@Override
public ProtocolDecoder getDecoder(IoSession arg0) throws Exception {
//return mCumulativeDecoder;
return mDecoder;
}
//加密
@Override
public ProtocolEncoder getEncoder(IoSession arg0) throws Exception {
return mEncoder;
}
}
MyTextLineDecoder类继承ProtocolDecoder.
decode方法是解码方法,dispose方法用于在销毁编码器时释放关联资源,finishDecode用于处理在IoSession关闭时剩余的读取数据。这里我们尽在decode中添加我们的代码。详细的ProtocolDecoder讲解参看博客http://www.himigame.com/apache-mina/839.html
MyTextLineDecoder.java:
package com.imooc.server;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
public class MyTextLineDecoder implements ProtocolDecoder {
@Override
public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)
throws Exception {
int startPosition = in.position(); //开始读取的位置
while (in.hasRemaining()) { //当iobuffer中还有字节可以读取时
byte b = in.get(); //读字节
if (b == '\n') {
int currentPositoin = in.position(); //记录当前读取的位置
int limit = in.limit(); //当前总长度
in.position(startPosition); //定向到起始位置
in.limit(currentPositoin); //终点为当前position
IoBuffer buf = in.slice(); //slice截取从起始位置到终点位置的字节
byte [] dest = new byte[buf.limit()];
buf.get(dest);
String str = new String(dest);
out.write(str);
in.position(currentPositoin); //重定向,还原到当前position
in.limit(limit); //还原到原来的limit
}
}
}
@Override
public void dispose(IoSession arg0) throws Exception {
}
@Override
public void finishDecode(IoSession arg0, ProtocolDecoderOutput arg1)
throws Exception {
}
}
关于Mina的IoBuffer的使用,参见 http://blog.csdn.net/u012841509/article/details/17259025和 http://www.cnblogs.com/sunwei2012/archive/2010/08/27/1810089.html。
这里面的操作是将每行的文字提取出来并输出。
Encoder加密就比较简单了,我们发送出去的内容都要encode一下,代码如下:
MyTextLineEncoder.java:
package com.imooc.server;
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.ProtocolEncoder;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
public class MyTextLineEncoder implements ProtocolEncoder {
@Override
public void dispose(IoSession arg0) throws Exception {
}
@Override
public void encode(IoSession session, Object message, ProtocolEncoderOutput out)
throws Exception {
String s =null;
if (message instanceof String) {
//判断是否message是否为字符串
s = (String) message;
}
if (s != null) {
//转码操作
CharsetEncoder charsetEndoer = (CharsetEncoder)session.getAttribute("encoder");
if (charsetEndoer == null) {
charsetEndoer = Charset.defaultCharset().newEncoder();
session.setAttribute("encoder", charsetEndoer);
}
IoBuffer ioBuffer = IoBuffer.allocate(s.length()); //开辟内存
ioBuffer.setAutoExpand(true); //设置内存自动扩展
ioBuffer.putString(s, charsetEndoer); //将字符串放入ioBuffer中
ioBuffer.flip();
out.write(ioBuffer);
}
}
}
这样我们的编解码就写完了。
下面我们来测试服务器端,用上一节课的SocketClient客户端给服务器端发消息。我们SocketClient中上节中提到有如下代码用于测试:
if (count % 2 ==0) {
writer.write("\n");
}
count++;
这里就可以说明此代码测试的意义,我让客户端先发送一个带换行符的字符串,然后发送不带换行符的字符串,然后再发不带换行符号,运行前注意将MinaServer.java中acceptor.getFilterChain().addLast里面改为自定义的MyTextLineFactory(我在源码中注释掉的,把注释去了然后把用默认Factory的那句给注释上)。让我们来看看运行效果:
客户端发送
服务器接收
大家可以很明显的看出问题,当客户端没有发送换行符的时候,服务器肯定没有响应,因为没有换行符作为结束,这个是没有问题的,然而后面有换行符的时候也就是输入bb后,服务器应该接收的是aabb,但是显示出来的只有bb,这就是数据丢失问题。我们怎么解决它呢?
这时候就用到了我们刚才没有介绍的CumulativeProtocolDecoder,这个类是Mina专门处理包和粘包的。下面看代码:
MyTextLineCumulativeDecoder.java
package com.imooc.server;
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;
//MyTextLineCumulativeDecoder与MyTextLineDecoder差不多,这里返回一个boolean类型,用此Decoder可以防止数据丢失
public class MyTextLineCumulativeDecoder extends CumulativeProtocolDecoder {
@Override
protected boolean doDecode(IoSession session, IoBuffer in,
ProtocolDecoderOutput out) throws Exception {
int startPosition = in.position();
while (in.hasRemaining()) {
byte b = in.get();
if (b == '\n') {
int currentPositoin = in.position();
int limit = in.limit();
in.position(startPosition);
in.limit(currentPositoin);
IoBuffer buf = in.slice();
byte [] dest = new byte[buf.limit()];
buf.get(dest);
String str = new String(dest);
out.write(str);
in.position(currentPositoin);
in.limit(limit);
return true;
}
}
in.position(startPosition); //如果没有\n换行符,保持position在初始位置
return false;
}
}
这里仅仅重写doDecode即可,代码整体与MyTextLineDecoder.java基本相同。有换行符的时候返回true,没有换行符的时候将起始位置设置为startPosition然后返回false即可。
下面运行测试,注意把MyTextLineFactory中getDecoder方法返回mCumulativeDecoder。
这次是正确的输出了。
客户端
服务器端我们讲解完了,下面讲解Mina客户端,这部分不是androidpn项目必须的,还是说明一下做个记录:
MinaClient.java:
package com.imooc.client;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
public class MinaClient {
public static void main(String[] args) throws Exception{
NioSocketConnector connector = new NioSocketConnector();
connector.setHandler(new MyClientHandler());
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));
ConnectFuture future = connector.connect(new InetSocketAddress("127.0.0.1", 9898)); //connect方法返回一个ConnectFuture对象
future.awaitUninterruptibly(); //阻塞住直到连接建立成功
IoSession session = future.getSession(); //得到session,得到session下面就可以写数据了,session.write
BufferedReader inputReader = new BufferedReader(new InputStreamReader(System.in));
String inputContent;
while (!(inputContent = inputReader.readLine()).equals("bye")) {
session.write(inputContent);
}
}
}
MyClientHandler.java:
package com.imooc.client;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
public class MyClientHandler extends IoHandlerAdapter {
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
System.out.println("exceptionCaught");
}
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
String s = (String) message;
System.out.println("messageReceived: " + s);
}
@Override
public void messageSent(IoSession session, Object message) throws Exception {
System.out.println("messageSent");
}
@Override
public void sessionClosed(IoSession session) throws Exception {
System.out.println("sessionClosed");
}
@Override
public void sessionCreated(IoSession session) throws Exception {
System.out.println("sessionCreated");
}
@Override
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
System.out.println("sessionIdle");
}
@Override
public void sessionOpened(IoSession session) throws Exception {
System.out.println("sessionOpened");
}
}
下一篇文章记录一下关于xmpp协议的有关内容。
Socket及Mina的源码:
http://download.csdn.net/detail/specialshoot/9435230