springboot+websocket[实现简单群聊功能]

文章参考:

菜鸟教程:
http://www.runoob.com/html/html5-websocket.html

SpringBoot2.0集成WebSocket,实现后台向前端推送信息:
https://blog.csdn.net/moshowgame/article/details/80275084

前言:

WebSocket 是一种在单个 TCP 连接上进行全双工通讯的协议。

全双工通讯协议:
又称为双向同时通信,即通信的双方可以同时发送和接收信息的信息交互方式。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

前端:

浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
创建websocket对象:

var Socket = new WebSocket(url, [protocol] );

websocket属性:

Socket.readyState//只读属性 readyState 表示连接状态
/*
0 - 表示连接尚未建立。
1 - 表示连接已建立,可以进行通信。
2 - 表示连接正在进行关闭。
3 - 表示连接已经关闭或者连接不能打开。
*/
Socket.bufferedAmount//只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。

WebSocket 事件:

事件		函数				描述
open		Socket.onopen		连接建立时触发
message		Socket.onmessage	客户端接收服务端数据时触发
error		Socket.onerror		通信发生错误时触发
close		Socket.onclose		连接关闭时触发

WebSocket 方法:

方法				描述
Socket.send()		使用连接发送数据
Socket.close()		关闭连接

本质:
WebSocket 协议本质上是一个基于 TCP 的协议。
为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息"Upgrade: WebSocket"表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道*的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。

Websocket 使用 ws 或 wss 的统一资源标志符,类似于 HTTPS,其中 wss 表示在 TLS 之上的 Websocket。如:
ws://example.com/wsapi
wss://secure.example.com/
Websocket 使用和 HTTP 相同的 TCP 端口,可以绕过大多数防火墙的限制。默认情况下,Websocket 协议使用 80 端口;运行在 TLS 之上时,默认使用 443 端口。

WebSocket的JavaScript实例:

 <script type="text/javascript">
         
            if ("WebSocket" in window){
               alert("您的浏览器支持 WebSocket!");
               
               // 打开一个 web socket
               var ws = new WebSocket("ws://localhost:9998/echo");
                
               ws.onopen = function(){
                  // Web Socket 已连接上,使用 send() 方法发送数据
                  ws.send("这里是发送的内容!");//发送数据
                  alert("数据发送中...");
               };
                
               ws.onmessage = function (evt) { 
                  var received_msg = evt.data;//接受的数据
                  alert("数据已接收...");
               };
                
               ws.onclose = function() { //关闭连接时调用
                  // 关闭 websocket
                  alert("连接已关闭..."); 
               };
               
            }else{
               // 浏览器不支持 WebSocket
               alert("您的浏览器不支持 WebSocket!");
            }
         
      </script>

当然接下来整合的时候不会使用上边的代码,上边只是一个案例.

后端:

依赖:

<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>
	<groupId>com.lcf.websocket-test</groupId>
	<artifactId>websocket-test</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>websocket-test</name>
	<description>websocket-test</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.3.RELEASE</version>
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<!-- spring-web -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- websocket-starter -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>

		<!-- springboot热部署 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>

		<!-- springboot测试 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<!-- 打包 -->
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

项目结构:
springboot+websocket[实现简单群聊功能]
WebSocketConfig:

package com.lcf.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @author : lichenfei
 * @date : 2019年3月19日
 * @time : 下午3:13:07
 *       <p>
 *       开启websocket支持
 */
@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
	return new ServerEndpointExporter();
    }

}

WebSocketServer:

package com.lcf.server;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.springframework.stereotype.Component;

/**
 * @author : lichenfei
 * @date : 2019年3月19日
 * @time : 下午3:18:09
 *
 */
@ServerEndpoint("/websocket/{name}")
@Component
public class WebSocketServer {

    // 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
    // concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
    // 与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    //用户名
    private String name;

    /**
     * 连接建立成功调用的方法
     * 
     * @author : lichenfei
     * @date : 2019年3月19日
     * @time : 下午3:25:30
     * @param session
     * @throws IOException
     *
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("name") String name) throws IOException {
	try {
	    System.out.println("This is onOpen......");
	    this.session = session;
	    // 加入set中
	    webSocketSet.add(this);
	    // 在线数加1
	    addOnlineCount();
	    // 设置当前连接的人
	    this.name = name + new SimpleDateFormat("HH:mm:ss").format(new Date());//按照时间设置name
	    // 群发消息
	    for (WebSocketServer item : webSocketSet) {
		try {
		    item.sendMessage(this.name + "上线了,当前人数为:" + getOnlineCount());
		} catch (IOException e) {
		    e.printStackTrace();
		}
	    }
	} catch (Exception e) {
	    System.err.println("建立连接内部方法出现异常......");
	    e.printStackTrace();
	}

    }

    /**
     * 连接关闭调用的方法
     * 
     * @author : lichenfei
     * @date : 2019年3月19日
     * @time : 下午4:20:31
     *
     */
    @OnClose
    public void onClose() {
	webSocketSet.remove(this); // 从set中删除
	subtractOnlineCount(); // 在线数减1
	System.out.println("有一连接关闭......当前在线人数为" + getOnlineCount());
    }

    /**
     * 收到客户端的消息
     * 
     * @author : lichenfei
     * @date : 2019年3月19日
     * @time : 下午4:27:50
     * @param message
     * @param session
     *
     */
    @OnMessage
    public void onMessage(String message, Session session) {
	System.out.println("来自客户端的消息:" + message);
	// 群发消息
	for (WebSocketServer item : webSocketSet) {
	    try {
		item.sendMessage(this.name + ":" + message);
	    } catch (IOException e) {
		e.printStackTrace();
	    }
	}
    }

    /**
     * 群发自定义消息,可以根据name判断给某个用户发送消息
     */
    public static void sendInfo(String message) {
	for (WebSocketServer item : webSocketSet) {
	    try {
		item.sendMessage(message);
	    } catch (IOException ignored) {
	    }
	}
    }

    @OnError
    public void onError(Session session, Throwable error) {
	System.err.println("websocket发生错误......");
	error.printStackTrace();
    }

    public void sendMessage(String message) throws IOException {
	this.session.getBasicRemote().sendText(message);
	//this.session.getAsyncRemote().sendText(message);//两种方式
    }

    private static synchronized int getOnlineCount() {
	return onlineCount;
    }

    private static synchronized void addOnlineCount() {
	WebSocketServer.onlineCount++;
    }

    private static synchronized void subtractOnlineCount() {
	WebSocketServer.onlineCount--;
    }

}

HTML页面:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<style>
body {
	text-align: center;
}

#message {
	min-height: 600px;
	height: 100%;
	width: 500px;
	background: beige;
	margin: auto;
}

#text {
	height: 107px;
	width: 443px;
}

#lala {
	float: left;
}
</style>
<body>
	<h1>Welcome</h1>
	<div id="message"></div>
	<br />
	<textarea id="text"></textarea>
	<button onclick="send()">发送</button>
</body>
<script>
	var websocket = null;
	//判断当前浏览器是否支持WebSocket
	if ('WebSocket' in window) {
		websocket = new WebSocket("ws://192.168.0.119:8889/websocket/啦啦");//注意前缀,ip,端口号 
	} else {
		alert('Not support websocket')
	}

	//连接发生错误的回调方法
	websocket.onerror = function() {
		//setMessageInnerHTML("error");
		console.log("连接发生错误......");
	};

	//连接成功建立的回调方法
	websocket.onopen = function(event) {
		//setMessageInnerHTML("open");
		console.log("连接成功......");
	}

	//接收到消息的回调方法
	websocket.onmessage = function(event) {
		setMessageInnerHTML(event.data);
		console.log("接受到服务端的消息:" + event.data);
	}

	//连接关闭的回调方法
	websocket.onclose = function() {
		//setMessageInnerHTML("close");
		console.log("关闭连接......");
	}

	//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
	window.onbeforeunload = function() {
		websocket.close();
	}

	//将消息显示在网页上
	function setMessageInnerHTML(innerHTML) {
		document.getElementById('message').innerHTML += '<div id = "lala">'
				+ innerHTML + '</div><br/>';
	}

	//关闭连接
	/* function closeWebSocket() {
		websocket.close();
	} */

	//发送消息
	function send() {
		var message = document.getElementById('text').value;
		websocket.send(message);
		//发送完清空
		document.getElementById('text').value = "";
	}
	
	
	
	
</script>

</html>

到这里基本上就完了.
测试:
打开浏览器访问:http://127.0.0.1:8889/html/demo1.html
springboot+websocket[实现简单群聊功能]
然后重新打开两个页面访问:http://127.0.0.1:8889/html/demo1.html
springboot+websocket[实现简单群聊功能]
springboot+websocket[实现简单群聊功能]
然后查看第一个页面:
springboot+websocket[实现简单群聊功能]
查看第二个页面:
springboot+websocket[实现简单群聊功能]
可以看到消息已经成功发送…
接下来尝试发送消息
springboot+websocket[实现简单群聊功能]
然后查看:
springboot+websocket[实现简单群聊功能]
springboot+websocket[实现简单群聊功能]
持续更新…