使用websocket实现聊天的原因
- http单向通讯协议,请求只能是客户端发起,且是无状态的,而websocket是双向通讯协议,可以有服务器发起也可以是客户端发起,用http实现聊天功能一般是通过轮询,但是轮询非常浪费服务器资源,而且慢,亲测过
- 重点websocket实现简单,速度快
上代码
后台代码:采用websocket的注解
package com.xh.procourt.controller;
/**
* Created: Administrator
* author:xmf
* Date:2018/7/20
* Time:9:49
*/
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.web.context.ContextLoader;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,value是客户端连接地址,这里定义了两个参数,和springmvc的配置指定格式的URL,
* 映射到对应的参数是一样的
*/
@ServerEndpoint(value = "/websocket/{msg_me}")//参数格式 自定义连接标识
public class WebSocketCt {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识类似于扣扣号,map是线程不安全的
private static Map<String, Session> webSocketSet = new HashMap<>();
/**
* 连接建立成功调用的方法
*
* @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据 @PathParam用来获取连接路径上的参数
*/
@OnOpen
public void onOpen(@PathParam(value = "msg_me") String param, Session session) {
webSocketSet.put(param, session); //加入map中
addOnlineCount(); //在线数加1
System.out.println("标识为----" + param + "----的用户连接成功,当前在线人数:::" + getOnlineCount());
//用map构造消息格式 懒得创建消息实体类
Map<String, Object> map = new HashMap<>();
map.put("senderId", "system");
map.put("msgContent", "欢迎连接");
//由于这里使用的string的消息类型所以在这里转成json,当然还有其它消息类型
try {
sendMessage(JSON.toJSON(map).toString(), session);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(@PathParam(value = "msg_me") String param) {
//根据自定义的标识移除此用户的连接
webSocketSet.remove(param);
subOnlineCount(); //在线数减1
System.out.println("标识为----" + param + "----的用户断开连接成功,当前在线人数:::" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息 格式发送人_接收人_消息
* @param session 可选的参数
*/
@OnMessage
public void onMessage(String message, Session session, @PathParam(value = "msg_me") String fromId) {
System.out.println("收到消息:::" + message.toString());
//将消息装成json对象
JSONObject msgObj = JSON.parseObject(message);
//得到前台传过来的消息接收者id
String toId = msgObj.getString("toId");
//得到发送者标识
String senderId = msgObj.getString("senderId");
//得到消息内容
String msgContent = msgObj.getString("msgContent");
for (String s : webSocketSet.keySet()) {
if (s.equals(toId)) {
Session session1 = webSocketSet.get(s);
try {
//用map构造消息格式 懒得创建消息实体类
Map<String, Object> map = new HashMap<>();
map.put("senderId", fromId);
map.put("msgContent", msgContent);
//由于这里使用的string的消息类型所以在这里转成json,当然还有其它消息类型
sendMessage(JSON.toJSON(map).toString(), session1);
} catch (IOException e) {
e.printStackTrace();
}
return;
}
}
}
/**
* 发生错误时调用
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
/**
* 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。作用是发送消息
*
* @throws java.io.IOException
* @param: message要发送的消息 session接收者的会话
*/
public static void sendMessage(String message, Session session) throws IOException {
session.getBasicRemote().sendText(message);
}
/**
* 功能描述:用来返回当前在线的总人数
*
* @param:
* @return:
* @auther: Administrator
* @date: 2018/7/20 9:37
*/
public static synchronized int getOnlineCount() {
return onlineCount;
}
/**
* 功能描述:在线总人数加一
*
* @param:
* @return:
* @auther: Administrator
* @date: 2018/7/20 9:37
*/
public static synchronized void addOnlineCount() {
WebSocketCt.onlineCount++;
}
/**
* 功能描述:在线总人数减一
*
* @param:
* @return:
* @auther: Administrator
* @date: 2018/7/20 9:38
*/
public static synchronized void subOnlineCount() {
WebSocketCt.onlineCount--;
}
}
前端代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<title>简易实现点对点聊天</title>
<body>
<div>
<input id="connectId" type="text">
<button type="button" id="connect">连接到聊天室</button>
</div>
<div id="msgInfo">
</div>
<div style="margin-top: 50px">
<div>
<div><span>接收者id:</span><input type="text" id="toId">
<span>要发送的消息:</span><input type="text" id="msg"></input>
<button id="send" type="button">发送消息</button>
</div>
</div>
</div>
</body>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script>
var websocket = null;
var conect = null;
//点击连接
$("#connect").click(function () {
//判断当前连接是否为空
if (websocket != null) {
alert("您已经连接了,请不要重复连接!");
return;
}
//自定义的唯一标识,如果两个人输入的连接标识相同则会覆盖原来这个标识对应的连接,这里只是为了简单演示
var connectId = $("#connectId").val();
if (connectId == null || connectId == '') {
alert("请先输入您的连接标识!");
return
}
//开始连接
// 首先判断是否 支持 WebSocket
if ('WebSocket' in window) {
//路径ws + ip + port + 自定义路径
conect = connectId;
websocket = new WebSocket("ws://192.168.10.20:8080//websocket/" + connectId);
} else {
alert("浏览器不支持连接!")
return;
}
// 打开时
websocket.onopen = function (evnt) {
console.log(" websocket.onopen ");
alert("连接成功!");
};
// 处理消息时
websocket.onmessage = function (evnt) {
//将消息转成json格式
var msg = JSON.parse(evnt.data);
$("#msgInfo").append("<p>收到--" + msg.senderId + "--给您发消息:<font color='red'>" + msg.msgContent + "</font></p>");
console.log(" websocket.onmessage ");
};
websocket.onerror = function (evnt) {
websocket == null;
console.log(" websocket.onerror ");
};
websocket.onclose = function (evnt) {
console.log(" websocket.onclose ");
websocket.close();
alert("连接关闭!");
};
});
//发送消息
$("#send").click(function () {
//先判断是否连接
if (websocket == null) {
alert("您还没有连接!!!");
return;
}
//接收者id
var toId = $("#toId").val();
//发送的消息内容
var msg = $("#msg").val();
if (toId == null || toId == '') {
alert("请先输入接收者");
return;
}
if (msg == null || msg == '') {
alert("消息不能为空!!!");
return;
}
//发送消息
//构造消息的json格式
var msgJson = {
senderId: conect,
msgContent: msg,
toId: toId
};
websocket.send(JSON.stringify(msgJson));
});
</script>
</html>
运行效果截图