一、前言
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
最近做的系统中,也涉及到websocket的使用,打算在这里记录一下相关知识,本篇博客主要包括:
- spring boot2.x整合websocket服务端;
- websocket客户端java代码版;
- websocket客户端HTML网页版。
二、服务端代码实现
- 创建一个spring boot工程,项目结构如下:
- 导入相关maven依赖:
<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-starter-test</artifactId>
<scope>test</scope>
</dependency>
- 自定义一个WebSocketServer服务类:
package com.learn.websocket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ServerEndpoint("/websocketlearn/{id}") // websocket连接url
@Component
public class WebSocketServer {
private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);
//存储当前server对象到map集合中
private static final Map<String, WebSocketServer> clientMap = new ConcurrentHashMap<String, WebSocketServer>();
// 当前会话
private Session session;
/**
* websocket建立连接成功时调用
*
* @param id
*/
@OnOpen
public void onOpen(@PathParam("id") String id, Session session) {
log.info("连接成功!");
this.session = session;
clientMap.put(id, this);
}
/**
* 关闭连接时调用
*/
@OnClose
public void onClose(Session session) {
log.info("关闭连接");
}
/**
* 接收到客户端消息以后调用
*
* @param message
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("接收到消息:" + message);
}
/**
* 发生异常时调用
*
* @param throwable
*/
@OnError
public void onError(Session session, Throwable throwable) {
log.info("连接异常!" + throwable);
}
/**
* 向指定id的客户端发送消息
*
* @param id
* @param message
*/
public void sendMessageToId(String id, String message) {
WebSocketServer webSocketServer = clientMap.get(id);
if (webSocketServer != null) {
webSocketServer.session.getAsyncRemote().sendText(message);
}
}
/**
* 给所有用户进行推送信息
*
* @param message 推送的信息
*/
public void sendMessageAll(String message) {
for (WebSocketServer item : clientMap.values()) {
item.session.getAsyncRemote().sendText(message);
}
}
}
- 增加websocket配置类WebSocketConfig:
@Configuration
public class WebSocketConfig {
/**
* 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
- 编写WebSocketController用于调用websocket服务端:
@RestController
@RequestMapping("/wsServer")
public class WebSocketController {
@Autowired
public WebSocketServer webSocketServer;
/**
* 给指定的用户推送信息
*
* @param id
* @param message
*/
@GetMapping("/sendToClient")
public void sendTo(String id, String message) {
webSocketServer.sendMessageToId(id, message);
}
/**
* 给所有用户推送信息
*
* @param msg 想要推送给用户的信息
*/
@GetMapping("/sendAllClient")
public void sendAll(String msg) {
webSocketServer.sendMessageAll(msg);
}
}
三、客户端代码实现(java代码版)
- 创建一个spring boot项目,项目结构如下:
- 导入相关maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
- 修改当前客户端项目的端口,application.properties:
server.port=8081
- 编写websocket客户端,需要继承WebSocketClient,提供一个带参数的构造方法和实现其抽象方法:
package com.learn.websocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
public class MyWebSocketClient extends WebSocketClient {
private static final Logger log = LoggerFactory.getLogger(WebSocketClient.class);
public MyWebSocketClient(URI serverUri) {
super(serverUri);
}
@Override
public void onOpen(ServerHandshake serverHandshake) {
log.info("连接成功!");
}
@Override
public void onMessage(String s) {
log.info("接收到服务端数据:" + s);
}
@Override
public void onClose(int i, String s, boolean b) {
log.info("关闭连接" + s);
}
@Override
public void onError(Exception e) {
log.info("发生异常" + e);
}
}
- 创建websocket相关工具类:
public class WebSocketUtils {
private static final Logger log = LoggerFactory.getLogger(WebSocketUtils.class);
public static WebSocketClient webSocketClient(String id) {
try {
MyWebSocketClient webSocketClient = new MyWebSocketClient(new URI("ws://localhost:8080/websocketlearn/" + id));
webSocketClient.connect();
return webSocketClient;
} catch (URISyntaxException e) {
e.printStackTrace();
log.error("创建websocket连接出现异常!", e);
}
return null;
}
}
- 为了方便测试,编写一个WebSocketController调用websocket:
@RequestMapping("/wsclient")
@RestController
public class WebSocketController {
private static final String CURRENT_ID = "1";
WebSocketClient webSocketClient = WebSocketUtils.webSocketClient(CURRENT_ID);
@RequestMapping("sendhllo")
public void sendfirst(String message) {
if(webSocketClient != null){
webSocketClient.send("hello," + message + "!");
}
}
}
测试
- 分别启动服务端sp-websocket-server工程和客户端sp-websocket-client工程:
客户端启动时输出了日志:
表明,客户端与服务端连接成功。
查看服务端控制台日志:
服务端成功接收到了信息。
已经实现了服务端和客户端正常通信。
四、客户端代码实现(HTML网页版)
编写websocket.html页面:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>webSocketTest</title>
</head>
<body>
<input id="id" type="text" />
<button onclick="oppen()">连接服务器</button>
<hr>
<input id="msg" type="text" />
<button onclick="sendMessage()">发送消息</button>
<hr>
<button onclick="closeWebSocket()">关闭连接</button>
<hr>
<div id="message"></div>
</body>
<script type="text/javascript">
var websocket = null;
//创建连接
function oppen(){
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
var id = document.getElementById('id').value;
if(id) {
//连接WebSocket节点
websockewt = new WebSocket("ws://localhost:8080/websocketlearn/" + id);
}else {
alert('请输入连接服务器的id')
}
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websockewt.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websockewt.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到消息的回调方法
websockewt.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websockewt.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websockewt.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
}
//关闭连接
function closeWebSocket(){
websockewt.close();
}
//发送消息
function sendMessage(){
var message = document.getElementById('msg').value;
websockewt.send(message);
}
</script>
</html>
测试
测试流程跟上面差不多,不做过多赘述,测试结果如下:
客户端:
服务端:
访问链接:http://localhost:8080/wsServer/sendToClient?id=10&message=我很好啊,你呢。
可以看到,正常通信是没有问题的。