1.配置环境
添加 log4j.properties 、jar包
log4j配置文件:
log4j.rootLogger=DEBUG,MINA,file
log4j.appender.MINA=org.apache.log4j.ConsoleAppender
log4j.appender.MINA.layout=org.apache.log4j.PatternLayout
log4j.appender.MINA.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss,SSS} %-5p %c{1} %x - %m%n
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/minademos.log
log4j.appender.file.MaxFileSize=5120KB
log4j.appender.file.MaxBackupIndex=10
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[VAMS][%d] %p | %m | [%t] %C.%M(%L)%n
2.入门实例
服务端代码:
public class Demo1Server {
private static Logger logger = Logger.getLogger(Demo1Server.class);
private static int PORT = 3005;
public static void main(String[] args) {
IoAcceptor acceptor = null; // 创建连接
try {
// 创建一个非阻塞的server端的Socket
acceptor = new NioSocketAcceptor();
// 设置过滤器(使用Mina提供的文本换行符编解码器)
acceptor.getFilterChain().addLast( //添加消息过滤器
"codec",
new ProtocolCodecFilter(new TextLineCodecFactory(Charset
.forName("UTF-8"),
LineDelimiter.WINDOWS.getValue(),
LineDelimiter.WINDOWS.getValue())));
// 设置读取数据的缓冲区大小
acceptor.getSessionConfig().setReadBufferSize(2048);
// 读写通道10秒内无操作进入空闲状态
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
// 绑定逻辑处理器
acceptor.setHandler(new Demo1ServerHandler()); // 添加业务处理
// 绑定端口
acceptor.bind(new InetSocketAddress(PORT));
logger.info("服务端启动成功... 端口号为:" + PORT);
} catch (Exception e) {
logger.error("服务端启动异常....", e);
e.printStackTrace();
}
}
}
注意:创建服务端最主要的就是绑定服务端的消息编码解码过滤器和业务逻辑处理器;
什么是编码与解码哪?大家知道,网络传输的数据都是二进制数据,而我们的程序不可能直接去操作二进制数据;这时候我们就需要来把接收到的字节数组转换为字符串,当然完全可以转换为任何一个java基本数据类型或对象,这就是解码!而编码恰好相反,就是把要传输的字符串转换为字节;编码是在发送消息时触发的。
上面使用是Mina自带的根据文本换行符编解码的TextLineCodec过滤器------ 指定参数为根据windows的换行符编解码,遇到客户端发送来的消息,看到windows换行符(\r\n)就认为是一个完整消息的结束符了;而发送给客户端的消息,都会在消息末尾添加上(\r\n)文本换行符;
服务端处理器代码:
public class Demo1ServerHandler extends IoHandlerAdapter {
public static Logger logger = Logger.getLogger(Demo1ServerHandler.class);
@Override
public void sessionCreated(IoSession session) throws Exception {
logger.info("服务端与客户端创建连接...");
}
@Override
public void sessionOpened(IoSession session) throws Exception {
logger.info("服务端与客户端连接打开...");
}
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
String msg = message.toString();
logger.info("服务端接收到的数据为:" + msg);
if ("bye".equals(msg)) { // 服务端断开连接的条件
session.close();
}
Date date = new Date();
session.write(date);
}
@Override
public void messageSent(IoSession session, Object message) throws Exception {
logger.info("服务端发送信息成功...");
}
@Override
public void sessionClosed(IoSession session) throws Exception {
}
@Override
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
logger.info("服务端进入空闲状态...");
}
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
logger.error("服务端发送异常...", cause);
}
}
自定义的业务逻辑处理器继承了IoHandlerAdapter类,它默认覆盖了父类的7个方法,其实我们最关心最常用的只有一个方法:messageReceived() ---- 服务端接收到一个消息后进行业务处理的方法;
接收并打印客户端信息,返回给客户端一个日期字符串;如果客户端传递的消息为“bye”,就是客户端告诉服务端,可以终止通话了,关闭与客户端的连接。
启动服务端:
2019-10-21 11:07:56,052 INFO Demo1Server - 服务端启动成功... 端口号为:3005
使用telnet测试服务端连接:
服务端日志:
2019-10-21 11:07:56,052 INFO Demo1Server - 服务端启动成功... 端口号为:3005
2019-10-21 11:10:04,349 INFO Demo1ServerHandler - 服务端与客户端创建连接...
2019-10-21 11:10:04,351 INFO Demo1ServerHandler - 服务端与客户端连接打开...
2019-10-21 11:10:06,686 INFO Demo1ServerHandler - 服务端接收到的数据为:
2019-10-21 11:10:06,694 INFO Demo1ServerHandler - 服务端发送信息成功...
2019-10-21 11:10:16,702 INFO Demo1ServerHandler - 服务端进入空闲状态...
2019-10-21 11:10:21,377 INFO Demo1ServerHandler - 服务端接收到的数据为:hello
2019-10-21 11:10:21,378 INFO Demo1ServerHandler - 服务端发送信息成功...
2019-10-21 11:10:25,228 INFO Demo1ServerHandler - 服务端接收到的数据为:world
2019-10-21 11:10:25,229 INFO Demo1ServerHandler - 服务端发送信息成功...
2019-10-21 11:10:35,236 INFO Demo1ServerHandler - 服务端进入空闲状态...
2019-10-21 11:13:43,590 INFO Demo1ServerHandler - 服务端接收到的数据为:bye
这个就是用Mina实现的服务端程序啦。功能很简单:服务端一直监听3005端口,如果有客户端连接上服务端并发送信息,服务端解析信息(以文本换行符为每条信息的结束符),并返回给服务端一个日期时间。
Mina的底层通信无疑是用socket实现的,它封装后提供给我们一个简单易用的接口。
客户端代码:
public class MinaClient01 {
private static Logger logger = Logger.getLogger(MinaClient01.class);
private static String HOST = "127.0.0.1";
private static int PORT = 3005;
public static void main(String[] args) {
// 创建一个非阻塞的客户端程序
IoConnector connector = new NioSocketConnector(); // 创建连接
// 设置链接超时时间
connector.setConnectTimeout(30000);
// 添加过滤器
connector.getFilterChain().addLast( //添加消息过滤器
"codec",
new ProtocolCodecFilter(new TextLineCodecFactory(Charset
.forName("UTF-8"), LineDelimiter.WINDOWS.getValue(),
LineDelimiter.WINDOWS.getValue())));
// 添加业务逻辑处理器类
connector.setHandler(new Demo1ClientHandler());// 添加业务处理
IoSession session = null;
try {
ConnectFuture future = connector.connect(new InetSocketAddress(
HOST, PORT));// 创建连接
future.awaitUninterruptibly();// 等待连接创建完成
session = future.getSession();// 获得session
session.write("我爱你mina");// 发送消息
} catch (Exception e) {
logger.error("客户端链接异常...", e);
}
session.getCloseFuture().awaitUninterruptibly();// 等待连接断开
connector.dispose();
}
}
客户端处理器代码:
public class Demo1ClientHandler extends IoHandlerAdapter {
private static Logger logger = Logger.getLogger(Demo1ClientHandler.class);
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
String msg = message.toString();
logger.info("客户端接收到的信息为:" + msg);
}
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
logger.error("客户端发生异常...", cause);
}
}
3.长连接和短连接
我们知道,java应用程序的入口是main()方法,启动一个main()方法相当于开始运行一个java应用程序,此时会运行一个Java虚拟机,操作系统中会启动一个进程,就是刚刚看到的“javaw.exe”。也就是每启动一个Java应用程序就是多出一个Java进程。
长连接的现象在网络中非常普遍,比如我们的QQ客户端程序,登录成功后与腾讯的服务器建立的就是长连接;除非主动关闭掉QQ客户端,或者是QQ服务端挂了,才会断开连接;看我们的服务端程序,就有关闭连接的条件:如果客户端发送信息“bye”,服务端就会主动断开连接!
与长连接相对应的是短连接,比如常说的请求/响应模式(HTTP协议就是典型的请求/响应模式)-----客户端向服务端发送一个请求,建立连接后,服务端处理并响应成功,此时就主动断开连接了!
短连接是一个简单而有效的处理方式,也是应用最广的。Mina是Java NIO实现的应用框架,更倾向于短连接的服务;问题是哪一方先断开连接呢?可以在服务端,也可以在客户端,但是提倡在服务端主动断开;
Mina的服务端业务逻辑处理类中有一个方法messageSent,他是在服务端发送信息成功后调用的:
@Override
public void messageSent(IoSession session, Object message) throws Exception {
//logger.info("服务端发送信息成功...");
session.close(); //发送成功后主动断开与客户端的连接
}
这时候客户端与服务端就是典型的短连接了;再次测试,会发现客户端发送请求,接收成功后就自动关闭了,进程只剩下服务端了!
到此为止,我们已经可以运行一个完整的基于TCP/IP协议的应用程序啦!
总结:
服务端程序或客户端程序创建过程:
创建连接-->添加消息过滤器(编码解码等)-->添加业务处理