websocket 是怎么连接的

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"背景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最近项目新增了一个 websocket 服务,用 nginx 做了一个简单的端口转发,然后调用的时候发现报错:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"error: Unexpected server response: 426"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"搜索引擎走一波,找到几篇相关文章:"}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"link","attrs":{"href":"https://nginx.org/en/docs/http/websocket.html","title":""},"content":[{"type":"text","text":"https://nginx.org/en/docs/http/websocket.html"}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.nginx.com/blog/websocket-nginx","title":""},"content":[{"type":"text","text":"https://www.nginx.com/blog/websocket-nginx"}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"解决方式也很简单,根据第一篇文章的说明,只要增加转发响应头的配置:"}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"proxy_set_header Upgrade $http_upgrade;"}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"proxy_set_header Connection 'upgrade';"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因为 websocket 的连接建立是基于 HTTP/1.1 的,所以有必要制定 http 协议:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"proxy_http_version: 1.1"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当然这不是必须的,如果服务默认是 HTTP/1.1 的话"}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具体配置如下:"}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"http {\n map $http_upgrade $connection_upgrade {\n default upgrade;\n '' close;\n }\n\n server {\n ...\n\n location /chat/ {\n proxy_pass http://backend;\n proxy_http_version 1.1;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection $connection_upgrade;\n }\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但多年踩坑经验告诉我,如果不了解某项技术的细节,早晚还要掉进下一个坑。所以打算好好研究下这里面的执行逻辑。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Websocket 建立连接的过程分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下图是 websocket "},{"type":"text","marks":[{"type":"strong"}],"text":"建立连接-数据传输-断开连接"},{"type":"text","text":" 的整个过程"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cd/cdba741ea4c5416440613bf8d0855565.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Http1.1 支持协议转换机制,具体说明参考 "},{"type":"link","attrs":{"href":"https://tools.ietf.org/html/rfc2616#section-14.42","title":""},"content":[{"type":"text","text":"https://tools.ietf.org/html/rfc2616#section-14.42"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"首先客户端发起 GET 请求"}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d7/d7593a0a9e59f23f957a22d0a7d4be1e.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里主要关注 Connection 和 Upgrade 两个请求头:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"设置 Connection 头的值为 Upgrade 来指示这是一个升级请求"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Upgrade 头制定要升级到的协议"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"服务端响应"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果服务端决定升级这次连接,就会返回 101 Switching Protocols 响应状态"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d3/d3bdd2deaaf08a12cb9a45e4a53f39c9.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"经过以上过程,Websocket 的连接就建立成功了"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":3,"normalizeStart":3},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"但为什么nginx 代理需要明确配置 Connection 和 Upgrade 请求头呢?"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原来,http 协议将消息头分为了两类:end-to-end 和 hop-by-hop,具体说明参考:"},{"type":"link","attrs":{"href":"https://tools.ietf.org/html/rfc2616#section-13.5.1","title":""},"content":[{"type":"text","text":"https://tools.ietf.org/html/rfc2616#section-13.5.1"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"他们的特性如下:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"end-to-end"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"该类型的消息头会被代理转发"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"该类型的消息头会被缓存"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"hop-to-hop: "}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"该类型的消息头不会被代理转发"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"该类型的消息头不会被缓存"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这样就不难理解,nginx 配置代理转发后,默认并不能转发 Connection 和 Upgrade 消息头,这样转发后的请求到达 Websockt 服务后就没有这两个头,因此就不能由 http 协议升级为 websocket 协议。因此需要在 nginx 中进行手动配置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下为 HTTP/1.1 中 hop-to-hop 类型的消息头:Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailers, Transfer-Encoding, Upgrade"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"连接为什么断开了"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从上面的抓包可以看到,最终 websocket 建立的连接被自动断开了,文档中也做出了解释,这是 nginx 的机制。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"默认情况下,如果被代理的服务 60 秒内没有进行数据传输,连接就会被断开。这个超时时间可以通过 proxy_read_timeout 指令设置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果要保持建立的连接不断开,就要通过心跳包等手段进行维持。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"注意"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"该升级机制只是 HTTP/1.1 有效,HTTP/2 已不支持该机制"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"nginx 会断开长时间没有数据传输的连接"}]}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章