基于C#實(shí)現(xiàn)一個(gè)簡單的即時(shí)通訊工具
系統(tǒng)架構(gòu)設(shè)計(jì)
整體架構(gòu)

技術(shù)選型
| 模塊 | 技術(shù)方案 | 說明 |
|---|---|---|
| 通信協(xié)議 | TCP+自定義二進(jìn)制協(xié)議 | 高效可靠,支持流式傳輸 |
| 網(wǎng)絡(luò)層 | System.Net.Sockets + Async/Await | 異步非阻塞IO模型 |
| 消息隊(duì)列 | Redis Pub/Sub | 解耦消息生產(chǎn)消費(fèi) |
| 數(shù)據(jù)庫 | SQL Server + Entity Framework | 用戶數(shù)據(jù)持久化 |
| 推送服務(wù) | SignalR | 實(shí)時(shí)消息推送 |
| 文件傳輸 | chunked transfer + 斷點(diǎn)續(xù)傳 | 支持大文件傳輸 |
核心模塊實(shí)現(xiàn)
通信網(wǎng)關(guān)(TCP服務(wù)端)
public class TcpGateway
{
private TcpListener _listener;
private ConcurrentDictionary<string, Socket> _clients = new();
public async Task StartAsync(string ip, int port)
{
_listener = new TcpListener(IPAddress.Parse(ip), port);
await _listener.StartAsync();
while (true)
{
var client = await _listener.AcceptTcpClientAsync();
_ = HandleClientAsync(client);
}
}
private async Task HandleClientAsync(TcpClient client)
{
using (client)
{
var stream = client.GetStream();
var buffer = new byte[1024 * 4];
while (client.Connected)
{
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
if (bytesRead == 0) break;
var msg = MessageParser.Decode(buffer, bytesRead);
await MessageDispatcher.Dispatch(msg);
}
}
}
}消息協(xié)議設(shè)計(jì)
public class MessageProtocol
{
public ushort Magic { get; set; } = 0xA55A;
public byte Version { get; set; }
public MessageType Type { get; set; }
public int BodyLength { get; set; }
public byte[] Body { get; set; }
}
public enum MessageType
{
Login = 1,
Text = 2,
File = 3,
Heartbeat = 4
}消息處理中心
public static class MessageDispatcher
{
public static async Task Dispatch(MessageProtocol msg)
{
switch (msg.Type)
{
case MessageType.Login:
await AuthService.Authenticate(msg.Body);
break;
case MessageType.Text:
await ChatService.SendMessage(msg);
break;
case MessageType.File:
await FileService.Upload(msg);
break;
case MessageType.Heartbeat:
HeartbeatManager.KeepAlive(msg.SessionId);
break;
}
}
}關(guān)鍵技術(shù)實(shí)現(xiàn)
高并發(fā)處理
// 使用線程池優(yōu)化資源
ThreadPool.SetMinThreads(50, 50);
// 異步處理消息
public async Task ProcessMessageAsync(Socket socket)
{
var buffer = new byte[1024 * 10];
while (true)
{
int bytesRead = await socket.ReceiveAsync(
new ArraySegment<byte>(buffer),
SocketFlags.None);
if (bytesRead == 0) break;
_ = Task.Run(() => HandleData(buffer, bytesRead));
}
}消息持久化
public class ChatRepository
{
private readonly DbContext _context;
public async Task SaveMessage(ChatMessage message)
{
_context.Messages.Add(message);
await _context.SaveChangesAsync();
// 寫入Redis消息隊(duì)列
await Redis.PublishAsync("chat_messages", message);
}
}文件傳輸
public class FileTransferService
{
public async Task SendFile(string filePath, Socket client)
{
using (var fileStream = File.OpenRead(filePath))
{
var buffer = new byte[1024 * 1024]; // 1MB分塊
int bytesRead;
while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await client.SendAsync(
new ArraySegment<byte>(buffer, 0, bytesRead),
SocketFlags.None);
}
}
}
}安全機(jī)制
通信加密
public class SecurityManager
{
private readonly SslStream _sslStream;
public async Task EstablishSecureConnectionAsync(Socket socket)
{
_sslStream = new SslStream(socket.GetStream(), false);
await _sslStream.AuthenticateAsServerAsync(serverCertificate);
}
public byte[] EncryptData(byte[] data)
{
return _sslStream.Encrypt(data);
}
}心跳機(jī)制
public class HeartbeatMonitor
{
private readonly ConcurrentDictionary<string, DateTime> _sessions = new();
public void UpdateHeartbeat(string sessionId)
{
_sessions[sessionId] = DateTime.Now;
}
public bool CheckTimeout(string sessionId, int timeoutSeconds = 30)
{
return (DateTime.Now - _sessions[sessionId]).TotalSeconds > timeoutSeconds;
}
}擴(kuò)展功能實(shí)現(xiàn)
群組管理
public class GroupService
{
public async Task CreateGroup(string groupName, List<string> members)
{
var groupId = Guid.NewGuid().ToString();
await _redis.HashSetAsync($"group:{groupId}",
new HashEntry[] {
new("name", groupName),
new("members", string.Join(",", members))
});
}
}消息撤回
public class MessageService
{
public async Task RecallMessage(string msgId)
{
var msg = await _redis.GetAsync<Message>($"msg:{msgId}");
if (msg != null && msg.Timestamp > DateTime.Now.AddMinutes(-2))
{
await _redis.PublishAsync("recall_message", msgId);
}
}
}部署方案
| 組件 | 部署方式 | 推薦配置 |
|---|---|---|
| 通信網(wǎng)關(guān) | Docker容器化 | 4核8G,Kestrel并發(fā)5000+ |
| Redis | 集群模式 | 3主3從,持久化AOF |
| 數(shù)據(jù)庫 | 主從復(fù)制 | 16核32G,SSD RAID10 |
| 文件存儲(chǔ) | MinIO分布式集群 | 4節(jié)點(diǎn),10Gbps內(nèi)網(wǎng)帶寬 |
測(cè)試方案
壓力測(cè)試
# 使用wrk進(jìn)行壓力測(cè)試 wrk -t12 -c400 -d30s http://localhost:5000/api/chat
監(jiān)控指標(biāo)
public class PerformanceMetrics
{
public long MessagesSent { get; set; }
public long MessagesReceived { get; set; }
public double CPUUsage { get; set; }
public double MemoryUsage { get; set; }
}知識(shí)擴(kuò)展
用 C# 搭建一個(gè)即時(shí)通訊應(yīng)用,最推薦的方式是使用微軟官方提供的 SignalR。它上手快、功能強(qiáng)大,能幫你省去很多底層的麻煩。下面是具體的實(shí)現(xiàn)思路:
第一步:創(chuàng)建項(xiàng)目并配置 SignalR
創(chuàng)建一個(gè)新的 ASP.NET Core Web 應(yīng)用(使用 Razor 頁面或 MVC 模型均可)。然后,在 Program.cs 文件中配置 SignalR 服務(wù):
// Program.cs
using SignalRChat.Hubs;
var builder = WebApplication.CreateBuilder(args);
// 1. 添加 SignalR 服務(wù)
builder.Services.AddSignalR();
var app = builder.Build();
// ... 其他配置
// 2. 映射 Hub 的端點(diǎn),客戶端將通過 "/chatHub" 訪問
app.MapHub<ChatHub>("/chatHub");
app.Run();第二步:創(chuàng)建核心樞紐 (Hub)
在項(xiàng)目根目錄下創(chuàng)建一個(gè) Hubs 文件夾,并新建 ChatHub.cs 類。這是整個(gè)通信的核心中樞。
// Hubs/ChatHub.cs
using Microsoft.AspNetCore.SignalR;
namespace SignalRChat.Hubs;
// 繼承 SignalR 的 Hub 基類
public class ChatHub : Hub
{
// 這個(gè)方法將被客戶端調(diào)用,以廣播消息
// Hub 是瞬時(shí)的,不要在字段中存儲(chǔ)狀態(tài)
public async Task SendMessage(string user, string message)
{
// Clients.All 代表所有連接的客戶端
// "ReceiveMessage" 是客戶端需要監(jiān)聽的方法名
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
// 你也可以在此添加其他方法,如點(diǎn)對(duì)點(diǎn)聊天、加群、退群等
// public async Task SendPrivateMessage(string targetUserId, string message)
// {
// await Clients.User(targetUserId).SendAsync("ReceiveMessage", ...);
// }
}要點(diǎn):ChatHub 類繼承自 SignalR 的 Hub 基類。你定義在其中的 public 方法,都可以被客戶端直接調(diào)用。需要注意,每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的 Hub 實(shí)例,所以請(qǐng)不要在它的字段里存儲(chǔ)狀態(tài)。
第三步:構(gòu)建客戶端界面
在 wwwroot 文件夾下創(chuàng)建一個(gè) HTML 文件,例如 index.html,用來作為聊天界面。
<!-- wwwroot/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>SignalR 聊天室</title>
</head>
<body>
<h2>SignalR 聊天室</h2>
<div>用戶: <input type="text" id="userInput" /> 消息: <input type="text" id="messageInput" /></div>
<button id="sendButton">發(fā)送</button>
<ul id="messagesList"></ul>
<!-- 1. 引用 SignalR 的 JavaScript 客戶端庫 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.7/signalr.min.js"></script>
<script>
"use strict";
// 2. 創(chuàng)建連接對(duì)象,指定 Hub 的 URL
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chatHub")
.build();
// 3. 設(shè)置消息接收器:監(jiān)聽來自 Hub 的 "ReceiveMessage" 調(diào)用
// 這里的 "ReceiveMessage" 必須與 C# Hub 中 SendAsync 的第一個(gè)參數(shù)一致
connection.on("ReceiveMessage", (user, message) => {
const li = document.createElement("li");
li.textContent = `${user}: ${message}`;
document.getElementById("messagesList").appendChild(li);
});
// 4. 啟動(dòng)連接
connection.start().catch(err => console.error(err.toString()));
// 5. 頁面邏輯:點(diǎn)擊按鈕時(shí),調(diào)用 Hub 的 "SendMessage" 方法
document.getElementById("sendButton").addEventListener("click", event => {
const user = document.getElementById("userInput").value;
const message = document.getElementById("messageInput").value;
// 這里的 "SendMessage" 必須與 C# Hub 中的方法名完全一致
connection.invoke("SendMessage", user, message).catch(err => console.error(err.toString()));
event.preventDefault();
});
</script>
</body>
</html>要點(diǎn):客戶端通過 HubConnectionBuilder 建立連接,并通過 .on() 監(jiān)聽來自服務(wù)器的消息,通過 .invoke() 調(diào)用服務(wù)器上的方法。最重要的,是確保 JavaScript 中的方法名和 C# Hub 里的方法名完全一致。
第四步:運(yùn)行和測(cè)試
調(diào)整 Program.cs,確保應(yīng)用能夠提供這個(gè)靜態(tài) HTML 文件。然后運(yùn)行項(xiàng)目,就可以在聊天室里暢聊了。
到此這篇關(guān)于基于C#實(shí)現(xiàn)一個(gè)簡單的即時(shí)通訊工具的文章就介紹到這了,更多相關(guān)C#即時(shí)通訊工具內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#使用Spire.PDF for .NET實(shí)現(xiàn)設(shè)置或修改PDF頁面尺寸
在文檔處理場景中,調(diào)整 PDF 頁面尺寸是常見需求,本文將介紹如何使用免費(fèi)庫 Free Spire.PDF for .NET 通過 C# 代碼設(shè)置或修改 PDF 頁面尺寸,有需要的小伙伴可以了解下2026-02-02
如何獲取C#中方法的執(zhí)行時(shí)間以及其代碼注入詳解
這篇文章主要給大家介紹了關(guān)于如何獲取C#中方法的執(zhí)行時(shí)間以及其代碼注入的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧2018-11-11
C#如何判斷.Net?Framework版本是否滿足軟件運(yùn)行需要的版本
這篇文章主要介紹了C#如何判斷.Net?Framework版本是否滿足軟件運(yùn)行需要的版本問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01
C#實(shí)現(xiàn)微信退款及對(duì)賬功能的示例詳解
在招聘報(bào)名系統(tǒng)里,考務(wù)費(fèi)支付是其中一個(gè)環(huán)節(jié),支付方式很多種,比如銀聯(lián)、微信、支付寶等等,本次我們以微信支付進(jìn)行舉例,在實(shí)際的應(yīng)用中,對(duì)于支付成功的考生,我們會(huì)遇到實(shí)現(xiàn)退款的需求,所以本文給大家介紹了使用C#實(shí)現(xiàn)微信退款及對(duì)賬,需要的朋友可以參考下2023-11-11
C#操作DataGridView獲取或設(shè)置當(dāng)前單元格的內(nèi)容
這篇文章介紹了C#操作DataGridView獲取或設(shè)置當(dāng)前單元格的內(nèi)容,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02

