首页 > 科技 > (建议收藏) | Spring Boot集成WebSocket

(建议收藏) | Spring Boot集成WebSocket

本号主要用于分享企业中常用的技术,更加侧重于实用,欢迎关注,便于浏览其它更多实用的历史文章。

一:HTTP和WebSocket

HTTP : 客户端 -> 服务器端

传统客户端(浏览器)向服务器获取数据只能使用http主动向服务器拉数据,因为http只能从客户端发起请求,没办法从服务器端发起请求。项目中如果要即时的获取消息就只能写个定时器,每隔几秒钟去向服务器发一个http请求,获取最新的数据。http虽然是短连接,但是定时每隔几秒钟去定时发请求,会不断的占用请求,当客户端从服务端没有拉取到数据时,此时这次连接就显的浪费。

WebSocket: 客户端 <-> 服务器端

WebSocket与http最大的不同就是客户端可以主动向服务器端拉取数据,服务器端也可以主动向客户端推送数据,当客户端启动的时候会首先建立一个长连接,当需要的时候服务器端就可以通过该长连接向客户端推送数据。

HTTP与WebSocket的区别

  • HTTP采用http协议,WebSocket采用ws协议
  • HTTP是短连接,连接响应后即断开;WebSocket是长连接(因是长连接所以同时连接的数量不能太大)
  • HTTP只能客户端向服务器发送请求,不能服务器端向客户端发起请求;WebSocket都可以

WebSocket常见使用场景

  1. 聊天
  2. 服务器端向客户端推送消息(如常见的右下角向上弹出个消息、新的未读消息数量等)

二:示例

1. pom.xml

org.springframework.boot

spring-boot-starter-thymeleaf

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-websocket

com.alibaba

fastjson

1.2.51

org.projectlombok

lombok

true

2. Configuration

/**

* 开启WebSocket支持

*/

@Configuration

public class WebSocketConfiguration {

@Bean

public ServerEndpointExporter serverEndpointExporter() {

return new ServerEndpointExporter();

}

}

3. WebSocketServer

@Data

@ToString

@RequiredArgsConstructor

public class Payload {

private String from;

private String to;

private String content;

}

import com.alibaba.fastjson.JSONObject;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Component;

import org.thymeleaf.util.DateUtils;

import javax.websocket.*;

import javax.websocket.server.PathParam;

import javax.websocket.server.ServerEndpoint;

import java.io.IOException;

import java.util.Date;

import java.util.Locale;

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.atomic.AtomicInteger;

/**

* ServerEndpoint 用户定义客户端连接的地址, 可以在路径上指定路径参数

*/

@Slf4j

@Component

@ServerEndpoint("/websocket/{userId}")

public class WebSocketServer {

/** 在线连接数量 */

private static final AtomicInteger onlineCount = new AtomicInteger(0);

private static ConcurrentHashMap sessionMap = new ConcurrentHashMap<>();

/** 当前连接的用户id */

private String userId;

/**

* 连接成功建立时调用该方法

* @param session

* @param userId

*/

@OnOpen

public void onOpen(Session session, @PathParam("userId") String userId) {

sessionMap.put(userId, session);

this.userId = userId;

int currentOnlineCount = onlineCount.incrementAndGet();

log.info("{} 连接创建成功,当前用户id为{}, 当前在线人数{}", now() , userId, currentOnlineCount);

JSONObject succesJson = new JSONObject();

succesJson.put("from", "服务器");

succesJson.put("to", userId);

succesJson.put("content", "WebSocket服务器连接成功!");

sendMessage(session, succesJson.toJSONString());

}

@OnMessage

public void onMessage(String message, Session session) {

sendMessage(session, message);

}

@OnError

public void onError(Session session, Throwable throwable) {

throwable.printStackTrace();

log.error(throwable.toString());

}

@OnClose

public void onClose(Session session) {

sessionMap.remove(this.userId);

int currentOnlineCount = onlineCount.decrementAndGet();

log.info("用户:{} 退出连接,当前连接数为:{}", this.userId, currentOnlineCount);

}

/**

* 向指定会话发送消息

* @param session 当前会话session

* @param message 消息内容

*/

public void sendMessage(Session session, String message) {

Payload payload = JSONObject.parseObject(message, Payload.class);

String toUserId = payload.getTo();

Session targetSession = sessionMap.get(toUserId);

if (targetSession == null) {

log.info("目标用户{} 已退出连接", toUserId);

return;

}

try {

targetSession.getBasicRemote().sendText(now() + " " + message);

} catch (IOException e) {

log.error("发送消息失败, 失败原因为:{}", e);

}

}

/**

* 向指定用户发送消息

* @param userId 用户id

* @param message 消息

*/

public void sendMessage(String userId, String message) {

Session session = sessionMap.get(userId);

if (session != null) {

JSONObject succesJson = new JSONObject();

succesJson.put("from", "服务器");

succesJson.put("to", userId);

succesJson.put("content", message);

this.sendMessage(session, succesJson.toJSONString());

} else {

log.info("用户{} 已退出连接,无法发送消息", userId);

}

}

private String now() {

return DateUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss", Locale.CHINESE);

}

}

4. Controller

@Controller

@RequestMapping("/im")

public class WebSocketController {

@Autowired

private WebSocketServer webSocketServer;

@RequestMapping("/users/{from}/{to}")

public ModelAndView index(@PathVariable("from") String from, @PathVariable("to") String to){

ModelAndView modelAndView = new ModelAndView("index");

modelAndView.addObject("from", from);

modelAndView.addObject("to", to);

return modelAndView;

}

/**

* 模拟服务器端向客户端推送消息

* @param userId

* @param message

*/

@ResponseBody

@RequestMapping("/push/{userId}")

public void mockPushMessageToClient(@PathVariable String userId, String message) {

webSocketServer.sendMessage(userId, message);

}

}

5. html

templates/index.html

Title

var webSocket = null;

var from = [[${from}]];

var to = [[${to}]];

function openSocket() {

if(typeof(WebSocket) == "undefined") {

console.log("您的浏览器不支持WebSocket, 请升级您的浏览器的版本");

}else{

// 连接WebSocket服务器端

var userId = [[${from}]];

webSocket = new WebSocket("ws://localhost:8080/websocket/" + userId);

// 打开事件

webSocket.onopen = function() {

console.log("连接服务器成功。");

};

// 获得消息事件

webSocket.onmessage = function(msg) {

console.log(msg.data);

document.getElementById('message').innerHTML += msg.data + '
';

};

// 关闭事件

webSocket.onclose = function() {

console.log("websocket已关闭");

};

// 错误事件

webSocket.onerror = function(socket, event) {

console.log("websocket发生了错误");

};

// 监听浏览器窗口关闭事件,当关闭窗口时关闭websocket连接,节省连接资源

window.onbeforeunload = function () {

websocket.close();

}

}

}

function sendMessage() {

if(typeof(WebSocket) == "undefined") {

console.log("您的浏览器不支持WebSocket");

}else {

var content = document.getElementById('msg').value;

var payload = {'from': from, 'to': to, 'content': content}

webSocket.send(JSON.stringify(payload));

}

}

本号主要用于分享企业中常用的技术,更加侧重于实用,欢迎关注,便于浏览其它更多实用的历史文章。

本文来自投稿,不代表本人立场,如若转载,请注明出处:http://www.souzhinan.com/kj/107157.html