应用环境:
最近研究一个项目,一个地图应用,基于百度地图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
org.apache.felix
maven-bundle-plugin
true
Spring-mvc.xml配置
text/html;charset=UTF-8
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
1000
1800
协议交换类。(注册、拦截器、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;
}
}
/**
* 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。谢谢大家!