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版,可能在运行的时候缺包。


    4.0.0
    map
    map
    0.0.1-SNAPSHOT
    war
    map
    
    
        UTF-8
        compile
        provided
        test
        4.3.5.RELEASE
        4.3.11.Final
        2.0.16
    
    
        
            javax.servlet
            servlet-api
            2.5
            ${scope.servlet}
        
        
            javax.servlet.jsp
            jsp-api
            2.2
            ${scope.servlet}
        
        
            junit
            junit
            4.12
            ${scope.test}
        
        
        
            org.springframework
            spring-aop
            ${spring.version}
            ${scope.project}
        

        
            org.springframework
            spring-aspects
            ${spring.version}
            ${scope.project}
        

        
            org.springframework
            spring-beans
            ${spring.version}
            ${scope.project}
        

        
            org.springframework
            spring-context-support
            ${spring.version}
            ${scope.project}
        

        
            org.springframework
            spring-context
            ${spring.version}
            ${scope.project}
        

        
            org.springframework
            spring-core
            ${spring.version}
            ${scope.project}
        

        
            org.springframework
            spring-instrument
            ${spring.version}
            ${scope.project}
        

        
            org.springframework
            spring-jdbc
            ${spring.version}
            ${scope.project}
        

        
            org.springframework
            spring-orm
            ${spring.version}
            ${scope.project}
        

        
            org.springframework
            spring-oxm
            ${spring.version}
            ${scope.project}
        

        
            org.springframework
            spring-tx
            ${spring.version}
            ${scope.project}
        

        
            org.springframework
            spring-web
            ${spring.version}
            ${scope.project}
        

        
            org.springframework
            spring-webmvc
            ${spring.version}
            ${scope.project}
        

        
            org.springframework
            spring-webmvc-portlet
            ${spring.version}
            ${scope.project}
        

        
            org.springframework
            spring-websocket
            ${spring.version}
            ${scope.project}
        

        
            aopalliance
            aopalliance
            1.0
        

        
            org.aspectj
            aspectjweaver
            1.8.9
            ${scope.project}
        

        
            org.hibernate
            hibernate-core
            ${hibernate.version}
            ${scope.project}
        

        
            oracle
            ojdbc6
            11.2.0.3
            ${scope.project}
        

        
            log4j
            log4j
            1.2.17
            ${scope.project}
        

        
            com.belerweb
            pinyin4j
            2.5.0
        

        
            joda-time
            joda-time
            2.9.7
        

        
            com.metaparadigm
            json-rpc
            1.0
        

        
            com.google.code.gson
            gson
            2.3.1
        

        
            com.hynnet
            json-lib
            2.4
        

        
            com.fasterxml.jackson.core
            jackson-core
            2.8.6
        

        
            com.fasterxml.jackson.core
            jackson-databind
            2.8.6
        

        
            org.codehaus.jackson
            jackson-mapper-asl
            1.9.13
        

        
            com.mchange
            c3p0
            0.9.5.2
        

        
            ognl
            ognl
            3.1.11
        

        
            log4j
            log4j
            1.2.17
            
                
                    com.sun.jmx
                    jmxri
                
                
                    com.sun.jdmk
                    jmxtools
                
                
                    javax.jms
                    jms
                
            
        

        
            org.slf4j
            slf4j-api
            1.7.2
        

        
            org.slf4j
            slf4j-log4j12
            1.7.2
        

        
            org.apache.mina
            mina-core
            2.0.16
        

        
            org.apache.mina
            mina-integration-beans
            2.0.16
        

        
            org.apache.xbean
            xbean-spring
            3.18
        

        
            cglib
            cglib
            2.2.2
        

        
            cglib
            cglib-nodep
            2.1_3
        

        
            org.javassist
            javassist
            3.20.0-GA
        

    
    
        
            
                maven-compiler-plugin
                2.3.2
                
                    1.8
                    1.8
                
            

            
                org.apache.felix
                maven-bundle-plugin
                true
            

        
    


Spring-mvc.xml配置


	
	
	
	
	 
		
 	 
	
		
		
			
				
			 	
			 	
			 	
			    
            
		
	
	
	
		
			
			
				
					
						text/html;charset=UTF-8
					
				
			
		
	
	
	
	
	
	
	
	
		 
		 

		 
	

Spring-config.xml数据、事物、切面


	
	
		
     	  
		
		 	
	

	
	
		
		
		
			
				org.hibernate.dialect.OracleDialect
				none
				${db.show_sql}
				${db.format_sql}
				true
				1
				0
				thread
				none
				
				false
				false
                jdbc:oracle:thin:@//10.58.7.166:1521/orcl
                oracle.jdbc.OracleDriver
			
		
		
		
			com.zxit.model
		
	
	
	
        
    
 
    
    
        
    

	
	
		
	
	
	
	
	
		
			
			
			  
		
	

	
	
		
		
	

Spring-mina.xml 用Spring管理MINA。




	
 		
	
	
	
	
	
		
			1000
		
		
			1800
		
	
	  
          
    
		
		
	
	
	   
	   
	
		
	
	
	
 		 
 		  
          
         
            

 		 
	
	
	
	
	    
         
    
    
    
	
		
			
				
				  
				 
				
				 
                
				
				
                
			
		
	
	
	
	
		
			
				
				
			
		
	
	
	
	
		
		
		
		
		



		
	
	
	
	
    
        
        
            
        
        
        
       
        
        
        
        
        
        
    
	
		
	








    







	
	



















     













  



整体的配置就是这些,特别要注意Spring-Mina的配置和官网上的demo有点不一样。用官网那个配置跑不起来!


	
		
			
				
				
			
		
	


协议交换类。(注册、拦截器、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 userSocketSessionMap = new HashMap();
	
	/**
	 * 给所有在线用户发送消息
	 * @param message
	 * @throws IOException
	 */
//	@Override
//	public void broadcast(TextMessage message) throws IOException {
//		System.out.println(userSocketSessionMap);
//		Iterator> it = userSocketSessionMap
//				.entrySet().iterator();
//		// 多线程群发
//		while (it.hasNext()) {
//			final Entry 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> it = userSocketSessionMap
				.entrySet().iterator();
		// 移除Socket会话
		while (it.hasNext()) {
			Entry 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> it = userSocketSessionMap.entrySet().iterator();
		// 移除Socket会话
		while (it.hasNext()) {
			Entry 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进行转发。

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


页面接收到Json并解析定位!

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


下面是并发,处理之后,100毫秒一次的UDP协议。不存在粘包、半包的问题。可以看到,后台发送消息10万次,均被web正常解析,没有出现JS错误。

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


好了就到这里,实在没精力了写了。如果有什么建议,大家可以写到楼下,我会抓紧Fix。谢谢大家!





你可能感兴趣的:(Java技术,JSP技术,HTML,JavaScript,Oracle)