.NET + Vue 基于 WebSocket 的聊天室全面实现

文章目录

    • 实现思路
    • 完整代码实现
    • [.NET 后端实现步骤](#.NET 后端实现步骤)
      • [1. 创建ASP.NET Core项目](#1. 创建ASP.NET Core项目)
      • [2. 添加WebSocket中间件 (Program.cs)](#2. 添加WebSocket中间件 (Program.cs))
      • [3. 启用静态文件服务 (添加以下代码到Program.cs)](#3. 启用静态文件服务 (添加以下代码到Program.cs))
      • [4. 运行应用](#4. 运行应用)
    • 实现原理
    • 部署说明

实现思路

  1. 后端(.NET)使用ASP.NET Core WebSocket实现实时通信
  2. 前端(Vue):使用原生WebSocket API与后端通信
  3. 功能:实时消息、用户列表、在线状态、消息历史

完整代码实现

html 复制代码
<!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">发送</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() {
                    // 在实际项目中,这里应该是你的后端WebSocket地址
                    // const wsUrl = 'wss://yourdomain.com/ws';
                    // 本地开发时可以使用以下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;
                            
                            // 发送加入聊天室的消息
                            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连接已关闭');
                            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: '系统',
                                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 后端实现步骤

1. 创建ASP.NET Core项目

bash 复制代码
dotnet new web -n ChatServer
cd ChatServer

2. 添加WebSocket中间件 (Program.cs)

csharp 复制代码
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);
                
                // 发送欢迎消息
                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. 启用静态文件服务 (添加以下代码到Program.cs)

csharp 复制代码
// 在var app = builder.Build();后添加
app.UseDefaultFiles();
app.UseStaticFiles();

4. 运行应用

bash 复制代码
dotnet run

实现原理

  1. WebSocket连接建立

    • 客户端通过WebSocket API连接到服务器
    • 服务器接受连接并保持持久连接
  2. 消息协议

    • 使用JSON格式进行消息交换
    • 消息类型包括:join(加入)、message(消息)、users(用户列表)、system(系统消息)
  3. 广播机制

    • 服务器维护所有活跃的WebSocket连接
    • 当收到消息时,服务器将消息广播给所有连接的客户端
  4. 用户管理

    • 使用字典存储WebSocket连接与用户的映射关系
    • 当用户加入或离开时更新用户列表并广播

部署说明

  1. 将Vue前端代码放入wwwroot文件夹
  2. 配置WebSocket中间件和路由
  3. 部署到支持WebSocket的服务器(如IIS、Kestrel、Nginx等)
  4. 生产环境应使用WSS(WebSocket Secure)协议

这个实现提供了一个完整的实时聊天室,包括用户加入/离开通知、实时消息传递和用户列表更新等功能。

相关推荐
libraG6 分钟前
vue样式问题
css·vue.js·scss
界面开发小八哥6 分钟前
.NET表格控件Spread .NET v18.0——支持富文本、增强PDF导出
信息可视化·pdf·.net·数据可视化·spread .net
超哥的一天22 分钟前
【前端】每天一个简单库的使用-vue-office
vue.js
前端付豪36 分钟前
🔥Vue3 Composition API 核心特性深度解析:为什么说它是前端的“终极武器”?
前端·vue.js
叶浩成5203 小时前
WebSocket实时通信系统——js技能提升
javascript·websocket·网络协议
在未来等你3 小时前
RabbitMQ面试精讲 Day 29:版本升级与平滑迁移
中间件·面试·消息队列·rabbitmq
设计师小聂!3 小时前
RabbitMQ详解
java·spring boot·分布式·rabbitmq·maven
百锦再3 小时前
WebSocket vs RabbitMQ:聊天室技术选型分析
websocket·网络协议·rabbitmq·消息·聊天室·messge
兮漫天4 小时前
bun + vite7 的结合,孕育的 Robot Admin 【靓仔出道】(二十)终章
前端·javascript·vue.js