nginx服务器 部署websocket项目 连接中断 报错 :Error in connection establishment: net::ERR_NAME_NOT_RESOLVED

项目需要弹窗告警,原本采用comet4j的方式进行,但是后来发现不支持tomcat8.5,于是打算使用webSocket的方式实现,webSocket是浏览器客户端和服务器后台实现的一种全双工通信方式,许多网页聊天工具都是采用该方式进行。

本地开发的时候都可以正常使用,但是在部署到nginx代理服务器的时候发现报了错误,连不上

Error in connection establishment: net::ERR_NAME_NOT_RESOLVED

后来发现是nginx服务器默认是不打开webSocket的功能的,这需要我们在nginx服务器上配置:

 location /test/ {
                proxy_pass http://test.com;
                proxy_redirect default;
                proxy_set_header Upgrade $http_upgrade; # allow websockets
                proxy_set_header Connection "upgrade";
                proxy_http_version 1.1;
                }

另外nginx设置了连接超时时间或者读取超时时间的时候,websoket会中断,那么需要我们维护socket连接,断线自动重连,代码如下,

websocket后台:

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import net.sf.json.JSONObject;


@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {
	
	static Logger log=LoggerFactory.getLogger(WebSocketServer.class);
    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();

    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;

    //接收sid
    private String sid="";
    /**
     * 连接建立成功调用的方法*/
    @OnOpen
    public void onOpen(Session session,@PathParam("sid") String sid) {
        this.session = session;
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //在线数加1
        log.info("有新窗口开始监听:"+sid+",当前在线人数为" + getOnlineCount());
        this.sid=sid;
        try {
        	 sendMessage("连接成功");
        } catch (IOException e) {
            log.error("websocket IO异常");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //从set中删除
        subOnlineCount();           //在线数减1
        log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(String message, Session session) {
    	log.info("收到来自窗口"+sid+"的信息:"+message);
        //群发消息
        for (WebSocketServer item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
            	log.error(e.toString());
                e.printStackTrace();
            }
        }
    }

	/**
	 * 
	 * @param session
	 * @param error
	 */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }
	/**
	 * 实现服务器主动推送
	 */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }
    
    /**
     * 发送告警消息
     * */
   public void sendMessage(Map<String,Object> dataMap)throws IOException{
	   try{if(dataMap!=null){
			JSONObject jsonObject = JSONObject.fromObject(dataMap);
			StringBuilder builder = new StringBuilder(jsonObject.toString());		
			//发送告警到前台
			this.session.getBasicRemote().sendText(builder.toString());
			log.info("发送成功");
			}	
	   }catch(Exception e) {
		   log.error(e.toString());
	   }
   }
    /**
     * 群发自定义消息
     * */
    public static void sendInfo(String message, String sid) throws IOException {
    	log.info("推送消息到窗口"+sid+",推送内容:"+message);
        for (WebSocketServer item : webSocketSet) {
            try {
            	//这里可以设定只推送给这个sid的,为null则全部推送
            	if(sid==null) {
            		item.sendMessage(message);
            	}else if(item.sid.equals(sid)){
            		item.sendMessage(message);
            	}
            } catch (IOException e) {
            	log.error(e.toString());
                continue;
            }
        }
    }
    /**
     * 群发自定义消息
     * */
    public static void sendInfoMap(Map<String,Object> dataMap, String sid) throws IOException {
    	log.info("推送消息到窗口"+sid+",推送内容:"+dataMap.toString());
        for (WebSocketServer item : webSocketSet) {
            try {
            	item.sendMessage(dataMap);
            	//这里可以设定只推送给这个sid的,为null则全部推送暂时全部放开
            	/*if(sid==null) {
            		item.sendMessage(dataMap);
            	}else if(item.sid.equals(sid)){
            		item.sendMessage(dataMap);
            	}*/
            } catch (IOException e) {
            	log.error(e.toString());
                continue;
            }
        }
    }
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }
}


前台页面以及js:

var websocket_connected_count = 0;
var onclose_connected_count = 0;
/**websocekt*/
function webSocketClient(){
	var socket;  
	if(typeof(WebSocket) == "undefined") {  
	    console.log("您的浏览器不支持WebSocket");  
	}else{  
	    console.log("您的浏览器支持WebSocket");  
	    	//httprequest请求id
	    	var sid = "<%=requestId%>";
	    	//实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接  
	        socket =new WebSocket("ws://127.0.0.1:8080/butlerBf/websocket/"+sid);   
	        //打开事件  
	        socket.onopen = function() {  
	            console.log("Socket 已打开"); 
	            //socket.send("这是来自客户端的消息" + location.href + new Date());  
	        };  
	        //获得消息事件  
	        socket.onmessage = function(msg) {  
	            if(msg.data!="您的浏览器支持WebSocket"&&msg.data!="Socket 已打开"&&msg.data!="连接成功"&&msg.data!="ping"&&msg.data!=""){
	            	console.log(msg);
	            	checkAuthority(msg.data);
	            	heartCheck.reset().start();
	            }
	        };  
	        //关闭事件  
	        socket.onclose = function(e) {  
	            console.log("Socket已关闭"); 
	            console.log(e);
	        };
	        //发生了错误事件  
	        socket.onerror = function() {  
	        	websocket_connected_count++;
	            if(websocket_connected_count <= 5){
	            	webSocketClient()
	            }
	            console.log("Socket发生了错误");  
	            //此时可以尝试刷新页面
	        }  
	        //离开页面时,关闭socket
	        //jquery1.8中已经被废弃,3.0中已经移除
	         $(window).unload(function(){  
	             socket.close();  
	         });  
	}
	// 心跳检测, 每隔一段时间检测连接状态,如果处于连接中,就向server端主动发送消息,来重置server端与客户端的最大连接时间,如果已经断开了,发起重连。
    var heartCheck = {
        timeout: 60000,        // 60s发一次心跳,比server端设置的连接时间稍微小一点,在接近断开的情况下以通信的方式去重置连接时间。
        serverTimeoutObj: null,
        reset: function(){
            clearTimeout(this.timeoutObj);
            clearTimeout(this.serverTimeoutObj);
            return this;
        },
        start: function(){
            var self = this;
            this.serverTimeoutObj = setInterval(function(){
                if(socket.readyState == 1){
                    console.log("连接状态,发送消息保持连接");
                    socket.send("ping");
                    heartCheck.reset().start();    // 如果获取到消息,说明连接是正常的,重置心跳检测
                }else{
                    console.log("断开状态,尝试重连");
                    webSocketClient();
                }
            }, this.timeout)
        }
    }

}

此外补充一点,如果nginx没有设置如下读取超时时间,websocket会一直断线重连,比较消耗内存,建议设置长一点:

location /test{
            root html;
	    proxy_pass  http://test.com;
	    proxy_set_header Upgrade $http_upgrade; # allow websockets
    	proxy_set_header Connection "upgrade";
    	proxy_http_version 1.1;
        proxy_connect_timeout 60s;#l连接超时时间,不能设置太长会浪费连接资源
	    proxy_read_timeout 500s;#读超时时间
	    proxy_send_timeout 500s;#写超时时间
            index  index.html index.htm;
        }
<think> 我们正在处理一个关于WebSocket连接使用wss(WebSocket Secure)时出现的SSL协议错误(net::ERR_SSL_PROTOCOL_ERROR)的问题。错误信息表明客户端尝试通过SSL/TLS建立安全的WebSocket连接时失败。 根据引用中的信息,我们可以总结出以下关键点: 1. 在HTTPS页面中,必须使用wss协议,否则浏览器会阻止不安全的连接(引用[1])。 2. 出现ERR_SSL_PROTOCOL_ERROR错误的原因可能是SSL配置不正确(引用[3]、引用[4])。 针对该错误的解决方案通常涉及以下几个方面: ### 1. 确保服务器支持SSL上的WebSocket(wss) - 服务器必须配置SSL证书,并监听443端口(或其他指定端口)以处理wss连接。 - 如果使用Nginx作为反向代理,需要正确配置SSL证书和WebSocket的代理设置。 ### 2. Nginx配置要点(参考引用[3]和引用[4]) 在Nginx配置中,需要为WebSocket的路径(本例中为`/websocket/message`)设置代理,并且要正确处理WebSocket的升级头(Upgrade头)。同时,SSL配置必须正确。 以下是一个针对该问题的Nginx配置示例: ```nginx server { # 监听443端口并启用SSL listen 443 ssl; server_name localhost; # 如果使用localhost,证书需要支持localhost(或者使用自签名证书并信任) # SSL证书和私钥的路径 ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/private.key; # SSL会话设置 ssl_session_timeout 5m; ssl_protocols TLSv1.2 TLSv1.3; # 推荐使用更安全的协议版本 ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; ssl_prefer_server_ciphers on; # 配置WebSocket代理 location /websocket/message { proxy_pass http://localhost:3030; # 注意:这里代理到后端WebSocket服务器的地址和端口 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header Host $host; # 可选:传递客户端IP proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 其他配置... } ``` ### 3. 自签名证书问题(本地开发环境) 在本地开发环境中使用`wss://localhost`时,通常需要自签名证书。浏览器不信任自签名证书,因此需要手动信任该证书。或者,可以临时在浏览器中允许不安全的内容(不推荐)。 解决方案: - 生成自签名证书时,确保正确设置Common Name(CN)为`localhost`。 - 将自签名证书导入到操作系统的受信任根证书颁发机构中(具体方法取决于操作系统)。 ### 4. 检查后端服务器是否支持wss 确保后端WebSocket服务器(在本例中运行在localhost:3030)已配置为支持SSL。如果Nginx负责SSL终结,那么后端服务器可以不启用SSL,而使用普通的WebSocket(ws)。但注意,在这种情况下,Nginx和后端服务器之间的连接可以是普通的HTTP(或ws),而Nginx与客户端之间则是HTTPS(wss)。 ### 5. 检查端口和防火墙 - 确保Nginx监听的443端口已打开,并且没有被防火墙阻止。 - 确保后端服务器(本例中的3030端口)也在运行且可访问。 ### 6. 使用有效的证书 如果是在生产环境,必须使用由可信证书颁发机构(CA)签发的证书。免费证书可以从Let's Encrypt获取。 ### 7. 在浏览器中测试 在浏览器中直接访问`https://localhost`(或你的域名),确保网站本身的HTTPS连接没有证书错误。如果HTTPS网站本身就有证书错误,那么wss连接同样会失败。 ### 逐步解决步骤 1. **验证Nginx配置**:使用`nginx -t`测试配置是否正确。 2. **重启Nginx**:使新配置生效。 3. **检查端口监听**:使用`netstat -tuln | grep 443`(Linux)或`Get-NetTCPConnection -LocalPort 443`(Windows PowerShell)检查443端口是否被监听。 4. **检查后端服务**:确保后端WebSocket服务在`http://localhost:3030`正常运行(使用ws连接测试)。 5. **浏览器测试**:在浏览器中访问`https://localhost`,确认没有证书警告。如果有,需要信任证书。 6. **测试wss连接**:使用JavaScript代码尝试建立wss连接,并观察浏览器开发者工具中的网络标签页。 ### 注意事项 - 在本地开发时,如果不想使用SSL,可以考虑在开发服务器上禁用SSL,并让浏览器允许在HTTPS页面上连接到非安全的WebSocket(不推荐,仅用于临时测试)。但生产环境必须使用wss。 - 如果使用Node.js作为后端服务器,并且直接处理wss连接,那么需要配置Node.js服务器的SSL选项(如使用`https`模块和证书文件)。但通常建议使用Nginx作为反向代理来处理SSL。 ### 相关问题 1. 如何为localhost生成自签名证书? 2. 如何将自签名证书添加到操作系统的受信任根证书? 3. 如何配置Spring Boot应用以支持wss? 4. 如何使用Let's Encrypt获取免费的SSL证书? 5. 如何调试Nginx的SSL握手过程? 请根据上述步骤逐一排查,通常可以解决wss连接中的SSL协议错误。 </think> ### 解决 WebSocket 连接 `wss://localhost:3030/websocket/message` 出现 `net::ERR_SSL_PROTOCOL_ERROR` 的方案 此错误表示客户端在 SSL/TLS 握手阶段失败,根本原因是 **SSL 证书配置不正确或连接协议不匹配**。以下是逐步解决方案: --- ### 一、核心检查点 1. **证书有效性** - 本地开发需为 `localhost` 生成有效证书(非自签名需信任证书) - 生产环境需 CA 签发的合法证书 2. **协议兼容性** - 客户端(浏览器)与服务器支持的 SSL/TLS 协议版本需匹配 3. **Nginx 代理配置** - 反向代理需正确传递 WebSocket 的 `Upgrade` 头 --- ### 二、逐步解决方案 #### 步骤 1:验证本地证书(开发环境) ```bash # 生成自签名证书(有效期365天) openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost" ``` 1. 将生成的 `cert.pem` 和 `key.pem` 配置到服务器 2. 将证书导入操作系统信任库(否则浏览器会拦截) #### 步骤 2:更新 Nginx 配置 参考引用[3][^3][^4]的配置,补充 WebSocket 路径支持: ```nginx server { listen 443 ssl; server_name localhost; # 证书路径(替换为你的实际路径) ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; # 协议配置(强制使用 TLSv1.2+) ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # WebSocket 代理配置 ✨关键部分✨ location /websocket/message { proxy_pass http://localhost:3030; # 后端服务端口 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header Host $host; } # 其他配置... } ``` #### 步骤 3:服务器端协议升级(Node.js 示例) ```javascript const https = require('https'); const WebSocket = require('ws'); const server = https.createServer({ cert: fs.readFileSync('/path/to/cert.pem'), key: fs.readFileSync('/path/to/key.pem') }); const wss = new WebSocket.Server({ server, path: "/websocket/message" // 路径需与客户端匹配 }); server.listen(3030); // 端口与 Nginx 配置一致 ``` #### 步骤 4:客户端连接调试 ```javascript // 浏览器中测试连接 const socket = new WebSocket('wss://localhost/websocket/message'); socket.onerror = (error) => { console.error("详细错误:", error); // 获取具体错误码 }; ``` --- ### 三、故障排查表 | 检测项 | 正确状态 | 检查命令/方法 | |-------------------------|-----------------------------------|----------------------------| | 证书是否有效 | 浏览器访问 `https://localhost` 无警告 | 手动访问 | | 443 端口监听 | `LISTEN` 状态 | `netstat -tuln \| grep 443` | | WebSocket 路径匹配 | Nginx 与后端路径完全一致 | 对比配置文件和客户端请求 | | 协议版本兼容性 | 服务端支持 TLSv1.2+ | `openssl s_client -connect localhost:443` | --- ### 四、进阶问题分析 1. **浏览器协议限制** 现代浏览器已禁用 TLSv1.0/TLSv1.1,需在 Nginx 中移除这些协议: ```nginx ssl_protocols TLSv1.2 TLSv1.3; # 禁止旧协议 ``` 2. **自签名证书信任问题**(开发环境) Chrome 中临时允许: ```url chrome://flags/#allow-insecure-localhost → Enabled ``` 3. **防火墙拦截** 确保本地防火墙允许 443 端口: ```bash sudo ufw allow 443/tcp ``` --- ### 五、流程图解 ```mermaid graph TD A[ERR_SSL_PROTOCOL_ERROR] --> B{问题分类} B --> C[证书无效] --> C1[生成有效证书] B --> D[协议不匹配] --> D1[升级至 TLSv1.2+] B --> E[代理配置错误] --> E1[检查 Nginx Upgrade 头] C1 --> F[连接成功] D1 --> F E1 --> F ``` --- ### 相关问题 1. 如何为 `localhost` 创建浏览器信任的自签名证书? 2. Nginx 反向代理 WebSocket 时如何保持长连接不断开? 3. 如何在 Chrome 开发者工具中调试 WebSocket 的 SSL 握手过程? 4. Let's Encrypt 证书如何应用到本地开发环境? 5. WebSocket 与 HTTP/2 在 TLS 配置上有何差异? [^3]: Nginx 需配置 `proxy_set_header Upgrade` 和 `Connection` 头部才能正确代理 WebSocket[^3] [^4]: SSL 协议必须禁用已淘汰的 SSLv3 并优先使用 TLSv1.2+[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Phoenix_smf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值