SpringMVC4.x+Mina+WebSocket+Hibernate4.x实现高并发网页及时通讯框架!(H5+BaiduMap-JSApi)

    xiaoxiao2021-03-26  11

    应用环境:

    最近研究一个项目,一个地图应用,基于百度地图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。谢谢大家!

    转载请注明原文地址: https://ju.6miu.com/read-650009.html

    最新回复(0)