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