Algunos desarrolladores de aplicaciones de red, dicen que la capa inferior es todo acerca de la programación de sockets, no podrá cierto en todos los puntos, pero muchas aplicaciones utilizan sockets de hecho. Cómo Alguna vez piensas en estas cuestiones, ¿cómo los navegadores se comunican con los servidores web cuando se navega en Internet? Cómo MSN usted y sus amigos se conecta? Muchos servicios como estos están utilizando sockets para transferir datos, por eso los sokets ocupan un lugar importante en la programación de la red hoy en día, y vamos a utilizar sockets en Go en esta sección.
Socket es de Unix, y "todo es un archivo" es la filosofía básica de Unix, así que todo puede funcionar con "abrir -> escritura / lectura -> Cerrar". Socket es una implementación de esta filosofía, conector de red es una E / S especial, y el zócalo es una clase de descriptor de archivo. Socket tiene una llamada a la función para la apertura de una toma como un archivo, se devuelve un descriptor de socket int, y se utiliza en las operaciones siguientes para crear la conexión, transferencia de datos, etc
Aquí hay dos tipos de bases que se utilizan comúnmente: Socket Stream (SOCK_STREAM) y socket de datagramas (SOCK_DGRAM). Socket Stream es orientado a la conexión, como TCP; socket de datagramas no tiene conexión, como UDP.
Antes de entender cómo sockets comunican entre sí, tenemos que encontrar la manera de asegurarse de que cada soket sea único, de lo contrario la comunicación está fuera de cuestión. Podemos dar a cada proceso un PID a nivel local, pero no es capaz de trabajar en red. Afortunadamente, TCP / IP nos ayuda esto a resolver este problema. Dirección IP de capa de red es única en la red de los ejércitos, y "protocolo + port" es único de aplicaciones en hosts, entonces podemos usar este principio para hacer que los sokets sean únicos.
Figure 8.1 network protocol layers
Las aplicaciones que se basan en TCP / IP están utilizando las API de sockets para la programación, y la red se convierte en parte importante de nuestras vidas, es por eso que algunas personas dicen que "todo está por sokets".
Sabemos que soket tiene dos tipos que son socket TCP y soket UDP, TCP y UDP son protocolos, y también necesitamos la dirección IP y el puerto a tener sokets únicos.
Internet Global utiliza TCP / IP como su protocolo, donde IP es la capa de red y parte fundamental de TCP / IP. IPv4 significa su versión es 4, el desarrollo hasta la fecha ha pasado más de 30 años.
El número de bits de la dirección IPv4 es de 32, lo que significa que 2 ^ 32 dispositivos son capaces de conectarse a Internet. Debido al rápido desarrollo de Internet, las direcciones IP son casi fuera de stock en los últimos años.
Formato de dirección: 127.0.0.1
, 172.122.121.111
.
IPv6 es la próxima versión o la próxima generación de Internet, se está haciendo para resolver los problemas de la implementación de IPv4. Su dirección tiene 128 bits de largo, por lo que no tendrá que preocuparse por la escasez de direcciones, por ejemplo, puede tener más de 1.000 direcciones IP por cada metro cuadrado de la tierra con IPv6. También se pueden mejorar otros problemas como peer to peer de conexión, calidad de servicio (QoS), seguridad, difusión múltiple, etc.
Formato de dirección: 2002:c0e8:82e7:0:0:0:c0e8:82e7
.
Package net
en Go ofrece muchos tipos, funciones y métodos para la programación de la red, la definición del IP de la siguiente manera:
type IP []byte
Functions ParseIP(s string) IP
se utiliza para convertir el formato de IP de IPv4 a IPv6:
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)
}
Devuelve el formato de IP correspondiente dirección IP determinada
Lo que podemos hacer cuando sabemos cómo visitar un servicio web a través de un puerto de red? Como cliente, podemos enviar una solicitud al puerto de red designado, y obtiene su retroalimentación; como un servidor, tenemos que obligar a un servicio al puerto de red designado, esperar a que las peticiones de los clientes y les da retroalimentación.
En el package net
, tiene un tipo llamado TCPConn` para este tipo de clientes y servidores, 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
se puede utilizar ya sea como cliente o servidor para leer y escribir datos.
También necesitamos un TCPAddr
para representar información de la dirección TCP:
type TCPAddr struct {
IP IP
Port int
}
Usamos function ResolveTCPAddr
para optener TCPAddr
in Go:
func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)
- Los argumentos de
net
pueden ser "tcp4", "tcp6" or "tcp", donde son TCP(IPv4-only), TCP(IPv6-only) or TCP(IPv4 or IPv6). addr
puede ser el nombre de dominio o dirección IP, así como "www.google.com:80" or "127.0.0.1:22".
Go utiliza la función DialTCP
en el paquete net
para crear una conexión TCP, y devuelve un objeto TCPConn
; después de la conexión creada, servidor tiene un objeto de conexión mismo tipo para esta conexión, e intercambiar datos entre sí. En general, los clientes envían peticiones al servidor a través de TCPConn
y obtienen informacion de los servidores; servidores de leer y analizar las solicitudes de los clientes, a continuación, devolver la retroalimentación. Esta conexión no será válida hasta que un lado la cierre. La función para crear la conexión es de la siguiente manera:
func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)
- Argumentos de
net
pueden ser de "tcp4", "tcp6" or "tcp", donde son TCP(IPv4-only), TCP(IPv6-only) or TCP(IPv4 or IPv6). laddr
representa la dirección local, póngalo ennil
en la mayoría de los casos.raddr
representa la dirección remota.
Vamos a escribir un ejemplo sencillo para simular una solicitud de un cliente para conectar un servidor web basado en HTTP. Necesitamos un encabezado de solicitud HTTP simple:
"HEAD / HTTP/1.0\r\n\r\n"
Formato de información la respuesta del Server puede de 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
Client code:
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 anterior, usamos la entrada del usuario como argumento service
y pasamos net.ResolveTCPAddr
para obtener un tcpAddr
, entonces pasamos tcpAddr
a la funcion DialTCP
para crear una conexión TCP conn
, a continuación, utilizar conn
para enviar información de la solicitud. Por último, utilice ioutil.ReadAll
para leer todo el contenido de conn , que es la retroalimentación servidor.
Tenemos un cliente TCP ahora, y también podemos utilizar el package net
para escribir un servidor TCP. En el lado del servidor, tenemos que unir el servicio al puerto inactivo específica, y escuchar a este puerto, por lo que es capaz de recibir solicitudes de clientes.
func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error)
func (l *TCPListener) Accept() (c Conn, err os.Error)
Los argumentos son los mismos que DialTCP
, vamos a implementar un servicio de sincronización de tiempo, el puerto es 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)) // don't care about return value
conn.Close() // we're finished with this client
}
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
Después del inicio del servicio, se espera las peticiones de los clientes. Cuando se hacen las solicitudes de clientes, Accept
y da retroalimentación de información de la hora actual. Vale la pena señalar que cuando se produce un error en el for
bucle, continúe funcionando en lugar de salir a causa del registro de errores de registro en el servidor es mejor que el accidente, lo que hace que el servicio sea estable.
El código anterior no es suficiente, porque no usamos goroutine aceptar la solicitud múltiple como mismo tiempo. Vamos a hacer mejor:
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)) // don't care about return value
// we're finished with this client
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
A través de la separación de los procesos de negocio para la función handleClient
, implementamos concurrencia para nuestro servicio. Sólo tienes que añadir go palabra clave para implementar la concurrencia, es una de las razones que goroutine es simple y de gran alcance.
Algunas personas pueden preguntarse: este servidor no hace nada significativo, lo que si tenemos que enviar varias solicitudes de diferente formato de la hora en una conexión, ¿cómo podemos hacer 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)) // set 2 minutes timeout
request := make([]byte, 128) // set maxium request length to 128KB to prevent flood attack
defer conn.Close() // close connection before exit
for {
read_len, err := conn.Read(request)
if err != nil {
fmt.Println(err)
break
}
if read_len == 0 {
break // connection already closed by client
} else if string(request) == "timestamp" {
daytime := strconv.FormatInt(time.Now().Unix(), 10)
conn.Write([]byte(daytime))
} else {
daytime := time.Now().String()
conn.Write([]byte(daytime))
}
request = make([]byte, 128) // clear last read content
}
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
En este ejemplo, utilizamos conn.Read () para leer constantemente las solicitudes del cliente, y no podemos estrecha conexión porque el cliente puede tener más peticiones. Debido al tiempo de espera de conn.SetReadDeadline () , que se cierra automáticamente cuando el cliente no ha enviado solicitud en un período de tiempo, por lo que salta de bloque de código de de bucle. Tenga en cuenta que la solicitud tiene que crear la limitación de tamaño máximo con el fin de prevenir el ataque de inundación; recurso limpio después de procesarse todas las solicitudes porque conn.Read () anexa nuevos contenidos en lugar de volver a escribir.
Control functions of TCP:
func DialTimeout(net, addr string, timeout time.Duration) (Conn, error)
Configuración de tiempo de espera de las conexiones, es adecuado para clientes y servidores:
func (c *TCPConn) SetReadDeadline(t time.Time) error
func (c *TCPConn) SetWriteDeadline(t time.Time) error
Configuración de tiempo de espera de escritura / lectura de una conexión:
func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error
Vale la pena considerar si mantener siempre la conexión entre el cliente y el servidor, conexión a tiempo puede reducir la sobrecarga de crear conexiones, es bueno para las aplicaciones que necesitan intercambiar datos con frecuencia.
Más información, póngase en bucle hasta la documentación oficial del paquete de net
.
El método sólo es diferente entre socket UDP y socket TCP está procesando para múltiples solicitudes en el lado del servidor, es porque UDP no tiene función como Accept . Otras funciones, sustituye solamente TCP con UDP
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)
UDP client code sample:
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)
}
}
UDP server code sample:
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)
}
}
A través de la descripción y programación de sockets TCP y UDP, podemos ver que el Go tiene muy buen soporte para la programación del zócalo, y son fáciles de usar. Go también ofrece muchas funciones para crear aplicaciones de socket de alto rendimiento.
- Directory
- Previous section: Web services
- Next section: WebSocket