一、通过WebSocket API与服务器进行握手
WebSocket的构造方法中有两个参数,其原型如下:WebSocket(url, [protocols]),url为需要连接的地址。WebSocket的协议头的写法有2个,一个是ws://,另一个是wss://,它们的区别就是后者相当于https,是加密的。如果url没有加端口号,当协议头为ws时默认为80端口,当协议头为wss时默认为443端口。如果需要连接其他端口,则需要向http协议一样,加上端口号,如ws://localhost:12345。url其他部分与标准URL一致,以下URL是合法的:ws://localhost:12345/path/?queryString=queryString。具体URL标准将在下面协议详解中详细说明。
第二个参数protocols为可选参数,代表WebSocket协议的子协议(可以是用户自定义的)。你可能已经注意到了第二个参数名为protocols最后有个s,它可以是一个字符串亦可以是一个数组,如果protocols是一个字符串,则它等价于一个单一值的数组。例如,
WebSocket(‘ws://localhost’,’subprotocol’)
等价于
WebSocket(‘ws://localhost’,[‘subprotocol’])
二、WebSocket URL解析
以下内容部分翻译自W3C规范
WebSocket URL解析组件解析URL步骤如下,这些步骤会返回一个主机名、端口号、资源名和一个安全标识符否则返回失败:
- 如果URL不是一个绝对URL,算法失败
- 将URL字符串转化为UTF-8格式
- 如果URL的模式转为小写(模式是不区分大小写的,即WS、WSS都是可以的)不是ws或wss,则算法失败
- 如果解析的URL结果中存在fragment(#后面的),则算法失败
- 如果是ws模式的URL,则将安全标识符设为false,如果是wss模式的URL则将安全标示符置为true
- 解析url的host名,并转为小写
- 如果有端口号则设置端口号,否则使用隐式端口号声明
- 如果端口号是隐式声明的,当安全标示符为true时,端口号是443,否则为80
- url的path作为资源名,可以是空字符串
- 如果资源名是非空的,则将其设置到结果中,否则将/作为资源名
- 如果URL请求字符串(?后面的内容)非空,则将请求字符串加入结果中,并使用?拼接(同HTTP中URL的请求字符串写法)
- 将主机名、端口号、资源名和安全标示符返回
三、浏览器发送握手包
通过本文第一部分我们可知,浏览器端发送握手请求的API非常简单,只需要new出一个WebSocket的对象,最少情况只需要带一个参数,如var ws = new WebSocket(‘ws://localhost’);
我们在http://dev.w3.org中打开控制台使用var ws = new WebSocket(‘ws://localhost’,’test’);浏览器会发出类似如下请求(注意:这里不会存在跨域问题,不需要在同域下即可,因为WebSocket是在TCP层面上传输数据):
GET ws://localhost/ HTTP/1.1 Host: localhost Connection: Upgrade Pragma: no-cache Cache-Control: no-cache Upgrade: websocket Origin: http://dev.w3.org Sec-WebSocket-Version: 13 Sec-WebSocket-Protocol:test User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.36 Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4 Sec-WebSocket-Key: 32pdAhmqFrFZik/MP7fU8A== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
其实这个就是一个标准的HTTP协议,WebSocket协议中的握手过程是HTTP协议,而一旦握手成功后,就不再是HTTP协议,而是直接通过TCP传输数据。
请求头中大部分内容与HTTP协议一致,这里不再解释,只解释那些不曾出现在普通请求的请求头。
Connection: Upgrade是固定的,表示需要转换为其他协议
Upgrade: websocket是固定的,说明要转换成的协议时WebSocket
Sec-WebSocket-Version: 13代表WebSocket的版本,目前版本是13
Sec-WebSocket-Key: 32pdAhmqFrFZik/MP7fU8A==这个是握手的认证串,服务端需要将此key进行一定处理后返回,再由浏览器验证有效性,必须符合算法结果才可正常建立连接(只是简单的哈希计算)
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits代表客户端支持的扩展类型
Sec-WebSocket-Protocol:test为子协议,是否存在取决于构造方法的第二个参数,如果第二个参数是个数组,则此处的值为数组按一个逗号和一个空格分隔,相当于[].join(‘, ‘);
其中Sec-WebSocket-Key是非常重要的请求头,我们会在服务端处理这个请求头,只有处理正确,浏览器才会正确的与服务器建立连接。