# 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)