aboutsummaryrefslogtreecommitdiff
path: root/files/es/web/api/websockets_api/writing_websocket_server/index.html
blob: b4af4cd030f891a0b76ee0946f81f50b094a6f46 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
---
title: Escribiendo un servidor WebSocket en C#
slug: Web/API/WebSockets_API/Writing_WebSocket_server
tags:
  - HTML5
  - Tutorial
  - WebSockets
translation_of: Web/API/WebSockets_API/Writing_WebSocket_server
original_slug: Web/API/WebSockets_API/Escribiendo_servidor_WebSocket
---
<h2 id="Introducción">Introducción</h2>

<p>Si deseas utilizar la API WebSocket, es conveniente si tienes un servidor. En este artículo te mostraré como puedes escribir uno en C#. Tú puedes hacer esto en cualquier lenguaje del lado del servidor, pero para mantener las cosas simples y más comprensibles, elegí el lenguaje de Microsoft<span style="line-height: 1.5;">.</span></p>

<p>Este servidor se ajusta a <a href="http://tools.ietf.org/html/rfc6455" title="http://tools.ietf.org/html/rfc6455">RFC 6455</a> por lo que solo manejará las conexiones de Chrome version 16, Firefox 11, IE 10 and superiores.</p>

<h2 id="Primeros_pasos">Primeros pasos</h2>

<p>WebSocket se comunica a través de conexiones <a href="https://es.wikipedia.org/wiki/Transmission_Control_Protocol" title="https://es.wikipedia.org/wiki/Transmission_Control_Protocol">TCP (Transmission Control Protocol)</a>, afortunadamente C# tiene una clase <a href="http://msdn.microsoft.com/es-es/library/system.net.sockets.tcplistener.aspx" title="http://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener.aspx">TcpListener</a> la cual hace lo que su nombre sugiere. Esta se encuentra en el namespace <em>System.Net.Sockets</em>.</p>

<div class="note">
<p><span style="line-height: 1.572;">Es una buena idea usar la instrucción <code>using</code></span><span style="line-height: 1.572;"> para escribir menos. Eso significa que no tendrás que re escribir el namespace de nuevo en cada ocasión.</span></p>
</div>

<h3 id="TcpListener">TcpListener</h3>

<p>Constructor:</p>

<pre class="brush: cpp">TcpListener(System.Net.IPAddress localaddr, int port)</pre>

<p><code>localaddr</code> especifica la IP a escuchar y <code>port</code> especifica el puerto.</p>

<div class="note">
<p>Para crear un objeto <code>IPAddress</code> desde un <code>string</code>, usa el método estático <code>Parse</code> de <code>IPAddres.</code></p>
</div>

<p><span style="line-height: 1.572;">Métodos</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;">S<code>ystem.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>
  Espera por una conexión TCP, la acepta y la devuelve como un objeto TcpClient.</span></li>
</ul>

<p><span style="line-height: 1.572;">Aquí está como utilizar lo que hemos aprendido:</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("El server se ha iniciado en 127.0.0.1:80.{0}Esperando una conexión...", Environment.NewLine);

        TcpClient client = server.AcceptTcpClient();

        Console.WriteLine("Un cliente conectado.");
    }
}
</pre>

<h3 id="TcpClient"><span style="line-height: 1.572;">TcpClient</span></h3>

<p>Métodos:</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>
  Obtiene el stream del canal de comunicación. Ambos lados del canal tienen capacidad de lectura y escritura.</li>
</ul>

<p>Propiedades:</p>

<ul>
 <li><code>int Available</code><br>
  Este es el número de bytes de datos que han sido enviados. El valor es cero hasta que <code><em>NetworkStream.DataAvailable</em></code> es <code><em>true</em></code>.</li>
</ul>

<h3 id="NetworkStream">NetworkStream</h3>

<p>Métodos:</p>

<pre class="brush: cpp">Write(Byte[] buffer, int offset, int size)</pre>

<p>Escribe bytes desde el <em>buffer;</em> el <em>offset</em> y el <em>size</em> determinan la longitud del mensaje.</p>

<pre><span class="brush: cpp" style="line-height: 1.572;">Read(Byte[] buffer, int offset, int size)</span></pre>

<p>Lee bytes al <em>buffer;</em> el <em>offset</em> y el <em>size </em>determinan la longitud del mensaje.</p>

<p>Ampliemos nuestro ejemplo anterior.</p>

<pre class="brush: cpp">TcpClient client = server.AcceptTcpClient();

Console.WriteLine("Un cliente conectado.");

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="Handshaking">Handshaking</h2>

<p>Cuando un cliente se conecta al servidor, envía una solicitud GET para actualizar la conexión al WebSocket desde una simple petición HTTP. Esto es conocido como <em>handshaking</em>.</p>

<p>Este código de ejemplo detecta el GET desde el cliente. Nota que esto bloqueará hasta los 3 primeros bytes del mensaje disponible. Soluciones alternativas deben ser investigadas para ambientes de producción.</p>

<pre><code>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 {

}</code></pre>

<p>Esta respuesta es fácil de construir, pero puede ser un poco díficil de entender. La explicación completa del <em>handshake </em>al servidor puede encontrarse en  <a href="https://developer.mozilla.org/es/docs/WebSockets-840092-dup/RFC%206455,%20section%204.2.2">RFC 6455, section 4.2.2</a>. Para nuestros propósitos, solo construiremos una respuesta simple.</p>

<p>Debes:</p>

<ol>
 <li>Obtener el valor de "<em>Sec-WebSocket-Key" </em>sin espacios iniciales ni finales de el encabezado de la solicitud</li>
 <li>Concatenarlo con "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"</li>
 <li>Calcular el código SHA-1 y Base64</li>
 <li>Escribe el valor <em>Sec-WebSocket-Accept</em> en el encabezado como parte de la respuesta HTTP.</li>
</ol>

<pre class="brush: cpp">if (new Regex("^GET").IsMatch(data)) {
    Byte[] response = Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" + Environment.NewLine
        + "Connection: Upgrade" + Environment.NewLine
        + "Upgrade: websocket" + Environment.NewLine
        + "Sec-WebSocket-Accept: " + Convert.ToBase64String (
            SHA1.Create().ComputeHash (
                Encoding.UTF8.GetBytes (
                    new Regex("Sec-WebSocket-Key: (.*)").Match(data).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
                )
            )
        ) + Environment.NewLine
        + Environment.NewLine);

    stream.Write(response, 0, response.Length);
}
</pre>

<h2 id="Decoding_messages">Decoding messages</h2>

<p>Luego de un <em>handshake</em> exitoso el cliente puede enviar mensajes al servidor, pero estos serán codificados.</p>

<p>Si nosotros enviamos "MDN", obtendremos estos bytes:</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>- 129:</p>

<table>
 <thead>
  <tr>
   <th scope="col">FIN (¿Es el mensaje completo?)</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: Puedes enviar tu mensaje en marcos, pero ahora debe mantener las cosas simples.<br>
 <span style="line-height: 1.572;">Opcode </span><em>0x1</em><span style="line-height: 1.572;"> significa que es un texto. </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">Lista completa de Opcodes</a></p>

<p>- 131:</p>

<p>Si el segundo byte menos 128 se encuentra entre 0 y 125, esta es la longitud del mensaje. Si es 126, los siguientes 2 bytes (entero sin signo de 16 bits), si es 127, los siguientes 8 bytes (entero sin signo de 64 bits) son la longitud.</p>

<div class="note">
<p>Puedo tomar 128, porque el primer bit siempre es 1.</p>
</div>

<p>- 61, 84, 35 y 6 son los bytes de la clave a decodificar. Cambian en cada oportunidad.</p>

<p>- Los bytes codificados restantes son el mensaje<span style="line-height: 1.572;">.</span></p>

<h3 id="Algoritmo_de_decodificación">Algoritmo de decodificación</h3>

<p>byte decodificado = byte codificado XOR (posición del byte codificado Mod 4) byte de la clave</p>

<p>Ejemplo en C#:</p>

<pre class="brush: cpp">Byte[] decoded = new Byte[3];
Byte[] encoded = new Byte[3] {112, 16, 109};
Byte[] key = Byte[4] {61, 84, 35, 6};

for (int i = 0; i &lt; encoded.Length; i++) {
    decoded[i] = (Byte)(encoded[i] ^ key[i % 4]);
}</pre>

<h2 id="Relacionado">Relacionado</h2>

<ul>
 <li><a href="/es/docs/WebSockets/Writing_WebSocket_servers">Escribiendo servidores WebSocket</a></li>
</ul>

<div id="cke_pastebin" style="position: absolute; top: 2209.23px; width: 1px; height: 1px; overflow: hidden; left: -1000px;"> </div>