转载请注明出处,谢谢配合javascript
这里以mina整合springMVC为例:html
//springMVC的配置: <!-- mina --> <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <!-- spring升级后此配置已失效 会报错 <entry key="java.net.SocketAddress"> <bean class="org.apache.mina.integration.beans.InetSocketAddressEditor" /> </entry> --> <entry key="java.net.SocketAddress" value="org.apache.mina.integration.beans.InetSocketAddressEditor"/> </map> </property> </bean> <!-- 配置业务处理类 --> <bean id="serviceHandler" class="org.springboot.mina.ServerHandler" /> <!-- 配置service --> <bean id="ioAcceptor" class="org.apache.mina.transport.socket.nio.NioSocketAcceptor" init-method="bind" destroy-method="unbind"> <property name="defaultLocalAddress" value=":9012" /> <property name="handler" ref="serviceHandler" /> <!--声明过滤器的集合--> <property name="filterChainBuilder" ref="filterChainBuilder" /> <property name="reuseAddress" value="true" /> </bean> <!-- 配置解码器 --> <bean id="codec" class="org.apache.mina.filter.codec.ProtocolCodecFilter"> <constructor-arg> <!-- <bean class="org.apache.mina.filter.codec.textline.TextLineCodecFactory" > <constructor-arg value="UTF-8"></constructor-arg> </bean> --> <!-- 自定义的 字符编码类 org.springboot.mina.codec.ICodeFactory--> <bean class="org.springboot.mina.codec.WebSocketCodecFactory" /> </constructor-arg> </bean> <!-- 配置日志拦截器 --> <bean id="logger" class="org.apache.mina.filter.logging.LoggingFilter"></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="codec" value-ref="codec" /> <entry key="logger" value-ref="logger" /> <!--心跳filter--> <!-- <entry key="keepAliveFilter" value-ref="keepAliveFilter" /> --> </map> </property> </bean> <!-- executorFilter多线程处理 --> <bean id="executorFilter" class="org.apache.mina.filter.executor.ExecutorFilter" /> <bean id="mdcInjectionFilter" class="org.apache.mina.filter.logging.MdcInjectionFilter"> <constructor-arg value="remoteAddress" /> </bean>
页面的代码片断,前端使用的是layim框架前端
<script> var socket = null; layui.use('layim', function(layim){ //基础配置 layim.config({ //获取主面板列表信息 init: { url: 'chat-init.do' //接口地址(返回的数据格式见下文) ,type: 'get' //默认get,通常可不填 } ,members: { url: 'find-chat-mem.do' ,data: {'owner':1}//测试 } ,uploadImage: { url: 'upload-file.do' } ,brief: false //是否简约模式(默认false,若是只用到在线客服,且不想显示主面板,能够设置 true) ,title: 'tom在线' //主面板最小化后显示的名称 ,min: false //用于设定主面板是否在页面打开时,始终最小化展示。默认false,即记录上次展开状态。 ,minRight: null //【默认不开启】用户控制聊天面板最小化时、及新消息提示层的相对right的px坐标,如:minRight: '200px' ,maxLength: 3000 //最长发送的字符长度,默认3000 ,right: '0px' //默认0px,用于设定主面板右偏移量。该参数可避免遮盖你页面右下角已经的bar。 ,copyright: true }); layim.on('sendMessage', function(res){ socket.send(JSON.stringify({ type: 'text' //随便定义,用于在服务端区分消息类型 ,data:res })); }); //监听收到的消息 socket.onmessage = function(res){ try{ console.log("onmessage receiver msg:"+res.data); res = JSON.parse(res.data);//字符串转json if(res.emit === "text"){ layim.getMessage(res.data); } }catch(e){ console.error(e); } }; }); //init websocket $(function(){ try { var url = "ws://127.0.0.1:9012"; if ('WebSocket' in window) { socket = new WebSocket(url); } else if ('MozWebSocket' in window) { socket = new MozWebSocket(url); } } catch (e) { console.error(e); } //链接成功时触发 socket.onopen = function(){ socket.send("connected successful..."); }; // 监听Socket的关闭 socket.onclose = function(event) { console.debug("receiver msg:"+event); }; socket.onerror=function(event){ console.error("receiver error msg:"+event); }; }); </script>
controller,经过访问http://localhost/to-chat.do创建通讯java
// @Controller public class ChatController { private static Logger log = LogManager.getLogger(ChatController.class); // private static final String USER = "USER",GROUP = "GROUP"; private static final String IMG_SUFFIX = "images/chat/"; @Autowired private ChatUserService chatUserService; @Autowired private ChatGroupService chatGroupService; @RequestMapping("/to-chat.do") public ModelAndView toChat(){ ModelAndView mv = new ModelAndView(); mv.setViewName("chat/chat.jsp"); return mv; } }
服务端:tomcat容器启动加载web
public class ServerHandler extends IoHandlerAdapter { private static Logger log = LogManager.getLogger(ServerHandler.class); public static Map<Long, IoSession> ioSession = new HashMap<Long, IoSession>(); public ServerHandler() { } @Override public void exceptionCaught(IoSession session, Throwable cause) throws Exception { } @Override public void messageReceived(IoSession session, Object message) { try { System.out.println("addr:" + session.getRemoteAddress() + ",message:\n" + message); MinaBean minaBean = (MinaBean) message; // 是不是握手请求 if (minaBean.isWebAccept()) { MinaBean sendMessage = minaBean; sendMessage.setContent(WebSocketUtil.getSecWebSocketAccept(minaBean .getContent())); session.write(sendMessage); ioSession.put(session.getId(), session); } else { // chat chat(session, minaBean); } } catch (ClassCastException e) { // 不处理 } catch (JsonSyntaxException e) { // 不作处理 } catch (Exception e) { e.printStackTrace(); } } @Override public void messageSent(IoSession session, Object message) throws Exception { System.out.println("server send msg to client ,message:" + message); log.debug("------------server send msg to client ,message:" + message); } @Override public void sessionClosed(IoSession session) throws Exception { // 将hash中的session移除。 // Memcached.remove(""+session.getId()); ioSession.remove(session.getId()); System.out.println(session + "退出map"); } @Override public void sessionCreated(IoSession session) throws Exception { System.out.println("=========cread session," + session.getRemoteAddress().toString()); log.debug(session.getRemoteAddress().toString() + "----------------------create"); } @Override public void sessionIdle(IoSession session, IdleStatus status) throws Exception { System.out.println("IDLE " + session.getIdleCount(status)); } @Override public void sessionOpened(IoSession session) throws Exception { System.out.println(session + "加入map"); } public static Map<Long, IoSession> getIoSession() { return ioSession; } /** * 聊天 * * @param session * @param minaBean */ public void chat(IoSession session, MinaBean minaBean) { ReceiverMsgDto receiverMsg = new ReceiverMsgDto(); receiverMsg = (ReceiverMsgDto) MinaEncoder.convertJSONToObject((String) minaBean .getContent(), receiverMsg); if (receiverMsg != null) { // 获取接收者 To to = receiverMsg.getData().getTo(); if (to == null) { log.warn("msg is null , exit!"); return; } // 1-1 if (to.getType().equalsIgnoreCase(ChatMessageTypeEnum.FRIEND.getType())) { IoSession is = ioSession.get(session.getId()); if (is == null) return; is.write(minaBean); } else { // 1-n Collection<IoSession> ioSessionSet = session.getService() .getManagedSessions().values(); for (IoSession is : ioSessionSet) { is.write(minaBean); } } } } public static void setIoSession(Map<Long, IoSession> ioSession) { ServerHandler.ioSession = ioSession; } }
自定义编解码器:(必须,由于若是不这样作,会致使握手不成功)spring
public class MinaEncoder extends DemuxingProtocolEncoder { public MinaEncoder() { addMessageEncoder(MinaBean.class, new BaseSocketBeanEncoder()); } private String buildResponseMsg(To to) { if (to == null) return ""; ResponseMsgDto res = new ResponseMsgDto(); res.setEmit(ChatMessageEnum.TEXT.getMessage()); //组装发送消息的类,这个能够根据本身的须要来封装字段 SendInfoDto send = new SendInfoDto(); res.setData(send); send.setAvatar(to.getAvatar()); send.setId(to.getId()); if (to.getType().equalsIgnoreCase(ChatMessageTypeEnum.FRIEND.getType())) send.setContent("你好:tom!");//为了测试,这边模拟回复前端发送来的消息 else if(to.getType().equalsIgnoreCase(ChatMessageTypeEnum.GROUP.getType())){ send.setContent("HI,我是新来的请多多关照!");//为了测试,这边模拟回复前端发送来的消息 send.setId(to.getId()); } send.setMine(false); send.setType(to.getType()); send.setTimestamp(new Date().getTime()); send.setUsername(to.getUsername()); return new Gson().toJson(res); } public static Object convertJSONToObject(String msg,Object obj){ try { Object resObj = new Gson().fromJson(msg,obj.getClass()); return resObj; } catch (ClassCastException e) { //不处理 } catch (JsonSyntaxException e) { //不处理 } return null; } class BaseSocketBeanEncoder implements MessageEncoder<MinaBean> { public void encode(IoSession session, MinaBean message, ProtocolEncoderOutput out) throws Exception { byte[] _protocol = null; if (message.isWebAccept()) { _protocol = message.getContent().getBytes("UTF-8"); } else { try { ReceiverMsgDto receiverMsg = new ReceiverMsgDto(); receiverMsg = (ReceiverMsgDto)convertJSONToObject((String) message.getContent(),receiverMsg); String responseMsg = "";// 响应结果 if (receiverMsg != null) { // 获取接收者 To to = receiverMsg.getData().getTo(); if (to != null) // 构建返回参数 responseMsg = buildResponseMsg(to); } message.setContent(responseMsg); } catch (ClassCastException e) { //不处理 } catch (JsonSyntaxException e) { //不处理 } _protocol = WebSocketUtil.encode(message.getContent()); } int length = _protocol.length; IoBuffer buffer = IoBuffer.allocate(length); buffer.put(_protocol); buffer.flip(); out.write(buffer); } }public class WebSocketCodecFactory implements ProtocolCodecFactory{ private ProtocolEncoder encoder; private ProtocolDecoder decoder; public WebSocketCodecFactory() { encoder = new MinaEncoder(); decoder = new MinaDecoder(); } @Override public ProtocolEncoder getEncoder(IoSession ioSession) throws Exception { return encoder; } @Override public ProtocolDecoder getDecoder(IoSession ioSession) throws Exception { return decoder; }
验证:启动tomcat,访问 http://localhost/to-chat.doapache
链接成功的日志json
28511 [pool-4-thread-1] INFO org.apache.mina.filter.logging.LoggingFilter - RECEIVED: MinaBean [content=connected successful...] addr:/127.0.0.1:57822,message: MinaBean [content=connected successful...]
接收前端发送来的信息(发送者的信息及发送内容,接受者的信息):后端
1080378 [pool-4-thread-2] INFO org.apache.mina.filter.logging.LoggingFilter - RECEIVED: MinaBean [content={"type":"text","data":{"mine":{"username":"tom","avatar":"http://127.0.0.1/SpringMVC/images/header/tom.jpg","id":1,"mine":true,"content":"你好"}, "to":{"id":2,"username":"timor","status":"online","avatar":"http://127.0.0.1/SpringMVC/images/header/timor.jpg","createTime":"Nov 30, 2016 4:01:02 PM","updateTime":"Nov 30, 2016 4:01:01 PM","name":"timor","type":"friend"}}}]后端给前端的响应日志:tomcat
1080433 [pool-4-thread-2] INFO org.apache.mina.filter.logging.LoggingFilter - SENT: MinaBean [content={"emit":"text","data":{"username":"timor","avatar":"http://127.0.0.1/images/header/timor.jpg","type":"friend","content":"你好:tom!","id":2,"mine":false,"timestamp":1503987895304}}]前端查看console.log信息,按下F12:
onmessage receiver msg:{"emit":"text","data":{"username":"timor","avatar":"http://127.0.0.1/SpringMVC/images/header/timor.jpg","type":"friend","content":"你好:tom!","id":2,"mine":false,"timestamp":1503987895304}}