从零开始,手把手教你实现基于 Websocket 的微服务
1. Websocket 简介
Websocket 协议是为了解决 HTTP 协议缺陷而产生的一种通信协议,它能够在客户端和服务器之间建立持久性的连接,并且允许双向通信。
HTTP 协议的请求与响应模式,其实并不适合实时通信的场景。比如聊天室、在线游戏等应用,都需要实时地推送消息到客户端,而 HTTP 协议则需要进行频繁的请求和响应操作,这就会导致网络延迟和更多的带宽消耗。
而 Websocket 则是允许服务器主动向客户端发送消息,而不需要客户端发起请求,从而提高了通信效率和实时性。因此,在微服务架构中,Websocket 技术非常适合作为微服务之间的通信方式。
2. 构建基于 Websocket 的微服务应用
2.1 准备工作
首先需要在项目中引入 Spring Boot 的 Websocket 模块依赖:
代码语言:javascript复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
然后创建一个 Spring Boot 的 Web 应用,并在启动类中添加 @EnableWebSocket 注解。
2.2 编写服务端代码
在服务端,需要定义一个 WebsocketConfig 类,并实现 WebSocketConfigurer 接口。在这个类中,可以自定义 Websocket 消息处理器,并注册到 Websocket 服务中。
代码语言:javascript复制@Configuration
@EnableWebSocket
public class WebsocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MyWebSocketHandler(), "/ws").setAllowedOrigins("*");
}
private static class MyWebSocketHandler extends TextWebSocketHandler {
private final List<WebSocketSession> sessions = new ArrayList<>();
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
for (WebSocketSession s : sessions) {
s.sendMessage(message);
}
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
}
}
}
上述代码中,MyWebSocketHandler 是自定义的消息处理器,可以处理客户端发送来的消息,并将消息发送给所有连接的客户端。其中,afterConnectionEstablished() 方法会在客户端和服务器之间建立连接时被调用,afterConnectionClosed() 方法则会在连接关闭时调用。
2.3 编写客户端代码
在客户端,需要构建一个基于 Websocket 的连接,并向服务端发送消息。
代码语言:javascript复制<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Websocket Demo</title>
</head>
<body>
<div id="output"></div>
<input type="text" id="input">
<button onclick="sendMessage()">Send</button>
<script>
const socket = new WebSocket("ws://localhost:8080/ws");
socket.onmessage = function(event) {
const output = document.getElementById("output");
const message = event.data;
output.innerHTML = "<p>" message "</p>";
}
function sendMessage() {
const input = document.getElementById("input");
const message = input.value;
socket.send(message);
}
</script>
</body>
</html>
上述代码中,WebSocket() 构造函数中的 ws://localhost:8080/ws 是服务端的 Websocket 地址。在发送消息时,则是调用 socket.send() 方法向服务端发送消息。而在收到服务端的消息时,则会触发 socket.onmessage() 回调函数,并将消息展示在网页中。
3. 技术实践案例:基于 Websocket 的在线聊天室
3.1 界面设计
本案例采用前后端分离的方式,使用 React 框架构建客户端界面
3.2 服务端实现
3.2.1 WebSocket 配置
创建一个 WebSocketConfig 类,并实现 WebSocketConfigurer 接口。在这个类中,注册自定义的 Websocket 消息处理器,并设置允许跨域请求。
代码语言:javascript复制@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new ChatWebSocketHandler(), "/chat")
.setAllowedOrigins("*")
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
private static class ChatWebSocketHandler extends TextWebSocketHandler {
private final List<WebSocketSession> sessions = new ArrayList<>();
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
for (WebSocketSession s : sessions) {
if (s != session) {
s.sendMessage(message);
}
}
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
}
}
}
上述代码中,ChatWebSocketHandler 是自定义的消息处理器,其中 handleTextMessage() 方法实现了用户发送消息到服务端,并将消息发送给所有连接的客户端。而 afterConnectionEstablished() 和 afterConnectionClosed() 方法则分别在建立连接和关闭连接时被调用。
3.2.2 Spring Security 配置
为了保证聊天室的安全性,需要对聊天室进行认证和授权。使用 Spring Security 可以方便地实现这个功能。
首先,需要添加 Spring Security 的依赖:
代码语言:javascript复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
然后创建一个 SecurityConfig 类,用于配置 Spring Security。
代码语言:javascript复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/chat")
.permitAll()
.and()
.logout()
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("{noop}password").roles("USER");
}
}
上述代码中,configure() 方法用于配置 Spring Security 的认证和授权规则。其中,“/login” 路径不需要认证就可以访问,“/chat” 路径则需要进行认证才能访问。同时,使用 inMemoryAuthentication() 方法可以在内存中定义用户和角色。
3.2.3 Controller 实现
在 Controller 中,需要分别对登录和聊天功能进行处理。
代码语言:javascript复制@Controller
public class ChatController {
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/chat")
public String chat() {
return "chat";
}
}
3.3 客户端实现
客户端使用 React 框架构建,并使用 axios 库进行网络请求。具体代码如下:
代码语言:javascript复制import React, { Component } from "react";
import axios from "axios";
class Login extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
};
}
handleUsernameChange = (event) => {
this.setState({ username: event.target.value });
};
handlePasswordChange = (event) => {
this.setState({ password: event.target.value });
};
handleSubmit = (event) => {
event.preventDefault();
const { username, password } = this.state;
axios
.post("/login", { username, password })
.then((res) => {
this.props.history.push("/chat");
})
.catch((error) => {
console.log(error);
});
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Username:
<input
type="text"
value={this.state.username}
onChange={this.handleUsernameChange}
/>
</label>
<br />
<label>
Password:
<input
type="password"
value={this.state.password}
onChange={this.handlePasswordChange}
/>
</label>
<br />
<button type="submit">Submit</button>
</form>
);
}
}
export default Login;
代码语言:javascript复制import React, { Component } from "react";
import axios from "axios";
class Chat extends Component {
constructor(props) {
super(props);
this.state = {
message: "",
messages: [],
};
}
componentDidMount() {
const socket = new WebSocket("ws://localhost:8080/chat");
socket.onmessage = (event) => {
const message = event.data;
this.setState((prevState) => ({
messages: [...prevState.messages, message],
}));
};
this.socket = socket;
}
componentWillUnmount() {
this.socket.close();
}
handleMessageChange = (event) => {
this.setState({ message: event.target.value });
};
handleSubmit = (event) => {
event.preventDefault();
const message = this.state.message;
this.socket.send(message);
this.setState({ message: "" });
};
render() {
return (
<div>
<ul>
{this.state.messages.map((message, index) => (
<li key={index}>{message}</li>
))}
</ul>
<form onSubmit={this.handleSubmit}>
<input
type="text"
value={this.state.message}
onChange={this.handleMessageChange}
/>
<button type="submit">Send</button>
</form>
</div>
);
}
}
export default Chat;
4. 总结
本文介绍了 Websocket 协议在微服务架构中的应用,并以基于 Websocket 的在线聊天室为例,详细介绍了服务端和客户端的实现方式。通过使用 Spring Boot 和 React 等流行的框架,可以方便地构建高效稳定的基于 Websocket 的微服务应用。