基于WebSocket和SpringBoot的群聊天室
引入
- 普通请求-响应方式:例如Servlet中HttpServletRequest和HttpServletResponse相互配合先接受请求、解析数据,再发出响应,处理完成后连接便断开了,没有数据的实时性可言。
- Ajax轮询:客户端定时发送多次Ajax请求,服务器不断响应,时间频率极小,虽然实时性有了卓越提高,但是大多数的请求是没有意义的。
- WebSocket长连接:客户端只需要向服务器发送一次Http请求,与服务器建立一个以sessioId标示的channel,便可以与服务器在自己的管道中实时通讯,连接是不断开的。在此介绍一个基于WebSocket的框架GoEasy,非常的方便简单,大家可以用来实现消息推送、实时聊天等功能。
Demo介绍
- 基于WebSocket的聊天室,可以发送接受消息并实时查看在线用户。
Maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.atguigu</groupId>
<artifactId>spring-boot-websocket-02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-websocket-02</name>
<description>Demo project for Spring Boot</description> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency> <dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>net.sf.ezmorph</groupId>
<artifactId>ezmorph</artifactId>
<version>1.0.3</version>
</dependency> </dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
WebSocket配置文件
- 后台基于SpringBoot非常的方便,只要编写配置类即可,注入的实体Bean为方法名。SpringBoot推荐Thymeleaf进行html渲染,但是老师说Thymeleaf相对于Jsp等其他渲染工具性能较差,咱也不知道为什么。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration
public class MyWebSocketConfig extends WebMvcConfigurerAdapter { @Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
} @Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
}
JavaScript
- 客户端通过js发送socket请求建立连接,连接成功后建立管道。通过onopen、onclose、onmessage等回调函数接收服务器响应的反馈。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>聊天页面</title>
</head>
<body>
<div class="container">
<div class="left">
<div class="top"></div>
<div class="bottom">
<div class="content">
<input type="text" name="content" id="content" value="输入文本内容">
</div>
<input type="button" value="发送" id="send">
</div>
</div>
<div class="right"></div>
</div>
</body>
</html>
<script th:inline="javascript">
window.onload=function(){
var username=[[${username}]];
if("WebSocket" in window){
var webSocket=new WebSocket("ws://10.7.84.48:8080/mywebsocket?username="+username);
//type 0上线 1下线 2聊天信息 3拉取在线用户
webSocket.onopen=function(e){
var data='{"type":"0","username":"'+username+'","content":""}';
webSocket.send(data);
}
window.onbeforeunload=function(){
var data='{"type":"1","username":"'+username+'","content":""}';
webSocket.send(data);
webSocket.close();
}
document.getElementById("send").onclick=function (ev) {
var content=document.getElementById("content").value;
var data='{"type":"2","username":"'+username+'","content":"'+content+'"}';
webSocket.send(data);
document.getElementsByClassName("top")[0]
.innerHTML+="<p><font color='red'>我: </font><font color='#8a2be2'>"+content+"</font></p>";
document.getElementById("content").value="";
var scrollDiv = document.getElementsByClassName('top')[0];
scrollDiv.scrollTop = scrollDiv.scrollHeight;
}
webSocket.onmessage=function (ev) {
var data=ev.data;
var obj=eval('('+data+')');
var type=obj.type;
switch (type) {
case 0:
document.getElementsByClassName("right")[0]
.innerHTML+="<p id="+obj.senSessionId+"><font color='blue'>"+obj.senName+"</font></p>";
break;
case 1:
var id=obj.senSessionId;
var parent=document.getElementsByClassName("right")[0];
var child=document.getElementById(id);
parent.removeChild(child);
break;
case 2:
document.getElementsByClassName("top")[0]
.innerHTML+="<p>"+obj.time.hours+":"+obj.time.minutes+":"+obj.time.seconds+" <font color='#4169e1'>"+obj.senName+": </font><font color='#8a2be2'>"+obj.content+"</font></p>";
var scrollDiv = document.getElementsByClassName('top')[0];
scrollDiv.scrollTop = scrollDiv.scrollHeight;
break;
case 3:
var map=obj.map;
for(var key in map){
document.getElementsByClassName("right")[0]
.innerHTML+="<p id="+key+"><font color='blue'>"+map[key]+"</font></p>";
}
break;
default:
} }
}
}
</script>
<style>
.container{
width: 700px;
height: 500px;
border: 1px solid black;
margin: 0px auto;
}
.left{
width: 75%;
height: 100%;
float: left;
}
.right{
width: 20%;
height: 100%;
float: left;
border-left: 1px solid black;
}
.top{
width: 100%;
height: 75%;
overflow-y: scroll;
}
.bottom{
width: 100%;
height: 25%;
border-top: 1px solid black;
}
.content{
width: 100%;
height: 65%;
border-bottom: 1px solid black;
}
#content{
width: 99%;
height: 92%;
}
#send{
float: right;
width: 70px;
height: 42px;
}
</style>
WebSocket服务端代码
import com.atguigu.springbootwebsocket02.bean.Msg;
import net.sf.json.JSONObject;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
@Component
@ServerEndpoint("/mywebsocket")
public class WebSocketListener {
private static int onlineCount=0; private static synchronized int getOnlineCount(){
return onlineCount;
}
private static synchronized int addOnlineCount(){
return ++onlineCount;
}
private static synchronized int subOnlineCount(){
return --onlineCount;
}
//用于区分每个WebSocket session.getId()
private Session session;
private String username;
private static CopyOnWriteArraySet<WebSocketListener>webSocketListeners
=new CopyOnWriteArraySet<>();
@OnOpen
public void onOpen(Session session){
this.session=session;
webSocketListeners.add(this);
WebSocketListener.addOnlineCount();
try {
this.username= URLDecoder.decode(session.getQueryString().split("=")[1],"utf8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//获取在线用户信息
try {
Map<String,String>map=new HashMap<>();
for(WebSocketListener webSocketListener
:webSocketListeners){
if(!webSocketListener.session.getId().equals(this.session.getId())){
map.put(webSocketListener.session.getId(),webSocketListener.username);
}
}
JSONObject jm=JSONObject.fromObject(map);
JSONObject jsonObject=new JSONObject();
jsonObject.put("type",3);
jsonObject.put("map",jm);
this.session.getBasicRemote().sendText(jsonObject.toString());
}catch (Exception e){
e.printStackTrace();
}
}
@OnClose
public void onClose(Session session){
webSocketListeners.remove(this);
WebSocketListener.subOnlineCount();
}
private void broadcast(String data){
for(WebSocketListener webSocketListener
:webSocketListeners){
try {
if(!webSocketListener.session.getId().equals(this.session.getId())){
webSocketListener.session.getBasicRemote().sendText(data);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
@OnMessage
public void onMessage(String data,Session session){
JSONObject jsonObject=JSONObject.fromObject(data);
Msg msg=new Msg();
msg.setSenName(jsonObject.getString("username"));
msg.setSenSessionId(session.getId());
msg.setType(Integer.parseInt(jsonObject.getString("type")));
msg.setTime(new Date());
msg.setContent(jsonObject.getString("content"));
JSONObject broadcast=JSONObject.fromObject(msg);
broadcast(broadcast.toString());
}
}
其他代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<form action="/enter" method="get">
<table border="1px">
<tr>
<td>User:</td>
<td><input type="text" name="username" value="输入用户名"/></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="登录"/></td>
</tr>
</table>
</form>
</body>
</html>
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; @Controller
public class MyWebSocketHandler { @RequestMapping(value = "/enter",method = RequestMethod.GET)
public String enterChat(
Model model,
@RequestParam(name="username",required = false)String username){
model.addAttribute("username",username);
return "index";
}
}
import java.util.Date; public class Msg {
private String senSessionId;
private String senName;
private String recSessionId="";
private String recName="";
private Date time=new Date();
private String content;
private Integer type; public Integer getType() {
return type;
} public void setType(Integer type) {
this.type = type;
} public String getSenSessionId() {
return senSessionId;
} public void setSenSessionId(String senSessionId) {
this.senSessionId = senSessionId;
} public String getSenName() {
return senName;
} public void setSenName(String senName) {
this.senName = senName;
} public String getRecSessionId() {
return recSessionId;
} public void setRecSessionId(String recSessionId) {
this.recSessionId = recSessionId;
} public String getRecName() {
return recName;
} public void setRecName(String recName) {
this.recName = recName;
} public Date getTime() {
return time;
} public void setTime(Date time) {
this.time = time;
} public String getContent() {
return content;
} public void setContent(String content) {
this.content = content;
} @Override
public String toString() {
return "Msg{" +
"senSessionId='" + senSessionId + '\'' +
", senName='" + senName + '\'' +
", recSessionId='" + recSessionId + '\'' +
", recName='" + recName + '\'' +
", time=" + time +
", content='" + content + '\'' +
", type=" + type +
'}';
}
}
基于WebSocket和SpringBoot的群聊天室的更多相关文章
- 基于Websocket开发的仿微信聊天室
一.运行环境及涉及技术:----------------------------------* Visual Studio 2019* SQL SERVER 2008 R2* .Net FrameWo ...
- SpringBoot 搭建简单聊天室
SpringBoot 搭建简单聊天室(queue 点对点) 1.引用 SpringBoot 搭建 WebSocket 链接 https://www.cnblogs.com/yi1036943655/p ...
- php websocket-网页实时聊天之PHP实现websocket(ajax长轮询和websocket都可以时间网络聊天室)
php websocket-网页实时聊天之PHP实现websocket(ajax长轮询和websocket都可以时间网络聊天室) 一.总结 1.ajax长轮询和websocket都可以时间网络聊天室 ...
- Android基于XMPP Smack openfire 开发的聊天室
Android基于XMPP Smack openfire 开发的聊天室(一)[会议服务.聊天室列表.加入] http://blog.csdn.net/lnb333666/article/details ...
- 基于Tomcat7、Java、WebSocket的服务器推送聊天室
http://blog.csdn.net/leecho571/article/details/9707497 http://blog.fens.me/java-websocket-intro/ jav ...
- Tomcat学习总结(4)——基于Tomcat7、Java、WebSocket的服务器推送聊天室
前言 HTML5 WebSocket实现了服务器与浏览器的双向通讯,双向通讯使服务器消息推送开发更加简单,最常见的就是即时通讯和对信息实时性要求比较高的应用.以前的服务器消息推送大 ...
- 如何用WebSocket实现一个简单的聊天室以及单聊功能
百度百科中这样定义WebSocket:WebSocket协议是基于TCP的一种新的网络协议.它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端.简单的说,We ...
- 基于Server-Sent Event的简单在线聊天室
Web即时通信 所谓Web即时通信,就是说我们可以通过一种机制在网页上立即通知用户一件事情的发生,是不需要用户刷新网页的.Web即时通信的用途有很多,比如实时聊天,即时推送等.如当我们在登陆浏览知乎时 ...
- 通过WebSocket实现一个简单的聊天室功能
WebSocket WebSocket是一个协议,它是是基于TCP的一种新的网络协议,TCP协议是一种持续性的协议,和HTTP不同的是,它可以在服务器端主动向客户端推送消息.通过这个协议,可以在建立一 ...
随机推荐
- zoom在清除浮动中的利用
zoom 是个困惑了好久的元素,今天对它有了个初步的认识 zoom , ie 的专属属性,在其他浏览器中不起作用,它的原本功能是设置或检测对象的缩放比例(只在ie下起作用) 比如 <div ...
- 虚拟机ubuntu和windows共享文件
设置虚拟机ubuntu和windows共享文件(方便复制文件到本机) (需要安装samba) 登陆ubuntu系统后,点击左边的文件夹.右击新建个文件夹. 设置为共享 ubuntu会提示你 ...
- node url
var url = require("url") url模块提供的三个方法: url.parse(urlStr[, parseQueryString][, slashesDenot ...
- java的动态代理原理
之前虽然会用JDK的动态代理,但是有些问题却一直没有搞明白.比如说:InvocationHandler的invoke方法是由谁来调用的,代理对象是怎么生成的,直到前几个星期才把这些问题全部搞明白了. ...
- [codeforces219D]Choosing Capital for Treeland树形dp
题意:给出一棵树,带有向边,找出某个点到达所有点需要反转的最少的边. 解题关键:和求树的直径的思路差不多,将求(父树-子树)的最大值改为求特定值.依然是两次dfs,套路解法. 对树形dp的理解:树形d ...
- fastIO模板
freadIO整理 namespace fastIO{ #define BUF_SIZE 100000 ; inline char nc() { static char buf[BUF_SIZE],* ...
- win32常用代码整理
1.ShellExecute [Use ShellAPI] ShellExecute(Handle, 'open', 'http://www.cnblogs.com/lovelp/', nil, ni ...
- sgu 321 The Spy Network (dfs+贪心)
321. The Spy Network Time limit per test: 0.5 second(s)Memory limit: 65536 kilobytes input: standard ...
- GridView.SelectedIndex
获取或设置 GridView 控件中的选中行的索引. C#复制 [System.ComponentModel.Bindable(true)] public virtual int SelectedIn ...
- python列表逆序三种方法
栗子: # 题目:将一个数组逆序输出. # # 程序分析:用第一个与最后一个交换. import random list =[random.randint(0,100) for _ in range( ...