本来8,这个模块是想用netty结合websocket,用主从reactor模式搞一个多线程实时通讯,但是一直没搞明白主服务器端口跟netty服务器端口不一样怎么破还有会带来什么影响的问题,所以目前暂时先用websocket搞一个实时通讯,等我搞明白的种种问题就再把netty接进去。
首先pom.xml不能少:
<!--websocket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
然后搞一个WebSocketServer.java:
package com.lin.karley.Netty.WebSocket;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import org.thymeleaf.util.StringUtils;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Description Description
* @Author Karley LIn
* @Date Created in 2020/6/1
*/
@Component
@ServerEndpoint("/chat/{sid}")
@SuppressWarnings("all")
public class WebSocketServer {
//静态变量,记录当前在线连接数
private static int onlineCount = 0;
//concurrent包的线程安全set,用来存放每个客户端对应的mywebsocket对象
private static ConcurrentHashMap<String,WebSocketServer> webSocketMap =new ConcurrentHashMap<>();
//与某客户端连接会话,需要通过它来给客户端发送数据
private Session session;
//接受sid
private String sid="";
/**
* 链接成功调用的方法
*/
@OnOpen
public void onOpen(Session session,@PathParam("sid") String sid){
this.session=session;
this.sid=sid;
if (webSocketMap.containsKey(sid)){
webSocketMap.remove(sid);
webSocketMap.put(sid,this);
}else {
webSocketMap.put(sid,this);
addOnlineCount();
}
try {
JSONObject jsonObject = new JSONObject();
// jsonObject.put("username",sid);
// jsonObject.put("msg","连接成功");
Set<Map.Entry<String, WebSocketServer>> entrySet = webSocketMap.entrySet();
Integer i =0;
for (Map.Entry<String,WebSocketServer> entry:entrySet){
// for (int i=0;i<webSocketMap.size();i++){
jsonObject.put(String.valueOf(i),entry.getKey());
System.out.println(jsonObject);
i++;
// }
// System.out.println(entry.getKey());
// webSocketMap.get(entry.getKey()).sendMessage(jsonObject.toJSONString());
}
for (Map.Entry<String,WebSocketServer> entry:entrySet){
webSocketMap.get(entry.getKey()).sendMessage(jsonObject.toJSONString());
}
}catch (IOException e){
System.out.println("WebSocket IO异常");
}
// System.out.println(this.sid);
// System.out.println(this);
System.out.println("有新链接加入!当前在线人数为"+getOnlineCount());
}
@OnClose
public void onClose(){
if (webSocketMap.containsKey(sid)){
webSocketMap.remove(sid);
subOnlineCount();
}
System.out.println("有人离开!当前在线人数为"+getOnlineCount());
}
@OnMessage
public void onMessage(String message,Session session){
System.out.println("来自客户端的信息"+message);
try {
if (!StringUtils.isEmpty(message)) {
JSONObject jsonObject = JSON.parseObject(message);
jsonObject.put("fromUserId", this.sid);
String toId = jsonObject.getString("toId");
// String msg = jsonObject.getString("msg");
if (!StringUtils.isEmpty(toId) && webSocketMap.containsKey(toId)) {
webSocketMap.get(toId).sendMessage(jsonObject.toJSONString());
webSocketMap.get(sid).sendMessage(jsonObject.toJSONString());
} else {
System.out.println("请求的userId" + toId + "不在该服务器上");
}
}
}catch (Exception e){
e.printStackTrace();
}
}
@OnError
public void onError(Session session,Throwable error){
System.out.println("发生错误");
error.printStackTrace();
}
/**
* 实现服务器主动推送
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException{
this.session.getBasicRemote().sendText(message);
}
/**
* 群发
*/
private void sendAll(String message){
for (String key : webSocketMap.keySet()){
try {
webSocketMap.get(key).sendMessage("连接成功");
}catch (IOException e){
e.printStackTrace();
}
}
}
/**
* 群发自定义消息
*/
public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException{
System.out.println("推送消息到窗口"+sid+",推送内容:"+message);
if (!sid.equals(null) && webSocketMap.containsKey(sid)){
webSocketMap.get(sid).sendMessage(message);
}else{
System.out.println("用户"+sid+"不在线");
}
}
private static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
private static synchronized int getOnlineCount() {
return onlineCount;
}
private static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
}
因为我这个聊天的模式比较特殊,是我作为管理员能有多个聊天选项,而普通用户只能有一个就是我的一个聊天选项,所以我的controller分了两步走
ModuleController.java:
@GetMapping("contactMe")
public String contactMe(Model model){
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();
String username = currentUser.getUsername();
List<User> users;
if (username.equals("karley")){
users = userService.selectAll();
}else{
users = Collections.singletonList(userService.selectByUsername("karley"));
}
// System.out.println(users);
model.addAttribute("users",users);
return "module/contactMe";
}
最后就是聊天页面了,css就省略了,contactMe.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>联系我 -Karley's Blog</title>
<link rel="stylesheet" href="/css/contactMe.css">
</head>
<body>
<div th:replace="~{common/bar::bar(active='contact')}"></div>
<div class="center-nav">
<!--<div class="layui-container">-->
<div class="left-nav layui-col-md3 layui-col-xs3">
<div id="username">
<span shiro:principal property="username"></span>
<div class="liner">
<p>------------联系人------------</p>
</div>
</div>
<div class="customers">
<div th:each="user:${users}" class="customer">
<p th:text="${user.username}"></p>
<p class="onlineStatus" th:id="${user.username}">已离线</p>
</div>
</div>
</div>
<div class="right-nav layui-col-md9 layui-col-xs9">
<div class="chatBox" id="chatBox">
<!--<div class="leftChat" id="sender"></div>-->
<!--<div class="rightChat" id="receiver"></div>-->
</div>
<div class="msgBox">
<form action="" onsubmit="return false">
<textarea name="message" id="message" required lay-verify="required" placeholder="请输入" class="layui-textarea"></textarea>
<div class="buttonGroup">
<button type="button" class="layui-btn" id="send">发送信息</button>
<button type="reset"class="layui-btn layui-btn-warm">清空</button>
</div>
</form>
</div>
</div>
<!--</div>-->
</div>
</body>
<script src="/js/jquery/jquery-3.4.1.min.js"></script>
<script>
jQuery(document).ready(function ($) {
var socket;
if (window.WebSocket) {
var username=$("#username span").text();
socket = new WebSocket("ws://localhost:8080/chat/"+username);
//相当于channelReado,event收到服务器端回送的消息
socket.onmessage=function(ev){
console.log(ev.data);
var Json=JSON.parse(ev.data);
var fromUserId = Json["fromUserId"];
if (fromUserId !=null){
if (fromUserId==username) {
$("#chatBox").append("<div class='rightChat'>"+Json["msg"]+"</div><br><br><br>");
}else{
$("#chatBox").append("<div class='leftChat'>"+Json["msg"]+"</div><br>");
}
}
for (var i in Json){
console.log(i);
console.log(Json[i]);
$("#"+Json[i]).text("在线");
}
};
//相当于链接开启
socket.onopen = function (ev) {
console.log(ev.data);
console.log("当前在线")
};
//链接关闭
socket.onclose = function (ev) {
$(".onlineStatus").text('已离线');
console.log("socket已关闭");
};
$("#send").click(function () {
send();
});
var toUser;
// function getUser(){
// toUser=$(this).childNodes.item(1).attr("id");
// console.log(toUser);
// }
if (username == "karley"){
$(".customer").click(function () {
toUser=$(this).children("p").last().attr("id");
console.log(toUser);
});
}else{
toUser="karley";
}
//发送消息到服务器
function send() {
// if (!window.socket){ //先判断socket是否创建好
// alert("websocket未创建好");
// return;
// }
if (socket.readyState == WebSocket.OPEN) {
//通过socket发送消息
var message = document.getElementById("message").value;
socket.send(JSON.stringify({toId:toUser,msg:message}));
console.log(JSON.stringify({toId:toUser,msg:message}));
$("#message").val("");
}else{
alert("发送失败");
}
}
}else{
alert("你的浏览器不支持websocket");
}
// $("#chatBox").children("div:last-child")[0].scrollIntoView();
});
</script>
</html>
唉我每次都想拿出代码片段分析,又每次不知从何说起,可能这就是理科生的悲哀8,话都不会说了,就只能全部贴上来了。
这是最后的效果啦:
Springboot+Websocket打造实时通讯聊天窗口演示