从零开始,手把手教你实现基于 Websocket 的微服务

2023-05-05 20:01:43 浏览数 (2)

从零开始,手把手教你实现基于 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 的微服务应用。

0 人点赞