Java 与 Node.js、 WebSocket 构建聊天系统

目录

  1. 引言
  2. WebSocket 概述
  3. 环境搭建
    • Java 环境配置
    • Node.js 环境配置
  4. 编写 Java WebSocket 服务端
  5. 编写 Node.js WebSocket 服务端
  6. 前端编写
    • HTML 结构设计
    • 前端 JavaScript 实现 WebSocket 连接
  7. 系统测试与部署
  8. 结语

第1章 引言

随着互联网技术的飞速发展,实时通信需求越来越普遍。即时聊天系统作为其中最基础也是最常见的应用,几乎随处可见。本次教程将带你构建一个基于Java和Node.js实现的WebSocket聊天系统,完整展示从后端到前端的实现过程。

第2章 WebSocket 概述

WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据,从而在很多应用场景里替代传统的轮询模式。

第3章 环境搭建

在开始编写代码之前,我们需要先搭建好开发环境。

3.1 Java 环境配置
  1. 下载并安装JDK (Java Development Kit)。建议使用最新版本, 官方下载地址
  2. 设置JAVA_HOME环境变量,指向JDK安装路径。
  3. 配置PATH环境变量,添加JAVA_HOME/bin路径。
3.2 Node.js 环境配置
  1. 下载并安装Node.js, 官网下载地址
  2. 安装完成后,可以通过以下命令确认安装成功:
    node -v
    npm -v
    
    • 1.
    • 2.

第4章 编写 Java WebSocket 服务端

首先,我们使用Java来实现一个简单的WebSocket服务端。项目需要使用javax.websocket包。

定义一个简单的WebSocket服务器:

import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.OnClose;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.logging.Logger;

@ServerEndpoint("/chat")
public class JavaWebSocketServer {

    private static final Logger logger = Logger.getLogger(JavaWebSocketServer.class.getName());

    @OnOpen
    public void onOpen(Session session) {
        logger.info("New connection opened: " + session.getId());
    }

    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        logger.info("New message received: " + message);
        // Echo the message back to the client
        session.getBasicRemote().sendText("Server: " + message);
    }

    @OnClose
    public void onClose(Session session) {
        logger.info("Connection closed: " + session.getId());
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

这个Java WebSocket服务端会监听客户端的连接,并将收到的消息回送给客户端。

接着创建服务器启动类:

import org.glassfish.tyrus.server.Server;

public class WebSocketServerLauncher {

    public static void main(String[] args) {
        Server server = new Server("localhost", 8080, "/ws", JavaWebSocketServer.class);

        try {
            server.start();
            System.out.println("WebSocket server started...");
            Thread.currentThread().join(); // Keep the server running
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            server.stop();
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

确保在你的项目pom.xml文件中添加对Tyrus的依赖:

<dependency>
    <groupId>org.glassfish.tyrus</groupId>
    <artifactId>tyrus-server</artifactId>
    <version>1.13.1</version>
</dependency>
<dependency>
    <groupId>org.glassfish.tyrus</groupId>
    <artifactId>tyrus-container-grizzly</artifactId>
    <version>1.13.1</version>
</dependency>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

启动该类就可以运行Java WebSocket服务了。

第5章 编写 Node.js WebSocket 服务端

接下来,我们使用Node.js来实现一个WebSocket服务端。我们需要引入ws模块。

创建一个文件server.js

const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });

server.on('connection', socket => {
    console.log('New connection opened');

    socket.on('message', message => {
        console.log(`Received: ${message}`);
        socket.send(`Server: ${message}`);
    });

    socket.on('close', () => {
        console.log('Connection closed');
    });
});

console.log('WebSocket server is running on ws://localhost:8080');
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

运行服务器:

node server.js
  • 1.

这样一个简单的WebSocket服务器就搭建好了。

第6章 前端编写

6.1 HTML 结构设计

创建一个简单的HTML文件index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket Chat</title>
</head>
<body>
    WebSocket Chat
    <div id="chatbox"></div>
    <input type="text" id="message" placeholder="Type a message...">
    <button id="send">Send</button>

    <script src="chat.js"></script>
</body>
</html>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
6.2 前端 JavaScript 实现 WebSocket 连接

创建一个文件chat.js

const chatbox = document.getElementById('chatbox');
const messageInput = document.getElementById('message');
const sendButton = document.getElementById('send');

// 根据使用的服务器选择不同的URL
const ws = new WebSocket('ws://localhost:8080/ws/chat'); // 使用Java WebSocket服务器
// const ws = new WebSocket('ws://localhost:8080'); // 使用Node.js WebSocket服务器

ws.onopen = () => {
    chatbox.innerHTML += '<p>Connected to the server</p>';
};

ws.onmessage = event => {
    chatbox.innerHTML += `<p>Server: ${event.data}</p>`;
};

ws.onclose = () => {
    chatbox.innerHTML += '<p>Disconnected from the server</p>';
};

sendButton.addEventListener('click', () => {
    const message = messageInput.value;
    if (message) {
        ws.send(message);
        chatbox.innerHTML += `<p>You: ${message}</p>`;
        messageInput.value = '';
    }
});
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

通过这两个文件,前端可以与WebSocket服务器进行连接并交互。

第7章 系统测试与部署

  1. 启动WebSocket服务器

    • 如果使用Java WebSocket服务器,运行WebSocketServerLauncher类。
    • 如果使用Node.js WebSocket服务器,运行node server.js
  2. 打开浏览器:双击index.html文件,确保浏览器与WebSocket服务器连接成功。

  3. 输入和发送消息:在输入框中输入消息并点击发送按钮,看是否能正常通信。

确保一切均正常工作,这样您已经成功创建了一个完整的WebSocket聊天系统!

好的,我们继续。下面是一些进阶内容,包括如何扩展功能,进行性能优化和安全性增强。

第9章 功能扩展

简单的聊天系统已经基本完成,但一个功能完备的聊天室通常还需要更多的功能支持,如用户管理、消息持久化等。

9.1 增加用户管理

为了实现用户管理,我们可以在WebSocket连接时,要求用户提供用户名,并使用一个HashMap(或类似的数据结构)保存用户信息。

首先,修改Java WebSocket服务器代码:

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

@ServerEndpoint("/chat")
public class JavaWebSocketServer {

    private static final Logger logger = Logger.getLogger(JavaWebSocketServer.class.getName());
    private static Map<Session, String> userSessions = new HashMap<>();

    @OnOpen
    public void onOpen(Session session) {
        // Connection established, wait for username
        try {
            session.getBasicRemote().sendText("Please provide your username:");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        if (!userSessions.containsKey(session)) {
            // Assume the first message is the username
            userSessions.put(session, message);
            session.getBasicRemote().sendText("Welcome " + message + "!");
            return;
        }

        String username = userSessions.get(session);
        logger.info(username + ": " + message);

        // Broadcast the message to all connected users
        for (Session s : userSessions.keySet()) {
            if (s.isOpen() && s != session) {
                s.getBasicRemote().sendText(username + ": " + message);
            }
        }
    }

    @OnClose
    public void onClose(Session session) {
        String username = userSessions.remove(session);
        if (username != null) {
            logger.info(username + " disconnected.");
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.

类似地,修改Node.js服务器代码:

const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });
const clients = new Map();

server.on('connection', socket => {
    let username = null;

    socket.send('Please provide your username:');

    socket.on('message', message => {
        if (!username) {
            username = message;
            clients.set(socket, username);
            socket.send(`Welcome ${username}!`);
            return;
        }

        console.log(`${username}: ${message}`);
        for (let [client, name] of clients.entries()) {
            if (client !== socket) {
                client.send(`${username}: ${message}`);
            }
        }
    });

    socket.on('close', () => {
        console.log(`${username} disconnected`);
        clients.delete(socket);
    });
});

console.log('WebSocket server is running on ws://localhost:8080');
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
9.2 聊天记录存储

实现聊天记录存储可以采用多种方法,这里介绍一种最简单的方法:将聊天记录存储到文件中。可以使用更多高级数据库如MySQL、MongoDB来存储数据。

在Java中保存聊天记录:

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class ChatLogger {

    private static final String LOG_FILE = "chatlog.txt";

    public static void log(String message) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(LOG_FILE, true))) {
            writer.write(message);
            writer.newLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// 调用log()方法来记录消息
@OnMessage
public void onMessage(String message, Session session) throws IOException {
    if (!userSessions.containsKey(session)) {
        // Assume the first message is the username
        userSessions.put(session, message);
        session.getBasicRemote().sendText("Welcome " + message + "!");
        return;
    }

    String username = userSessions.get(session);
    String fullMessage = username + ": " + message;
    logger.info(fullMessage);
    ChatLogger.log(fullMessage);

    // Broadcast the message to all connected users
    for (Session s : userSessions.keySet()) {
        if (s.isOpen() && s != session) {
            s.getBasicRemote().sendText(fullMessage);
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.

Node.js中保存聊天记录:

const fs = require('fs');

function logMessage(message) {
    fs.appendFileSync('chatlog.txt', message + '\n');
}

server.on('connection', socket => {
    let username = null;

    socket.send('Please provide your username:');

    socket.on('message', message => {
        if (!username) {
            username = message;
            clients.set(socket, username);
            socket.send(`Welcome ${username}!`);
            return;
        }

        const fullMessage = `${username}: ${message}`;
        console.log(fullMessage);
        logMessage(fullMessage);

        for (let [client, name] of clients.entries()) {
            if (client !== socket) {
                client.send(fullMessage);
            }
        }
    });

    socket.on('close', () => {
        console.log(`${username} disconnected`);
        clients.delete(socket);
    });
});
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.

第10章 性能优化

  1. 减少广播延迟:可以通过适当的技术手段减少消息的广播延迟,例如使用更高性能的网络库。
  2. 负载均衡:在用户大量增加时,可以使用负载均衡策略来分配客户端请求至多个服务器实例。
  3. 消息队列:使用消息队列(如RabbitMQ、Kafka等)来处理高并发情况下的消息传递。

第11章 安全性增强

  1. 身份验证:在生产环境中,应对用户进行身份验证操作,例如通过JWT(JSON Web Token)进行认证。
  2. 数据加密:使用WSS(WebSocket Secure)协议,以确保客户端与服务端之间的数据传输是加密的。
  3. 防止恶意attack:设置合适的消息格式验证、限制消息频率,以防止DoS(拒绝服务)attack。

第12章 结语

通过本篇教程,我们从零开始构建了一个基于Java和Node.js的WebSocket聊天系统,并进行了功能扩展、性能优化和安全性增强。这些知识不仅能够帮助你掌握WebSocket相关技术,也能为你的实际项目提供一些参考。