應用環境:
最近研究一個項目,一個地圖應用,基於百度地圖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>
<!-- org\springframework -->
<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 UDP\TCP分開 通過工廠方法注入 -->
<!-- <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。謝謝大家!