由于毕设的需要,想要做一个匿名聊天.
一、基本需求
- 一个人发送的消息其他人都能看的见,服务端无需保存消息,未上线的用户看不到历史消息
- 能够区分某条消息是自己还是别人发送的,因为自己发送的消息会用不同颜色显示。
- 能够显示在线的人数,且人数发生变化后能立马变化
二、简介
网络通信通常有三种方式:
- 单向通信:只能A跟B说话,B不能跟A说话
- 半双工通信:A能和B说话,B也能跟A说话,但A和B不能同时说话
- 全双工通信:A和B能够同时说话
传统的Http协议只能由客户端发起,无法做到服务器端主动向客户端推送消息。如果需要知晓服务器的信息,需要客户端维持轮询,很消耗性能。
WebSocket最显著的特点就是能够全双工通信,服务器端能够主动的向客户端推送消息.很适合这种聊天或者推送系统。
WebSocket 教程 - 阮一峰的网络日志
HTML5 WebSocket | 菜鸟教程
三、代码实现
1.后端代码实现
- 导入websocket依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
- 新建webSocket配置类
WebSocketConfig
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter getServerEndpointExporter() {
return new ServerEndpointExporter();
}
}
- 新建WebSocketServer,对于ws请求进行处理,类似于Controller
@Component
@ServerEndpoint(Api.CHAT_ROOM + "/{name}/{uuid}")
@Slf4j
/**
* 通过websocket实现匿名聊天室
*/
public class WebSocketServer {
//通过AtomicInteger控制在线的人数,onOpen时count+1,onClose时count-1
private static AtomicInteger count = new AtomicInteger(0);
//因为每个客户端都会有对应的WebSocketServer,通过ConCurrentHashMap将客户端保存下来,区分客户端的方法是客户端上次昵称的时候还要带上一个uuid,map的key为uuid+name
private static Map<String, WebSocketServer> servers = new ConcurrentHashMap();
private Session session = null;
private String name = null;
//因为是匿名聊天,所以用户名无法区分用户,通过js生成的uuid来区分
private String uuid = null;
/**
* 连接创建成功
*/
@OnOpen
public void onOpen(Session session, @PathParam("name") String name, @PathParam("uuid") String uuid) {
//log.info("创建连接");
this.session = session;
this.name = name;
this.uuid = uuid;
count.incrementAndGet();
servers.put(name + uuid, this);
try {
JSONObject result = new JSONObject();
result.put("count", count);
//将你上线的消息发给所有的在线用户,不能就通过形参中的session发送,那样就只有你只知道你上线了,而要遍历所有的WebSocketServer,拿到每个用户的session,下面的发送消息和下线同理.
if (uuid != null && servers.containsKey(name+uuid)) {
for (WebSocketServer server : servers.values()) {
server.session.getBasicRemote().sendText(result.toString());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 连接关闭
*/
@OnClose
public void onClose() {
count.decrementAndGet();
servers.remove(name + uuid);
if(servers.size()!=0){
JSONObject result = new JSONObject();
result.put("count", count);
for (WebSocketServer server : servers.values()) {
try {
server.session.getBasicRemote().sendText(result.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 接受消息并通过session返回消息
*/
@OnMessage
public void onMessage(String message) throws IOException {
//log.info("onmessage: "+message);
JSONObject result = new JSONObject();
result.put("time", TimeUtil.getTime());
result.put("message", message);
result.put("name", name);
if (uuid != null && servers.containsKey(name+uuid)) {
for (WebSocketServer server : servers.values()) {
server.session.getBasicRemote().sendText(result.toString());
}
}
}
@OnError
public void onError(Session session, Throwable error) {
try {
JSONObject result = new JSONObject();
result.put("count", count);
session.getBasicRemote().sendText(result.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.前端代码实现
前端与后端类似,只需要new WebSocket(url)对象,重写回调方法进行逻辑处理.
bootstrap、jquery、uuidv4.min.js自行下载
chat.html
<html>
<head>
<link rel="shortcut icon" href="/common/images/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="/user/css/bootstrap.min.css">
<link rel="stylesheet" href="/user/css/chat.css">
<title>上海电机学院BBS-电机人自己的BBS</title>
</head>
<body>
<div class="chat">
<div class="panel panel-default">
<div class="panel-heading" id="count"></div>
<div id="div_msg" class="panel-body" style="width: 500px; height: 630px;">
<div id="message-list" style="margin-bottom:10px;position:relative;left:0px;">
</div>
</div>
<div class="panel-heading">
<textarea id="message" class="form-control" style="height:100px;resize:none;" placeholder="发送的内容"></textarea>
<button id="btn_send" type="button" style="width: 100%; margin-top: 5px;" class="btn btn-info">发送</button>
</div>
</div>
</div>
<script src="/common/js/jquery-3.1.1.js"></script>
<script src="/common/js/bootstrap.min.js"></script>
<script src="/common/js/uuidv4.min.js"></script>
<script src="/user/js/chat.js"></script>
</body>
</html>
chat.css
.chat {
width: 500px;
height: 700px;
margin: 50 auto;
}
chat.js
let URL = "ws:localhost:8080";
$(function () {
let name = prompt("请输入您的昵称");
if (name == null || name == "") {
return;
}
//通过uuid区分不同客户端
let socket = new WebSocket(URL + "/api/chat/" + name + "/" + uuidv4());
socket.onopen = function () {
};
socket.onmessage = function (msg) {
var result = JSON.parse(msg.data);
if (null != result.count) {
$("#count").empty().append("在线人数: " + result.count);
}
if (null != result.message && null != result.time && null != result.name) {
//<div style="color:green">你 16:37:33</div>等待服务器Websocket握手包...-->
var section = $("<div></div>").css("margin-bottom", "10px");
$("#message-list").append(section);
var element = $("<div></div>");
element.css("color", name == result.name ? "green" : "blue").empty().append(result.name + " " + result.time);
section.append(element);
element.after(result.message);
console.log(result.message)
}
};
socket.onclose = function () {
};
socket.onerror = function () {
}
$("#btn_send").click(function () {
socket.send($("#message").val());
$("#message").val('');
});
});