aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/web/api/websockets_api
diff options
context:
space:
mode:
authorPeter Bengtsson <mail@peterbe.com>2020-12-08 14:40:17 -0500
committerPeter Bengtsson <mail@peterbe.com>2020-12-08 14:40:17 -0500
commit33058f2b292b3a581333bdfb21b8f671898c5060 (patch)
tree51c3e392513ec574331b2d3f85c394445ea803c6 /files/zh-cn/web/api/websockets_api
parent8b66d724f7caf0157093fb09cfec8fbd0c6ad50a (diff)
downloadtranslated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.gz
translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.bz2
translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.zip
initial commit
Diffstat (limited to 'files/zh-cn/web/api/websockets_api')
-rw-r--r--files/zh-cn/web/api/websockets_api/index.html208
-rw-r--r--files/zh-cn/web/api/websockets_api/websocket_server_vb.net/index.html270
-rw-r--r--files/zh-cn/web/api/websockets_api/writing_a_websocket_server_in_java/index.html201
-rw-r--r--files/zh-cn/web/api/websockets_api/writing_websocket_client_applications/index.html186
-rw-r--r--files/zh-cn/web/api/websockets_api/writing_websocket_server/index.html273
-rw-r--r--files/zh-cn/web/api/websockets_api/writing_websocket_servers/index.html244
6 files changed, 1382 insertions, 0 deletions
diff --git a/files/zh-cn/web/api/websockets_api/index.html b/files/zh-cn/web/api/websockets_api/index.html
new file mode 100644
index 0000000000..ecc7894c38
--- /dev/null
+++ b/files/zh-cn/web/api/websockets_api/index.html
@@ -0,0 +1,208 @@
+---
+title: WebSockets
+slug: Web/API/WebSockets_API
+tags:
+ - References
+ - WebSockets
+translation_of: Web/API/WebSockets_API
+---
+<p>{{DefaultAPISidebar("Websockets API")}}</p>
+
+<p><strong>WebSockets</strong> 是一种先进的技术。它可以在用户的浏览器和服务器之间打开交互式通信会话。使用此API,您可以向服务器发送消息并接收事件驱动的响应,而无需通过轮询服务器的方式以获得响应。</p>
+
+<h2 id="接口">接口</h2>
+
+<dl>
+ <dt><a href="/en-US/docs/Web/API/WebSocket" title="en/WebSockets/WebSockets reference/WebSocket"><code>WebSocket</code></a></dt>
+ <dd>用于连接WebSocket服务器的主要接口,之后可以在这个连接上发送 和接受数据。</dd>
+ <dt><code><a href="/en-US/docs/Web/API/CloseEvent" title="en/WebSockets/WebSockets reference/CloseEvent">CloseEvent</a></code></dt>
+ <dd>连接关闭时WebSocket对象发送的事件。</dd>
+ <dt><a href="/en-US/docs/Web/API/MessageEvent" title="en/WebSockets/WebSockets reference/MessageEvent"><code>MessageEvent</code></a></dt>
+ <dd>当从服务器获取到消息的时候WebSocket对象触发的事件。</dd>
+</dl>
+
+<h2 class="Tools" id="Tools" name="Tools">工具</h2>
+
+<ul>
+ <li><a href="https://hacks.mozilla.org/2017/06/introducing-humblenet-a-cross-platform-networking-library-that-works-in-the-browser/">HumbleNet</a>: 一个在浏览器中工作的跨平台网络库。它由一个围绕websocket和WebRTC的C包装器组成,抽象了跨浏览器的差异,方便了为游戏和其它应用程序创建多用户网络功能。</li>
+ <li><a href="https://github.com/uWebSockets/uWebSockets">µWebSockets</a>:由<a href="https://isocpp.org/">C++11</a>和<a href="http://nodejs.org/" title="http://nodejs.org/">Node.js</a> 实现的高度可扩展的WebSocket服务器和客户端.。</li>
+ <li><a href="https://github.com/ClusterWS/ClusterWS">ClusterWS</a>:  轻量级、快速和强大的框架,用于在<a href="http://nodejs.org/" title="http://nodejs.org/">Node.js</a>.中构建可伸缩的WebSocket应用程序。</li>
+ <li><a class="external" href="http://socket.io" title="http://socket.io/">Socket.IO</a>: 一个基于长轮询/WebSocket的<a href="http://nodejs.org" title="http://nodejs.org/">Node.js</a>第三方传输协议。</li>
+ <li><a href="http://socketcluster.io/">SocketCluster</a>: 一个用于<a href="http://nodejs.org" title="http://nodejs.org/">Node.js</a>的pub/sub专注于可伸缩 WebSocket框架。</li>
+ <li><a href="https://github.com/Worlize/WebSocket-Node" title="https://github.com/Worlize/WebSocket-Node">WebSocket-Node</a>: 一个用 <a href="http://nodejs.org/" title="http://nodejs.org/">Node.js</a>实现WebSocket服务器API。</li>
+ <li><a href="http://www.totaljs.com/">Total.js</a>:一个用<a href="http://www.nodejs.org/">Node.js</a> 实现的的Web应用程序框架(例如:WebSocket聊天)。</li>
+ <li><a href="https://www.npmjs.com/package/faye-websocket">Faye</a>: 一个 <a href="http://nodejs.org/" title="http://nodejs.org/">Node.js</a>的<a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API">WebSocket</a> (双向连接)和 <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventSource/">EventSource</a> (单向连接)的服务器和客户端。</li>
+ <li><a href="http://signalr.net/">SignalR</a>: SignalR在可用时将隐藏使用WebSockets,在不可用时将优雅地使用其他技术和技术,而应用程序代码保持不变。</li>
+ <li><a href="https://caddyserver.com/docs/websocket">Caddy</a>: 能够将任意命令(stdin/stdout)代理为websocket的web服务器。</li>
+ <li><a href="https://github.com/websockets/ws">ws</a>: 一个流行的WebSocket客户端和服务器 <a href="http://nodejs.org/" title="http://nodejs.org/">Node.js</a>库。</li>
+ <li><a href="https://github.com/bigstepinc/jsonrpc-bidirectional">jsonrpc-bidirectional</a>: 易于使用异步RPC库,通过单个WebSocket或RTCDataChannel (WebRTC)连接支持双向调用。TCP / SCTP /等。客户端和服务器可以各自承载自己的JSONRPC和服务器端点。</li>
+ <li><a href="https://github.com/elpheria/rpc-websockets">rpc-websockets</a>: JSON-RPC 2.0在websocket上实现Node.js和JavaScript。</li>
+</ul>
+
+<h2 class="Related_Topics" id="Related_Topics" name="Related_Topics">相关话题</h2>
+
+<ul>
+ <li><a href="/en-US/docs/AJAX" title="AJAX">AJAX</a></li>
+ <li><a href="/en-US/docs/JavaScript" title="JavaScript">JavaScript</a></li>
+</ul>
+
+<h2 id="参见">参见</h2>
+
+<ul>
+ <li><a class="external" href="http://tools.ietf.org/html/rfc6455">RFC 6455 — The WebSocket Protocol</a></li>
+ <li><a class="external" href="http://www.w3.org/TR/websockets/">WebSocket API Specification</a></li>
+ <li><a href="/en-US/docs/Server-sent_events" title="Server-sent_events">Server-Sent Events</a></li>
+</ul>
+
+<h2 id="浏览器兼容性">浏览器兼容性</h2>
+
+<p>{{CompatibilityTable}}</p>
+
+<div id="compat-desktop">
+<table class="compat-table">
+ <tbody>
+ <tr>
+ <th>Feature</th>
+ <th>Chrome</th>
+ <th>Edge</th>
+ <th>Firefox (Gecko)</th>
+ <th>Internet Explorer</th>
+ <th>Opera</th>
+ <th>Safari</th>
+ </tr>
+ <tr>
+ <td>Version -76 support {{obsolete_inline}}</td>
+ <td>6</td>
+ <td>{{CompatNo}}</td>
+ <td>{{CompatGeckoDesktop("2.0")}}</td>
+ <td>{{CompatNo}}</td>
+ <td>11.00 (disabled)</td>
+ <td>5.0.1</td>
+ </tr>
+ <tr>
+ <td>Protocol version 7 support {{obsolete_inline}}</td>
+ <td>{{CompatNo}}</td>
+ <td>{{CompatNo}}</td>
+ <td>{{CompatGeckoDesktop("6.0")}}<br>
+ {{property_prefix("Moz")}}</td>
+ <td>{{CompatNo}}</td>
+ <td>{{CompatNo}}</td>
+ <td>{{CompatNo}}</td>
+ </tr>
+ <tr>
+ <td>Protocol version 10 support {{obsolete_inline}}</td>
+ <td>14</td>
+ <td>{{CompatNo}}</td>
+ <td>{{CompatGeckoDesktop("7.0")}}<br>
+ {{property_prefix("Moz")}}</td>
+ <td>HTML5 Labs</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatUnknown}}</td>
+ </tr>
+ <tr>
+ <td>Standard - RFC 6455 Support</td>
+ <td>16</td>
+ <td>{{CompatVersionUnknown}}</td>
+ <td>{{CompatGeckoDesktop("11.0")}}</td>
+ <td>10</td>
+ <td>12.10</td>
+ <td>6.0</td>
+ </tr>
+ <tr>
+ <td>Usable in Workers</td>
+ <td>{{CompatVersionUnknown}}</td>
+ <td>{{CompatVersionUnknown}}</td>
+ <td>{{CompatGeckoDesktop("37.0")}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatUnknown}}</td>
+ </tr>
+ </tbody>
+</table>
+</div>
+
+<div id="compat-mobile">
+<table class="compat-table">
+ <tbody>
+ <tr>
+ <th>Feature</th>
+ <th>Android</th>
+ <th>Edge</th>
+ <th>Firefox Mobile (Gecko)</th>
+ <th>IE Mobile</th>
+ <th>Opera Mobile</th>
+ <th>Safari Mobile</th>
+ </tr>
+ <tr>
+ <td>Version -76 support {{obsolete_inline}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatNo}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatUnknown}}</td>
+ </tr>
+ <tr>
+ <td>Protocol version 7 support {{obsolete_inline}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatNo}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatUnknown}}</td>
+ </tr>
+ <tr>
+ <td>Protocol version 8 support (IETF draft 10) {{obsolete_inline}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatNo}}</td>
+ <td>{{CompatGeckoMobile("7.0")}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatUnknown}}</td>
+ </tr>
+ <tr>
+ <td>Standard - RFC 6455 Support</td>
+ <td>4.4</td>
+ <td>{{CompatVersionUnknown}}</td>
+ <td>{{CompatGeckoDesktop("11.0")}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>12.10</td>
+ <td>6.0</td>
+ </tr>
+ <tr>
+ <td>Usable in Workers</td>
+ <td>{{CompatVersionUnknown}}</td>
+ <td>{{CompatVersionUnknown}}</td>
+ <td>{{CompatGeckoMobile("37.0")}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatUnknown}}</td>
+ <td>{{CompatUnknown}}</td>
+ </tr>
+ </tbody>
+</table>
+</div>
+
+<h3 id="Gecko_notes">Gecko notes</h3>
+
+<p>Firefox中的WebSocket支持正在继续跟踪WebSocket规范的发展。Firefox 6实现了底层协议的version 7,而Firefox 7实现了version 8(如IETF draft 10所指定的)。Firefox移动版在Firefox7.0支持WebSocket。</p>
+
+<h4 id="Gecko_6.0">Gecko 6.0</h4>
+
+<p>在Gecko 6.0 {{geckoRelease("6.0")}}之前,一些网站认为<code>WebSocket</code>对象是错误的,意味着<code>WebSocket</code>服务没有前缀,此对象已重命名为<code>MozWebSocket</code>。</p>
+
+<h4 id="Gecko_7.0">Gecko 7.0</h4>
+
+<p>从Gecko 7.0 {{geckoRelease("7.0")}}中开始,<code>network.websocket.max-connections是</code>用于确定每次可以打开的WebSocket连接的最大数量的最大连接首选项。默认值是200。</p>
+
+<h4 id="Gecko_8.0">Gecko 8.0</h4>
+
+<p>从Gecko 8.0 {{geckoRelease("8.0")}}中开始,<code>WebSocket</code>协议的deflate-stream扩展已经被禁用,因为它已经在规范草案中废弃了。这解决了某些站点的不兼容性问题。</p>
+
+<h4 id="Gecko_11.0">Gecko 11.0</h4>
+
+<p>在Gecko 11.0之前,传入和传出消息的大小都限制在16MB。它们现在的大小可能高达 2 GB 。然而,请注意,内存限制(尤其是在移动设备上)使其成为理论上的最大限制,而不是实际的最大限制。实际上,在没有足够内存的设备上,这种大小的传输将会失败。</p>
+
+<p>此外,ArrayBuffer对二进制数据的收发支持已经实现。</p>
+
+<p><br>
+ 从Gecko 11.0开始,WebSocket API不需要前缀。</p>
diff --git a/files/zh-cn/web/api/websockets_api/websocket_server_vb.net/index.html b/files/zh-cn/web/api/websockets_api/websocket_server_vb.net/index.html
new file mode 100644
index 0000000000..3969f9c5ea
--- /dev/null
+++ b/files/zh-cn/web/api/websockets_api/websocket_server_vb.net/index.html
@@ -0,0 +1,270 @@
+---
+title: WebSocket Server Vb.NET
+slug: Web/API/WebSockets_API/WebSocket_Server_Vb.NET
+translation_of: Web/API/WebSockets_API/WebSocket_Server_Vb.NET
+---
+<p>{{gecko_minversion_header("2")}}{{draft}}</p>
+
+<p>下面的示例没有优化。没有使用 .NET 4.5 Websocket。<br>
+ <br>
+ 当前版本:</p>
+
+<ul>
+ <li>包含了一个System.Net.Sockets.TcpClient类的泛型集合</li>
+ <li>特性 - 自定义事件和委托处理程序</li>
+ <li>特性 -  线程化和实现Timers.Timer</li>
+ <li>演示如何使用网络流将帧写回客户机(opCode 0001)</li>
+ <li>是否打算作为本教程和其他贡献者的起点</li>
+</ul>
+
+<p> </p>
+
+<pre>Imports System.Net.Sockets
+Imports System.Net
+Imports System
+Imports System.Text
+Imports System.Text.RegularExpressions
+
+
+Namespace TypeDef.WebSocket
+
+ Public Class Client
+ Dim _TcpClient As System.Net.Sockets.TcpClient
+
+ Public Delegate Sub OnClientDisconnectDelegateHandler()
+ Public Event onClientDisconnect As OnClientDisconnectDelegateHandler
+
+
+ Sub New(ByVal tcpClient As System.Net.Sockets.TcpClient)
+ Me._TcpClient = tcpClient
+ End Sub
+
+
+ Function isConnected() As Boolean
+ Return Me._TcpClient.Connected
+ End Function
+
+
+ Sub HandShake()
+ Dim stream As NetworkStream = Me._TcpClient.GetStream()
+ Dim bytes As Byte()
+ Dim data As String
+
+ While Me._TcpClient.Connected
+ While (stream.DataAvailable)
+ ReDim bytes(Me._TcpClient.Client.Available)
+ stream.Read(bytes, 0, bytes.Length)
+ data = System.Text.Encoding.UTF8.GetString(bytes)
+
+ If (New System.Text.RegularExpressions.Regex("^GET").IsMatch(data)) Then
+
+ Dim response As Byte() = System.Text.Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" &amp; Environment.NewLine &amp; "Connection: Upgrade" &amp; Environment.NewLine &amp; "Upgrade: websocket" &amp; Environment.NewLine &amp; "Sec-WebSocket-Accept: " &amp; Convert.ToBase64String(System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(New Regex("Sec-WebSocket-Key: (.*)").Match(data).Groups(1).Value.Trim() &amp; "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))) &amp; Environment.NewLine &amp; Environment.NewLine)
+
+ stream.Write(response, 0, response.Length)
+ Exit Sub
+ Else
+ 'We're going to disconnect the client here, because he's not handshacking properly (or at least to the scope of this code sample)
+ Me._TcpClient.Close() 'The next While Me._TcpClient.Connected Loop Check should fail.. and raise the onClientDisconnect Event Thereafter
+ End If
+ End While
+ End While
+ RaiseEvent onClientDisconnect()
+ End Sub
+
+
+ Sub CheckForDataAvailability()
+ If (Me._TcpClient.GetStream().DataAvailable) Then
+ Dim stream As NetworkStream = Me._TcpClient.GetStream()
+  Dim frameCount = 2
+ Dim bytes As Byte()
+ Dim data As String
+ ReDim bytes(Me._TcpClient.Client.Available)
+ stream.Read(bytes, 0, bytes.Length) 'Read the stream, don't close it..
+
+ Try
+ Dim length As UInteger = bytes(1) - 128 'this should obviously be a byte (unsigned 8bit value)
+
+  If length &gt; -1 Then
+ If length = 126 Then
+ length = 4
+ ElseIf length = 127 Then
+ length = 10
+ End If
+ End If
+
+ 'the following is very inefficient and likely unnecessary..
+ 'the main purpose is to just get the lower 4 bits of byte(0) - which is the OPCODE
+
+ Dim value As Integer = bytes(0)
+ Dim bitArray As BitArray = New BitArray(8)
+
+ For c As Integer = 0 To 7 Step 1
+ If value - (2 ^ (7 - c)) &gt;= 0 Then
+ bitArray.Item(c) = True
+ value -= (2 ^ (7 - c))
+ Else
+ bitArray.Item(c) = False
+ End If
+ Next
+
+
+ Dim FRRR_OPCODE As String = ""
+
+ For Each bit As Boolean In bitArray
+ If bit Then
+ FRRR_OPCODE &amp;= "1"
+ Else
+ FRRR_OPCODE &amp;= "0"
+ End If
+ Next
+
+
+ Dim FIN As Integer = FRRR_OPCODE.Substring(0, 1)
+ Dim RSV1 As Integer = FRRR_OPCODE.Substring(1, 1)
+ Dim RSV2 As Integer = FRRR_OPCODE.Substring(2, 1)
+ Dim RSV3 As Integer = FRRR_OPCODE.Substring(3, 1)
+ Dim opCode As Integer = Convert.ToInt32(FRRR_OPCODE.Substring(4, 4), 2)
+
+
+
+ Dim decoded(bytes.Length - (frameCount + 4)) As Byte
+ Dim key As Byte() = {bytes(frameCount), bytes(frameCount+1), bytes(frameCount+2), bytes(frameCount+3)}
+
+ Dim j As Integer = 0
+  For i As Integer = (frameCount + 4) To (bytes.Length - 2) Step 1
+ decoded(j) = Convert.ToByte((bytes(i) Xor masks(j Mod 4)))
+  j += 1
+ Next
+
+
+
+ Select Case opCode
+ Case Is = 1
+ 'Text Data Sent From Client
+
+ data = System.Text.Encoding.UTF8.GetString(decoded)
+ 'handle this data
+
+ Dim Payload As Byte() = System.Text.Encoding.UTF8.GetBytes("Text Recieved")
+ Dim FRRROPCODE As Byte() = Convert.ToByte("10000001", 2) 'FIN is set, and OPCODE is 1 or Text
+ Dim header as byte() = {FRRROPCODE, Convert.ToByte(Payload.Length)}
+
+
+ Dim ResponseData As Byte()
+ ReDim ResponseData((header.length + Payload.Length) - 1)
+ 'NOTEWORTHY: if you Redim ResponseData(header.length + Payload.Length).. you'll add a 0 value byte at the end of the response data..
+ 'which tells the client that your next stream write will be a continuation frame..
+
+ Dim index as integer = 0
+
+ Buffer.BlockCopy(header, 0, ResponseData, index, header.length)
+ index += header.length
+
+ Buffer.BlockCopy(payload, 0, ResponseData, index, payload.length)
+ index += payload.length
+ stream.Write(ResponseData, 0, ResponseData.Length)
+ Case Is = 2
+ '// Binary Data Sent From Client
+ data = System.Text.Encoding.UTF8.GetString(decoded)
+ Dim response As Byte() = System.Text.Encoding.UTF8.GetBytes("Binary Recieved")
+ stream.Write(response, 0, response.Length)
+ Case Is = 9 '// Ping Sent From Client
+ Case Is = 10 '// Pong Sent From Client
+ Case Else '// Improper opCode.. disconnect the client
+ _TcpClient.Close()
+ RaiseEvent onClientDisconnect()
+ End Select
+ Catch ex As Exception
+ _TcpClient.Close()
+ RaiseEvent onClientDisconnect()
+ End Try
+ End If
+ End Sub
+ End Class
+
+
+
+ Public Class Server
+ Inherits System.Net.Sockets.TcpListener
+
+ Delegate Sub OnClientConnectDelegate(ByVal sender As Object, ByRef Client As WebSocket.Client)
+ Event OnClientConnect As OnClientConnectDelegate
+
+
+ Dim WithEvents PendingCheckTimer As Timers.Timer = New Timers.Timer(500)
+ Dim WithEvents ClientDataAvailableTimer As Timers.Timer = New Timers.Timer(50)
+ Property ClientCollection As List(Of WebSocket.Client) = New List(Of WebSocket.Client)
+
+
+
+ Sub New(ByVal url As String, ByVal port As Integer)
+ MyBase.New(IPAddress.Parse(url), port)
+ End Sub
+
+
+ Sub startServer()
+ Me.Start()
+ PendingCheckTimer.Start()
+ End Sub
+
+
+
+ Sub Client_Connected(ByVal sender As Object, ByRef client As WebSocket.Client) Handles Me.OnClientConnect
+ Me.ClientCollection.Add(client)
+ AddHandler client.onClientDisconnect, AddressOf Client_Disconnected
+ client.HandShake()
+ ClientDataAvailableTimer.Start()
+ End Sub
+
+
+ Sub Client_Disconnected()
+
+ End Sub
+
+
+ Function isClientDisconnected(ByVal client As WebSocket.Client) As Boolean
+ isClientDisconnected = False
+ If Not client.isConnected Then
+ Return True
+ End If
+ End Function
+
+
+ Function isClientConnected(ByVal client As WebSocket.Client) As Boolean
+ isClientConnected = False
+ If client.isConnected Then
+ Return True
+ End If
+ End Function
+
+
+ Private Sub PendingCheckTimer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles PendingCheckTimer.Elapsed
+ If Pending() Then
+ RaiseEvent OnClientConnect(Me, New CORE.TypeDef.WebSocket.Client(Me.AcceptTcpClient()))
+ End If
+ End Sub
+
+
+ Private Sub ClientDataAvailableTimer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles ClientDataAvailableTimer.Elapsed
+ Me.ClientCollection.RemoveAll(AddressOf isClientDisconnected)
+ If Me.ClientCollection.Count &lt; 1 Then ClientDataAvailableTimer.Stop()
+
+ For Each Client As WebSocket.Client In Me.ClientCollection
+ Client.CheckForDataAvailability()
+ Next
+ End Sub
+ End Class
+End Namespace
+
+Sub Main() 'Program Entry point
+ Dim thread As System.Threading.Thread = New System.Threading.Thread(AddressOf StartWebSocketServer)
+ 'Application.Add("WebSocketServerThread", thread) 'Global.asax - context.Application .. I left this part in for web application developers
+ thread.Start()
+End Sub
+
+Public Shared WebSocketServer As TypeDef.WebSocket.Server
+Public Shared Sub StartWebSocketServer()
+ WebSocketServer = New TypeDef.WebSocket.Server("127.0.0.1", 8000)
+ WebSocketServer.startServer()
+End Sub
+</pre>
diff --git a/files/zh-cn/web/api/websockets_api/writing_a_websocket_server_in_java/index.html b/files/zh-cn/web/api/websockets_api/writing_a_websocket_server_in_java/index.html
new file mode 100644
index 0000000000..b2d4814299
--- /dev/null
+++ b/files/zh-cn/web/api/websockets_api/writing_a_websocket_server_in_java/index.html
@@ -0,0 +1,201 @@
+---
+title: Writing a WebSocket server in Java
+slug: Web/API/WebSockets_API/Writing_a_WebSocket_server_in_Java
+translation_of: Web/API/WebSockets_API/Writing_a_WebSocket_server_in_Java
+---
+<h2 id="引言">引言</h2>
+
+<p>你可以通过这个例子知道如何用甲骨文的Java语言来创建一个WebSocket服务。<br>
+ <br>
+ 虽然其他的服务端语言也能创建WebSocket服务,但是通过这个例子你可以看到使用Java来做这件事会更简单。</p>
+
+<p>这个服务符合协议<a href="http://tools.ietf.org/html/rfc6455" title="http://tools.ietf.org/html/rfc6455">RFC 6455</a>, 所以它只处理Chrome版本16,Firefox 11,IE 10及更高版本的连接。</p>
+
+<h2 id="第一步">第一步</h2>
+
+<p>WebSocket通过<a href="http://en.wikipedia.org/wiki/Transmission_Control_Protocol" title="http://en.wikipedia.org/wiki/Transmission_Control_Protocol">TCP(传输控制协议)</a>通信. Java的<a href="http://docs.oracle.com/javase/8/docs/api/java/net/ServerSocket.html">ServerSocket</a> 类位于java.net包中。</p>
+
+<h3 id="ServerSocket">ServerSocket</h3>
+
+<p>构造器:</p>
+
+<p><code>ServerSocket(int port)</code></p>
+
+<p>实例化ServerSocket类时,它将绑定到port参数指定的端口号。</p>
+
+<p><span style="line-height: 1.572;">实现代码片段一:</span></p>
+
+<pre><code>import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+import java.util.Scanner;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class WebSocket {
+ public static void main(String[] args) throws IOException, NoSuchAlgorithmException {
+ ServerSocket server = new ServerSocket(80);
+ try {
+ System.out.println("Server has started on 127.0.0.1:80.\r\nWaiting for a connection...");
+ Socket client = server.accept();
+ System.out.println("A client connected.");</code></pre>
+
+<h3 id="Socket">Socket</h3>
+
+<p>方法:</p>
+
+<ul>
+ <li><code>java.net.</code><a href="http://docs.oracle.com/javase/8/docs/api/java/net/Socket.html" title="class in java.net">Socket</a><code> <span class="memberNameLink"><a href="http://docs.oracle.com/javase/8/docs/api/java/net/Socket.html#getInputStream--">getInputStream</a></span>()</code><br>
+ 返回这个Socket的输入流InputStream</li>
+ <li><code>java.net.</code><a href="http://docs.oracle.com/javase/8/docs/api/java/net/Socket.html" title="class in java.net">Socket</a><code> <span class="memberNameLink"><a href="http://docs.oracle.com/javase/8/docs/api/java/net/Socket.html#getOutputStream--">getOutputStream</a></span>()</code><br>
+ 返回这个Socket的输出流OutputStream</li>
+</ul>
+
+<h3 id="OutputStream">OutputStream</h3>
+
+<p>方法:</p>
+
+<p><code>write(byte[] b, int off, int len)</code></p>
+
+<p>将从数组<code>b</code>中的下标<em><code>off</code></em>开始的<em><code>len</code></em>个字节写入此输出流。</p>
+
+<h3 id="InputStream">InputStream</h3>
+
+<p>方法:</p>
+
+<p><code>int read(byte[] b, int off, int len)</code></p>
+
+<p>将输入流中最多 <code>len</code> 个字节写入<code>byte[] b</code>,写入起始下标为<code>off</code>。尝试读取多达 <code>len</code> 字节,但可能读取较少数量。以整数形式返回实际读取的字节数。<em> </em></p>
+
+<p>代码片段二:</p>
+
+<pre><code>InputStream in = client.getInputStream();
+ OutputStream out = client.getOutputStream();
+ Scanner s = new Scanner(in, "UTF-8");</code></pre>
+
+<h2 id="握手">握手</h2>
+
+<p>当客户端连接到服务器时,它会发送GET请求以从简单的HTTP请求升级到WebSocket的连接。这被称为握手。</p>
+
+<pre><code>try {
+ String data = s.useDelimiter("\\r\\n\\r\\n").next();
+ Matcher get = Pattern.compile("^GET").matcher(data);</code></pre>
+
+<p>创建响应比理解为什么必须以这种方式来创建响应更容易。</p>
+
+<p>你必须:</p>
+
+<ol>
+ <li>获取Sec-WebSocket-Key请求标头的值,去除头部和尾部的所有空格</li>
+ <li>追加字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"</li>
+ <li>使用SHA-1计算拿到结果值并进行Base64编码</li>
+ <li>将其作为HTTP响应的一部分写回Sec-WebSocket-Accept响应头的值</li>
+</ol>
+
+<pre><code>if (get.find()) {
+ Matcher match = Pattern.compile("Sec-WebSocket-Key: (.*)").matcher(data);
+ match.find();
+ byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n"
+ + "Connection: Upgrade\r\n"
+ + "Upgrade: websocket\r\n"
+ + "Sec-WebSocket-Accept: "
+ + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1").digest((match.group(1) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes("UTF-8")))
+ + "\r\n\r\n").getBytes("UTF-8");
+ out.write(response, 0, response.length);</code></pre>
+
+<h2 id="解码消息">解码消息</h2>
+
+<p>握手成功后,客户端可以向服务器发送消息,但现在这些已经过编码的消息需要解码。</p>
+
+<p>如果客户端发送 "abcdef",我们会拿到这些字节数据:</p>
+
+<table>
+ <tbody>
+ <tr>
+ <td>129</td>
+ <td>134</td>
+ <td>167</td>
+ <td>225</td>
+ <td>225</td>
+ <td>210</td>
+ <td>198</td>
+ <td>131</td>
+ <td>130</td>
+ <td>182</td>
+ <td>194</td>
+ <td>135</td>
+ </tr>
+ </tbody>
+</table>
+
+<p>- 129:</p>
+
+<table>
+ <thead>
+ <tr>
+ <th scope="col">FIN (消息是完整的吗?)</th>
+ <th scope="col">RSV1</th>
+ <th scope="col">RSV2</th>
+ <th scope="col">RSV3</th>
+ <th scope="col">Opcode</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>0</td>
+ <td>0</td>
+ <td>0</td>
+ <td>0x1=0001</td>
+ </tr>
+ </tbody>
+</table>
+
+<p>FIN: 你可以分多次发送一个完整的消息。但现在为了简单,操作码0x1表示这是一个完整的消息。<span style="line-height: 1.572;"> </span><a href="http://tools.ietf.org/html/rfc6455#section-5.2" style="line-height: 1.572;" title="http://tools.ietf.org/html/rfc6455#section-5.2">Full list of Opcodes</a></p>
+
+<p>- 134:</p>
+
+<p>如果第二个字节减去128在0到125之间,则这是消息的长度。 如果是126,则后面的2个字节(16位无符号整数),如果是127,则后面的8个字节(64位无符号整数,最高有效位必须为0)是长度。</p>
+
+<div class="note">
+<p>我可以拿128,因为第一位总是1。</p>
+</div>
+
+<p>- 167, 225, 225 和 210 是要解码的密钥<code>key</code>的字节。它每次都在变化。</p>
+
+<p>- 剩余的编码字节是消息数据部分。</p>
+
+<h3 id="解码算法">解码算法</h3>
+
+<p><code>decoded[i] = (byte) (encoded[i] ^ key[i &amp; 0x3]);</code></p>
+
+<p>Java例子:</p>
+
+<pre><code>byte[] decoded = new byte[6];
+ byte[] encoded = new byte[] { (byte) 198, (byte) 131, (byte) 130, (byte) 182, (byte) 194, (byte) 135 };
+ byte[] key = new byte[] { (byte) 167, (byte) 225, (byte) 225, (byte) 210 };
+ for (int i = 0; i &lt; encoded.length; i++) {
+ decoded[i] = (byte) (encoded[i] ^ key[i &amp; 0x3]);
+ }
+ }
+ } finally {
+ s.close();
+ }
+ } finally {
+ server.close();
+ }
+ }
+}</code></pre>
+
+<h2 id="相关链接">相关链接</h2>
+
+<ul>
+ <li><a href="/zh-CN/docs/WebSockets/Writing_WebSocket_servers">Writing WebSocket servers</a></li>
+</ul>
+
+<div id="cke_pastebin" style="position: absolute; top: 2209.23px; width: 1px; height: 1px; overflow: hidden; left: -1000px;"></div>
diff --git a/files/zh-cn/web/api/websockets_api/writing_websocket_client_applications/index.html b/files/zh-cn/web/api/websockets_api/writing_websocket_client_applications/index.html
new file mode 100644
index 0000000000..c5fefb2336
--- /dev/null
+++ b/files/zh-cn/web/api/websockets_api/writing_websocket_client_applications/index.html
@@ -0,0 +1,186 @@
+---
+title: 编写WebSocket客户端应用
+slug: Web/API/WebSockets_API/Writing_WebSocket_client_applications
+tags:
+ - WebSocket
+ - 指南
+translation_of: Web/API/WebSockets_API/Writing_WebSocket_client_applications
+---
+<p>WebSocket客户端应用程序使用WebSocket API通过WebSocket协议与WebSocket服务器通信。</p>
+
+<p>{{AvailableInWorkers}}</p>
+
+<div class="warning">
+<p>本文中的示例代码片段来自我们的 WebSocket 聊天应用示例,<a href="https://github.com/mdn/samples-server/tree/master/s/websocket-chat">源代码在此处</a>,then <a href="https://mdn-samples.mozilla.org/s/websocket-chat">也可以在这里试一试</a>。现在示例中有一个 bug,使用不安全的 WebSockets 连接需要更新使用安全的 WebSocket,我们将很快修复。</p>
+</div>
+
+<h2 id="创建_WebSocket_对象">创建 WebSocket 对象</h2>
+
+<p>为了使用 WebSocket 协议通信,你需要创建一个 <a href="/en/WebSockets/WebSockets_reference/WebSocket" title="en/WebSockets/WebSockets reference/WebSocket"><code>WebSocket</code></a> 对象;这将会自动地尝试建立与服务器的连接。</p>
+
+<p>WebSocket 构造函数接受一个必要参数和一个可选参数:</p>
+
+<pre class="syntaxbox">WebSocket WebSocket(
+  in DOMString url,
+  in optional DOMString protocols
+);
+</pre>
+
+<dl>
+ <dt><code>url</code></dt>
+ <dd>要连接的URL;这应当是 WebSocket  服务器会响应的URL。</dd>
+ <dt><code>protocols</code> {{ optional_inline() }}</dt>
+ <dd>一个协议字符串或一个协议字符串数组。这些字符串用来指定子协议,这样一个服务器就可以实现多个WebSocket子协议(比如你可能希望一个服务器可以根据指定的 <code>protocol</code> 来应对不同的互动情况)。如果不指定协议字符串则认为是空字符串。</dd>
+</dl>
+
+<p>构造函数可能抛出以下异常:</p>
+
+<dl>
+ <dt><code>SECURITY_ERR</code></dt>
+ <dd>尝试连接的端口被阻塞。</dd>
+</dl>
+
+<dl>
+</dl>
+
+<h3 id="连接错误">连接错误</h3>
+
+<p>如果尝试连接过程中发生错误,那么首先一个名为 "error" 的事件会被发送给 <a href="/en/WebSockets/WebSockets_reference/WebSocket" title="WebSocket"><code>WebSocket</code></a> 对象(然后调用其<code>onerror</code> handler),然后 <a href="/en/WebSockets/WebSockets_reference/CloseEvent" title="CloseEvent"><code>CloseEvent</code></a> 被发送给<a href="/en/WebSockets/WebSockets_reference/WebSocket" title="WebSocket"><code>WebSocket</code></a>  (然后调用其 <code>onclose</code> handler)以说明连接关闭的原因。</p>
+
+<p>在 Firefox 11 中,通常会从 Mozilla 平台的控制台中收到一个描述性的错误信息,以及一个通过 <code><a href="/en/WebSockets/WebSockets_reference/CloseEvent" title="CloseEvent">CloseEvent</a></code> 在 <a class="external" href="http://tools.ietf.org/html/rfc6455#section-7.4" title="RFC 6455 Section 7.4">RFC 6455, Section 7.4</a> 中定义的错误代码。</p>
+
+<h3 id="示例">示例</h3>
+
+<p>本例创建了一个新的 WebSocket,连接到地址为 <code><span class="nowiki">ws://www.example.com/socketserver</span></code> 的服务器。请求中命名了一个自定义的协议 "protocolOne",这一部分可以省略。</p>
+
+<pre class="brush: js">var exampleSocket = new WebSocket("ws://www.example.com/socketserver", "protocolOne");
+</pre>
+
+<p>返回后,<code>exampleSocket.readyState</code> 参数为 <code>CONNECTING</code>。一旦连接可以传送数据,<code>readyState</code> 就会变成 <code>OPEN</code> 。</p>
+
+<p>如果你想建立一个支持协议可选的连接,你可以指定协议的列表:</p>
+
+<pre class="brush: js">var exampleSocket = new WebSocket("ws://www.example.com/socketserver", ["protocolOne", "protocolTwo"]);
+</pre>
+
+<p>一旦连接建立了(也就是说 <code>readyState</code> 是 <code>OPEN</code>) <code>exampleSocket.protocol</code> 就会告诉你服务器选择了哪个协议。</p>
+
+<p>上面的例子中 <code>ws</code> 替代了 <code>http</code>,同样地 <code>wss 也会替代https</code>. 建立WebSocket链接有赖于 <a href="/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism">HTTP Upgrade mechanism</a>, 所以当我们使用 <code><span class="nowiki">ws://www.example.com</span></code>或者 <code><span class="nowiki">wss://www.example.com</span></code>来访问HTTP服务器的时候协议会隐式地升级。</p>
+
+<h2 id="向服务器发送数据">向服务器发送数据</h2>
+
+<p>一旦你的连接打开完成,你就可以向服务器发送数据了。对每一个要发送的消息使用 <code>WebSocket</code> 对象的 <a href="/en/WebSockets/WebSockets_reference/WebSocket#send()" title="en/WebSockets/WebSockets reference/WebSocket#send()"><code>send()</code></a> 方法:</p>
+
+<pre class="brush: js">exampleSocket.send("Here's some text that the server is urgently awaiting!");
+</pre>
+
+<p>你可以把数据作为字符串,{{ domxref("Blob") }},或者<a href="/en/JavaScript_typed_arrays/ArrayBuffer" title="en/JavaScript typed arrays/ArrayBuffer"><code>ArrayBuffer</code></a>来发送。</p>
+
+<div class="note"><strong>注意:</strong> 在版本11之前,Firefox只支持以字符串的形式发送数据。</div>
+
+<p>因为连接的建立是异步的,而且容易失败,所以不能保证刚创建WebSocket对象时使用 <code>send()</code> 方法会成功。我们至少可以确定企图在链接建立起来之后立马发送数据,可以通过注册 <code>onopen</code> 事件处理器解决:</p>
+
+<pre class="brush: js">exampleSocket.onopen = function (event) {
+  exampleSocket.send("Here's some text that the server is urgently awaiting!");
+};
+</pre>
+
+<h3 id="使用_JSON_发送对象">使用 JSON 发送对象</h3>
+
+<p>你可以方便地使用<a href="/en/JSON" title="en/JSON">JSON</a> 来向服务器发送复杂一些的数据。例如一个聊天程序与服务器交互的协议可以通过封装在JSON里的数据来实现:</p>
+
+<pre class="brush: js">// 服务器向所有用户发送文本
+function sendText() {
+ // 构造一个 msg 对象, 包含了服务器处理所需的数据
+  var msg = {
+    type: "message",
+    text: document.getElementById("text").value,
+    id: clientID,
+    date: Date.now()
+  };
+
+  // 把 msg 对象作为JSON格式字符串发送
+  exampleSocket.send(JSON.stringify(msg));
+
+  // 清空文本输入元素,为接收下一条消息做好准备。
+  document.getElementById("text").value = "";
+}
+</pre>
+
+<h2 id="接收服务器发送的消息">接收服务器发送的消息</h2>
+
+<p>WebSockets 是一个基于事件的 API;收到消息的时候,一个 "message" 消息会被发送到 <code>onmessage</code> 函数。为了开始监听传入数据,可以进行如下操作:</p>
+
+<pre class="brush: js">exampleSocket.onmessage = function (event) {
+  console.log(event.data);
+}
+</pre>
+
+<h3 id="接受与解析_JSON_对象">接受与解析 JSON 对象</h3>
+
+<p>考虑在 {{ anch("Using JSON to transmit objects") }}中提到的聊天应用客户端。客户端会收到各种类型的数据包,比如:</p>
+
+<ul>
+ <li>登陆握手</li>
+ <li>消息文本</li>
+ <li>用户列表更新</li>
+</ul>
+
+<p>解析这些收到的消息的代码可能是这样的:</p>
+
+<pre><code>exampleSocket.onmessage = function(event) {
+ var f = document.getElementById("chatbox").contentDocument;
+ var text = "";
+ var msg = JSON.parse(event.data);
+ var time = new Date(msg.date);
+ var timeStr = time.toLocaleTimeString();
+
+ switch(msg.type) {
+ case "id":
+ clientID = msg.id;
+ setUsername();
+ break;
+ case "username":
+ text = "&lt;b&gt;User &lt;em&gt;" + msg.name + "&lt;/em&gt; signed in at " + timeStr + "&lt;/b&gt;&lt;br&gt;";
+ break;
+ case "message":
+ text = "(" + timeStr + ") &lt;b&gt;" + msg.name + "&lt;/b&gt;: " + msg.text + "&lt;br&gt;";
+ break;
+ case "rejectusername":
+ text = "&lt;b&gt;Your username has been set to &lt;em&gt;" + msg.name + "&lt;/em&gt; because the name you chose is in use.&lt;/b&gt;&lt;br&gt;"
+ break;
+ case "userlist":
+ var ul = "";
+ for (i=0; i &lt; msg.users.length; i++) {
+ ul += msg.users[i] + "&lt;br&gt;";
+ }
+ document.getElementById("userlistbox").innerHTML = ul;
+ break;
+ }
+
+ if (text.length) {
+ f.write(text);
+ document.getElementById("chatbox").contentWindow.scrollByPages(1);
+ }
+};</code></pre>
+
+<p>这里我们使用 <a href="/en/JavaScript/Reference/Global_Objects/JSON/parse" title="en/JavaScript/Reference/Global Objects/JSON/parse"><code>JSON.parse()</code></a> 来将JSON转换回原始对象,然后检查并根据其内容做下一步动作。</p>
+
+<h3 id="文本数据的格式">文本数据的格式</h3>
+
+<p>通过WebSocket连接收到的文本是 UTF-8 格式的。</p>
+
+<p>在 Gecko 9.0 {{ geckoRelease("9.0") }} 之前,一部分有效的 UTF-8 文本中的非字符将导致连接被中断。现在 Gecko 已经允许这些值。</p>
+
+<h2 id="关闭连接">关闭连接</h2>
+
+<p>当你不需要再用 WebSocket 连接了,调用 WebSocket <a href="/en/WebSockets/WebSockets_reference/WebSocket#close()" title="en/WebSockets/WebSockets reference/WebSocket#close()"><code>close()</code></a>方法:</p>
+
+<pre class="brush: js">exampleSocket.close();
+</pre>
+
+<p>关闭连接前最好检查一下 socket 的 <code>bufferedAmount</code> 属性,以防还有数据要传输。</p>
+
+<h2 id="安全方面的考虑">安全方面的考虑</h2>
+
+<p>WebSocket 不应当用于混合的上下文环境;也就是说,不应该在用HTTPS加载的页面中打开非安全版本的WebSocket,反之亦然。而实际上一些浏览器也明确禁止这一行为,包括 Firefox 8 及更高版本。</p>
diff --git a/files/zh-cn/web/api/websockets_api/writing_websocket_server/index.html b/files/zh-cn/web/api/websockets_api/writing_websocket_server/index.html
new file mode 100644
index 0000000000..dbc31715ae
--- /dev/null
+++ b/files/zh-cn/web/api/websockets_api/writing_websocket_server/index.html
@@ -0,0 +1,273 @@
+---
+title: 用C#来编写WebSocket服务器
+slug: Web/API/WebSockets_API/Writing_WebSocket_server
+tags:
+ - HTML5
+ - NeedsMarkupWork
+ - Tutorial
+ - WebSockets
+translation_of: Web/API/WebSockets_API/Writing_WebSocket_server
+---
+<h2 id="介绍">介绍</h2>
+
+<p>如果你想学习如何使用 WebSocket API,那么有一台服务器将会是非常有用的。在本文中,我将向你展示如何使用C#来写后端。你可以使用任何可用于后端开发的语言来做这个事<span style="line-height: 1.5;">, 但是,要为了使例子简明易懂,我选择微软的C#。</span></p>
+
+<p>此服务器符合 <a href="http://tools.ietf.org/html/rfc6455" title="http://tools.ietf.org/html/rfc6455">RFC 6455</a> 因此,因此它只处理来自 Chrome16,Firefox 11,IE 10 及更高版本的连接。</p>
+
+<h2 id="第一步">第一步</h2>
+
+<p>WebSockets 通过 <a href="http://en.wikipedia.org/wiki/Transmission_Control_Protocol" title="http://en.wikipedia.org/wiki/Transmission_Control_Protocol">TCP (传输控制协议)</a> 连接进行通信.。幸运的是, C# 中有一个 <a href="http://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener.aspx" title="http://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener.aspx">TcpListener </a>类。 它位于 <em>System.Net.Sockets</em> 的命名空间。</p>
+
+<div class="note">
+<p><span style="line-height: 1.572;">最好使用 <code>using</code></span><span style="line-height: 1.572;"> 关键字来包含命名空间,这样在你写代码的时候就不需要指定详细的命名空间。</span></p>
+</div>
+
+<h3 id="TcpListener">TcpListener</h3>
+
+<p>构造函数:</p>
+
+<pre class="brush: cpp">TcpListener(System.Net.IPAddress localaddr, int port)</pre>
+
+<p><code>localaddr</code> 是监听地址,  <code>port</code> 是监听端口.</p>
+
+<div class="note">
+<p><em>如果字符串创建 <code>IPAddress</code> 对象,请使用 Parse静态方法。</em></p>
+</div>
+
+<p><span style="line-height: 1.572;">方法</span><span style="line-height: 1.572;">:</span></p>
+
+<ul>
+ <li><code><span style="line-height: 1.572;">Start()</span></code></li>
+ <li><span style="line-height: 1.572;"><code>System.Net.Sockets.<a href="http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.aspx" title="http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.aspx">TcpClient</a> AcceptTcpClient()</code><br>
+ 等一个Tcp 连接, 并接受一个返回的TcpClient对象。</span></li>
+</ul>
+
+<p><span style="line-height: 1.572;">下面是基于服务端的实现:</span></p>
+
+<pre class="brush: cpp">​using System.Net.Sockets;
+using System.Net;
+using System;
+
+class Server {
+ public static void Main() {
+ TcpListener server = new TcpListener(IPAddress.Parse("127.0.0.1"), 80);
+
+ server.Start();
+ Console.WriteLine("Server has started on 127.0.0.1:80.{0}Waiting for a connection...", Environment.NewLine);
+
+ TcpClient client = server.AcceptTcpClient();
+
+ Console.WriteLine("A client connected.");
+ }
+}
+</pre>
+
+<h3 id="TcpClient"><span style="line-height: 1.572;">TcpClient</span></h3>
+
+<p>方法:</p>
+
+<ul>
+ <li><code>System.Net.Sockets.<a href="http://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream.aspx" title="http://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream.aspx">NetworkStream</a> GetStream()</code><br>
+ 获取一个通信通道的流,通道两边都具有读写能力。</li>
+</ul>
+
+<p>属性:</p>
+
+<ul>
+ <li><code>int Available</code><br>
+ 这个属性表示已经发送了多少个字节的数据。它的值为零,直到 <code>NetworkStream.DataAvailable</code> 为true。</li>
+</ul>
+
+<h3 id="NetworkStream">NetworkStream</h3>
+
+<p>方法:</p>
+
+<ul>
+ <li><code>Write(Byte[] buffer, int offset, int size)</code><br>
+ 根据buffer数组写入字节流,offset与size参数决定了消息的长度。</li>
+ <li><code><span class="brush: cpp" style="line-height: 1.572;">Read(Byte[] buffer, int offset, int size)</span></code><br>
+ 将字节流读取到 <code>buffer</code> 中。 <code>offset</code> 和 <code>size</code> 参数决定了消息的长度。</li>
+</ul>
+
+<p>让我们扩充一下我们的示例.</p>
+
+<pre class="brush: cpp">TcpClient client = server.AcceptTcpClient();
+
+Console.WriteLine("A client connected.");
+
+NetworkStream stream = client.GetStream();
+
+//enter to an infinite cycle to be able to handle every change in stream
+while (true) {
+ while (!stream.DataAvailable);
+
+ Byte[] bytes = new Byte[client.Available];
+
+ stream.Read(bytes, 0, bytes.Length);
+}</pre>
+
+<h2 id="握手">握手</h2>
+
+<p>当一个客户端连接到服务器时,它会发送一个GET请求将现在一个简单的HTTP请求升级为一个WebSocket请求。这被称为握手。</p>
+
+<p>下面是一段检测从客户端发来的GET请求的代码。需要注意的是,下面的程序在没有收到消息开头的3个有效字节前将处于阻塞状态。在生产环境下,应该考虑使用可用于替代的解决方案。</p>
+
+<pre class="brush: cpp">using System.Text;
+using System.Text.RegularExpressions;
+
+while(client.Available &lt; 3)
+{
+ // wait for enough bytes to be available
+}
+
+Byte[] bytes = new Byte[client.Available];
+
+stream.Read(bytes, 0, bytes.Length);
+
+//translate bytes of request to string
+String data = Encoding.UTF8.GetString(bytes);
+
+if (Regex.IsMatch(data, "^GET")) {
+
+} else {
+
+}</pre>
+
+<p>回应的消息很容易构造,但是可能会有一点难以理解。完整的关于服务器握手的解释可以在 <a href="RFC 6455, section 4.2.2">RFC 6455, section 4.2.2</a> 找到。从我们的目的出发,我们将构造一个简单的回应消息。</p>
+
+<p>你必须:</p>
+
+<ol>
+ <li>获取请求头中"Sec-WebSocket-Key"字段的值,这个字段值不能有任何的前导和后继空格字符</li>
+ <li>将它与"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"(一个 RFC 6455 中规定的特殊的 GUID )拼接起来</li>
+ <li>计算新的值的 SHA-1 和 Base64 哈希值</li>
+ <li>将哈希值写回到一个HTTP响应头,作为"Sec-WebSocket-Accept"字段的值</li>
+</ol>
+
+<pre class="brush: cpp"><em>
+</em>if (new System.Text.RegularExpressions.Regex("^GET").IsMatch(data))
+{
+    const string eol = "\r\n"; // HTTP/1.1 defines the sequence CR LF as the end-of-line marker
+
+    Byte[] response = Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" + eol
+        + "Connection: Upgrade" + eol
+        + "Upgrade: websocket" + eol
+        + "Sec-WebSocket-Accept: " + Convert.ToBase64String(
+            System.Security.Cryptography.SHA1.Create().ComputeHash(
+                Encoding.UTF8.GetBytes(
+                    new System.Text.RegularExpressions.Regex("Sec-WebSocket-Key: (.*)").Match(data).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+                )
+            )
+        ) + eol
+        + eol);
+
+    stream.Write(response, 0, response.Length);
+}
+</pre>
+
+<h2 id="解密消息">解密消息</h2>
+
+<p>在一次成功的握手之后,客户端将向服务器发送加密后的消息</p>
+
+<p>如果我们发送了 "MDN",那么我们会得到下面这些字节:</p>
+
+<table>
+ <tbody>
+ <tr>
+ <td>129</td>
+ <td>131</td>
+ <td>61</td>
+ <td>84</td>
+ <td>35</td>
+ <td>6</td>
+ <td>112</td>
+ <td>16</td>
+ <td>109</td>
+ </tr>
+ </tbody>
+</table>
+
+<p>让我们看看这些字节意味着什么。</p>
+
+<p>第一个字节,当前值是129,是按位组成的,分解如下:</p>
+
+<table>
+ <thead>
+ <tr>
+ <th scope="col">FIN (Bit 0)</th>
+ <th scope="col">RSV1 (Bit 1)</th>
+ <th scope="col">RSV2 (Bit 2)</th>
+ <th scope="col">RSV3 (Bit 3)</th>
+ <th scope="col">Opcode (Bit 4:7)</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>0</td>
+ <td>0</td>
+ <td>0</td>
+ <td>0x1=0001</td>
+ </tr>
+ </tbody>
+</table>
+
+<ul>
+ <li>FIN 位:这个位表明是否整个消息都已经从客户端被发送出去。消息可能以多个帧的形式发送,但现在我们将情景考虑得简单一些。</li>
+ <li>RSV1, RSV2, RSV3:除非规定的扩展协议支持将它们赋为非0值,否则这些位必须为0。</li>
+ <li><span style="line-height: 1.572;">Opcode:</span><span style="line-height: 1.572;">这些位描述了接收的消息的类型。Opcode 0x1 意味着这是一条文本消息。</span><a href="http://tools.ietf.org/html/rfc6455#section-5.2" style="line-height: 1.572;" title="http://tools.ietf.org/html/rfc6455#section-5.2">Opcodes值的完整罗列</a></li>
+</ul>
+
+<p>第二个字节,当前值是131,是另一个按位组成的部分,分解如下:</p>
+
+<table>
+ <thead>
+ <tr>
+ <th scope="col">MASK (Bit 0)</th>
+ <th scope="col">Payload Length (Bit 1:7)</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>0x83=0000011</td>
+ </tr>
+ </tbody>
+</table>
+
+<ul>
+ <li>MASK 位:定义了是否"Payload data"进行了掩码计算。如果值设置为1,那么在Masking-Key字段中会有一个掩码密钥,并且它可以用来进行"Payload data"的去掩码计算。所有从客户端发到服务器的消息中此位都会被置1。</li>
+ <li>Payload Length:如果这个值在0与125之间,那么这个值就是消息的长度。如果这个值是126,那么接下来的2个字节(16位无符号整数)是消息长度。如果这个值是127,那么接下来的8个字节(64位无符号整数)是消息长度。</li>
+</ul>
+
+<div class="note">
+<p>因为在客户端到服务器的消息中第一位总是1,所以你可以将这个字节减去128去除 MASK 位。</p>
+</div>
+
+<p>需要注意的是MASK位在我们的消息中被置为1。这意味着接下来的4个字节(61, 84, 35, 6) 是用于解码消息的掩码字节。这些字节在每个消息中都不是固定不变的。</p>
+
+<p>剩下的字节是加密后的消息载荷。</p>
+
+<h3 id="解密算法">解密算法</h3>
+
+<p><em>D<sub>i</sub></em> = <em>E<sub>i</sub></em> XOR <em>M</em><sub>(<em>i</em> mod 4)</sub></p>
+
+<p>D 是解密后的消息数组, <em>E</em> 是被加密的消息数组,<em> M</em> 是掩码字节数组, <em>i</em> 是需要解密的消息字节的序号。</p>
+
+<p>C# 示例:</p>
+
+<pre class="brush: cpp">Byte[] decoded = new Byte[3];
+Byte[] encoded = new Byte[3] {112, 16, 109};
+Byte[] mask = new Byte[4] {61, 84, 35, 6};
+
+for (int i = 0; i &lt; encoded.Length; i++) {
+ decoded[i] = (Byte)(encoded[i] ^ mask[i % 4]);
+}</pre>
+
+<h2 id="有关文档">有关文档</h2>
+
+<ul>
+ <li><a href="/en-US/docs/WebSockets/Writing_WebSocket_servers">编写 WebSocket 服务器</a></li>
+</ul>
+
+<div id="cke_pastebin" style="position: absolute; top: 2209.23px; width: 1px; height: 1px; overflow: hidden; left: -1000px;"><em> </em></div>
diff --git a/files/zh-cn/web/api/websockets_api/writing_websocket_servers/index.html b/files/zh-cn/web/api/websockets_api/writing_websocket_servers/index.html
new file mode 100644
index 0000000000..8f602c8247
--- /dev/null
+++ b/files/zh-cn/web/api/websockets_api/writing_websocket_servers/index.html
@@ -0,0 +1,244 @@
+---
+title: 编写 WebSocket 服务器
+slug: Web/API/WebSockets_API/Writing_WebSocket_servers
+tags:
+ - Ping & Pong
+ - Pings和Pongs:WebSockets的心跳
+ - WebSocket
+ - WebSockets 心跳
+translation_of: Web/API/WebSockets_API/Writing_WebSocket_servers
+---
+<p><strong>WebSocket服务器是一个</strong>TCP应用程序,监听服务器上任何遵循特定协议的端口,就这么简单。创建自定义服务器的任务往往听起来很吓人,然而,在您选择的平台上实现一个简单的WebSocket服务器是很容易的。</p>
+
+<p>WebSocket服务器可以用任何实现了<a href="https://en.wikipedia.org/wiki/Berkeley_sockets">Berkeley sockets</a>的服务器端编程语言编写,如C(++)或Python甚至<a href="/en-US/docs/PHP">PHP</a>和<a href="/en-US/docs/Web/JavaScript/Server-Side_JavaScript">服务器端JavaScript</a>。 这不是任何特定语言的教程,而是作为指导,以方便编写自己的服务器。</p>
+
+<p>您需要知道HTTP的工作原理,并具有中级编程经验。 根据语言帮助(Depending on language support),可能需要TCP套接字的知识。 本指南的范围是介绍编写WebSocket服务器所需的最低知识。</p>
+
+<div class="note">
+<p>阅读最新的官方WebSockets规范, <a href="http://datatracker.ietf.org/doc/rfc6455/?include_text=1">RFC 6455</a>. 第1节和第4-7节对服务器实现者特别有意思。第10节讨论安全性,你应该在暴露你的服务器之前仔细阅读它。</p>
+</div>
+
+<p>WebSocket服务器在这里被解释得非常底层。 WebSocket服务器通常是独立的专用服务器(出于负载平衡或其他实际原因),因此您通常会使用<a href="https://en.wikipedia.org/wiki/Reverse_proxy">反向代理</a>(例如常规HTTP服务器)来检测WebSocket握手,预处理这些握手,并将这些客户端发送给 一个真正的WebSocket服务器。(例如)这意味着您不必使用cookie和身份验证处理程序来扩充服务器代码。</p>
+
+<h2 id="WebSocket_握手"><a name="Handshake">WebSocket 握手</a></h2>
+
+<p>首先,服务器必须使用标准的TCP套接字来监听传入的套接字连接。 根据您的平台,这可能已经为您处理。 例如,假设您的服务器正在监听example.com,端口8000,并且您的套接字服务器响应<code>/chat</code>上的GET请求。 .</p>
+
+<div class="warning">
+<p><strong>警告:</strong>服务器可以监听它选择的任何端口,但是如果它选择了80或443以外的端口,防火墙和/或代理服务器可能会有问题。 端口443上的连接往往会更容易成功,但是当然,这需要一个安全的连接(TLS / SSL)。 另外请注意,大多数浏览器(特别是Firefox 8+)不允许从安全页面连接到不安全的WebSocket服务器。</p>
+</div>
+
+<p>握手是WebSockets中的“Web”。 这是从HTTP到WS的桥梁。 在握手过程中,有关连接的详细信息正在初始化中,如果条件不利,任何一方可以在完成之前退出。 服务器必须小心了解客户要求的一切,否则会产生安全问题。</p>
+
+<h3 id="客户端握手请求">客户端握手请求</h3>
+
+<p>即使您正在构建服务器,客户端仍然必须启动WebSocket握手过程。 所以你必须知道如何解释客户的请求。 客户端将发送一个相当标准的HTTP请求,看起来像这样(HTTP版本必须是1.1或更高,方法必须是<code>GET</code>):</p>
+
+<pre><code>GET /chat HTTP/1.1
+Host: example.com:8000
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+Sec-WebSocket-Version: 13</code></pre>
+
+<p>客户可以在这里请求扩展和/或子协议;详情请见<a href="#Miscellaneous">杂项</a>。当然,你也可以在这里加上你所需要的一般请求头如<code>User-Agent</code>, <code>Referer</code>, <code>Cookie</code>或者认证头。WebSocket没有作要求,忽略它们也是安全的。在大多数情况下,反向代理已经做了这些处理。</p>
+
+<p>如果任何请求头信息不被理解或者具有不正确的值,则服务器应该发送“<a href="https://developer.mozilla.org/en-US/docs/HTTP/Response_codes#400">400 Bad Request</a>”并立即关闭套接字。 像往常一样,它也可能会给出HTTP响应正文中握手失败的原因,但可能永远不会显示消息(浏览器不显示它)。 如果服务器不理解该版本的WebSocket,则应该发送一个<code>Sec-WebSocket-Version</code>头,其中包含它理解的版本。 (本指南解释了最新的v13)。 下面我们来看看奇妙的请求头<code>Sec-WebSocket-Key</code>。</p>
+
+<div class="note">
+<p><strong>提示:</strong> 所有浏览器将会发送一个 <code><a href="https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS#Origin">Origin</a></code>请求头。 你可以将这个请求头用于安全方面(检查是否是同一个域,白名单/ 黑名单等),如果你不喜欢这个请求发起源,你可以发送一个<a href="https://developer.mozilla.org/en-US/docs/HTTP/Response_codes#403">403 Forbidden</a>。需要注意的是非浏览器只能发送一个模拟的 <code>Origin</code>。大多数应用会拒绝不含这个请求头的请求.。</p>
+</div>
+
+<div class="note">
+<p><strong>提示:</strong> 请求URI(这里的是<code>/chat</code>)在规范里没有定义。很多开发者聪明地把这点用于控制多功能WebSocket应用。例如<code>example.com/chat</code>会请求一个多方会话应用,而在相同服务器上<code>example.com/game</code>则会请求一个多玩家游戏应用。</p>
+</div>
+
+<div class="note">
+<p><strong>注意:</strong> <a href="https://developer.mozilla.org/en-US/docs/HTTP/Response_codes">常规HTTP状态码</a>只能在握手之前使用。 握手成功后,你必须使用一组不同的代码(在规范的第7.4节中定义)。</p>
+</div>
+
+<h3 id="服务器握手响应">服务器握手响应</h3>
+
+<p>当<strong>服务器</strong>收到握手请求时,它应该发回一个特殊的响应,表明协议将从HTTP变为WebSocket。看起来像这样(记住每个请求头以 <code>\r\n</code>结尾,并在最后一个之后放置一个额外的 <code>\r\n</code>):</p>
+
+<pre><strong>HTTP/1.1 101 Switching Protocols</strong>
+Upgrade: websocket
+Connection: Upgrade
+<strong>Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
+
+</strong></pre>
+
+<p>另外,服务器可以在这时候决定插件或子协议,详情参见<a href="/zh-CN/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#Miscellaneous">杂项</a>。  <code>Sec-WebSocket-Accept</code> 参数很有趣,它需要服务器通过客户端发送的<code>Sec-WebSocket-Key</code> 计算出来。 怎样计算呢, 把客户发送的<span style="line-height: 1.5em;"> </span><code style="font-size: 14px;">Sec-WebSocket-Key</code><span style="line-height: 1.5em;"> 和 "</span><code style="font-size: 14px;">258EAFA5-E914-47DA-95CA-C5AB0DC85B11</code><span style="line-height: 1.5em;">" (这个叫做 "</span><a href="https://en.wikipedia.org/wiki/Magic_string" style="line-height: 1.5em;">魔法值</a><span style="line-height: 1.5em;">")连接起来,把结果用<a href="https://zh.wikipedia.org/wiki/SHA-1">SHA-1</a>编码,再用<a href="https://zh.wikipedia.org/wiki/Base64">base64</a>编码一次,就可以了。 </span></p>
+
+<div class="note">
+<p><strong>参考:</strong>这看起来繁复的处理使得客户端明确服务端是否支持WebSocket。这是十分重要的,如果服务端接收到一个WebSocket连接但是把数据作为HTTP请求理解可能会导致安全问题。</p>
+</div>
+
+<p>所以如果Sec-WebSocket-Key是“<code>dGhlIHNhbXBsZSBub25jZQ==</code>”,Sec-WebSocket-Accept将是“<code>s3pPLMBiTxaQ9kYGzzhZRbK+xOo=</code>”。 一旦服务器发送这个请求头,握手就完成了,你可以开始交换数据!</p>
+
+<div class="note">
+<p>服务端可以在发送握手回复前发送其他请求头,诸如Set-Cookie,请求认证或通过状态码重定向。</p>
+</div>
+
+<h3 id="跟踪客户端">跟踪客户端</h3>
+
+<p>这并不直接与WebSocket协议相关,但是在这里值得一提的是:你的服务器将不得不跟踪客户的套接字,所以你不会再和已经完成握手的客户握手。 同一个客户端IP地址可以尝试连接多次(但是如果客户端尝试过多的连接,服务器可以拒绝它们以免遭<a href="https://en.wikipedia.org/wiki/Denial_of_service">拒绝服务攻击</a>)。</p>
+
+<h2 id="交换数据帧">交换数据帧</h2>
+
+<p>客户端或服务端都可以在任何时间点发送数据——这就是WebSocket的魅力。然而,从这些被称为“帧”的数据中提取信息就不是十分愉快的体验了。尽管所有的帧都遵从相同的格式规范,从客户端发送到服务端的数据都被 <a href="https://en.wikipedia.org/wiki/XOR_cipher">异或加密</a>(用一个32位的key)格式化。详情请参见规范的第5节。</p>
+
+<h3 id="格式">格式</h3>
+
+<p>每个数据帧(从客户端到服务器,反之亦然)遵循相同的格式:</p>
+
+<pre>Frame format:
+​​
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-------+-+-------------+-------------------------------+
+ |F|R|R|R| opcode|M| Payload len | Extended payload length |
+ |I|S|S|S| (4) |A| (7) | (16/64) |
+ |N|V|V|V| |S| | (if payload len==126/127) |
+ | |1|2|3| |K| | |
+ +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+ | Extended payload length continued, if payload len == 127 |
+ + - - - - - - - - - - - - - - - +-------------------------------+
+ | |Masking-key, if MASK set to 1 |
+ +-------------------------------+-------------------------------+
+ | Masking-key (continued) | Payload Data |
+ +-------------------------------- - - - - - - - - - - - - - - - +
+ : Payload Data continued ... :
+ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ | Payload Data continued ... |
+ +---------------------------------------------------------------+</pre>
+
+<p>掩码明确告知我们消息是否经过格式化。从客户端来的消息必须经过格式化,所以你的服务器必须要求这个掩码是1(事实上,<a href="http://tools.ietf.org/html/rfc6455#section-5.1">规范5.1节</a>规定了如果客户端发送了没有格式化的消息,你的服务器应该断开连接)</p>
+
+<p>当向客户端发送帧时,不要对其进行掩码,也不要设置掩码位。稍后我们将解释屏蔽。注意:即使使用安全套接字,也必须屏蔽消息。RSV1-3可以忽略,它们是用于扩展的。</p>
+
+<p>操作码字段定义了如何解释有效负载数据:0x0表示延续,0x1表示文本(总是用UTF-8编码),0x2表示二进制,以及其他所谓的“控制代码”,稍后将对此进行讨论。在这个版本的WebSockets中,0x3到0x7和0xB到0xF没有任何意义。</p>
+
+<p>FIN位告诉我们这是不是系列的最后一条消息。如果是0,那么服务器将继续侦听消息的更多部分;否则,服务器应该考虑传递的消息。不仅仅是这样。</p>
+
+<h3 id="解码有效载荷长度">解码有效载荷长度</h3>
+
+<p>要读取有效负载数据,您必须知道何时停止读取。这就是为什么有效载荷长度很重要。不幸的是,这有点复杂。要阅读它,请遵循以下步骤:</p>
+
+<ol>
+ <li>读取9-15(包括)位并将其解析为无符号整型。如果长度小于等于125,那么就是长度;你就完成了。如果是126,到第二步。如果是127,到步骤3。</li>
+ <li>读取下面的16位,并将其解释为无符号整型。你就完成了。</li>
+ <li>读取接下来的64位,并将其解释为无符号整型(最重要的位必须为0)。</li>
+</ol>
+
+<h3 id="读取和解密数据">读取和解密数据</h3>
+
+<p>如果设置了掩码位(对于客户机到服务器的消息应该是这样),则读取接下来的4个字节(32位);这是掩蔽键。一旦有效负载长度和掩蔽键被解码,您就可以继续从套接字读取字节数。让我们调用已编码的数据和密钥掩码。要获得解码,可以通过编码的八位元(字节,即文本数据的字符)和XOR八位元(i模4)掩码的第四个八位元进行循环。在伪代码中(恰好是有效的JavaScript):</p>
+
+<pre>var DECODED = "";
+for (var i = 0; i &lt; ENCODED.length; i++) {
+ DECODED[i] = ENCODED[i] ^ MASK[i % 4];
+<span style="line-height: 1.5;">}</span></pre>
+
+<p>现在,您可以根据应用程序了解解码意味着什么。</p>
+
+<h3 id="消息帧">消息帧</h3>
+
+<p>FIN和操作码字段一起工作,以发送分裂为独立帧的消息。这称为消息碎片。片段只能在操作码0x0到0x2上可用。</p>
+
+<p>回想一下,操作码告诉了帧应该做什么。如果是0x1,有效载荷就是文本。如果是0x2,有效载荷就是二进制数据。但是,如果是0x0,则该帧是一个延续帧。这意味着服务器应该将帧的有效负载连接到从该客户机接收到的最后一个帧。下面是一个粗略的示意图,其中服务器对发送文本消息的客户机做出反应。第一个消息在单个帧中发送,而第二个消息跨三个帧发送。FIN和操作码的详细信息只显示给客户:</p>
+
+<pre style="font-size: 14px;"><strong>Client:</strong> FIN=1, opcode=0x1, msg="hello"
+<strong>Server:</strong> <em>(process complete message immediately) </em>Hi.
+<strong>Client:</strong> FIN=0, opcode=0x1, msg="and a"
+<strong>Server:</strong> <em>(listening, new message containing text started)</em>
+<strong>Client:</strong> FIN=0, opcode=0x0, msg="happy new"
+<strong>Server:</strong> <em>(listening, payload concatenated to previous message)</em>
+<strong>Client:</strong> FIN=1, opcode=0x0, msg="year!"
+<strong>Server:</strong> <em>(process complete message) </em>Happy new year to you too!</pre>
+
+<p>注意,第一个框架包含一个完整的消息(具有FIN=1和opcode!=0x0),因此服务器可以根据需要进行处理或响应。客户机发送的第二帧具有文本有效负载(opcode=0x1),但是整个消息还没有到达(FIN=0)。该消息的所有剩余部分都用延续帧(opcode=0x0)发送,消息的最终帧用FIN=1标记。<a href="http://tools.ietf.org/html/rfc6455#section-5.4">Section 5.4 of the spec</a>描述了消息帧。</p>
+
+<h2 id="Pings和Pongs:WebSockets的心跳">Pings和Pongs:WebSockets的心跳</h2>
+
+<p>在经过握手之后的任意时刻里,无论客户端还是服务端都可以选择发送一个ping给另一方。 当ping消息收到的时候,接受的一方必须尽快回复一个pong消息。 例如,可以使用这种方式来确保客户端还是连接状态。</p>
+
+<p>一个ping 或者 pong 都只是一个常规的帧, 只是这个帧是一个<strong>控制帧</strong>。Ping消息的opcode字段值为 <code>0x9</code>,pong消息的opcode值为  <code>0xA</code> 。当你获取到一个ping消息的时候,回复一个跟ping消息有相同载荷数据的pong消息 (对于ping和pong,最大载荷长度位125)。 你也有可能在没有发送ping消息的情况下,获取一个pong消息,当这种情况发生的时候忽略它。</p>
+
+<div class="note">
+<p>如果在你有机会发送一个pong消息之前,你已经获取了超过一个的ping消息,那么你只发送一个pong消息。</p>
+</div>
+
+<h2 id="关闭连接">关闭连接</h2>
+
+<p>客户端或服务器端都可以通过发送一个带有指定控制序列的控制帧以开始关闭连接握手(参见<a href="http://tools.ietf.org/html/rfc6455#section-5.5.1">章节5.5.1</a>)。对端收到这个控制帧会回复一个关闭帧,关闭发起端关闭连接。任何在关闭连接后接收到的数据都会被丢弃。</p>
+
+<h2 id="杂项"><a name="Miscellaneous">杂项</a></h2>
+
+<div class="note">
+<p>WebSocket代码、扩展、子协议等在 <a href="http://www.iana.org/assignments/websocket/websocket.xml">IANA WebSocket Protocol Registry</a>.注册。</p>
+</div>
+
+<p>WebSocket扩展和子协议是在握手过程中通过头信息进行协商的。有时候,扩展和子协议看起来太相似而不可能是不同的东西,但是有一个明显的区别。扩展控制WebSocket框架并修改有效负载,而子协议构造WebSocket有效负载,从不修改任何东西。扩展是可选的和通用的(比如压缩);子协议是强制性的和本地化的(就像聊天和MMORPG游戏一样)。</p>
+
+<h3 id="扩展">扩展</h3>
+
+<div class="note">
+<p>本节需要扩张。请编辑如果你有这样做的准备。</p>
+</div>
+
+<p>Think of an extension as compressing a file before e-mailing it to someone. Whatever you do, you're sending the <em>same</em> data in different forms. The recipient will eventually be able to get the same data as your local copy, but it is sent differently. That's what an extension does. WebSockets defines a protocol and a simple way to send data, but an extension such as compression could allow sending the same data but in a shorter format.</p>
+
+<div class="note">
+<p>扩展在规范的 5.8, 9, 11.3.2, and 11.4 进行了解释</p>
+</div>
+
+<p><em>TODO</em></p>
+
+<h3 id="子协议">子协议</h3>
+
+<p>可以把子协议理解成一个自定义<a href="https://en.wikipedia.org/wiki/XML_schema">XML schema</a>或<a href="https://en.wikipedia.org/wiki/Document_Type_Definition">文件类型声明</a>。你仍然使用XML和它的语法,但是还要额外受限于你声明的格式。</p>
+
+<p>WebSocket子协议就是像这样的东西。它们不作任何假设实现,只是确立框架。就像一个文件类型或概要。与文件类型或概要类似,通信双方都需要同意子协议;于文件类型或概要不同的是,子协议在服务端实现,而不能由客户端参考第三方。</p>
+
+<div class="note">
+<p>子协议在规范的章节1.9,4.2,11.3.4和11.5有做解释。</p>
+</div>
+
+<p>如果客户端需要指定子协议,需要发送如下消息头<strong>作为握手信息的一部分</strong>:</p>
+
+<pre>GET /chat HTTP/1.1
+...
+Sec-WebSocket-Protocol: soap, wamp
+</pre>
+
+<p>等价于:</p>
+
+<pre>...
+Sec-WebSocket-Protocol: soap
+Sec-WebSocket-Protocol: wamp
+</pre>
+
+<p>现在,服务端需要选择一个客户端建议且服务端支持的子协议。如果有多于一个的话使用客户端发送的第一个。如果我们的服务端可以支持<code>soap</code>和<code>wamp</code>,则在握手回复时,它会发送:</p>
+
+<pre>Sec-WebSocket-Protocol: soap
+</pre>
+
+<div class="warning">
+<p>服务器不能发送多个<code>Sec-Websocket-Protocol</code>。如果服务器不想使用任何子协议,它就不应该发送任何<code>Sec-WebSocket-Protocol header</code>。发送空白header是不正确的。如果客户端没有得到它想要的子协议,它可以关闭连接。</p>
+</div>
+
+<p>如果您希望您的服务器遵守某些子协议,那么很自然地,您需要服务器上的额外代码。假设我们使用的是子协议JSON。在这个子协议中,所有数据都以JSON的形式传递。如果客户端请求这个协议,而服务器想要使用它,服务器将需要一个JSON解析器。实际上,这是库的一部分,但是服务器需要传递数据。</p>
+
+<div class="note">
+<p><strong>提示:</strong>为了避免命名冲突,建议将你的子协议名称加上域名字符串。如果您正在构建一个自定义聊天应用程序,该应用程序使用的是Example Inc.独有的专有格式,那么您可以使用这个:<code>Sec-WebSocket-Protocol: chat.example.com</code>.注意,这不是必需的,它只是一个可选的约定,您可以使用任何字符串。</p>
+</div>
+
+<h2 id="关联">关联</h2>
+
+<ul>
+ <li><a href="https://github.com/alexhultman/libwshandshake">WebSocket handshake library in C++</a></li>
+ <li><a href="/en-US/docs/WebSockets/Writing_WebSocket_server" title="/en-US/docs/WebSockets/Writing_WebSocket_server">Tutorial: Websocket server in C#</a></li>
+ <li><a href="/en-US/docs/WebSockets/Writing_WebSocket_client_applications">Writing WebSocket client applications</a></li>
+ <li><a href="/en-US/docs/WebSockets/WebSocket_Server_Vb.NET">Tutorial: Websocket server in VB.NET</a></li>
+</ul>