translated socket and websocket sections
This commit is contained in:
410
es/08.1.md
Normal file
410
es/08.1.md
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
# 8.1 Sockets
|
||||||
|
|
||||||
|
Algunos desarrolladores de aplicaciones red dicen que las capas inferiores de progración son basadas en sockets. Esto puede no ser cierto para todos los casos, pero muchas aplicaciones modernas en realidad usan sockets para obtener ventaja. Te has preguntado ¿cuántos navegadores se conectan con servidores web cuando navegas por internet? O ¿cómo MSN te conecta a ti y a tus amigos cuando están en el mismo chat enviando un mensaje en tiempo real? Muchos servicios como estos utilizan sockets para transferir datos. Como puedes ver, los sockets ocupan una posición muy importante en la programación de red hoy, y vamos a aprender a usar sockets en Go en esta sección.
|
||||||
|
|
||||||
|
## ¿Qué es un socket?
|
||||||
|
|
||||||
|
Los sockets se originaron de Unix, y dada la filosofía básica de "todo es un archivo", todo puede ser operado con "abrir -> escribir/leer -> cerrar". Los sockets implementan esta filosofía. Los sockets tienen una función para abrir un socket justo como lo haces con un archivo. Esto retorna un descriptor del socket que puede ser usado para operaciones como crear conexiones, transferir información, etc.
|
||||||
|
|
||||||
|
Hay dos tipos de sockets que son comúnmente usados que son los sockets de flujo (SOCK_STREAM) y sockets de datagramas (SOCK_DGRAM). Los sockets de flujo son orientadas a conexion, como TCP, mientras que los sockets de datagramas no establecen conexiones, como UDP.
|
||||||
|
|
||||||
|
## Comunicación entre sockets
|
||||||
|
|
||||||
|
Antes que entendamos como los sockets se comunican uno con otro, necesitamos asegurarnos que cada socket es único, de otra manera, establecer comunicaciones confiables está fuera del alcance. Podemos darle a cada proceso un único identificador (PID) que sirve a nuestro ambiente local, sin embargo, esto no funciona sobre la red. Afortunadamente TCP/IP nos ayuda con este problema. La dirección IP de la capa de red es única en una red de hosts. y el "protocolo + puerto" es único entre una red de aplicaciones. Entonecs podemos usar estor principios para hacer sockets que sean únicos.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Figura 8.1 capas del protocolo de red.
|
||||||
|
|
||||||
|
Las aplicaciones que están basadas en TCP/IP todas usan sockets en su código de una manera u otra. Dado que las aplicaciones web se están volviendo cada vez mas prevalentes en los días modernos, no es de extrañarse que los programadores digan que "todo es sobre sockets".
|
||||||
|
|
||||||
|
## Conocimiento básico de Sockets
|
||||||
|
|
||||||
|
Sabemos que existen dos tipos de sockets, los TCP y los UDP. TCP y UDP son protocolos, y como mencioné, también necesitamos dirección IP y número de puerto para tener sockets únicos.
|
||||||
|
|
||||||
|
### IPv4
|
||||||
|
|
||||||
|
El internet globarl usa TCP/IP como su protocolo, donde IP es la capa de red y una parte principal de TCP/IP. IPv4 significa que es la versión 4; el dsarrollo de infraestructura lleva adelantado 30 años.
|
||||||
|
|
||||||
|
El número de bits en una dirección IPv4 es 32, lo que significa que solamente 2^32 dispositivos son capaces de conectarse a internet. A razón del rápido desarrollo del internet, las direcciones IP sobrepasaron este límite en los años recientes.
|
||||||
|
|
||||||
|
Formato de las direcciones IP: `127.0.0.1`, `172.122.121.111`.
|
||||||
|
|
||||||
|
### IPv6
|
||||||
|
|
||||||
|
IPv6 es la siguiente versión o la siguiente generación de internet. Ha sido desarrollada para solucionar muchos de los problemas inherentes a IPv4. Dispositivos usando IPv6 tienen una dirección de 128 bits de largo, entonces no tenemos que preocuparnos por la unicidad de la dirección. Para colocar esto en perspectiva, significa que puedes tener mas de 1000 direcciones IP por cada metro cuadrado en la tierra con IPv6. Otroso problema como conexión peer to peer, calida de servicio (QoS), seguridad, broadcast múltiple, etc también han sido mejorados.
|
||||||
|
|
||||||
|
Formato de direcciones: `2002:c0e8:82e7:0:0:0:c0e8:82e7`.
|
||||||
|
|
||||||
|
### Tipos IP en Go
|
||||||
|
|
||||||
|
el paquete `net` de Go provee muchos tipos, funciones y métodos para programación de red. La definición de IP es como sigue:
|
||||||
|
```
|
||||||
|
type IP []byte
|
||||||
|
```
|
||||||
|
LA función `ParseIP(s string) IP` es para convertir la versión IPv4 o IPv6 a una IP.
|
||||||
|
```
|
||||||
|
package main
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: %s ip-addr\n", os.Args[0])
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
name := os.Args[1]
|
||||||
|
addr := net.ParseIP(name)
|
||||||
|
if addr == nil {
|
||||||
|
fmt.Println("Invalid address")
|
||||||
|
} else {
|
||||||
|
fmt.Println("The address is ", addr.String())
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Este retorna la correspondiente formato IP para una correspondiente dirección IP.
|
||||||
|
|
||||||
|
## Socket TCP
|
||||||
|
|
||||||
|
¿Qué podemos hacer cuando sabemos como visitar un servicio web a través de un puerto de red? Como un cliente, podemos enviar una petición apuntada al puerto de red y obtener la respuesta; como un servidor necesitamos enlazar un servicio a un puerto de red, esperar por por las peticiones de los clientes y darles una respuesta.
|
||||||
|
|
||||||
|
En Go, el paquete `net` tiene un tipo llamado `TCPConn` que facilita este tipo de interacción de cliente/servidor. Este tipo tiene dos funciones principales:
|
||||||
|
```
|
||||||
|
func (c *TCPConn) Write(b []byte) (n int, err os.Error)
|
||||||
|
func (c *TCPConn) Read(b []byte) (n int, err os.Error)
|
||||||
|
```
|
||||||
|
`TCPConn` puede ser usado como cliente o servidor para leer y escribir información:
|
||||||
|
|
||||||
|
También necesitamos una `TCPAddr` para representar la información de la dirección TCP:
|
||||||
|
```
|
||||||
|
type TCPAddr struct {
|
||||||
|
IP IP
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Usamos la función `ResolveTCPAddr` para obtener una `TCPAddr` en Go:
|
||||||
|
```
|
||||||
|
func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)
|
||||||
|
```
|
||||||
|
- Los argumentos de `net` pueden ser "tcp4", "tcp6" o "tcp", donde cada uno significa IPV4 Solamente, IPv6 solamente o IPv4 o IPv6.
|
||||||
|
- `addr` puede ser un nombre de dominio o una dirección ip, como "www.google.com:80" o "127.0.0.1:22"
|
||||||
|
|
||||||
|
### Cliente TCP
|
||||||
|
|
||||||
|
Los clientes de Go usan la función `DialTCP` en el paquete `net` para crear una conexión TCP, donde el resultado es un objeto `TCPConn`; después que una conexión es establecida, el servidor tiene el mismo tipo de objeto de conexión para la conexión actual, y cliente y servidor pueden comenzar a intercambiar información. En general, los clientes envían una petición a través de una `TCPConn` y reciben información del servidor; los servidores leen y analizan las peticiones, y devuelven una respuesta. Esta conexión permanecerá válida hasta que el cliente o el servidor la cierre. La función para crear una conexión es como sigue:
|
||||||
|
```
|
||||||
|
func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)
|
||||||
|
```
|
||||||
|
- Los argumentos de `net` pueden ser "tcp4", "tcp6" o "tcp", donde cada uno significa IPV4 Solamente, IPv6 solamente o IPv4 o IPv6.
|
||||||
|
- `laddr` representa la conexión local, definida a `nil` la mayoría de las veces.
|
||||||
|
- `raddr` representa la dirección remota.
|
||||||
|
|
||||||
|
Vamos a escribir un ejemplo para simular la petición de un cliente solicitando conexión a un servidor basado en una petición HTTP. Necesitamos un encabezado HTTP simple:
|
||||||
|
```
|
||||||
|
"HEAD / HTTP/1.0\r\n\r\n"
|
||||||
|
```
|
||||||
|
La respuesta del servidor puede verse de la siguiente manera:
|
||||||
|
```
|
||||||
|
HTTP/1.0 200 OK
|
||||||
|
ETag: "-9985996"
|
||||||
|
Last-Modified: Thu, 25 Mar 2010 17:51:10 GMT
|
||||||
|
Content-Length: 18074
|
||||||
|
Connection: close
|
||||||
|
Date: Sat, 28 Aug 2010 00:43:48 GMT
|
||||||
|
Server: lighttpd/1.4.23
|
||||||
|
```
|
||||||
|
Código del cliente:
|
||||||
|
```
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0])
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
service := os.Args[1]
|
||||||
|
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
|
||||||
|
checkError(err)
|
||||||
|
conn, err := net.DialTCP("tcp", nil, tcpAddr)
|
||||||
|
checkError(err)
|
||||||
|
_, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
|
||||||
|
checkError(err)
|
||||||
|
result, err := ioutil.ReadAll(conn)
|
||||||
|
checkError(err)
|
||||||
|
fmt.Println(string(result))
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
func checkError(err error) {
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
En el ejemplo superior, usamos la entrada de un usuario como el argumento de `service` para `net.ResolveTCPAddr` para obtener un `tcpAddr`. Pasando `tcpAddr` a la función `DialTCP`, creamos una conexión TCP `conn`. Entonces podemos usar `conn` para enviar la petición al servidor. Finalmente, usamos `ioutil.ReadAll` para leer todo el contenido de `conn`, que contiene la respuesta del servidor.
|
||||||
|
|
||||||
|
### Servidor TCP
|
||||||
|
|
||||||
|
Ya tenemos un cliente TCP, También podemos usar el paquete `net` para escribir servidores TCP. En el lado del servidor necesitamos enlazar nuestro servicio a un puerto específico y esperar por una conexión de cliente.
|
||||||
|
```
|
||||||
|
func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error)
|
||||||
|
func (l *TCPListener) Accept() (c Conn, err os.Error)
|
||||||
|
```
|
||||||
|
Los argumentos requeridos aquí sin idénticos para por el la función `DialTCP` que usamos antes.
|
||||||
|
Implementemos una sincronización usando el puerto 7777:
|
||||||
|
```
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
service := ":7777"
|
||||||
|
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
|
||||||
|
checkError(err)
|
||||||
|
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||||
|
checkError(err)
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
daytime := time.Now().String()
|
||||||
|
conn.Write([]byte(daytime)) // No nos preocupamos por el valor de respuesta
|
||||||
|
conn.Close() // terminamos la conexión con el cliente
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func checkError(err error) {
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Después que el servicio es iniciado, esperamos por las peticiones de los clientes. Cuando se recibe una petición de un cliente, este la acepta (`Accept`) y retorna una respuesta que contiene la información del tiempo actual. Vale la pena preocuparse por los errores que pueden ocurrir en el ciclo `for`, el servicio continúa corriendo en vez de salir. En vez de terminar, el servidor almacenará el error en un log de errores del servidor.
|
||||||
|
|
||||||
|
El código de arriba todavía no es suficientemente bueno. Nosotros no usamos rutinas, lo que nos permitirá aceptar peticiones simultaneas. Hagamos esto ahora:
|
||||||
|
```
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
service := ":1200"
|
||||||
|
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
|
||||||
|
checkError(err)
|
||||||
|
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||||
|
checkError(err)
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go handleClient(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleClient(conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
daytime := time.Now().String()
|
||||||
|
conn.Write([]byte(daytime)) // No nos preocupamos por el valor de retorno
|
||||||
|
// Terminamos con este cliente
|
||||||
|
}
|
||||||
|
func checkError(err error) {
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Al separar nuestra proceso de negocio del la función `handleClient` y usando la palabra reservada `go`, ya hemos implementado concurrencia para nuestro servicio. Esto es una buena demostración del poder y la simplicidad de las rutinas.
|
||||||
|
|
||||||
|
Algunos deben de estar pensando lo siguiente: este servidor no hace nada útil. ¿Qué si necesitamos enviar múltiples peticiones para diferentes formatos de tiempo en una sola conexión? ¿Cómo hacemos eso?
|
||||||
|
```
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
service := ":1200"
|
||||||
|
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
|
||||||
|
checkError(err)
|
||||||
|
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||||
|
checkError(err)
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go handleClient(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleClient(conn net.Conn) {
|
||||||
|
conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) // Definimos un tiempo de cancelación de dos minutos
|
||||||
|
request := make([]byte, 128) // Definimos la longitud máxima de la petición para prevenir ataques de desbordamiento.
|
||||||
|
defer conn.Close() // Cerramos la conexión antes de salir
|
||||||
|
for {
|
||||||
|
read_len, err := conn.Read(request)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if read_len == 0 {
|
||||||
|
break // Conexión cerrada por el cliente
|
||||||
|
} else if string(request[:read_len]) == "timestamp" {
|
||||||
|
daytime := strconv.FormatInt(time.Now().Unix(), 10)
|
||||||
|
conn.Write([]byte(daytime))
|
||||||
|
} else {
|
||||||
|
daytime := time.Now().String()
|
||||||
|
conn.Write([]byte(daytime))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkError(err error) {
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
En este ejemplo usamos `conn.Read()` para constantemente leer las peticiones de un cliente. No podemos cerrar la conexión porque los clientes pueden enviar mas de una petición. Usando `conn.SetReadDeadline()` la conexión se cerrará automáticamente después que pase este periodo de tiempo. Cuando el tiempo de caducidad ha pasado, nuestro programa termina desde el ciclo `for`. Nota que las peticiones (`request`) necesitan ser creadas con la limitante de tamaño en para prevenir ataques de desbordamiento.
|
||||||
|
|
||||||
|
### Controlando las conexiones TCP
|
||||||
|
|
||||||
|
Funciones de control para conexiones TCP:
|
||||||
|
```
|
||||||
|
func DialTimeout(net, addr string, timeout time.Duration) (Conn, error)
|
||||||
|
```
|
||||||
|
Para definir el tiempo límite de las conexiones, estos son los métodos para ser usados por clientes y servidores:
|
||||||
|
```
|
||||||
|
func (c *TCPConn) SetReadDeadline(t time.Time) error
|
||||||
|
func (c *TCPConn) SetWriteDeadline(t time.Time) error
|
||||||
|
```
|
||||||
|
Definir el tiempo límite de una conexión:
|
||||||
|
```
|
||||||
|
func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error
|
||||||
|
```
|
||||||
|
Vale la pena tomar algún tiempo para pensar en cuanto quieres que sea el tiempo límite para la conexión. Conexiones largas pueden reducir el la carga extra producida por la creación de conexiones y es ua buena opción para aplicaciones que necesita intercambiar información frecuentemente.
|
||||||
|
|
||||||
|
Para información mas detallada, busca la documentación oficial del paquete `net` de Go.
|
||||||
|
|
||||||
|
## Sockets UDP
|
||||||
|
|
||||||
|
La única diferencia entre un socket UDP y uno TCP es el método de procesamiento para múltiples conexiones en el lado del servidor. Esto radica en que UDP no tinene una función como `Accept`. Todas las otras funciones tienen una contraparte `UDP`; slo reemplaza `TCP` por `UDP` en las funciones mencionadas arriba:
|
||||||
|
```
|
||||||
|
func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error)
|
||||||
|
func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error)
|
||||||
|
func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error)
|
||||||
|
func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err os.Error
|
||||||
|
func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err os.Error)
|
||||||
|
```
|
||||||
|
Ejemplo de un client UDP:
|
||||||
|
```
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0])
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
service := os.Args[1]
|
||||||
|
udpAddr, err := net.ResolveUDPAddr("udp4", service)
|
||||||
|
checkError(err)
|
||||||
|
conn, err := net.DialUDP("udp", nil, udpAddr)
|
||||||
|
checkError(err)
|
||||||
|
_, err = conn.Write([]byte("anything"))
|
||||||
|
checkError(err)
|
||||||
|
var buf [512]byte
|
||||||
|
n, err := conn.Read(buf[0:])
|
||||||
|
checkError(err)
|
||||||
|
fmt.Println(string(buf[0:n]))
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
func checkError(err error) {
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Fatal error ", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Ejemplo de servidor UDP:
|
||||||
|
```
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
service := ":1200"
|
||||||
|
udpAddr, err := net.ResolveUDPAddr("udp4", service)
|
||||||
|
checkError(err)
|
||||||
|
conn, err := net.ListenUDP("udp", udpAddr)
|
||||||
|
checkError(err)
|
||||||
|
for {
|
||||||
|
handleClient(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func handleClient(conn *net.UDPConn) {
|
||||||
|
var buf [512]byte
|
||||||
|
_, addr, err := conn.ReadFromUDP(buf[0:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
daytime := time.Now().String()
|
||||||
|
conn.WriteToUDP([]byte(daytime), addr)
|
||||||
|
}
|
||||||
|
func checkError(err error) {
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Fatal error ", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
## Resumen
|
||||||
|
|
||||||
|
A través de la descripción y algunos códigos de ejemplos usando sockets TCP y UDP, podemos ver que Go provee un excelente soporte para la programación por sockets, y que son divertidos y fáciles de usar. Go también provee muchas funciones para construir aplicaciones de alto rendimiento con sockets.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- [Índice](preface.md)
|
||||||
|
- Sección previa: [Servicios web](08.0.md)
|
||||||
|
- Siguiente secición: [WebSocket](08.2.md)
|
||||||
160
es/08.2.md
Normal file
160
es/08.2.md
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
# 8.2 WebSockets
|
||||||
|
|
||||||
|
Los WebSockets son una característica importante de HTML5. Esto implementa sockets basados en navegadores, lo que permite a los navegadores tener comunicación en dos vías con los servidores. La mayoría de navegadores populares como Firefox, Chrome y Safari proveen soporte para WebSockets.
|
||||||
|
|
||||||
|
Las personas utilizan "roll polling" para los servicios de mensajes instantaneos antes que los WebSockets nacieran, lo que permitía alos clientes enviar peticiones HTTP periódicamente. El servidor entonces retornaba la información mas reciente a los clientes. La desventaja de este método es que requiere que los clientes mantengan enviando muchas conexiones al servidor, lo que consume mucho ancho de banda.
|
||||||
|
|
||||||
|
Los WebSockets una un tipo especial de encabezado que reduce el número de apretones de manos requeridos entre el servidor y el cliente, a solo uno, para establecer la conexión. Esta conexión permanecerá activa por todo su tiempo de vida, y puedes usar JavaScript para enviar o recibir información de esta conexión, como en el caso de un socket TCP convencional. Esto resuelve muchos de los dolores de cabeza de las aplicaciones web en tiempo real y le trae las siguientes ventajas al protocolo HTTP:
|
||||||
|
|
||||||
|
- Solo una conexión TCP para un solo cliente web.
|
||||||
|
- Los servidores de WebSockets pueden enviar información a los clientes.
|
||||||
|
- Los encabezados mas livianos reducen la carga de transmisión de red.
|
||||||
|
|
||||||
|
Las URLs de los WebSockets comienzan con un ws:// o un wss://. La siguiente figura muestra el proceso de comunicación de un websocket. Un encabezado particular es enviado al servidor como parte del apretón de manos y la conexión es establecida. Entonces, servidores y cliente son capaces de recibir y enviar información a través del WebSocket
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Figura 8.2 Principio de los websockets
|
||||||
|
|
||||||
|
## Principios de los websockets
|
||||||
|
|
||||||
|
El protocolo de websockets es muy fácil. Después de completar el apretón de manos inicial, una conexión es establecida. La comunicación subsecuenta comenzará con un "\x00" y terminará con un "\xFF". Este prefijo y sufijo será visible a los clientes, porque el websocket romperá los datos entre ambos finales, produciendo los datos puros.
|
||||||
|
|
||||||
|
Las conexiones por websockets son pedidas por los navegadores y respondidas por los servidores. Después la conexión es establecida. Este proceso se llama "apretón de manos".
|
||||||
|
|
||||||
|
Considera las siguientes peticiones y respuestas
|
||||||
|
Consider the following requests and responses:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Figura 8.3 Peticiones y respuestas de un websocket
|
||||||
|
|
||||||
|
"Sec-WebSocket-key" es generada aleatoreamente, como puedes haberlo adivinado, y es codificada a base 64. Los servidores encesitan concatenar esta llave a una cadena después de aceptar una petición:
|
||||||
|
```
|
||||||
|
258EAFA5-E914-47DA-95CA-C5AB0DC85B11
|
||||||
|
```
|
||||||
|
|
||||||
|
Supón que tenemos `f7cb4ezEAl6C3wRaU6JORA==`, Entonces enviamos:
|
||||||
|
```
|
||||||
|
f7cb4ezEAl6C3wRaU6JORA==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
|
||||||
|
```
|
||||||
|
Usa sha1 para computar el valor binario y luego base 64 para codificarlo. Entonces tendremos:
|
||||||
|
```
|
||||||
|
rE91AJhfC+6JdVcVXOGJEADEJdQ=
|
||||||
|
```
|
||||||
|
Usa esto como valor en el encabezado de respuesta `Sec-WebSocket-Accept`.
|
||||||
|
|
||||||
|
## WebSocket en Go
|
||||||
|
La librería estándar de Go no provee soporte para WebSockets. Sin embargo el paquete `websocket` que es un subpaquete de `go.net` lo es, y es oficialmente mantenido mantenido y soportado.
|
||||||
|
|
||||||
|
Usa `go get` para instalar este paquete:
|
||||||
|
```
|
||||||
|
go get golang.org/x/net/websocket
|
||||||
|
```
|
||||||
|
Los WebSockets tienen lados de cliente y servidor. Veamos un ejemplo simple de un usuario ingresa alguna información en el lado del cliente y la envía al lado del servidor a través de un WebSocket, seguido por el servidor reenviando la información al cliente.
|
||||||
|
|
||||||
|
Lado del cliente:
|
||||||
|
```
|
||||||
|
<html>
|
||||||
|
<head></head>
|
||||||
|
<body>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var sock = null;
|
||||||
|
var wsuri = "ws://127.0.0.1:1234";
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
|
||||||
|
console.log("onload");
|
||||||
|
|
||||||
|
sock = new WebSocket(wsuri);
|
||||||
|
|
||||||
|
sock.onopen = function() {
|
||||||
|
console.log("connected to " + wsuri);
|
||||||
|
}
|
||||||
|
|
||||||
|
sock.onclose = function(e) {
|
||||||
|
console.log("connection closed (" + e.code + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
sock.onmessage = function(e) {
|
||||||
|
console.log("message received: " + e.data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function send() {
|
||||||
|
var msg = document.getElementById('message').value;
|
||||||
|
sock.send(msg);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<h1>WebSocket Echo Test</h1>
|
||||||
|
<form>
|
||||||
|
<p>
|
||||||
|
Message: <input id="message" type="text" value="Hello, world!">
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<button onclick="send();">Send Message</button>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
Como puedes ver, es muy facil utilizar las funciones Javascript del lado del cliente para establecer la conección. El evento `onopen` es activado después de terminar exitosamente el proceso del apretón de manos. Esto le dice al cliente que la conexión ha sido creada exitosamente. El cliente trata de abrir una conexions usualmente ata estos cuatro eventos:
|
||||||
|
|
||||||
|
- 1)onopen: activado cuando la conexión es establecida.
|
||||||
|
- 2)onmessage: activado después de recibir un mensaje.
|
||||||
|
- 3)onerror: activado cuando un error ha ocurrido.
|
||||||
|
- 4)onclose: activuado cuando la conexión ha sido cerrada.
|
||||||
|
|
||||||
|
Código del servidor:
|
||||||
|
```
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Echo(ws *websocket.Conn) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for {
|
||||||
|
var reply string
|
||||||
|
|
||||||
|
if err = websocket.Message.Receive(ws, &reply); err != nil {
|
||||||
|
fmt.Println("Can't receive")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Received back from client: " + reply)
|
||||||
|
|
||||||
|
msg := "Received: " + reply
|
||||||
|
fmt.Println("Sending to client: " + msg)
|
||||||
|
|
||||||
|
if err = websocket.Message.Send(ws, msg); err != nil {
|
||||||
|
fmt.Println("Can't send")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.Handle("/", websocket.Handler(Echo))
|
||||||
|
|
||||||
|
if err := http.ListenAndServe(":1234", nil); err != nil {
|
||||||
|
log.Fatal("ListenAndServe:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Cuando un cliente envía la información, el cliente la recive (`Receive`) y usa un `Send` para enviar la respuesta.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Figura 8.4 Servidor WebSocket recibiendo información.
|
||||||
|
|
||||||
|
A través de el ejemplo de arriba, podemos ver que la implemenación de un cliente y servior WebSocket es muy conveniente. Podemos usar el paquete `net` directamente en Go. Con el rápido desarrollo de HTML5, pienso que los WebSockets tendrán un rol mucho mas importante en las aplicaciones web modernas; deberíamos ser capaces al menos de estar familiarizados con ellos.
|
||||||
|
|
||||||
|
## Enlaces
|
||||||
|
|
||||||
|
- [Índice](preface.md)
|
||||||
|
- Sección previa: [Sockets](08.1.md)
|
||||||
|
- Siguiente sección: [REST](08.3.md)
|
||||||
Reference in New Issue
Block a user