SpringMVC4.x+Mina+WebSocket+Hibernate4.x實現高併發網頁及時通訊框架!(H5+BaiduMap-JSApi)

應用環境:

最近研究一個項目,一個地圖應用,基於百度地圖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;
	}

	

}


NetAndWebMsgServiceImpl.java處理Socket信息並向注入的websocketService發送,打通Socket和Web頁面通訊的橋樑。這是一個非常簡陋的模型。

/**
 * 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。謝謝大家!





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章