一、问题场景
我前阵子接了个在线客服的小项目,后端用ASP.NET Core写,前端要实现用户发消息后即时显示,就用了WebSocket技术。本地用Kestrel单跑的时候,一切正常,连了之后能一直收发消息。结果一部署到生产环境,用Nginx做反向代理,刚连上10秒左右,前端就弹出连接异常的提示,后台Nginx的日志里还出现了400 Bad Request的错误,查了好几天才找到问题所在,原来是Nginx的配置里少了几个关键项。
二、问题产生的核心原因
1.1 转发头缺失
我们平时用HTTP请求的时候,后端会拿到客户端的真实IP、协议类型(HTTP还是HTTPS)这些信息,但如果是通过Nginx代理过来的,后端拿到的就会是Nginx的IP和协议。如果不把这些转发头传给后端,后端就会误以为请求是Nginx发的,不是真实用户的,处理起来就会出错,WebSocket连接自然断了。举个生活化的例子:你寄快递,快递员只写了自己的地址,没写你的真实地址,快递肯定送不到正确的地方,WebSocket的转发头就相当于给后端传你的真实地址。
1.2 HTTP版本不兼容
Nginx默认用的是HTTP/1.0,这个老版本的HTTP协议不支持WebSocket需要的“协议升级”功能。WebSocket是基于HTTP/1.1的,需要在请求里告诉服务器“我要把这个HTTP连接换成WebSocket长连接”,HTTP/1.0听不懂这个指令,就像老款手机不支持5G信号一样,直接拒绝,导致连接失败。
三、配置调整的具体步骤
1.1 Nginx端的核心配置调整
要改几个关键的配置项,都是WebSocket和转发头必须的,改完之后Nginx才能正确处理WebSocket请求,把真实信息传给后端。
1.2 ASP.NET Core端的配合调整
后端也要改一点,确保能正确接收Nginx传过来的转发头,不要用代理前的错误信息,比如不要用代理的IP当客户端IP。
四、完整示例演示
1.1 技术栈说明
这里用的都是生产环境常用的稳定版本:ASP.NET Core 6.0(LTS版本,稳定好用,社区支持久)、Nginx 1.18.0(多数服务器预装的版本,兼容性好)。
1.2 ASP.NET Core的核心代码
只写和WebSocket相关的核心部分,其他项目配置省略,代码里加详细注释,方便理解:
var builder = WebApplication.CreateBuilder(args);
// 添加WebSocket服务,这里配置开发环境的允许跨域,生产环境要换成固定域名
builder.Services.AddWebSocketManager(options =>
{
options.AllowedOrigins.Add("*"); // 开发环境允许所有来源,生产环境换你的前端域名,比如"https://yourchat.com"
options.KeepAliveInterval = TimeSpan.FromSeconds(30); // 每30秒发一次心跳包,维持WebSocket连接
});
var app = builder.Build();
// 必须先启用WebSocket中间件,要放在其他中间件前面
app.UseWebSockets();
// 处理WebSocket请求的路由,前端访问/ws路径就能触发WebSocket连接
app.Map("/ws", async (HttpContext context) =>
{
// 先判断是不是WebSocket请求,不是的话返回400错误
if (!context.WebSockets.IsWebSocketRequest)
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}
// 正式接受WebSocket连接,获取连接实例
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
var receiveBuffer = new byte[1024 * 4]; // 接收数据的缓冲区,设成4KB足够
// 长循环处理客户端消息,只要连接没断就一直运行
while (true)
{
// 接收客户端发来的消息
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
// 如果收到的是文本消息,就处理后返回
if (result.MessageType == WebSocketMessageType.Text)
{
var receivedMessage = Encoding.UTF8.GetString(receiveBuffer, 0, result.Count);
// 实际项目这里要加业务逻辑,比如转发消息给其他在线用户,这里先做示例返回
var responseMessage = Encoding.UTF8.GetBytes($"客服收到:{receivedMessage}");
// 把响应消息发给客户端
await webSocket.SendAsync(new ArraySegment<byte>(responseMessage, 0, responseMessage.Length), WebSocketMessageType.Text, true, CancellationToken.None);
}
// 如果客户端主动关闭连接,就退出循环
if (result.CloseStatus.HasValue)
{
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
break;
}
}
});
app.Run();
1.3 Nginx的核心配置代码
站点配置文件(比如/etc/nginx/conf.d/mychat.conf),关键配置都加了注释,新手也能看懂:
server {
listen 80;
server_name yourdomain.com; # 换成自己的域名,比如chat.example.com
# 托管前端静态文件,比如HTML、JS、CSS,不用单独搭静态服务器
location / {
root /var/www/mychat/frontend; # 前端文件存放的实际路径
index index.html; # 默认首页文件
}
# WebSocket请求的转发配置,所有/ws开头的请求都转发给后端ASP.NET Core服务
location /ws {
# 必须设置HTTP版本为1.1,这是WebSocket协议升级的核心要求,旧版本HTTP不支持
proxy_http_version 1.1;
# 传递客户端发来的Upgrade头,告诉Nginx这个请求要升级成WebSocket连接
proxy_set_header Upgrade $http_upgrade;
# 设置Connection头为upgrade,确认要切换协议,这行是WebSocket必须的,漏了就失败
proxy_set_header Connection "upgrade";
# 下面这些是转发头,把真实客户端的信息传给后端,解决IP和协议错误的问题
proxy_set_header X-Forwarded-For $remote_addr; # 存客户端真实IP
proxy_set_header X-Forwarded-Proto $scheme; # 存客户端用的协议(http/https),生产环境用HTTPS要改成https
proxy_set_header Host $host; # 存客户端请求的主机名,确保后端生成链接正确
# 调整代理超时时间,默认60秒,改成300秒,避免Nginx把空闲的WebSocket连接断开
proxy_read_timeout 300;
proxy_send_timeout 300;
# 转发到后端的ASP.NET Core Kestrel服务,默认端口是5000,要和你实际的端口一致
proxy_pass http://127.0.0.1:5000;
}
}
五、注意事项
1.1 Nginx版本要求
必须用Nginx 1.3.13以上的版本,这个版本才正式支持WebSocket的协议升级功能,旧版本(比如1.10左右)的配置不会生效,导致连接还是断。可以在服务器上运行nginx -v命令查看版本,太低的话要升级。
1.2 HTTPS环境的特殊配置
如果生产环境用HTTPS,Nginx会终止HTTPS连接,然后转发HTTP请求给后端,这时候要确保X-Forwarded-Proto的值是https,不然后端生成的跳转链接会是http,用户访问会有安全提示。另外,WebSocket连接也要用加密的wss协议,Nginx的HTTPS配置里要加ssl_certificate和ssl_certificate_key的路径。
1.3 心跳配置的细节
WebSocket长连接如果长时间没数据,Nginx会默认断开,所以要在后端配置KeepAliveInterval,比如每30秒发一次心跳包,或者在Nginx里把proxy_read_timeout设成300秒以上,确保超时时间比心跳间隔长,这样连接就不会被莫名断开。
1.4 生产环境的安全限制
开发环境里允许所有跨域来源,但生产环境必须把AllowedOrigins改成具体的前端域名,不然会有安全问题,阻止非信任来源的WebSocket请求,避免恶意用户的连接攻击。
六、技术优缺点
1.1 优点
- 负载均衡:可以把WebSocket连接分散到多个后端ASP.NET Core实例,提升并发能力,适合高并发的实时场景,比如在线教育的弹幕、电商的实时客服;
- SSL终止:不用在每个后端实例里配置HTTPS,Nginx可以统一处理SSL加密,节省后端服务器的资源,减少配置复杂度;
- 静态托管:Nginx本身就可以托管前端静态文件,不用单独搭Nginx或者Apache,简化部署流程,适合小型项目;
- 日志管理:Nginx的日志功能强大,方便调试和排查问题,比单实例的日志更容易分析,比如快速找到是连接失败的时间和原因。
1.2 缺点
- 配置复杂:WebSocket的配置比普通HTTP多几个关键项,稍有疏漏就会出错,比如漏了proxy_http_version 1.1或者Connection头,连接就会直接失败;
- 超时问题:需要额外配置心跳和超时,不然空闲连接会被莫名断开,要手动调整参数,增加了运维的工作量;
- 调试麻烦:要同时看Nginx和ASP.NET Core的日志,找问题比单实例麻烦,比如400错误可能是Nginx配置错,也可能是后端处理错,要两边交叉验证。
七、应用场景
这个配置主要用在所有需要实时交互的场景,比如:
- 在线客服系统:用户发消息后即时显示,不用刷新页面,提升用户体验;
- 协同编辑工具:多人同时写文档,每个人的修改即时同步,比如在线表格、文档协作;
- 实时通知系统:社交软件的消息提醒、监控系统的告警通知,用户不用刷新就能看到新消息;
- 在线游戏:棋牌游戏、对战游戏,需要实时同步玩家的操作,延迟要低;
- 直播弹幕系统:用户发弹幕后即时显示在直播页面,互动性强,这些场景都要用WebSocket,部署时如果用Nginx,就必须按照上面的配置来做,不然连接会不稳定。
八、总结
其实解决这个问题的核心就记住几个关键点:Nginx必须设HTTP版本为1.1,要传递Upgrade和Connection头,要传X-Forwarded-For、X-Forwarded-Proto这些转发头,还要调整超时时间,加心跳配置。只要把这几个配置项写对,ASP.NET Core的WebSocket就能在Nginx后面正常工作。遇到问题的时候,先看Nginx的错误日志,再看后端的日志,一般都是配置项漏了或者写错了,按照上面的示例改一遍,基本就能解决。现在我的客服项目已经稳定运行了,部署之后再也没出现过连接断开的问题,用户反馈都很好。
评论
围绕“ASP.NET Core部署在Nginx后方时WebSocket连接被断开,转发头信息与HTTP版本怎么调整才正常”参与讨论