注意:学习本文章一定要打开自己的开发工具,代码中有详细的解释。电脑不在身边建议先收藏,方便日后观看。最后祝大家技术突飞猛进,早日拿到心仪的offer。
基础讲解
Spring Boot 服务端
这里我们需要先介绍几个注解
注解名称 | 说明 |
---|---|
@ServerEndpoint | 指定的客户端请求的URI |
@OnOpen | 建立连接成功调用 |
@OnClose | 关闭连接时调用 |
@OnMessage | 收到客户端消息后调用的方法 |
@OnError | 发生错误触发 |
Spring Boot 客户端
注解名称 | 说明 |
---|---|
@OnOpen | 建立连接成功调用 |
@OnClose | 关闭连接时调用 |
@OnMessage | 客户端接收服务端数据时触发 |
@OnError | 发生错误触发 |
创建步骤:
- 自定义一个类继承WebSocketClient
- 声明一个带参的构造方法(参数为ServerEndpoint指定的URI)
例如:ws://localhost:8080/websocketdemo- 将参数通过super(serverUri)传给父类的构造方法;
- 请看下方详细代码
HTML 客户端
//创建WebSocket对象
var websocket = new WebSocket("ws://localhost:8080/websocketdemo");
四大监听参数
事件名称 | 调用方式 | 触发监听事件 |
---|---|---|
open | websocket.onopen | 连接建立时触发 |
message | websocket.onmessage | 客户端接收服务端数据时触发 |
error | websocket.onerror | 通信发生错误时触发 |
close | websocket.onclose | 连接关闭时触发 |
请求方法
方法 | 说明 |
---|---|
websocket.send() | 像服务端发送数据 |
websocket.close() | 关闭连接 |
基础示例代码
Spring Boot服务端
maven引入依赖
<!--WebSocket核心依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.4.1</version>
<scope>compile</scope>
</dependency>
开启服务端
这里主要就是四大监听参数,以及设置URI地址。
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
//这里的服务地址为:ws://localhost:8080/websocketdemo
@ServerEndpoint("/websocketdemo")
@Component
public class MyDemoServer {
/**
* 百度搜索:程序员小哲
* 建立连接成功调用
* @param session
*/
@OnOpen
public void onOpen(Session session) {
System.out.println("连接成功!");
}
/**
* 百度搜索:程序员小哲
* 关闭连接时调用
* @param session
*/
@OnClose
public void onClose(Session session) {
System.out.println("关闭连接!");
}
/**
* 百度搜索:程序员小哲
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(Session session,String message) {
System.out.println("接收消息:"+message);
}
/**
* 百度搜索:程序员小哲
* @param session
* @param throwable
*/
@OnError
public void onError(Session session, Throwable throwable) {
System.out.println("连接异常:{}"+throwable);
}
}
自动注册@ServerEndpoint注解
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
/**
* ServerEndpointExporter 作用
* 这个Bean会自动注册@ServerEndpoint
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
Spring Boot 客户端
稍微复杂,可以使用HTML客户端
maven引入依赖
<!--WebSocket核心依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.4.1</version>
<scope>compile</scope>
</dependency>
创建一个类,继承WebSocketClient
添加一个带参构造
重写下面四个方法
直接copy源码
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
public class MyWebSocketClient extends WebSocketClient {
public MyWebSocketClient(URI serverUri) {
super(serverUri);
}
@Override
public void onOpen(ServerHandshake serverHandshake) {
System.out.println("连接成功!");
}
@Override
public void onMessage(String s) {
System.out.println("接收到服务端数据:"+s);
}
@Override
public void onClose(int i, String s, boolean b) {
System.out.println("关闭连接"+s);
}
@Override
public void onError(Exception e) {
System.out.println("发生异常"+e);
}
}
再自定义一个类,创建一个static静态方法返回一个WebSocketClient对象
直接copy源码
这里就是创建了连接服务器的方法。
import com.xiaozhe.websocket.MyWebSocketClient;
import org.java_websocket.client.WebSocketClient;
import java.net.URI;
import java.net.URISyntaxException;
public class WebSocketDemo {
public static WebSocketClient webSocketClient() {
try {
MyWebSocketClient webSocketClient = new MyWebSocketClient(new URI("ws://localhost:8080/websocketdemo"));
webSocketClient.connect();
return webSocketClient;
} catch (URISyntaxException e) {
e.printStackTrace();
}
return null;
}
}
编写一个Collections类,向服务端发送消息
我们可以使用浏览器url方式访问,也可以通过postman等测试工具来进行访问。
url : localhost:8080/sendfirst
import com.xiaozhe.bo.FirstClient;
import org.java_websocket.client.WebSocketClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class WebSocketController {
WebSocketClient webSocketClient = WebSocketDemo.webSocketClient();
@RequestMapping("sendfirst")
public void sendfirst() {
webSocketClient.send("百度搜索:程序员小哲");
}
}
HTML 客户端
直接copy源码
简单的h5客户端代码,可以创建连接,断开连接,向服务端发送数据。
<!DOCTYPE HTML>
<html>
<head>
<title>WebSocketDemo</title>
</head>
<body>
<button onclick="oppen()">创建链接</button>
<button onclick="closeWebSocket()">断开连接</button>
<hr>
<input id="text" type="text" />
<button onclick="send()">向服务器发送数据</button>
<div id="message"></div>
</body>
<script type="text/javascript">
var websocket = null;
//创建连接
function oppen(){
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
//连接WebSocket节点
websocket = new WebSocket("ws://localhost:8080/websocketdemo");
}
else{
alert('当前浏览器不支持websocket');
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("错误!");
};
//连接成功建立的回调方法
websocket.onopen = function(event){
setMessageInnerHTML("连接成功!");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("连接关闭!");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
}
//关闭连接
function closeWebSocket(){
websocket.close();
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
进阶示例代码
之前的例子是实现单个客户端向服务端发送信息,但是如果服务端向指定的客户端发消息应该怎么做呢。那么请看下面的代码:
建议大家将代码复制到IDE中,便于查看,代码中有详细注释便于理解。
Spring Boot服务端
自动注册@ServerEndpoint注解
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
/**
* ServerEndpointExporter 作用
* 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
编写服务端代码
这里我们在主要的四大监听方法之外加了一些属性和方法。大家可以新建一个Spring boot项目来进行下面的项目。
import com.alibaba.fastjson.JSONObject;
import com.example.demo.zo.Xiao;
import com.example.demo.zo.Zhe;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ServerEndpoint("/websocketdemoxiaozhe")
@Component
public class MyServer {
//将所有客户端对象储存到Map集合中
private static Map<String, MyServer> clients = new ConcurrentHashMap<String, MyServer>();
//会话
private Session session;
//会话唯一标识,使用cid来确定唯一的
private String cid;
/**
* 百度搜索:程序员小哲
* 建立连接成功调用
* @param session
*/
@OnOpen
public void onOpen(Session session) {
System.out.println("连接成功!");
}
/**
* 百度搜索:程序员小哲
* 关闭连接时调用
* @param session
*/
@OnClose
public void onClose(Session session) {
System.out.println("关闭连接!");
}
/**
* 百度搜索:程序员小哲
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(Session session,String message) {
// message是一个json
// 将message进行反序列化
// 详见我的另一篇博客:https://blog.csdn.net/xiaozhezhe0470/article/details/110652012
JSONObject jsonObject = JSONObject.parseObject(message);
Zhe zhe = JSONObject.toJavaObject(jsonObject, Zhe.class);
//由于我们是在创建之后才提交cid,所以我们需要在获取提交的信息的时候进行判断,查看是哪一次的请求
if (zhe.getHi() != null){//第二次之后的请求
System.out.println(zhe.getHi());
}else {//第一次请求
Xiao xiao = JSONObject.toJavaObject(jsonObject, Xiao.class);
System.out.println(xiao);
this.cid = xiao.getCid();
this.session = session;
clients.put(cid,this);
try {
//将信息返回到指定用户的页面中
sendMessageTo("恭喜"+xiao.getWork()+xiao.getCname()+"注册成功!您的账号为:"+cid,cid);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 百度搜索:程序员小哲
* @param session
* @param throwable
*/
@OnError
public void onError(Session session, Throwable throwable) {
System.out.println("连接异常:{}"+throwable);
}
/**
* 百度搜索:程序员小哲
* 向指定的客户端发送数据
* @param message 推送的信息
* @param cid 客户id
*/
public void sendMessageTo(String message, String cid) throws IOException {
MyServer myWebSocket = clients.get(cid);
myWebSocket.session.getAsyncRemote().sendText(message);
}
/**
* 百度搜索:程序员小哲
* 给所有用户进行推送信息
* @param message 推送的信息
*/
public void sendMessageAll(String message) throws IOException {
for (MyServer item : clients.values()) {
item.session.getAsyncRemote().sendText(message);
}
}
}
向客户端推送请求
在这里我们新加了一个基础代码里面没有的类,就是给指定的用户推送消息。
import com.example.demo.config.MyServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
public class ServiceToClientDemo {
@Autowired
public MyServer myServer;
/**
* 给指定的用户推送信息
* @param cid 用户注册后弹出的cid
* @param message 想要推送给用户的信息
*/
@GetMapping("/sendToClient")
public void sendTo(String cid,String message) throws IOException {
myServer.sendMessageTo(message,cid);
}
/**
* 给所有用户推送信息
* @param msg 想要推送给用户的信息
*/
@GetMapping("/sendAllClient")
public void sendAll(String msg) throws IOException {
myServer.sendMessageAll(msg);
}
}
所用实体类
引入依赖
使用实体类之前需要引入@Data的依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
第一次请求实体类
import lombok.Data;
@Data
public class Xiao {
private String cid;
private String work;
private String cname;
}
第二次请求实体类
import lombok.Data;
@Data
public class Zhe {
private String hi;
}
HTML客户端
与基础代码相比,多了一个登陆认证。这里需要注意的是,我们需要先创建连接,然后登陆认证,最后在向服务端发送数据,这里没有加验证,不按照顺序来会报错的。
再有一个就是我在两个文本框里面加了默认的JSON大家直接点击即可,方便大家操作。
<!DOCTYPE HTML>
<html>
<head>
<title>Test My WebSocket</title>
</head>
<body>
<button onclick="oppen()">创建连接</button>
<button onclick="closeWebSocket()">断开连接</button>
<hr>
<input id="text01" type="text" value="{ 'cid': '713', 'cname': '小哲', 'work': '程序员' }"/>
<button onclick="first()">登陆认证</button>
<hr>
<input id="text" type="text" value="{ 'hi': '你好呀' }" />
<button onclick="send()">向服务器发送数据</button>
<div id="message"></div>
</body>
<script type="text/javascript">
var websocket = null;
//创建连接
function oppen(){
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
//var message01 = document.getElementById('text01').value;
//连接WebSocket节点
websocket = new WebSocket("ws://localhost:8080/websocketdemoxiaozhe");
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
}
//关闭连接
function closeWebSocket(){
websocket.close();
}
//登陆账号
function first(){
var message01 = document.getElementById('text01').value;
websocket.send(message01);
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
超级进阶示例代码
大家有没有觉得每次创建连接在进行注册非常的麻烦,有没有一种办法可以直接在连接的时候就进行注册呢?那么请看下面的代码。
Spring Boot服务端代码
改动之处
一、 @ServerEndpoint("/websocketdemoxiaozhe01/{cid}")
在这里我们在后面加上了一个参数,就是我们的唯一标识cid。
二、 public void onOpen(@PathParam(“cid”) String cid, Session session) {…}
我们在onOpen方法里面多加了一个参数,注意第一个参数一定要用@PathParam修饰,不然项目跑不起来
然后就没有什么不一样了。直接上代码copy到开发工具上。
maven引入依赖
<!--WebSocket核心依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.4.1</version>
<scope>compile</scope>
</dependency>
自动注册@ServerEndpoint注解
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
/**
* ServerEndpointExporter 作用
* 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
开启服务端代码
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ServerEndpoint("/websocketdemoxiaozhe01/{cid}")
@Component
public class MyDemoServer {
//将当前对象储存到Map集合中
private static Map<String, MyDemoServer> clients = new ConcurrentHashMap<String, MyDemoServer>();
//会话
private Session session;
//会话唯一标识
private String cid;
/**
* 百度搜索:程序员小哲
* 建立连接成功调用
* @param session
*/
@OnOpen
public void onOpen(@PathParam("cid") String cid, Session session) {
System.out.println("连接成功!");
this.cid = cid;
this.session = session;
clients.put(cid,this);
try {
//将信息返回到指定用户的页面中
sendMessageTo("恭喜注册成功!您的账号为:"+cid,cid);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 百度搜索:程序员小哲
* 关闭连接时调用
* @param session
*/
@OnClose
public void onClose(Session session) {
System.out.println("关闭连接!");
}
/**
* 百度搜索:程序员小哲
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(Session session,String message) {
System.out.println(message);
}
/**
* 百度搜索:程序员小哲
* @param session
* @param throwable
*/
@OnError
public void onError(Session session, Throwable throwable) {
System.out.println("连接异常:{}"+throwable);
}
/**
* 百度搜索:程序员小哲
* 向指定的客户端发送数据
* @param message 推送的信息
* @param cid 客户id
*/
public void sendMessageTo(String message, String cid) throws IOException {
MyDemoServer myWebSocket = clients.get(cid);
myWebSocket.session.getAsyncRemote().sendText(message);
}
/**
* 百度搜索:程序员小哲
* 给所有用户进行推送信息
* @param message 推送的信息
*/
public void sendMessageAll(String message) throws IOException {
for (MyDemoServer item : clients.values()) {
item.session.getAsyncRemote().sendText(message);
}
}
}
向客户端推送请求
示例url:http://localhost:8080/sendToClient01?cid=111&message=你好
示例url:http://localhost:8080/sendAllClient01?message=你们好
import com.xiaozhe.mywebsocketserver.server.MyDemoServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
public class ServiceToClientDemo {
@Autowired
MyDemoServer myDemoServer;
/**
* 给指定的用户推送信息
* @param cid 用户注册后弹出的cid
* @param message 想要推送给用户的信息
*/
@GetMapping("/sendToClient01")
public void sendTo(String cid,String message) throws IOException {
myDemoServer.sendMessageTo(message,cid);
}
/**
* 给所有用户推送信息
* @param msg 想要推送给用户的信息
*/
@GetMapping("/sendAllClient01")
public void sendAll(String msg) throws IOException {
myDemoServer.sendMessageAll(msg);
}
}
H5 客户端代码
H5客户端唯一的区别就是在创建连接的时候将cid一起传到了后台。
websocket = new WebSocket(“ws://localhost:8080/websocketdemoxiaozhe01/” + message01);
<!DOCTYPE HTML>
<html>
<head>
<title>Test My WebSocket</title>
</head>
<body>
websocketid
<input id="text01" type="text" value="111"/>
<button onclick="oppen()">创建链接</button>
<hr>
<input id="text" type="text" />
<button onclick="send()">向服务器发送数据</button>
<button onclick="closeWebSocket()">断开连接</button>
<div id="message"></div>
</body>
<script type="text/javascript">
var websocket = null;
//创建连接
function oppen(){
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
var message01 = document.getElementById('text01').value;
//连接WebSocket节点
websocket = new WebSocket("ws://localhost:8080/websocketdemoxiaozhe01/" + message01);
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
}
//关闭连接
function closeWebSocket(){
websocket.close();
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
彩蛋(获取源码)
- 有问题可以扫码评论,我会一一进行回复。
- 公众号以更名为:程序员小哲
- 公众号内回复websocket领取本章源码