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協議的應用程序啦!
總結:
服務端程序或客戶端程序創建過程:
創建連接-->添加消息過濾器(編碼解碼等)-->添加業務處理