java使用mina和websocket通信

时间:2022-04-29
本文章向大家介绍java使用mina和websocket通信,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

这里以mina整合springMVC为例:

//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建立通信

//

@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容器启动加载

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;
	}

}

自定义编解码器:(必须,因为如果不这样做,会导致握手不成功)

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.do

连接成功的日志

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"}}}]

后端给前端的响应日志:

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}}