使用 Go 和 React 构建实时聊天应用程序





5.00/5 (9投票s)
一个使用 Go WebSocket 后端和 React 前端构建的实时聊天应用程序,支持用户之间的即时消息传递。
引言
在本文中,我们将使用 Go 作为后端(WebSocket 服务器)和 React 作为前端,逐步介绍一个实时聊天室应用程序的开发过程。该项目展示了现代 Web 应用程序如何使用 WebSocket 实现即时、实时的通信。
项目概述
该聊天应用程序允许用户实时连接并发送消息。当用户发送消息时,消息会被广播给所有已连接的客户端,无需刷新页面。我们使用 Go 来处理服务器端的 WebSocket 连接,并使用 React 来处理客户端的用户界面渲染。
项目结构
该项目包含两部分
- Go 后端:一个处理实时通信的 WebSocket 服务器。
- React 前端:用于发送和接收消息的基于 Web 的用户界面。
以下是项目结构的概述
├── real-time-chat/ # Go WebSocket server (backend) ├── chatroom/ # React chat application (frontend) └── README.md # Project documentation
Go WebSocket 服务器(后端)
为什么选择 Go?
Go(或 Golang)因其并发模型和对 I/O 操作的高效处理,是构建高性能网络应用程序的优秀语言。对于此项目,Go 的 net/http 包和 gorilla/websocket 包提供了一种高效处理 WebSocket 连接的方式,确保消息的实时交换。
必备组件
1. 安装 VS Code
如果尚未安装,请从官方网站下载并安装 VS Code。
2. 安装 Go
确保您的系统已安装 Go。从Go 网站下载。
安装完成后,通过运行以下命令确认 Go 已正确设置
go version
确保 $GOPATH
和 $GOROOT
在环境变量中已正确配置。
3. 安装 VS Code 的 Go 扩展
- 打开 VS Code。
- 转到扩展面板(位于左侧边栏或按
Ctrl+Shift+X
)。 - 搜索 Go(由 Google 的 Go 团队开发)并安装它。
此扩展将提供代码检查、自动格式化、智能感知和其他开发工具。
4. 设置 Go 工具
安装 Go 扩展后,VS Code 会提示您安装一些额外的 Go 工具(如 gopls
、gofmt
、用于调试的 delve
等)。这些工具可增强您的开发体验。
当提示安装工具时,点击全部安装,或者可以通过运行以下命令手动安装
go install golang.org/x/tools/gopls@latest go install golang.org/x/lint/golint@latest go install github.com/go-delve/delve/cmd/dlv@latest go install golang.org/x/tools/cmd/goimports@latest
5. Go Modules 支持
如果使用 Go modules(用于依赖管理),请确保您位于 Go module 项目中。使用以下命令初始化新模块
go mod init project-name
设置后端
步骤 1:创建 Go WebSocket 服务器
我们首先创建 Go 后端,它会监听 WebSocket 连接,管理活动客户端,并在它们之间广播消息。
以下是 Go 后端关键组件的简化分解
- WebSocket 连接:服务器与客户端建立 WebSocket 连接。
- 客户端管理:服务器使用一个 map 来跟踪已连接的客户端。
- 广播消息:当一个客户端发送消息时,服务器会实时将其广播给所有其他客户端。
步骤 2:实现 Go WebSocket 服务器
- 创建项目目录
首先,为聊天应用程序创建一个文件夹。
mkdir real-time-chat
cd real-time-chat
- 初始化 Go 模块
为了使用 Go modules 进行依赖管理,请使用 go mod
初始化项目
go mod init real-time-chat
- 组织项目结构
real-time-chat/ │ ├── go.mod # For dependency management ├── main.go # Entry point of your app ├── handlers/ │ └── websocket.go # WebSocket-related logic └── public/ └── index.html # Frontend (optional: for testing)
- 创建
main.go
作为应用程序入口点
main.go
文件将作为应用程序的入口点。该文件将设置服务器并处理传入的连接。
以下是 main.go
文件
package main
import (
"log"
"net/http"
"real-time-chat/handlers"
)
func main() {
fs := http.FileServer(http.Dir("./public"))
http.Handle("/", fs)
http.HandleFunc("/ws", handlers.HandleConnections)
log.Println("Server started on :8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
将包名声明为 main
,这意味着它是应用程序的入口点。每个运行的 Go 应用程序都以 main
包开始。
我们导入了
- log:用于记录服务器事件(例如,启动服务器、错误)。
- net/http:提供 HTTP 功能来创建 Web 服务器并处理请求。
- real-time-chat/handlers:一个自定义包,其中定义了 WebSocket 连接逻辑。这是我们之前讨论过的文件(
handlers/websocket.go
)。 http.FileServer
:用于提供静态文件(如 HTML、CSS 和 JS)。在这里,它提供了public/
目录中的文件。http.Handle("/", fs)
:将所有对根 URL(/
)的请求路由到文件服务器,因此当用户访问https://:8080
时,他们将看到public/
文件夹中的静态index.html
文件。
- 创建
handlers/websocket.go
文件用于 WebSocket 逻辑
将 WebSocket 逻辑分离到一个名为 handlers/
的文件夹下的新 websocket.go
文件中。这将使代码更加模块化和有条理。
websocket.go
文件
package handlers
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
var clients = make(map[*websocket.Conn]bool) // Connected clients
var broadcast = make(chan Message) // Channel for broadcasting messages
// Message defines the structure of the messages exchanged
type Message struct {
Username string `json:"username"`
Message string `json:"message"`
Timestamp string `json:"timestamp"`
Typing bool `json:"typing"` // Indicates if the user is typing
}
// HandleConnections handles new WebSocket requests from clients
func HandleConnections(w http.ResponseWriter, r *http.Request) {
ws, err := upgrader.Upgrade(w, r, nil) // Upgrade HTTP to WebSocket
if err != nil {
log.Fatal(err)
}
defer ws.Close() // Close the WebSocket connection when the function returns
clients[ws] = true // Register the new client
for {
var msg Message
// Read a new message as JSON and map it to a Message object
err := ws.ReadJSON(&msg)
if err != nil {
log.Printf("error: %v", err)
delete(clients, ws) // Remove the client from the list if there is an error
break
}
// Send the message to the broadcast channel
broadcast <- msg
}
}
// HandleMessages broadcasts incoming messages to all clients
func HandleMessages() {
log.Println("HandleMessages running")
for {
// Get the next message from the broadcast channel
msg := <-broadcast
// Send it to every connected client
for client := range clients {
err := client.WriteJSON(msg) // Write message to the client
if err != nil {
log.Printf("error: %v", err)
client.Close() // Close the connection if there's an error
delete(clients, client) // Remove the client
}
}
}
}
http.HandleFunc
:在/ws
处注册一个新的 WebSocket 连接路由。当客户端连接到ws://:8080/ws
时,此路由会处理它。handlers.HandleConnections
:此函数(在websocket.go
中定义)处理每个客户端的 WebSocket 连接。它将 HTTP 连接升级为 WebSocket 连接。log.Println("Server started on :8080")
:记录一条消息,表明服务器已启动并正在运行。http.ListenAndServe(":8080", nil)
:在端口 8080 上启动 HTTP 服务器。第一个参数(:8080
)指定地址(在此情况下为端口 8080),第二个参数(nil
)表示它将使用默认的ServeMux
来处理路由。
- 添加一个用于测试的前端
要测试 WebSocket 功能,只需在 public/
文件夹中添加一个 index.html
文件,其中包含基本的 HTML/JS 来连接到 WebSocket。
以下是 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Real-Time Chat</title>
</head>
<body>
<h2>WebSocket Chat</h2>
<div id="messages"></div>
<input id="username" type="text" placeholder="Username" />
<input id="message" type="text" placeholder="Message" />
<button onclick="sendMessage()">Send</button>
<script>
const socket = new WebSocket('ws://:8080/ws');
socket.onmessage = function(event) {
const messages = document.getElementById('messages');
const message = document.createElement('div');
message.textContent = event.data;
messages.appendChild(message);
};
function sendMessage() {
const username = document.getElementById('username').value;
const message = document.getElementById('message').value;
socket.send(JSON.stringify({username: username, message: message}));
}
</script>
</body>
</html>
运行 Go 后端
要运行 Go 服务器
cd real-time-chat/ go run main.go
服务器现在将监听 ws://:8080/ws 上的 WebSocket 连接。
然后访问 https://:8080
React 前端
为什么选择 React?
React 是一个流行的用于构建用户界面的 JavaScript 库。其组件化的架构允许高效的 UI 更新,非常适合需要即时显示消息的实时应用程序。
设置前端
步骤 1:创建一个新的 React 应用
我们使用 create-react-app(带有 TypeScript)创建了前端。前端连接到 WebSocket 服务器并监听消息以实时显示。
npx create-react-app chatroom --template typescript
cd chatroom
步骤 2:为聊天添加表情符号选择器
npm install emoji-mart
步骤 3:创建 ChatRoom 组件
以下是 React 组件的分解
WebSocket 连接:客户端连接到 Go WebSocket 服务器。
状态管理:应用程序使用 React 的 useState 和 useEffect hooks 来管理消息和 WebSocket 连接。
实时更新:当服务器广播消息时,前端会立即显示。
在 src/
文件夹内,创建一个名为 ChatRoom.tsx
的新文件。
以下是 ChatRoom.tsx
import React, { useState, useEffect, useRef, ChangeEvent, KeyboardEvent } from "react";
import Picker from '@emoji-mart/react';
import data from '@emoji-mart/data';
interface Message {
username: string;
message: string;
timestamp: string;
typing: boolean;
}
const ChatRoom: React.FC = () => {
const [username, setUsername] = useState<string>("");
const [message, setMessage] = useState<string>("");
const [chat, setChat] = useState<Message[]>([]);
const [typingUser, setTypingUser] = useState<string | null>(null);
const [ws, setWs] = useState<WebSocket | null>(null);
const [showPicker, setShowPicker] = useState<boolean>(false);
const messageRef = useRef<HTMLInputElement>(null);
// WebSocket connection
useEffect(() => {
const socket = new WebSocket("ws://:8080/ws");
socket.onmessage = (event) => {
const messageData: Message = JSON.parse(event.data);
if (messageData.typing && messageData.username !== username) {
setTypingUser(messageData.username); // Show who is typing
} else if (!messageData.typing) {
setChat((prevChat) => [...prevChat, messageData]);
setTypingUser(null); // Stop showing the typing indicator
}
};
setWs(socket);
// Cleanup WebSocket connection
return () => {
socket.close();
};
}, [username]);
// Handle sending the message
const sendMessage = () => {
if (ws && message && username) {
const timestamp = new Date().toLocaleTimeString();
const msg: Message = { username, message, timestamp, typing: false };
ws.send(JSON.stringify(msg));
setMessage("");
}
};
// Detect when Enter key is pressed
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
sendMessage();
}
};
// Handle message input change
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setMessage(e.target.value);
if (ws && username) {
const typingMessage: Message = { username, message: "", timestamp: "", typing: true };
ws.send(JSON.stringify(typingMessage));
}
};
// Add emoji to the message
const addEmoji = (emoji: any) => {
setMessage((prevMessage) => prevMessage + emoji.native);
setShowPicker(false);
};
return (
<div className="chatroom-container">
<div className="chatbox">
<h2>Chat Room</h2>
<div className="chat-inputs">
<input
type="text"
placeholder="Enter your username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div className="chat-window">
{typingUser && <div className="typing-indicator">{typingUser} is typing...</div>}
{chat.map((msg, index) => (
<div
key={index}
className={`chat-message ${msg.username === username ? "own-message" : ""}`}
>
<div className="chat-message-info">
<img
src={`https://avatars.dicebear.com/api/initials/${msg.username}.svg`}
alt="avatar"
className="chat-avatar"
/>
<strong className="username">{msg.username}</strong>
<span className="timestamp"> at {msg.timestamp}</span>
</div>
<div>{msg.message}</div>
</div>
))}
</div>
<div className="chat-inputs">
<input
ref={messageRef}
type="text"
placeholder="Enter your message"
value={message}
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
<button onClick={sendMessage}>Send</button>
<button onClick={() => setShowPicker(!showPicker)}>😊</button>
{showPicker && <Picker data={data} onEmojiSelect={addEmoji} />}
</div>
</div>
</div>
);
};
export default ChatRoom;
-
WebSocket 连接:
- 当组件挂载时(
useEffect
),将建立到 Go 后端的 WebSocket 连接,地址为ws://:8080/ws
。 - WebSocket 监听传入消息(
socket.onmessage
)并更新聊天历史记录。 - 当组件卸载时,WebSocket 连接会关闭以清理资源。
- 当组件挂载时(
-
发送消息:
sendMessage
函数将用户消息和用户名作为 JSON 对象通过 WebSocket 发送。- 捕获 Enter 键按下事件,以便用户在按下 Enter 键时发送消息。
-
显示聊天:
chat
状态存储整个聊天历史记录。- 每条新消息都会被添加到
chat
并渲染到聊天窗口中。
步骤 4:将 ChatRoom 组件添加到 App.tsx
import ChatRoom from "./ChatRoom";
import './App.css';
function App() {
return (
<div className="App">
<ChatRoom />
</div>
);
}
export default App;
步骤 4:添加 CSS 样式
在 App.css
中为聊天室添加样式
/* Center the chatroom on the page */
.chatroom-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f9f9f9;
}
.chatbox {
width: 500px;
background-color: #fff;
padding: 20px;
border-radius: 10px;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
}
h2 {
text-align: center;
font-size: 1.5em;
margin-bottom: 20px;
}
/* Adjust the input layout */
.chat-inputs {
margin-bottom: 10px;
display: flex;
justify-content: space-between;
}
.chat-inputs input[type="text"] {
flex-grow: 1;
padding: 10px;
border-radius: 5px;
border: 1px solid #ccc;
margin-right: 10px;
}
.chat-inputs button {
padding: 10px 15px;
border: none;
background-color: #007bff;
color: #fff;
border-radius: 5px;
cursor: pointer;
}
.chat-inputs button:hover {
background-color: #0056b3;
}
/* Emoji button */
.chat-inputs button:nth-child(3) {
background-color: #ffcc00;
}
.chat-inputs button:nth-child(3):hover {
background-color: #e6b800;
}
/* Chat window styling */
.chat-window {
height: 300px;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
overflow-y: auto;
margin-bottom: 10px;
}
.chat-message {
display: flex;
flex-direction: column;
padding: 10px;
border-radius: 8px;
margin-bottom: 10px;
}
.own-message {
background-color: #d1ffd1;
align-self: flex-end;
}
.chat-message-info {
display: flex;
align-items: center;
}
.username {
margin-right: 5px; /* Add some space between username and timestamp */
}
.timestamp {
margin-left: 5px; /* Ensure a small space between 'at' and the timestamp */
}
.chat-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 10px;
}
.typing-indicator {
font-style: italic;
color: gray;
}
/* Adjust button styles */
button {
cursor: pointer;
}
运行 React 前端
要启动 React 前端
cd chatroom/
npm start
前端将可通过 https://:3000 访问。
使用 WebSocket 进行实时消息传递
当消息从前端发送时,它会通过 WebSocket 传输到 Go 服务器,然后服务器将消息广播给所有已连接的客户端。WebSocket 支持全双工通信,确保消息即时接收,从而提供无缝的实时聊天体验。
示例工作流程
- 用户 A 在聊天框中输入消息并按“发送”。
- 消息通过 WebSocket 连接发送到 Go 服务器。
- 服务器将消息广播给所有已连接的客户端(包括用户 A)。
- 所有客户端实时更新其聊天窗口以显示新消息。
结论
在本文中,我们介绍了如何使用 Go 作为后端和 React 作为前端来构建一个简单的实时聊天应用程序。该应用程序利用 WebSocket 实现客户端之间的实时通信。Go 和 React 都是功能强大的技术,可用于构建可扩展、高性能的应用程序。