# 3. 客户端详解 #### 1. 介绍 上一篇文章[websocket之简单的服务器端(二)](http://www.rails365.net/articles/websocket-zhi-jian-dan-de-fu-wu-qi-duan-er)介绍了两个简单的websocket服务器,并且介绍了如何用javascript连接上websocket服务器。除了能用浏览器的javascript连接上,还可以用任何编程语言,因为websocket协议是基于TCP协议请求的,只要能发送TCP socket请求,就可以发送websocket请求,这篇文章来讲述如何用ruby来发送websocket请求,并讲讲其原理。 #### 2. websocket-ruby [websocket-ruby](https://github.com/imanel/websocket-ruby)是一个纯ruby实现websocket请求的gem,它支持很多版本的websocket。比如官方列出的: - [hixie-75](http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75) - [hixie-76](http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76) - [all hybi drafts (00-13)](http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17) - [RFC 6455](http://datatracker.ietf.org/doc/rfc6455/) 学习它,可以让我们对websocket协议的客户端和服务器的实现更为了解。 首先安装它。 ``` $ gem install "websocket" ``` 来看一个最简单的例子,客户端请求websocket请求。 ``` @handshake = WebSocket::Handshake::Server.new # Parse client request @handshake << <<EOF GET /demo HTTP/1.1\r Upgrade: websocket\r Connection: Upgrade\r Host: example.com\r Origin: http://example.com\r Sec-WebSocket-Version: 13\r Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r \r EOF # All data received? @handshake.finished? # No parsing errors? @handshake.valid? # Create response puts @handshake.to_s ``` 因为我们说过websocket协议是基于tcp协议之上,所以我们可以发送类似的socket请求。 `@handshake`变量就是我们socket请求的内容。我们主要来看这部分。 其中,第二行代码`@handshake << <<EOF`发送的内容,跟之前上一篇文章在浏览器的请求头信息是差不多的,其中来看看`Sec-WebSocket-Version`和`Sec-WebSocket-Key`。 `Sec-WebSocket-Version`表示的是websocket使用的版本,客户端和服务器端都会根据客户端发送的版本号,进行相应的处理,不同的版本对应不同的处理方式,这些都是websocket-ruby实现好的。 比如源码中是这样实现的: ``` # https://github.com/imanel/websocket-ruby/blob/master/lib/websocket/handshake/client.rb#L103 def include_version @handler = case @version when 75 then Handler::Client75.new(self) when 76, 0 then Handler::Client76.new(self) when 1..3 then Handler::Client01.new(self) when 4..10 then Handler::Client04.new(self) when 11..17 then Handler::Client11.new(self) else fail WebSocket::Error::Handshake::UnknownVersion end end ``` Sec-WebSocket-Key是用base64算法加密过的随机串,每次请求都不一样,上面是自己指定的,但是它可以由客户端计算出来,比如 ``` # https://github.com/imanel/websocket-ruby/blob/master/lib/websocket/handshake/handler/client04.rb#L33 def key @key ||= Base64.encode64((1..16).map { rand(255).chr } * '').strip end ``` 现在回头来看看上面的演示代码到底输出了什么样的结果。 ``` HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= ``` 返回的状态码是101,并且返回了Sec-WebSocket-Accept的内容。 Sec-WebSocket-Accept的计算方式是这样的,把客户端发送过来的“Sec-WebSocket-Key”加上一个魔幻字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”。使用SHA-1加密,之后进行BASE-64编码,将结果做为“Sec-WebSocket-Accept”头的值,返回给客户端。 它的算法是这样的: ``` # https://github.com/imanel/websocket-ruby/blob/master/lib/websocket/handshake/handler/server04.rb#L31 def signature return unless key string_to_sign = "#{key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11" Base64.encode64(Digest::SHA1.digest(string_to_sign)).chomp end ``` #### 3. websocket-client-simple [websocket-client-simple](https://github.com/shokai/websocket-client-simple)是对websocket-ruby这个gem的进一步封装,它的源码只有一个文件。还记得上一篇文章,用javascript写websocket请求的例子吗,ruby也可以有类似的语法,就是用这个gem。 ``` require 'websocket-client-simple' ws = WebSocket::Client::Simple.connect 'ws://localhost:8080/echo' ws.on :message do |msg| puts "received data: " + msg.data end ws.on :open do ws.send 'hello!!!' end ws.on :close do |e| p e exit 1 end ws.on :error do |e| p e end loop do ws.send STDIN.gets.strip end ``` 这个例子演示了,输入什么,websocket就会返回相同的输入。 本篇完结。 下一篇: [websocket之实现简易聊天室(四)](http://www.rails365.net/articles/websocket-zhi-shi-xian-jian-yi-liao-tian-shi-si)