基于Vue+.NET全面實現(xiàn)WebSocket聊天室
更新時間:2025年08月26日 08:41:07 作者:百錦再@新空間
這篇文章主要為大家詳細介紹了如何基于Vue+.NET全面實現(xiàn)WebSocket聊天室,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
實現(xiàn)思路
后端(.NET):使用ASP.NET Core WebSocket實現(xiàn)實時通信
前端(Vue):使用原生WebSocket API與后端通信
功能:實時消息、用戶列表、在線狀態(tài)、消息歷史
完整代碼實現(xiàn)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>.NET + Vue WebSocket聊天室</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #333;
min-height: 100vh;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.chat-container {
width: 90%;
max-width: 1000px;
background: rgba(255, 255, 255, 0.95);
border-radius: 15px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: column;
height: 90vh;
}
.chat-header {
background: #4a6fc0;
color: white;
padding: 20px;
text-align: center;
position: relative;
}
.status-indicator {
position: absolute;
top: 20px;
right: 20px;
display: flex;
align-items: center;
font-size: 14px;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
}
.connected {
background: #2ecc71;
}
.disconnected {
background: #e74c3c;
}
.chat-main {
display: flex;
flex: 1;
overflow: hidden;
}
.users-panel {
width: 250px;
background: #f5f7fa;
border-right: 1px solid #e0e6ed;
padding: 20px;
overflow-y: auto;
}
.users-list {
list-style: none;
}
.user-item {
display: flex;
align-items: center;
padding: 10px;
margin-bottom: 10px;
border-radius: 8px;
background: white;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #4a6fc0;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 10px;
}
.chat-content {
flex: 1;
display: flex;
flex-direction: column;
}
.messages-container {
flex: 1;
padding: 20px;
overflow-y: auto;
background: white;
}
.message {
margin-bottom: 15px;
max-width: 80%;
display: flex;
flex-direction: column;
}
.message-self {
align-self: flex-end;
align-items: flex-end;
}
.message-other {
align-self: flex-start;
align-items: flex-start;
}
.message-bubble {
padding: 12px 15px;
border-radius: 18px;
margin-bottom: 5px;
position: relative;
word-break: break-word;
}
.message-self .message-bubble {
background: #4a6fc0;
color: white;
border-bottom-right-radius: 4px;
}
.message-other .message-bubble {
background: #f0f2f5;
color: #333;
border-bottom-left-radius: 4px;
}
.message-sender {
font-size: 12px;
color: #7f8c8d;
margin-bottom: 3px;
}
.message-time {
font-size: 11px;
color: #95a5a6;
}
.input-area {
padding: 20px;
background: #f5f7fa;
border-top: 1px solid #e0e6ed;
display: flex;
}
.message-input {
flex: 1;
padding: 12px 15px;
border: 1px solid #dce4ec;
border-radius: 25px;
outline: none;
font-size: 16px;
transition: border-color 0.3s;
}
.message-input:focus {
border-color: #4a6fc0;
}
.send-button {
margin-left: 10px;
padding: 12px 25px;
background: #4a6fc0;
color: white;
border: none;
border-radius: 25px;
cursor: pointer;
font-weight: bold;
transition: background 0.3s;
}
.send-button:hover {
background: #3b5998;
}
.send-button:disabled {
background: #acb5c1;
cursor: not-allowed;
}
.login-panel {
padding: 30px;
text-align: center;
background: white;
}
.username-input {
padding: 12px 15px;
border: 1px solid #dce4ec;
border-radius: 25px;
outline: none;
font-size: 16px;
width: 300px;
margin-right: 10px;
}
.join-button {
padding: 12px 25px;
background: #4a6fc0;
color: white;
border: none;
border-radius: 25px;
cursor: pointer;
font-weight: bold;
}
@media (max-width: 768px) {
.chat-main {
flex-direction: column;
}
.users-panel {
width: 100%;
border-right: none;
border-bottom: 1px solid #e0e6ed;
max-height: 150px;
}
.message {
max-width: 90%;
}
}
</style>
</head>
<body>
<div id="app">
<div class="chat-container">
<div class="chat-header">
<h1>WebSocket 聊天室</h1>
<div class="status-indicator">
<div class="status-dot" :class="isConnected ? 'connected' : 'disconnected'"></div>
{{ isConnected ? '已連接' : '未連接' }}
</div>
</div>
<div v-if="!username" class="login-panel">
<h2>請輸入用戶名加入聊天</h2>
<div style="margin-top: 20px;">
<input v-model="inputUsername" @keyup.enter="joinChat" class="username-input" placeholder="用戶名">
<button @click="joinChat" class="join-button">加入聊天</button>
</div>
</div>
<div v-else class="chat-main">
<div class="users-panel">
<h3>在線用戶 ({{ users.length }})</h3>
<ul class="users-list">
<li v-for="user in users" :key="user" class="user-item">
<div class="user-avatar">{{ user.charAt(0).toUpperCase() }}</div>
<div>{{ user }}</div>
</li>
</ul>
</div>
<div class="chat-content">
<div class="messages-container" ref="messagesContainer">
<div v-for="(message, index) in messages" :key="index"
:class="['message', message.sender === username ? 'message-self' : 'message-other']">
<div v-if="message.sender !== username" class="message-sender">{{ message.sender }}</div>
<div class="message-bubble">{{ message.text }}</div>
<div class="message-time">{{ message.time }}</div>
</div>
</div>
<div class="input-area">
<input v-model="inputMessage" @keyup.enter="sendMessage"
class="message-input" placeholder="輸入消息..." :disabled="!isConnected">
<button @click="sendMessage" class="send-button" :disabled="!isConnected">發(fā)送</button>
</div>
</div>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
ws: null,
isConnected: false,
inputUsername: '',
username: '',
inputMessage: '',
messages: [],
users: [],
reconnectAttempts: 0,
maxReconnectAttempts: 5
},
mounted() {
// 嘗試從本地存儲獲取用戶名
const savedUsername = localStorage.getItem('chat_username');
if (savedUsername) {
this.inputUsername = savedUsername;
}
},
methods: {
joinChat() {
if (!this.inputUsername.trim()) return;
this.username = this.inputUsername.trim();
localStorage.setItem('chat_username', this.username);
this.connectWebSocket();
},
connectWebSocket() {
// 在實際項目中,這里應(yīng)該是你的后端WebSocket地址
// const wsUrl = 'wss://yourdomain.com/ws';
// 本地開發(fā)時可以使用以下URL
const wsUrl = 'ws://localhost:5000/ws';
try {
this.ws = new WebSocket(wsUrl);
this.ws.onopen = () => {
console.log('WebSocket連接已建立');
this.isConnected = true;
this.reconnectAttempts = 0;
// 發(fā)送加入聊天室的消息
this.sendWebSocketMessage({
type: 'join',
username: this.username
});
};
this.ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
this.handleWebSocketMessage(message);
} catch (error) {
console.error('消息解析錯誤:', error);
}
};
this.ws.onclose = () => {
console.log('WebSocket連接已關(guān)閉');
this.isConnected = false;
this.attemptReconnect();
};
this.ws.onerror = (error) => {
console.error('WebSocket錯誤:', error);
this.isConnected = false;
};
} catch (error) {
console.error('WebSocket連接失敗:', error);
this.attemptReconnect();
}
},
attemptReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = Math.min(1000 * this.reconnectAttempts, 10000);
console.log(`嘗試重新連接 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
setTimeout(() => {
this.connectWebSocket();
}, delay);
}
},
sendWebSocketMessage(message) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(message));
}
},
handleWebSocketMessage(message) {
switch (message.type) {
case 'message':
this.messages.push({
sender: message.sender,
text: message.text,
time: this.formatTime(new Date())
});
this.scrollToBottom();
break;
case 'users':
this.users = message.users;
break;
case 'system':
this.messages.push({
sender: '系統(tǒng)',
text: message.text,
time: this.formatTime(new Date())
});
this.scrollToBottom();
break;
}
},
sendMessage() {
if (!this.inputMessage.trim() || !this.isConnected) return;
this.sendWebSocketMessage({
type: 'message',
sender: this.username,
text: this.inputMessage.trim()
});
this.inputMessage = '';
},
scrollToBottom() {
this.$nextTick(() => {
const container = this.$refs.messagesContainer;
if (container) {
container.scrollTop = container.scrollHeight;
}
});
},
formatTime(date) {
return date.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
});
}
},
beforeDestroy() {
if (this.ws) {
this.ws.close();
}
}
});
</script>
</body>
</html>
.NET 后端實現(xiàn)步驟
1. 創(chuàng)建ASP.NET Core項目
dotnet new web -n ChatServer cd ChatServer
2. 添加WebSocket中間件 (Program.cs)
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseWebSockets();
// 存儲所有連接的WebSocket和用戶名
var connections = new Dictionary<WebSocket, string>();
app.Map("/ws", async context =>
{
if (context.WebSockets.IsWebSocketRequest)
{
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
await HandleWebSocketConnection(webSocket, connections);
}
else
{
context.Response.StatusCode = 400;
}
});
async Task HandleWebSocketConnection(WebSocket webSocket, Dictionary<WebSocket, string> connections)
{
var buffer = new byte[1024 * 4];
try
{
// 接收第一條消息(用戶加入)
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
var jsonDoc = JsonDocument.Parse(message);
var type = jsonDoc.RootElement.GetProperty("type").GetString();
if (type == "join")
{
var username = jsonDoc.RootElement.GetProperty("username").GetString();
connections[webSocket] = username;
// 通知所有用戶更新用戶列表
await BroadcastUserList(connections);
// 發(fā)送歡迎消息
var welcomeMsg = new
{
type = "system",
text = $"{username} 加入了聊天室"
};
await BroadcastMessage(JsonSerializer.Serialize(welcomeMsg), connections);
}
else if (type == "message")
{
var sender = jsonDoc.RootElement.GetProperty("sender").GetString();
var text = jsonDoc.RootElement.GetProperty("text").GetString();
var chatMsg = new
{
type = "message",
sender = sender,
text = text
};
await BroadcastMessage(JsonSerializer.Serialize(chatMsg), connections);
}
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
catch (Exception ex)
{
Console.WriteLine($"WebSocket錯誤: {ex.Message}");
}
finally
{
// 移除連接并通知其他用戶
if (connections.TryGetValue(webSocket, out var username))
{
connections.Remove(webSocket);
// 通知用戶離開
var leaveMsg = new
{
type = "system",
text = $"{username} 離開了聊天室"
};
await BroadcastMessage(JsonSerializer.Serialize(leaveMsg), connections);
// 更新用戶列表
await BroadcastUserList(connections);
}
}
}
async Task BroadcastUserList(Dictionary<WebSocket, string> connections)
{
var userListMsg = new
{
type = "users",
users = connections.Values.ToArray()
};
var json = JsonSerializer.Serialize(userListMsg);
await BroadcastMessage(json, connections);
}
async Task BroadcastMessage(string message, Dictionary<WebSocket, string> connections)
{
var bytes = Encoding.UTF8.GetBytes(message);
var data = new ArraySegment<byte>(bytes);
foreach (var socket in connections.Keys)
{
if (socket.State == WebSocketState.Open)
{
await socket.SendAsync(data, WebSocketMessageType.Text, true, CancellationToken.None);
}
}
}
app.Run();
3. 啟用靜態(tài)文件服務(wù) (添加以下代碼到Program.cs)
// 在var app = builder.Build();后添加 app.UseDefaultFiles(); app.UseStaticFiles();
4. 運行應(yīng)用
dotnet run
實現(xiàn)原理
WebSocket連接建立:
- 客戶端通過WebSocket API連接到服務(wù)器
- 服務(wù)器接受連接并保持持久連接
消息協(xié)議:
- 使用JSON格式進行消息交換
- 消息類型包括:join(加入)、message(消息)、users(用戶列表)、system(系統(tǒng)消息)
廣播機制:
- 服務(wù)器維護所有活躍的WebSocket連接
- 當(dāng)收到消息時,服務(wù)器將消息廣播給所有連接的客戶端
用戶管理:
- 使用字典存儲WebSocket連接與用戶的映射關(guān)系
- 當(dāng)用戶加入或離開時更新用戶列表并廣播
部署說明
- 將Vue前端代碼放入wwwroot文件夾
- 配置WebSocket中間件和路由
- 部署到支持WebSocket的服務(wù)器(如IIS、Kestrel、Nginx等)
- 生產(chǎn)環(huán)境應(yīng)使用WSS(WebSocket Secure)協(xié)議
這個實現(xiàn)提供了一個完整的實時聊天室,包括用戶加入/離開通知、實時消息傳遞和用戶列表更新等功能。
到此這篇關(guān)于基于Vue+.NET全面實現(xiàn)WebSocket聊天室的文章就介紹到這了,更多相關(guān)Vue .NET實現(xiàn)WebSocket聊天室內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

