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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码匠的流水账

使用proguard混淆springboot代码

本文的proguard配置仅仅是根据自身工程的情况来量身定做的,不是通用的,具体的场景还需要根据具体情况对proguard参数进行调整。

61510
来自专栏代码拾遗

​SpringMVC 教程 - Handler Method

由注解@RequestMapping注解修饰的处理请求的函数的签名非常的灵活,可以使用controller函数支持的一系列参数和返回值。

11910
来自专栏轻量级微服务

Spring Boot 注入外部配置到应用内部的静态变量

至此,即可在 Spring Boot 全局任意引用 StaticProperties.CUSTOM_NAME

13030
来自专栏Hongten

Hibernate延迟加载 lazy loading

/**  *  * @author XHW  *  * @date 2011-7-18  *  */ public class HibernateTest...

19920
来自专栏微服务生态

玩转Flume之核心架构深入解析

前段时间我们分享过玩转Flume+Kafka原来也就那点事儿和Flume-NG源码分析-整体结构及配置载入分析这二篇文章,主要介绍了flume的简单使用和配置文...

11630
来自专栏Java与Android技术栈

给 Java 和 Android 构建一个简单的响应式Local Cache

首先,Local Cache 不是类似于 Redis、Couchbase、Memcached 这样的分布式 Cache。Local Cache 适用于在单机环境...

9320
来自专栏锦小年的博客

python学习笔记6.8-类的状态机

说起状态机,很多人可能就要不屑一顾了,无非就是switch case语句嘛,或者是if … else if … else 语句嘛,这一类程序员写的状态机只能说实...

29170
来自专栏nnngu

04 Spring的@Autowired注解、@Resource注解、@Service注解

什么是注解 传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop、事务,这么做有两个缺点: 1、如果所有的内容都配置在.xml文件中,那...

46340
来自专栏jianhuicode

Binder进程间通信详解

39840
来自专栏walterlv - 吕毅的博客

Introducing MSTestEnhancer to make unit test result easy to read

发布于 2018-03-05 06:21 更新于 2018-08...

9010

扫码关注云+社区

领取腾讯云代金券