This is a Web Project,本文代码支持绑定多个端口....
首先是Web.xml文件
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext*.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
下面是用于模拟短信协议的实体类
package com.jadyer.model; /** * 模拟短信协议内容的对象 * @see M sip:wap.fetion.com.cn SIP-C/2.0 //状态行,一般表示协议的名字、版本号等 * @see S: 1580101xxxx //短信的发送号码 * @see R: 1880202xxxx //短信的接收号码 * @see L: 21 //短信的字节数 * @see 你好!!Hello World!! //短信的内容 * @see 上面每行的末尾使用ASCII的10(\n)作为换行符 */ public class SmsObject { private String sender; //短信发送者 private String receiver; //短信接收者 private String message; //短信内容 /*三个属性的getter和setter略*/ }
下面是服务端的消息处理器ServerHandler.java
package com.jadyer.handler; import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IoSession; import com.jadyer.model.SmsObject; public class ServerHandler extends IoHandlerAdapter { @Override public void messageReceived(IoSession session, Object message) throws Exception { SmsObject sms = (SmsObject)message; System.out.println("The message received from Client is [" + sms.getMessage() + "]"); SmsObject respSms = new SmsObject(); respSms.setSender("13716700602"); respSms.setReceiver("15025302990"); respSms.setMessage("ok,收到消息了小伙儿...."); session.write(respSms); } @Override public void sessionOpened(IoSession session) throws Exception{ System.out.println("InComing Client:" + session.getRemoteAddress()); } }
下面是我们自定义的编码器CmccSipcEncoder.java
package com.jadyer.codec; 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.ProtocolEncoderAdapter; import org.apache.mina.filter.codec.ProtocolEncoderOutput; import com.jadyer.model.SmsObject; public class CmccSipcEncoder extends ProtocolEncoderAdapter { private final String charset; private final CharsetEncoder charsetEncoder; public CmccSipcEncoder(String charset){ this.charset = charset; this.charsetEncoder = Charset.forName(charset).newEncoder(); } @Override public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { SmsObject sms = (SmsObject)message; IoBuffer buffer = IoBuffer.allocate(100).setAutoExpand(true); String statusLine = "M sip:wap.fetion.com.cn SIP-C/2.0"; String sender = sms.getSender(); String receiver = sms.getReceiver(); String smsContent = sms.getMessage(); buffer.putString(statusLine+'\n', charsetEncoder); buffer.putString("S: "+sender+'\n', charsetEncoder); buffer.putString("R: "+receiver+'\n', charsetEncoder); //使用String类与Byte[]类型之间的转换方法获得转为字节流后的字节数 buffer.putString("L: "+smsContent.getBytes(charset).length+'\n', charsetEncoder); buffer.putString(smsContent, charsetEncoder); buffer.flip(); out.write(buffer); } }
下面是我们自定义的解码器CmccSipcDecoder.java
package com.jadyer.codec; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; 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; import com.jadyer.model.SmsObject; public class CmccSipcDecoder extends CumulativeProtocolDecoder { private final CharsetDecoder charsetDecoder; public CmccSipcDecoder(String charset){ this.charsetDecoder = Charset.forName(charset).newDecoder(); } @Override protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { IoBuffer buffer = IoBuffer.allocate(100).setAutoExpand(true); int i = 1; //记录解析到了短信协议中的哪一行(\n) int matchCount = 0; //记录在当前行中读取到了哪一个字节 String statusLine="", sender="", receiver="", length="", sms=""; while(in.hasRemaining()){ byte b = in.get(); buffer.put(b); if(10==b && 5>i){ //10==b表示换行:该短信协议解码器使用\n(ASCII的10字符)作为分解点 matchCount++; if(1 == i){ buffer.flip(); //limit=position,position=0 statusLine = buffer.getString(matchCount, charsetDecoder); statusLine = statusLine.substring(0, statusLine.length()-1); //移除本行的最后一个换行符 matchCount = 0; //本行读取完毕,所以让matchCount=0 buffer.clear(); } if(2 == i){ buffer.flip(); sender = buffer.getString(matchCount, charsetDecoder); sender = sender.substring(0, sender.length()-1); matchCount = 0; buffer.clear(); } if(3 == i){ buffer.flip(); receiver = buffer.getString(matchCount, charsetDecoder); receiver = receiver.substring(0, receiver.length()-1); matchCount = 0; buffer.clear(); } if(4 == i){ buffer.flip(); length = buffer.getString(matchCount, charsetDecoder); length = length.substring(0, length.length()-1); matchCount = 0; buffer.clear(); } i++; }else if(5 == i){ matchCount++; if(Long.parseLong(length.split(": ")[1]) == matchCount){ buffer.flip(); sms = buffer.getString(matchCount, charsetDecoder); i++; break; } }else{ matchCount++; } } SmsObject smsObject = new SmsObject(); smsObject.setSender(sender.split(": ")[1]); smsObject.setReceiver(receiver.split(": ")[1]); smsObject.setMessage(sms); out.write(smsObject); return false; //告诉Mina:本次数据已全部读取完毕,故返回false } }
下面是位于src下的log4j.properties
log4j.rootLogger=DEBUG,CONSOLE #use ConsoleAppender for ConsoleOut log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Threshold=DEBUG log4j.appender.CONSOLE.Target=System.out log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=[%d{yyyyMMdd HH:mm:ss SSS}][%t][%C{1}.%M]%m%n
下面是核心的配置文件applicationContext-mina.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!-- 构造过滤器链 --> <bean id="ioFilterChainBuilder" class="org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder"> <property name="filters"> <map> <!-- 日志过滤器 --> <entry key="logging"> <bean class="org.apache.mina.filter.logging.LoggingFilter"/> </entry> <!-- 协议编解码过滤器 --> <entry key="codec"> <bean class="org.apache.mina.filter.codec.ProtocolCodecFilter"> <constructor-arg index="0"> <bean class="com.jadyer.codec.CmccSipcEncoder"> <constructor-arg value="UTF-8"/> </bean> </constructor-arg> <constructor-arg index="1"> <bean class="com.jadyer.codec.CmccSipcDecoder"> <constructor-arg value="UTF-8"/> </bean> </constructor-arg> </bean> </entry> </map> </property> </bean> <!-- 构造Server端 --> <bean id="myServer" class="com.jadyer.MinaStartup" init-method="bind"> <property name="reuseAddress" value="true"/> <property name="writeTimeout" value="10000"/> <property name="bothIdleTime" value="20"/> <property name="filterChainBuilder" ref="ioFilterChainBuilder"/> <property name="handler"> <bean class="com.jadyer.handler.ServerHandler"/> </property> <property name="socketAddresses"> <list> <bean class="java.net.InetSocketAddress"> <constructor-arg index="0" value="192.168.1.2"/> <constructor-arg index="1" value="15566"/> </bean> <bean class="java.net.InetSocketAddress"> <constructor-arg index="0" value="192.168.1.2"/> <constructor-arg index="1" value="15569"/> </bean> </list> </property> </bean> <!-- 构造Server端 --> <!-- reuseAddress:该属性用于指定端口是否可重用,详细介绍请参考java.net.ServerSocket.setReuseAddress()方法 --> <!-- 说白了就是,如果有Client正在此端口上wait,若这时重启Server,可能导致重启失败or无法bing此port --> <!-- defaultLocalAddress:该属性可以传入java.net.InetSocketAddress(就好像我现在写的这样),也可以直接传字符串 --> <!-- 如果传入字符串,可以写成"192.168.1.2:15566"或者冒号端口":15566"或者直接端口"15566" --> <!-- 当写入字符串时,则需明确指定<bean class="org.springframework......CustomEditorConfigurer"> --> <!-- 这是因为NioSocketAcceptor需要的是InetSocketAddress对象,于是就需要一个编辑器将注入的字符串构造为InetSocketAddress对象 --> <!-- Mina2.x自带的org.apache.mina.integration.beans包中提供了很多的属性编辑器,它用来实现与所有的DI框架整合而不仅限于Spring --> <!-- <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <entry key="java.net.SocketAddress"> <bean class="org.apache.mina.integration.beans.InetSocketAddressEditor"/> </entry> </map> </property> </bean> --> <!-- 下面这种写法,只能绑定一个端口 --> <!-- 如果想自由的,控制Mina2.x服务的启停,包括绑定多个端口等,那么使用上面的com.jadyer.MinaStartup显然是一种好办法 --> <!-- 用坦克<[email protected]>的话说就是:刚才那招虽然很傻,但是必杀 --> <!-- <bean id="myServer" class="org.apache.mina.transport.socket.nio.NioSocketAcceptor" init-method="bind" destroy-method="unbind"> <property name="reuseAddress" value="true"/> <property name="filterChainBuilder" ref="ioFilterChainBuilder"/> <property name="handler"> <bean class="com.jadyer.handler.ServerHandler"/> </property> <property name="defaultLocalAddress"> <bean class="java.net.InetSocketAddress"> <constructor-arg index="0" value="192.168.1.2"/> <constructor-arg index="1" value="15566"/> </bean> </property> </bean> --> </beans>
下面是我们自定义的用于启动Mina服务的MinaStartup.java
package com.jadyer; import java.io.IOException; import java.net.SocketAddress; import java.util.List; import org.apache.mina.core.filterchain.IoFilterChainBuilder; import org.apache.mina.core.service.IoHandler; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; /** * 用于启动Mina2.x服务 * @create Oct 31, 2012 4:38:54 PM * @author 玄玉<http://blog.csdn/net/jadyer> */ public class MinaStartup { private IoHandler handler; //处理器 private List<SocketAddress> socketAddresses; //监听地址 private IoFilterChainBuilder filterChainBuilder; //过滤器链 private int bothIdleTime; //双向发呆时间 private int writeTimeout; //写操作超时时间 private boolean reuseAddress; //监听的端口是否可重用 public final void bind() throws IOException { NioSocketAcceptor acceptor = new NioSocketAcceptor(); acceptor.setHandler(this.handler); acceptor.setReuseAddress(this.reuseAddress); acceptor.setFilterChainBuilder(this.filterChainBuilder); acceptor.getSessionConfig().setWriteTimeout(this.writeTimeout); acceptor.getSessionConfig().setBothIdleTime(this.bothIdleTime); if(this.socketAddresses==null || this.socketAddresses.size()<1){ throw new RuntimeException("监听SocketAddress数不得小于1"); } acceptor.bind(this.socketAddresses); if(acceptor.isActive()){ System.out.println("写 超 时: " + this.writeTimeout + "ms"); System.out.println("发呆配置: Both Idle " + this.bothIdleTime + "s"); System.out.println("端口重用: " + this.reuseAddress); System.out.println("服务端初始化完成......"); System.out.println("服务已启动....开始监听...." + acceptor.getLocalAddresses()); }else{ System.out.println("服务端初始化失败......"); } } public void setHandler(IoHandler handler) { this.handler = handler; } public void setSocketAddresses(List<SocketAddress> socketAddresses) { this.socketAddresses = socketAddresses; } public void setFilterChainBuilder(IoFilterChainBuilder filterChainBuilder) { this.filterChainBuilder = filterChainBuilder; } public void setBothIdleTime(int bothIdleTime) { this.bothIdleTime = bothIdleTime; } public void setWriteTimeout(int writeTimeout) { this.writeTimeout = writeTimeout; } public void setReuseAddress(boolean reuseAddress) { this.reuseAddress = reuseAddress; } }
至此为止,Mina2.x就与Spring2.5.6集成完了
下面,就可以把该Web应用发布到Tomcat上了
启动Tomcat时,Mina2.x服务也就会被自动启动
如果想手工启动Mina2.x服务,可以使用下面这个MainApp.java
package com.jadyer; import org.springframework.context.support.FileSystemXmlApplicationContext; /** * 读取Spring的xml文件并启动应用 * @create Oct 30, 2012 11:41:27 AM * @author 玄玉<http://blog.csdn/net/jadyer> */ public class MainApp { public static void main(String[] args) { new FileSystemXmlApplicationContext("classpath:applicationContext*.xml"); } }
最后再附上一个测试类MyClient.java
package com.jadyer.client; import java.net.InetSocketAddress; import org.apache.mina.core.service.IoConnector; import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.transport.socket.nio.NioSocketConnector; import com.jadyer.codec.CmccSipcDecoder; import com.jadyer.codec.CmccSipcEncoder; import com.jadyer.model.SmsObject; public class MyClient { public static void main(String[] args) { IoConnector connector = new NioSocketConnector(); connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new CmccSipcEncoder("UTF-8"), new CmccSipcDecoder("UTF-8"))); connector.setHandler(new ClientHandler()); //connector.connect(new InetSocketAddress("192.168.1.2", 15566)); connector.connect(new InetSocketAddress("192.168.1.2", 15569)); } private static class ClientHandler extends IoHandlerAdapter { @Override public void sessionOpened(IoSession session) throws Exception { SmsObject sms = new SmsObject(); sms.setSender("15025302990"); sms.setReceiver("13716700602"); sms.setMessage("Hi Jadyer,这是我用Mina2.x发给你的消息...."); session.write(sms); } @Override public void messageReceived(IoSession session, Object message) throws Exception { System.out.println("The message received from Server is [" + ((SmsObject)message).getMessage() + "]"); session.close(false); session.getService().dispose(); } } }