Go笔记-socket

SOCKET

基础知识

Socket有两种:TCP Socket和UDP Socket,TCP和UDP是协议。肯定一个进程须要三元组,IP地址、协议和端口。html

IPv4的地址位数为32位,地址格式相似这样:127.0.0.1 172.122.121.111。nginx

IPv6采用128位地址长度,地址格式相似这样:2002:c0e8:82e7:0:0:0:c0e8:82e7。程序员

IP IN GO

IP类型的定义在net包中。golang

使用下面的语句获取一个ip类型:web

var ip net.IP = net.ParseIP("127.0.0.1")

TCP SOCKET

net.TCPConn可做为客户端和服务器端交互的通道,利用它的Read()和Write()能够读写数据。算法

net.TCPAddr表示一个TCP的地址信息:浏览器

type TCPAddr struct {
    IP IP
    Port int
  }

得到TCPAddr:安全

func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)

TCP client

Go语言中经过net包中的DialTCP函数来创建一个TCP链接,并返回一个TCPConn类型的对象,当链接创建时服务器端也建立一个同类型的对象,此时客户端和服务器段经过各自拥有的TCPConn对象来进行数据交换。通常而言,客户端经过TCPConn对象将请求信息发送到服务器端,读取服务器端响应的信息。服务器端读取并解析来自客户端的请求,并返回应答信息,这个链接只有当任一端关闭了链接以后才失效,否则这链接能够一直在使用。创建链接的函数定义以下:服务器

func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)
  • net参数是"tcp4"、"tcp6"、"tcp"中的任意一个,分别表示TCP(IPv4-only)、TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一个)websocket

  • laddr表示本机地址,通常设置为nil

    • raddr表示远程的服务地址

    示例:

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

从conn中读取数据时最好不要直接用 conn.Read(),建议用:

reader := bufio.NewReader(sender.tcpConn)
respStr, err := reader.ReadString('\n')

TCP server

在服务器端咱们须要绑定服务到指定的非激活端口,并监听此端口,当有客户端请求到达的时候能够接收到来自客户端链接的请求。net包中有相应功能的函数,函数定义以下:

func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error)
func (l *TCPListener) Accept() (c Conn, err os.Error)
  • 示例:下面实现一个简单的时间同步服务,监听7777端口
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)
    }
}
  • 示例:经过读取客户端的数据来返回不一样的时间格式,并保持一个长链接

这里只写了handleClient(),其余代码参考上面

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
    }
}

conn.SetReadDeadline()设置了超时,当必定时间内客户端无请求发送,conn便会自动关闭,下面的for循环即会由于链接已关闭而跳出。须要注意的是,request在建立时须要指定一个最大长度以防止flood attack;每次读取到请求处理完毕后,须要清理request,由于conn.Read()会将新读取到的内容append到原内容以后。

控制TCP链接

TCP有不少链接控制函数,咱们日常用到比较多的有以下几个函数:

func DialTimeout(net, addr string, timeout time.Duration) (Conn, error)

设置创建链接的超时时间,客户端和服务器端都适用,当超过设置时间时,链接自动关闭。

func (c *TCPConn) SetReadDeadline(t time.Time) error
func (c *TCPConn) SetWriteDeadline(t time.Time) error

用来设置写入/读取一个链接的超时时间。当超过设置时间时,链接自动关闭。

func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error

设置客户端是否和服务器端保持长链接,能够下降创建TCP链接时的握手开销,对于一些须要频繁交换数据的应用场景比较适用。

tcp设置超时:

conn.SetDeadline(time.Now().Add(timeout))
	defer func() {
		var zero time.Time
		conn.SetDeadline(zero) // 重置
	}()

tcp超时检测:

if neterr, ok := err.(net.Error); ok && neterr.Timeout()

测试连接是否能连通:

if _, err := sender.tcpConn.Read(empty); err == io.EOF {
		fmt.Println("%s closed, reconnection now.")
		sender.Close()
		sender.Init()
	}

http://stackoverflow.com/questions/12741386/how-to-know-tcp-connection-is-closed-in-golang-net-package

TCP链接网站示例

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"os"
)

func Tcptest() {
	service := "www.csdn.net:80" // 后面要跟端口号
	conn, err := net.Dial("tcp", service)
	checkError2(err)

	_, err = conn.Write([]byte("HEAR / HTTP/1.0\r\n\r\n"))
	checkError2(err)

	result, err := readFully2(conn)
	checkError2(err)

	fmt.Println(string(result))
	os.Exit(0)
}

func checkError2(err error) {
	if err != nil {
		fmt.Fprint(os.Stderr, "Fatal error: %s", err.Error())
		os.Exit(1)
	}
}

func readFully2(conn net.Conn) ([]byte, error) {
	defer conn.Close()

	result := bytes.NewBuffer(nil)
	var buf [512]byte
	for {
		n, err := conn.Read(buf[0:])
		result.Write(buf[0:n])
		if err != nil {
			if err == io.EOF {
				break
			}
			return nil, err
		}
	}
	return result.Bytes(), nil
}

结果示例:

HTTP/1.1 404 Not Found
Server: ngx_openresty
Date: Sat, 20 Sep 2014 07:49:31 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 162
Connection: close

<html>
<head><title>404 Not Found</title></head>
<body bgcolor="white">
\<center><h1>404 Not Found</h1></center>
\<hr><center>nginx</center>
</body>
</html>

上面的两个斜杠只是为了能够在页面上显示。

UDP SOCKET

Go语言包中处理UDP Socket和TCP Socket不一样的地方就是在服务器端处理多个客户端请求数据包的方式不一样,UDP缺乏了对客户端链接请求的Accept函数。其余基本几乎如出一辙,只有TCP换成了UDP而已。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的客户端代码以下所示,咱们能够看到不一样的就是TCP换成了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)
    }
}

咱们来看一下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)
    }
}

DIAL

Dial()函数的原型以下:

func Dial(net, addr string) (Conn, error)

其中net参数是网络协议的名字,addr参数是IP地址或域名,而端口号以“:”的形式跟随在地址或域名的后面,端口号可选。若是链接成功,返回链接对象,不然返回error。 咱们来看一下几种常见协议的调用方式。 TCP连接:

conn, err := net.Dial("tcp", "192.168.0.10:2100")

UDP连接:

conn, err := net.Dial("udp", "192.168.0.12:975")

ICMP连接(使用协议名称):

conn, err := net.Dial("ip4:icmp", "www.baidu.com")

ICMP连接(使用协议编号):

conn, err := net.Dial("ip4:1", "10.0.0.3")

目前,Dial()函数支持以下几种网络协议:"tcp"、"tcp4"(仅限IPv4)、"tcp6"(仅限IPv6)、"udp"、"udp4"(仅限IPv4)、"udp6"(仅限IPv6)、"ip"、"ip4"(仅限IPv4)和"ip6" (仅限IPv6)。

在成功创建链接后,咱们就能够进行数据的发送和接收。发送数据时,使用conn的Write()成员方法,接收数据时使用Read()方法

WebSocket

WebSocket采用了一些特殊的报头,使得浏览器和服务器只须要作一个握手的动做,就能够在浏览器和服务器之间创建一条链接通道。且此链接会保持在活动状态,你可使用JavaScript来向链接写入或从中接收数据,就像在使用一个常规的TCP Socket同样。它解决了Web实时化的问题,相比传统HTTP有以下好处:

  • 一个Web客户端只创建一个TCP链接
  • Websocket服务端能够推送(push)数据到web客户端.
  • 有更加轻量级的头,减小数据传送量

WebSocket URL的起始输入是ws://或是wss://(在SSL上)。

WebSocket原理

WebSocket的协议颇为简单,在第一次handshake经过之后,链接便创建成功,其后的通信数据都是以”\x00″开头,以”\xFF”结尾。在客户端,这个是透明的,WebSocket组件会自动将原始数据“掐头去尾”。

浏览器发出WebSocket链接请求,而后服务器发出回应,而后链接创建成功,这个过程一般称为“握手” (handshaking)。请看下面的请求和反馈信息:

websocket请求

在请求中的"Sec-WebSocket-Key"是随机的,对于成天跟编码打交到的程序员,一眼就能够看出来:这个是一个通过base64编码后的数据。服务器端接收到这个请求以后须要把这个字符串链接上一个固定的字符串:

258EAFA5-E914-47DA-95CA-C5AB0DC85B11

即:f7cb4ezEAl6C3wRaU6JORA==链接上那一串固定字符串,生成一个这样的字符串:

f7cb4ezEAl6C3wRaU6JORA==258EAFA5-E914-47DA-95CA-C5AB0DC85B11

对该字符串先用 sha1安全散列算法计算出二进制的值,而后用base64对其进行编码,便可以获得握手后的字符串:

rE91AJhfC+6JdVcVXOGJEADEJdQ=

将之做为响应头Sec-WebSocket-Accept的值反馈给客户端。

GO中的WebSocket

Go语言标准包里面没有提供对WebSocket的支持,可是在由官方维护的go.net子包中有对这个的支持,你能够经过以下的命令获取该包:

go get code.google.com/p/go.net/websocket

更丰富的网络通讯

实际上,Dial()函数是对DialTCP()、DialUDP()、DialIP()和DialUnix()的封装。咱们也能够直接调用这些函数,它们的功能是一致的。这些函数的原型以下:

func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err error) 
func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err error) 
func DialIP(netProto string, laddr, raddr *IPAddr) (*IPConn, error) 
func DialUnix(net string, laddr, raddr *UnixAddr) (c *UnixConn, err error)

上面TCP示例也可使用下面的代码来实现:

import (
	"fmt"
	"io/ioutil"
	"net"
)
	service := "www.csdn.net:80"
	tcpAddr, err := net.ResolveTCPAddr("tcp4", service) // 解析地址和端口
	checkError2(err)

	conn, err := net.DialTCP("tcp", nil, tcpAddr) // 创建链接,参2表示本地地址,参3表示远程地址
	checkError2(err)

	_, err = conn.Write([]byte("HEAR / HTTP/1.0\r\n\r\n")) // 写数据
	checkError2(err)

	result, err := ioutil.ReadAll(conn) // 调用工具类,用Buffer来读
	checkError2(err)

	fmt.Println(string(result))

工具函数

验证IP地址有效性

import (
	"fmt"
	"net"
)

func Othertest_main() {
	ip := net.ParseIP("127.0.0.1")
	fmt.Println(ip.String()) // 127.0.0.1

	ip = net.ParseIP("123")
	fmt.Println(ip) // <nil>
}

建立子网掩码

mask := net.IPv4Mask(255, 255, 0, 0)
fmt.Println(mask) // ffff0000

IP地址的默认子网掩码

ip := net.ParseIP("127.0.0.1")
mask := ip.DefaultMask()
fmt.Println(mask) // ff000000

根据域名得到IP地址

addr := "www.baidu.com"
ipaddr, err := net.ResolveIPAddr("ip", addr)
if err != nil {
	fmt.Println(err)
} else {
	fmt.Println(ipaddr) // 61.135.169.121
}

addrs, err := net.LookupHost("www.baidu.com")
if err != nil {
	fmt.Println(err)
	return
}
for i, v := range addrs {
	fmt.Println(i, "=>", v)
}
//0 => 61.135.169.121
//1 => 61.135.169.125

获取本机地址

conn, err := net.Dial("udp", "baidu.com:80")
	if err != nil {
		fmt.Println(err)
		return ""
	}
	defer conn.Close()
	ipPort := conn.LocalAddr().String()
	return strings.Split(ipPort, ":")[0]
相关文章
相关标签/搜索