使用 WebSockets 创建一个简单的聊天应用程序
使用 WebSockets 创建一个简单的聊天应用程序
您将学到什么
在本教程中,我们将创建一个简单的实时聊天应用程序。它将包含一个聊天面板(用于存储您加入后收到的消息)、一个当前连接的用户列表以及一个用于发送消息的输入框。我们将使用 WebSocket 来实现这一点,因为 WebSocket 通过单个 TCP 连接为我们提供了全双工通信通道,这意味着我们无需发出额外的 HTTP 请求即可发送和接收消息。WebSocket 连接始终保持打开状态,从而大大降低了延迟(和复杂性)。
依赖项
首先,我们需要创建一个带有一些依赖项的 Maven 项目:(→ 教程)
<dependencies>
<dependency>
<groupId>io.javalin</groupId>
<artifactId>javalin-bundle</artifactId>
<version>6.6.0</version>
</dependency>
<dependency>
<groupId>com.j2html</groupId>
<artifactId>j2html</artifactId>
<version>1.6.0</version>
</dependency>
</dependencies>
Javalin 应用程序
Javalin 应用程序非常简单。我们需要:
- 用于跟踪会话/用户名对的地图。
- 用户数量计数器(昵称自动增加)
- 用于连接/消息/关闭的 websocket 处理程序
- 向所有用户广播消息的方法
- 一种以 HTML(或 JSON,如果您愿意)创建消息的方法
import io.javalin.Javalin;
import io.javalin.http.staticfiles.Location;
import io.javalin.websocket.WsContext;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static j2html.TagCreator.article;
import static j2html.TagCreator.attrs;
import static j2html.TagCreator.b;
import static j2html.TagCreator.p;
import static j2html.TagCreator.span;
public class JavalinWebsocketExampleApp {
private static final Map<WsContext, String> userUsernameMap = new ConcurrentHashMap<>();
private static int nextUserNumber = 1; // Assign to username for next connecting user
public static void main(String[] args) {
Javalin app = Javalin.create(config -> {
config.staticFiles.add("/public", Location.CLASSPATH);
config.router.mount(router -> {
router.ws("/chat", ws -> {
ws.onConnect(ctx -> {
String username = "User" + nextUserNumber++;
userUsernameMap.put(ctx, username);
broadcastMessage("Server", (username + " joined the chat"));
});
ws.onClose(ctx -> {
String username = userUsernameMap.get(ctx);
userUsernameMap.remove(ctx);
broadcastMessage("Server", (username + " left the chat"));
});
ws.onMessage(ctx -> {
broadcastMessage(userUsernameMap.get(ctx), ctx.message());
});
});
});
}).start(7070);
}
// Sends a message from one user to all users, along with a list of current usernames
private static void broadcastMessage(String sender, String message) {
userUsernameMap.keySet().stream().filter(ctx -> ctx.session.isOpen()).forEach(session -> {
session.send(
Map.of(
"userMessage", createHtmlMessageFromSender(sender, message),
"userlist", userUsernameMap.values()
)
);
});
}
// Builds a HTML element with a sender-name, a message, and a timestamp
private static String createHtmlMessageFromSender(String sender, String message) {
return article(
b(sender + " says:"),
span(attrs(".timestamp"), new SimpleDateFormat("HH:mm:ss").format(new Date())),
p(message)
).render();
}
}
构建 JavaScript 客户端
为了演示我们的应用程序可以正常工作,我们可以构建一个 JavaScript 客户端。首先,我们创建 index.html:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WebsSockets</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="chatControls">
<input id="message" placeholder="Type your message">
<button id="send">Send</button>
</div>
<ul id="userlist"> <!-- Built by JS --> </ul>
<div id="chat"> <!-- Built by JS --> </div>
<script src="websocketDemo.js"></script>
</body>
</html>
完成我们的聊天应用程序所需的最后一步是创建websocketDemo.js:
// small helper function for selecting element by id
let id = id => document.getElementById(id);
//Establish the WebSocket connection and set up event handlers
let ws = new WebSocket("ws://" + location.hostname + ":" + location.port + "/chat");
ws.onmessage = msg => updateChat(msg);
ws.onclose = () => alert("WebSocket connection closed");
// Add event listeners to button and input field
id("send").addEventListener("click", () => sendAndClear(id("message").value));
id("message").addEventListener("keypress", function (e) {
if (e.keyCode === 13) { // Send message if enter is pressed in input field
sendAndClear(e.target.value);
}
});
function sendAndClear(message) {
if (message !== "") {
ws.send(message);
id("message").value = "";
}
}
function updateChat(msg) { // Update chat-panel and list of connected users
let data = JSON.parse(msg.data);
id("chat").insertAdjacentHTML("afterbegin", data.userMessage);
id("userlist").innerHTML = data.userlist.map(user => "<li>" + user + "</li>").join("");
}
就这样!现在尝试打开localhost:7070 几个不同的浏览器窗口(可以同时看到),然后自言自语。
结论
嗯,很简单!我们实现了一个无需轮询的实时聊天应用,服务器和客户端代码不到 100 行。虽然实现起来非常简单,我们至少应该将用户列表和消息的发送分开(这样就不用每次有人发送消息时都重建用户列表),但由于本教程的重点是 WebSocket,所以我选择在自己觉得合适的范围内尽可能精简地实现。
作者:Jeebiz 创建时间:2025-05-04 00:22
最后编辑:Jeebiz 更新时间:2025-05-04 00:55
最后编辑:Jeebiz 更新时间:2025-05-04 00:55