1. 介绍

    上一篇文章websocket之简单的服务器端(二)介绍了两个简单的websocket服务器,并且介绍了如何用javascript连接上websocket服务器。除了能用浏览器的javascript连接上,还可以用任何编程语言,因为websocket协议是基于TCP协议请求的,只要能发送TCP socket请求,就可以发送websocket请求,这篇文章来讲述如何用ruby来发送websocket请求,并讲讲其原理。

    1. websocket-ruby

    websocket-ruby是一个纯ruby实现websocket请求的gem,它支持很多版本的websocket。比如官方列出的:

    1. hixie-75
    2. hixie-76
    3. all hybi drafts (00-13)
    4. RFC 6455

    学习它,可以让我们对websocket协议的客户端和服务器的实现更为了解。
    首先安装它。

    1. $ gem install "websocket"

    来看一个最简单的例子,客户端请求websocket请求。

    1. @handshake = WebSocket::Handshake::Server.new
    2. # Parse client request
    3. @handshake << <<EOF
    4. GET /demo HTTP/1.1\r
    5. Upgrade: websocket\r
    6. Connection: Upgrade\r
    7. Host: example.com\r
    8. Origin: http://example.com\r
    9. Sec-WebSocket-Version: 13\r
    10. Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r
    11. \r
    12. EOF
    13. # All data received?
    14. @handshake.finished?
    15. # No parsing errors?
    16. @handshake.valid?
    17. # Create response
    18. puts @handshake.to_s

    因为我们说过websocket协议是基于tcp协议之上,所以我们可以发送类似的socket请求。
    @handshake变量就是我们socket请求的内容。我们主要来看这部分。
    其中,第二行代码@handshake << <<EOF发送的内容,跟之前上一篇文章在浏览器的请求头信息是差不多的,其中来看看Sec-WebSocket-Version和Sec-WebSocket-Key。
    Sec-WebSocket-Version表示的是websocket使用的版本,客户端和服务器端都会根据客户端发送的版本号,进行相应的处理,不同的版本对应不同的处理方式,这些都是websocket-ruby实现好的。
    比如源码中是这样实现的:

    1. # https://github.com/imanel/websocket-ruby/blob/master/lib/websocket/handshake/client.rb#L103
    2. def include_version
    3. @handler = case @version
    4. when 75 then Handler::Client75.new(self)
    5. when 76, 0 then Handler::Client76.new(self)
    6. when 1..3 then Handler::Client01.new(self)
    7. when 4..10 then Handler::Client04.new(self)
    8. when 11..17 then Handler::Client11.new(self)
    9. else fail WebSocket::Error::Handshake::UnknownVersion
    10. end
    11. end

    Sec-WebSocket-Key是用base64算法加密过的随机串,每次请求都不一样,上面是自己指定的,但是它可以由客户端计算出来,比如

    1. # https://github.com/imanel/websocket-ruby/blob/master/lib/websocket/handshake/handler/client04.rb#L33
    2. def key
    3. @key ||= Base64.encode64((1..16).map { rand(255).chr } * '').strip
    4. end

    现在回头来看看上面的演示代码到底输出了什么样的结果。

    1. HTTP/1.1 101 Switching Protocols
    2. Upgrade: websocket
    3. Connection: Upgrade
    4. 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”头的值,返回给客户端。
    它的算法是这样的:

    1. # https://github.com/imanel/websocket-ruby/blob/master/lib/websocket/handshake/handler/server04.rb#L31
    2. def signature
    3. return unless key
    4. string_to_sign = "#{key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
    5. Base64.encode64(Digest::SHA1.digest(string_to_sign)).chomp
    6. end
    1. websocket-client-simple

    websocket-client-simple是对websocket-ruby这个gem的进一步封装,它的源码只有一个文件。还记得上一篇文章,用javascript写websocket请求的例子吗,ruby也可以有类似的语法,就是用这个gem。

    1. require 'websocket-client-simple'
    2. ws = WebSocket::Client::Simple.connect 'ws://localhost:8080/echo'
    3. ws.on :message do |msg|
    4. puts "received data: " + msg.data
    5. end
    6. ws.on :open do
    7. ws.send 'hello!!!'
    8. end
    9. ws.on :close do |e|
    10. p e
    11. exit 1
    12. end
    13. ws.on :error do |e|
    14. p e
    15. end
    16. loop do
    17. ws.send STDIN.gets.strip
    18. end

    这个例子演示了,输入什么,websocket就会返回相同的输入。
    本篇完结。