轉載請註明出處,謝謝配合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}}