概述
应用环境:
最近研究一个项目,一个地图应用,基于百度地图JS-API开发的。
需要和现有的旧项目对接,旧项目也是公司的,但是是十年左右的技术架构,原来受到技术和硬件的限制,采用Socket为主要的通讯方式。并且由于公司人力资源有限不可能对这个旧项目大刀阔斧的改动。所以博主建议的采用webservice为主要的通讯方式啥的,全是扯淡,不能实际实现。
然后博主想过用MQ,一则,没怎么用到过项目里。二则,还是上面说的,没人给你对接,跨不过Socket这个鸿沟。后来博主心生一计,如果我把后台Socket嵌入到项目里,并且能和公司的项目无缝融合,把Socket发过来的协议内容处理后转化成JSON,交给前台用websocket做消息处理不就行了。
这里可能有同学觉得信息量好大。又是Socket又是WebSocket,然后肯定还要考虑持久化,IOC就不用说了。所以在这里博主选了几个需要用的东西:
Spring
SpringMVC
Hibernate(因为不牵扯到复杂查询,所以Mybatis没优势,所以没用。H在封装完BaseDao和BaseServiceImpl后,基本上是不用写任何CRUD的实现的)
Mina(Apache的高并发Socekt通讯框架。)具体:http://mina.apache.org
前端环境是WebSocket+H5、reconnecting-websocket.js(完成websocket的断线重连)
应用环境:Chrome、FF、IE11,可内迁到IOS、安卓,博主已实测。
不多废话,上POM。这也是一个Beta版,可能在运行的时候缺包。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>map</groupId>
<artifactId>map</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>map</name>
<description/>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<scope.project>compile</scope.project>
<scope.servlet>provided</scope.servlet>
<scope.test>test</scope.test>
<spring.version>4.3.5.RELEASE</spring.version>
<hibernate.version>4.3.11.Final</hibernate.version>
<mina.version>2.0.16</mina.version>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>${scope.servlet}</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>${scope.servlet}</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>${scope.test}</scope>
</dependency>
<!--
orgspringframework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
<version>${spring.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${spring.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc-portlet</artifactId>
<version>${spring.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<scope>${scope.project}</scope>
</dependency>
<dependency>
<groupId>com.belerweb</groupId>
<artifactId>pinyin4j</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.7</version>
</dependency>
<dependency>
<groupId>com.metaparadigm</groupId>
<artifactId>json-rpc</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.hynnet</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>3.1.11</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<exclusions>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>2.0.16</version>
</dependency>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-integration-beans</artifactId>
<version>2.0.16</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.18</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.1_3</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.20.0-GA</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
</project>
Spring-mvc.xml配置
<!-- 支持AOP -->
<aop:aspectj-autoproxy />
<!-- 注解扫描 -->
<context:component-scan base-package="com.zxit"
/>
<!-- 全局拦截器 -->
<mvc:interceptors>
<bean class="com.zxit.interceptor.MVCInterceptor"></bean>
</mvc:interceptors>
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="cacheSeconds" value="0" />
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"></bean>
<bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
<bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
<bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
</list>
</property>
</bean>
<!-- 启动mvc注解模式 -->
<mvc:annotation-driven>
<mvc:message-converters>
<!-- 加入ajax乱码过滤,使其支持utf-8的有效返回值 -->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 对模型视图名称的解析,即在模型视图名称添加前后缀 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:suffix=".jsp">
</bean>
<!-- 时间格式转换 -->
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean" />
<!-- 处理文件上传 批量导入的时候能用用 其他估计也用不到 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8" /> <!-- 默认编码 (ISO-8859-1) -->
<property name="maxInMemorySize" value="10240" /> <!-- 最大内存大小 (10240) -->
<!--
<property name="uploadTempDir" value="/upload/" /> 上传后的目录名 (WebUtils#TEMP_DIR_CONTEXT_ATTRIBUTE) -->
<property name="maxUploadSize" value="-1" /> <!-- 最大文件大小,-1为无限止(-1) -->
</bean>
Spring-config.xml数据、事物、切面
<!-- 数据源 -->
<!-- 不需要安装任何数据库的客户端程序,只需要按照格式配置即可。当然程序提供用客户端的连接方式,详见下面的注释 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="oracle.jdbc.driver.OracleDriver" />
<property name="jdbcUrl" value="jdbc:oracle:thin:@${db.jdbcUrl}" />
<property name="user" value="${db.user}" />
<property name="password" value="${db.password}" />
</bean>
<!-- SessionFactory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- ibernate的相关属性配置 -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>
<prop key="hibernate.hbm2ddl.auto">none</prop>
<prop key="hibernate.show_sql">${db.show_sql}</prop>
<prop key="hibernate.format_sql">${db.format_sql}</prop>
<prop key="hibernate.jdbc.use_streams_for_binary">true</prop>
<prop key="hibernate.jdbc.fetch_size">1</prop>
<prop key="hibernate.jdbc.batch_size">0</prop>
<prop key="current_session_context_class">thread</prop>
<prop key="javax.persistence.validation.mode">none</prop>
<!-- 二级缓存,3年内这个肯定用不到 -->
<prop key="hibernate.cache.use_second_level_cache">false</prop>
<prop key="hibernate.cache.use_query_cache">false</prop>
<prop key="hibernate.connection.url">jdbc:oracle:thin:@//10.58.7.166:1521/orcl</prop>
<prop key="hibernate.connection.driver_class">oracle.jdbc.OracleDriver</prop>
</props>
</property>
<!-- 实体类扫描器 -->
<property name="packagesToScan">
<value>com.zxit.model</value>
</property>
</bean>
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate4.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<!--配置一个JdbcTemplate实例 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 事务管理器 -->
<bean id="txManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 事务注解 -->
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="false" />
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="find*" read-only="true" />
<tx:method name="init*" read-only="true" />
<tx:method name="*" />
<!-- 其他事务在require中运行 -->
</tx:attributes>
</tx:advice>
<!-- aop事务切面 -->
<aop:config>
<aop:pointcut expression="execution(public * com.zxit.service.impl.*.*(..))" id="businessService" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="businessService" />
</aop:config>
Spring-mina.xml 用Spring管理MINA。
<import resource="classpath*:spring-config.xml" />
<!--
消息主体 -->
<bean id="netAndWebMsgService" class="com.zxit.service.impl.NetAndWebMsgServiceImpl">
<property name="systemConfig" ref="systemConfig"></property>
</bean>
<!-- 多线程处理过滤器,为后面的操作开启多线程,一般放在编解码过滤器之后,开始业务逻辑处理 -->
<bean id="executorFilter" class="org.apache.mina.filter.executor.ExecutorFilter">
<constructor-arg index="0">
<value>1000</value>
</constructor-arg>
<constructor-arg index="1">
<value>1800</value>
</constructor-arg>
</bean>
<bean id="mdcInjectionFilter" class="org.apache.mina.filter.logging.MdcInjectionFilter">
<constructor-arg value="remoteAddress" />
</bean>
<!-- Mina自带日志过滤器 默认级别为debug -->
<bean id="loggingFilter" class="org.apache.mina.filter.logging.LoggingFilter">
<property name="messageReceivedLogLevel" ref="info"></property>
<property name="exceptionCaughtLogLevel" ref="info"></property>
</bean>
<!-- 累加数据包解码器:解断丢包、粘包问题 -->
<bean id="textCodecFilter" class="org.apache.mina.filter.codec.ProtocolCodecFilter">
<constructor-arg>
<!-- 处理对象流时候用ObjectSerializationCodecFactory -->
<!-- <bean class="org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory" /> -->
<!--构造函数的参数传入自己实现的对象-->
<bean class="com.zxit.socket.MyCodeFactory"></bean>
<!--
<bean class="org.apache.mina.filter.codec.textline.TextLineCodecFactory" /> 默认实现对象-->
</constructor-arg>
</bean>
<!-- 枚举类型 依赖注入
需要先通过此类进行类型转换-->
<bean id="info" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField" value="org.apache.mina.filter.logging.LogLevel.INFO" />
</bean>
<bean id="filterChainBuilder"
class="org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder">
<property name="filters">
<map>
<!--mina自带的线程池filter-->
<entry key="executor" value-ref="executorFilter" />
<entry key="mdcInjectionFilter" value-ref="mdcInjectionFilter" />
<!--<entry key="codecFilter" value-ref="codecFilter" />-->
<!--自己实现的编解码器filter-->
<entry key="codecFilter" value-ref="textCodecFilter" />
<entry key="loggingFilter" value-ref="loggingFilter" />
<!--心跳filter-->
<entry key="keepAliveFilter" value-ref="keepAliveFilter" />
</map>
</property>
</bean>
<!-- 设置 I/O 接受器,并指定接收到请求后交给 mainHandler 进行处理PropertyEditor -->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="java.net.SocketAddress" value="org.apache.mina.integration.beans.InetSocketAddressEditor">
</entry>
</map>
</property>
</bean>
<!-- session config
UDP+TCP合一 通过工厂方法注入 -->
<bean id="ioAcceptor" class="org.apache.mina.transport.socket.nio.NioSocketAcceptor" init-method="bind" destroy-method="unbind">
<property name="defaultLocalAddress" value=":1234" /><!-- TCP监听端口 -->
<property name="handler" ref="netAndWebMsgService" />
<property name="reuseAddress" value="true" />
<property name="filterChainBuilder" ref="filterChainBuilder" />
<!-- 默认启用的线程个数是CPU 的核数+1, -->
<!--
实测: -->
<!--
当线程达到15以上时,基本可以忽略10毫秒的连续发包,10万个UDP包粘包只有10个以内 -->
<!--
当线程达到15以上时,基本可以忽略100毫秒的连续发包,10万个UDP包粘包为0 -->
<constructor-arg index="0" value="1"></constructor-arg>
</bean>
<!--心跳检测filter-->
<bean id="keepAliveFilter" class="org.apache.mina.filter.keepalive.KeepAliveFilter">
<!--构造函数的第一个参数传入自己实现的工厂-->
<constructor-arg>
<bean class="com.zxit.socket.MyKeepAliveMessageFactory"></bean>
</constructor-arg>
<!--第二个参数需要的是IdleStatus对象,value值设置为读写空闲-->
<constructor-arg type = "org.apache.mina.core.session.IdleStatus" value="BOTH_IDLE" >
</constructor-arg>
<!--心跳频率,不设置则默认60s
-->
<property name="requestInterval" value="5" />
<!--心跳超时时间,不设置则默认30s
-->
<property name="requestTimeout" value="10" />
<!--不设置默认false-->
<property name="forwardEvent" value="true" />
</bean>
<!-- session config
UDPTCP分开
通过工厂方法注入 -->
<!--
<bean id="tcpAcceptor" class="org.apache.mina.transport.socket.nio.NioSocketAcceptor" init-method="bind" destroy-method="unbind">
-->
<!--
<property name="defaultLocalAddress" value=":10000" />
-->
<!--
<property name="handler" ref="tcpHandler" />
-->
<!--
<property name="filterChainBuilder" ref="filterChainBuilder" />
-->
<!--
<property name="reuseAddress" value="true" />
-->
<!--
</bean> -->
<!--
<bean id="tcpHandler" class="com.umaiw.socket.TCPHandler">
-->
<!--
</bean>
-->
<!--
<bean id="udpAcceptor" class="org.apache.mina.transport.socket.nio.NioDatagramAcceptor" init-method="bind" destroy-method="unbind">
-->
<!--
<property name="defaultLocalAddress" value=":10001" />
-->
<!--
<property name="handler" ref="udpHandler" />
-->
<!--
<property name="filterChainBuilder" ref="filterChainBuilder" />
-->
<!--
</bean> -->
<!--
<bean id="udpHandler" class="com.umaiw.socket.UdpHandler">
-->
<!--
</bean>
-->
<!--
作为多端口协议服务器 -->
<!--
<bean id="ioAccepServer" class="org.apache.mina.integration.spring.IoAcceptorFactoryBean">
-->
<!--
<property name="target">
-->
<!--
<bean class="org.apache.mina.transport.socket.nio.NioSocketAcceptor" />
-->
<!--
</property>
-->
<!--
<property name="bindings">
-->
<!--
<list> -->
<!--
<bean class="org.apache.mina.integration.spring.Binding">
-->
<!--
监听端口:8888
-->
<!--
<property name="address" value=":8888" /> -->
<!--
SampleHandler:引用定义的服务器handler
-->
<!--
<property name="handler" ref="SampleHandler" /> -->
<!--
<property name="serviceConfig">
-->
<!--
<bean class="org.apache.mina.transport.socket.nio.NioSocketAcceptor">
-->
<!--
<property name="filterChainBuilder" ref="filterChainBuilder" />
-->
<!--
<property name="reuseAddress" value="true" />
-->
<!--
</bean>
-->
<!--
</property>
-->
<!--
</bean>
-->
<!--
<bean class="org.apache.mina.integration.spring.Binding">
-->
<!--
<property name="address" value=":9999" />
-->
<!--
<property name="handler" ref="bossSampleHandler" />
-->
<!--
<property name="serviceConfig">
-->
<!--
<bean class="org.apache.mina.transport.socket.nio.NioSocketAcceptor">
-->
<!--
<property name="filterChainBuilder" ref="filterChainBuilder" />
-->
<!--
<property name="reuseAddress" value="true" />
-->
<!--
</bean>
-->
<!--
</property>
-->
<!--
</bean>
-->
<!--
</list>
-->
<!--
</property>
-->
<!--
</bean> -->
<!--
自定义服务端handler -->
<!--
<bean id="SampleHandler" class="com.zxit.socket.NetSocketHandler" />
-->
<!--
<bean id="bossSampleHandler" class="com.zxit.socket.NetSocketHandler" /> -->
整体的配置就是这些,特别要注意Spring-Mina的配置和官网上的demo有点不一样。用官网那个配置跑不起来!
<!-- 设置 I/O 接受器,并指定接收到请求后交给 mainHandler 进行处理PropertyEditor -->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="java.net.SocketAddress" value="org.apache.mina.integration.beans.InetSocketAddressEditor">
</entry>
</map>
</property>
</bean>
协议交换类。(注册、拦截器、WebsocketHandler)可以自行找,这里不再累述。
WebSocketServiceImpl.java 用于转发websocket封装好的JSON消息到页面。接口集成了WebsocketHandler。
/**
* WebSocket处理器
* @Date 2015年6月11日 下午1:19:50
*/
@Service("webSocketService")
public class WebSocketServiceImpl implements WebSocketService {
public static final Map<String, WebSocketSession> userSocketSessionMap = new HashMap<String, WebSocketSession>();
/**
* 给所有在线用户发送消息
* @param message
* @throws IOException
*/
// @Override
// public void broadcast(TextMessage message) throws IOException {
//
System.out.println(userSocketSessionMap);
//
Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap
//
.entrySet().iterator();
//
// 多线程群发
//
while (it.hasNext()) {
//
final Entry<String, WebSocketSession> entry = it.next();
//
if (entry.getValue().isOpen()) {
//
//entry.getValue().sendMessage(message);
//
new Thread(new Runnable() {
//
public void run() {
//
try {
//
if (entry.getValue().isOpen()) {
//
entry.getValue().sendMessage(message);
//
}
//
} catch (IOException e) {
//
e.printStackTrace();
//
}
//
}
//
}).start();
//
}
//
}
// }
/**
* 给某个用户发送消息
* @param userid
* @param message
* @throws IOException
*/
public void sendMessageToUser(String userid, TextMessage message)throws IOException {
WebSocketSession session = userSocketSessionMap.get(userid);
System.out.println(message);
if (session != null && session.isOpen()) {
session.sendMessage(message);
} else {
System.out.println("用户:"+userid +"websocketSession已失效!");
}
}
/**
* 建立连接后
* 接受到所有存在的会话用户上下文
* 并且存入websocekt的map集合
*/
@Override
public void afterConnectionEstablished(WebSocketSession session)
throws Exception {
String uid = (String) session.getAttributes().get("uid");
//Zdxxb zdxxb = (Zdxxb)session.getAttributes().get("zdxxb");
if (userSocketSessionMap.get(uid) == null) {
userSocketSessionMap.put(uid, session);
}
}
/**
* sendMessage方法封装在下面
* 消息处理,在客户端通过Websocket API发送的消息会经过这里,然后进行相应的处理
*/
@Override
public void handleMessage(WebSocketSession session,WebSocketMessage<?> message) throws Exception {
if (message.getPayloadLength() == 0)
return;
Position position = new Gson().fromJson(message.getPayload().toString(),Position.class);
System.out.println(position.toString());
sendMessageToUser("RP01", new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(position)));
}
/**
* 消息传输错误处理
*/
@Override
public void handleTransportError(WebSocketSession session,
Throwable exception) {
if (session.isOpen()) {
try {
session.close();
} catch (IOException e) {
// e.printStackTrace();
}
}
Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap
.entrySet().iterator();
// 移除Socket会话
while (it.hasNext()) {
Entry<String, WebSocketSession> entry = it.next();
if (entry.getValue().getId().equals(session.getId())) {
userSocketSessionMap.remove(entry.getKey());
System.out.println("Socket会话已经移除:用户ID" + entry.getKey());
break;
}
}
}
/**
* 关闭连接后
*/
@Override
public void afterConnectionClosed(WebSocketSession session,
CloseStatus closeStatus) throws Exception {
Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap.entrySet().iterator();
// 移除Socket会话
while (it.hasNext()) {
Entry<String, WebSocketSession> entry = it.next();
if (entry.getValue().getId().equals(session.getId())) {
userSocketSessionMap.remove(entry.getKey());
System.out.println("Socket会话已经移除用户ID=" + entry.getKey());
System.out.println("Websocket:" + entry.getKey() + "已经关闭");
break;
}
}
}
@Override
public boolean supportsPartialMessages() {
return false;
}
}
/**
* NetSocket和WebSocket的初步整合
* 注意:springmvc和spring的作用域不同,SpringMVC的IOC容器可以看作是springIOC的一个小型作用域
* 所以从软件设计模式上来说:
* SpringMVC 的 IOC 容器中的 bean 可以来引用 Spring IOC 容器中的 bean.
* Spring IOC 容器中的 bean不能来引用 SpringMVC IOC 容器中的 bean!
* 不过我们如果依然希望拿到bean可以用@Bean来解决
* @since 2017年1月17日
* @author nanxiaofeng
* @version 1.0
* 更改人:
* 更改日期:
*/
@Service("netAndWebMsgService")
public class NetAndWebMsgServiceImpl extends IoHandlerAdapter implements NetAndWebMsgService {
public
NetAndWebMsgServiceImpl(){
}
private SystemConfig systemConfig;
public SystemConfig getSystemConfig() {
return systemConfig;
}
public void setSystemConfig(SystemConfig systemConfig) {
this.systemConfig = systemConfig;
}
@Bean
public WebSocketService webSocketService(){
return new WebSocketServiceImpl();
}
// @Bean
// public ParseEntityService parseEntityService(){
//
return new ParseEntityServiceImpl();
// }
@Override
public void sendSocketMsgToWeb(String msg) {
JSONObject jsonObject = JSONObject.fromObject(msg);
//这里只处理半包就行了
Position position = UtilTools.convertToObj(jsonObject,Position.class);
try {
webSocketService().sendMessageToUser(position.getMsgTo(), new TextMessage(msg));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
IoBuffer bbuf = (IoBuffer) message;
System.out.println("message = " + bbuf + bbuf.limit());
//直接推送,需要业务服务器自行处理
byte[] byten = new byte[bbuf.limit()];
bbuf.get(byten, bbuf.position(), bbuf.limit());
String msg = new String(byten,Charset.forName("gb2312"));
//启用缓存池
//
bbuf.get(byten);
//
StringBuilder stringBuilder = new StringBuilder();
//
for(int i = 0; i < byten.length; i++){
//
stringBuilder.append((char) byten[i]); //可以根据需要自己改变类型
//
}
//
msg = stringBuilder.toString();
System.out.println("客户端收到消息" + msg);
sendSocketMsgToWeb(msg);
}
@Override
public void messageSent(IoSession session, Object message) throws Exception {
if (message instanceof IoBuffer) {
IoBuffer buffer = (IoBuffer) message;
byte[] bb = buffer.array();
for (int i = 0; i < bb.length; i++) {
System.out.print((char) bb[i]);
}
}
}
// 抛出异常触发的事件
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
cause.printStackTrace();
session.close(true);
}
// 连接关闭触发的事件
@Override
public void sessionClosed(IoSession session) throws Exception {
System.out.println("Session closed...");
}
// 建立连接触发的事件
@Override
public void sessionCreated(IoSession session) throws Exception {
System.out.println("Session created...");
SocketAddress remoteAddress = session.getRemoteAddress();
System.out.println(remoteAddress);
}
// 会话空闲
@Override
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
System.out.println("Session idle...");
}
/**
* 打开连接触发的事件
*它与sessionCreated的区别在于
*一个连接地址(A)第一次请求Server会建立一个Session默认超时时间为1分钟
*此时若未达到超时时间这个连接地址(A)再一次向Server发送请求即是sessionOpened
*连接地址(A)第一次向Server发送请求或者连接超时后向Server发送请求时会同时触发sessionCreated和sessionOpened两个事件
*/
@Override
public void sessionOpened(IoSession session) throws Exception {
System.out.println("Session Opened...");
SocketAddress remoteAddress = session.getRemoteAddress();
System.out.println(remoteAddress);
}
}
注意 webSocketService并不能像我们以前@Resource一样注入,这里我也想了一下,大概因为NetAndWebxxxxx.java是SpringIOC的域,而WebSocketService是SpringMVC IOC的作用域,所以这里直接注入,是null。具体,我也没时间整非常明白,后面需要着重看一下这个。这个类里面所有的@Resource都是null。
@Bean
public WebSocketService webSocketService(){
return new WebSocketServiceImpl();
}
至于百度的JS-Api就不说了,官网上有http://lbsyun.baidu.com/index.php?title=jspopular,自行了解。
下面是运行效果。
从虚拟机的发一个UDP消息给Web服务。
Web服务根据已有的用户Map进行转发。
页面接收到Json并解析定位!
下面是并发,处理之后,100毫秒一次的UDP协议。不存在粘包、半包的问题。可以看到,后台发送消息10万次,均被web正常解析,没有出现JS错误。
好了就到这里,实在没精力了写了。如果有什么建议,大家可以写到楼下,我会抓紧Fix。谢谢大家!
最后
以上就是呆萌音响为你收集整理的SpringMVC4.x+Mina+WebSocket+Hibernate4.x实现高并发网页及时通讯框架!(H5+BaiduMap-JSApi)的全部内容,希望文章能够帮你解决SpringMVC4.x+Mina+WebSocket+Hibernate4.x实现高并发网页及时通讯框架!(H5+BaiduMap-JSApi)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复