
文章目录

实现思路
- 后端(.NET):使用ASP.NET Core WebSocket实现实时通信
- 前端(Vue):使用原生WebSocket API与后端通信
- 功能:实时消息、用户列表、在线状态、消息历史
完整代码实现
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
实现原理

-
WebSocket连接建立:
- 客户端通过WebSocket API连接到服务器
- 服务器接受连接并保持持久连接
-
消息协议:
- 使用JSON格式进行消息交换
- 消息类型包括:join(加入)、message(消息)、users(用户列表)、system(系统消息)
-
广播机制:
- 服务器维护所有活跃的WebSocket连接
- 当收到消息时,服务器将消息广播给所有连接的客户端
-
用户管理:
- 使用字典存储WebSocket连接与用户的映射关系
- 当用户加入或离开时更新用户列表并广播
部署说明
- 将Vue前端代码放入wwwroot文件夹
- 配置WebSocket中间件和路由
- 部署到支持WebSocket的服务器(如IIS、Kestrel、Nginx等)
- 生产环境应使用WSS(WebSocket Secure)协议
这个实现提供了一个完整的实时聊天室,包括用户加入/离开通知、实时消息传递和用户列表更新等功能。