这里以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}}