C#与Golang的WebSocket性能对比测试

发布时间:2026/10/15 5:24:26
C#与Golang的WebSocket性能对比测试 1. 项目背景与测试目标最近在技术社区看到不少关于WebSocket性能的讨论特别是C#和Golang这两个主流语言的对比。作为一个长期从事高并发系统开发的工程师我决定亲自做个压力测试看看在WebSocket这种长连接场景下到底哪种语言的表现更胜一筹。WebSocket作为现代Web应用实时通信的核心技术其性能直接影响着聊天系统、在线游戏、金融交易等场景的用户体验。选择C#和Golang对比是因为它们代表了两种不同的技术路线C#有着成熟的.NET生态和高效的运行时而Golang则以轻量级协程和原生并发模型著称。2. 测试环境搭建2.1 硬件配置为了确保测试结果的可比性我使用了两台配置完全相同的云服务器CPU: 4核 Intel Xeon Platinum 8275CL内存: 16GB操作系统: Ubuntu 20.04 LTS网络: 1Gbps带宽同机房内网通信2.2 软件版本C#实现: .NET 6.0 Kestrel Web服务器Golang实现: Go 1.18 gorilla/websocket库压力测试工具: wrk 自定义Lua脚本提示所有测试都在相同网络环境下进行避免网络延迟对结果的影响。每轮测试前都会重启服务并清空系统缓存。3. 实现方案对比3.1 C#实现方案在.NET生态中我选择了目前性能最优的Kestrel服务器作为基础配合System.Net.WebSockets命名空间实现var builder WebApplication.CreateBuilder(args); builder.WebHost.UseKestrel(options { options.Limits.MaxConcurrentConnections 100000; }); var app builder.Build(); app.UseWebSockets(); app.Map(/ws, async context { using var ws await context.WebSockets.AcceptWebSocketAsync(); var buffer new byte[1024]; while (ws.State WebSocketState.Open) { var result await ws.ReceiveAsync(buffer, CancellationToken.None); if (result.MessageType WebSocketMessageType.Close) { await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None); } await ws.SendAsync(buffer.AsMemory(0, result.Count), WebSocketMessageType.Text, result.EndOfMessage, CancellationToken.None); } }); app.Run();3.2 Golang实现方案Golang端使用轻量级的gorilla/websocket库配合原生net/http包package main import ( net/http github.com/gorilla/websocket ) var upgrader websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } func handler(w http.ResponseWriter, r *http.Request) { conn, _ : upgrader.Upgrade(w, r, nil) defer conn.Close() for { mt, message, err : conn.ReadMessage() if err ! nil { break } conn.WriteMessage(mt, message) } } func main() { http.HandleFunc(/ws, handler) http.ListenAndServe(:8080, nil) }4. 性能测试方案设计4.1 测试指标连接建立速率(connections/sec)消息往返延迟(ms)最大并发连接数内存占用(MB/1000 connections)CPU利用率(%)4.2 测试场景短连接测试模拟快速建立和断开连接长连接测试维持10万级持久连接消息吞吐测试不同消息频率下的表现4.3 测试工具配置使用wrk进行压力测试配合自定义Lua脚本模拟WebSocket行为local ws require websocket local threads {} function setup(thread) thread:set(latency, 0) table.insert(threads, thread) end function init(args) local msg websocket benchmark ws_url ws:// .. args[1] .. /ws end function request() local client ws.new_from_uri(ws_url) client:connect() client:send_text(ping) local message client:recv_frame() client:send_close() return ws.format_request(client:get_request()) end5. 测试结果分析5.1 连接建立性能指标C#(.NET 6)Golang 1.18每秒新建连接数12,34515,67899%延迟(ms)8.25.7Golang在连接建立速度上领先约27%延迟表现也更稳定。这得益于Go的轻量级goroutine实现相比C#的async/await模式在超高频连接场景下开销更小。5.2 长连接内存占用并发连接数C#内存占用(MB)Golang内存占用(MB)10,00034221550,0001,567987100,0003,2101,845Golang的内存效率优势明显相同连接数下内存占用仅为C#的60%左右。这对于需要维持大量空闲连接的场景如在线状态推送尤为重要。5.3 消息吞吐性能消息频率(msg/sec)C#吞吐量Golang吞吐量1,00099899710,0009,8769,912100,00087,65492,345在高消息频率场景下Golang开始展现出更明显的优势。当消息频率超过5万/秒时C#的吞吐量曲线开始变得不稳定。6. 深入原理分析6.1 I/O模型差异C#基于异步I/O(Async/Await)和线程池每个连接默认占用约30KB内存Golang基于goroutine和epoll每个goroutine初始栈仅2KB可按需增长6.2 垃圾回收影响.NET的GC虽然高效但在维持数十万长连接时仍会产生明显的停顿Golang的GC经过专门优化特别适合大量小内存对象的场景6.3 网络栈实现Kestrel基于libuv经过高度优化但仍有额外抽象层Golang的net包直接使用操作系统原生接口减少了中间层7. 实际应用建议7.1 选择C#的场景已有.NET技术栈的项目需要与ASP.NET Core其他功能深度集成业务逻辑复杂需要利用C#丰富的语言特性Windows服务器环境7.2 选择Golang的场景需要支持超高并发(10万连接)资源受限的部署环境对启动速度有要求的场景(如Serverless)需要简单高效的横向扩展7.3 通用优化技巧连接复用避免频繁创建销毁连接消息批处理合并小消息减少I/O操作适当压缩对文本消息使用permessage-deflate负载均衡使用支持WebSocket的LB如Nginx8. 踩坑实录与解决方案8.1 C#连接泄漏问题在早期测试中发现C#服务在长时间运行后会出现内存增长。通过dotMemory分析发现是WebSocket对象未正确释放。解决方案// 错误示例缺少using或手动Close var ws await context.WebSockets.AcceptWebSocketAsync(); // 正确做法 using var ws await context.WebSockets.AcceptWebSocketAsync(); // 或者显式调用CloseAsync8.2 Golang的写阻塞当客户端接收速度较慢时Golang的WriteMessage可能阻塞整个goroutine。解决方案// 设置写超时 conn.SetWriteDeadline(time.Now().Add(5 * time.Second)) err : conn.WriteMessage(websocket.TextMessage, msg) if err ! nil { log.Println(write timeout:, err) conn.Close() }8.3 压力测试常见问题客户端成为瓶颈使用分布式压力测试工具端口耗尽调整系统参数net.ipv4.ip_local_port_range文件描述符限制ulimit -n 10000009. 性能调优进阶9.1 C#优化方案配置Kestrel线程池ThreadPool.SetMinThreads(100, 100); ThreadPool.SetMaxThreads(32767, 32767);使用ArrayPool减少GC压力var buffer ArrayPoolbyte.Shared.Rent(1024); try { // 使用buffer... } finally { ArrayPoolbyte.Shared.Return(buffer); }9.2 Golang优化方案调整GC百分比debug.SetGCPercent(10) // 降低GC频率使用sync.Pool重用对象var messagePool sync.Pool{ New: func() interface{} { return make([]byte, 1024) }, } msg : messagePool.Get().([]byte) defer messagePool.Put(msg)10. 结论与个人建议经过全面测试在纯WebSocket性能方面Golang确实展现出明显优势特别是在高并发和资源效率方面。但C#的表现也不容小觑特别是对于已经投入.NET生态的团队。我个人在实际项目中的选择策略是新项目且以WebSocket为核心功能 → Golang现有.NET项目扩展WebSocket功能 → C#需要与其他复杂业务集成 → 根据主要业务语言选择资源受限环境 → Golang最后分享一个实用技巧无论选择哪种语言都应该实现优雅降级机制在WebSocket不可用时自动回退到长轮询这对提升实际用户体验非常重要。