Go语言Socket编程

建立Socket:使用socket()函数。
绑定Socket:使用bind()函数。
监听:使用listen()函数。或者连接:使用connect()函数。
接受连接:使用accept()函数。
接收:使用receive()函数。或者发送:使用send()函数。

《Go语言编程》

5 

网 络 编 程

本章我们将全面介绍如何使用Go语言开发网络程序。Go语言标准库里提供的net包,支持基于IP层、TCP/UDP层及更高层面(如HTTP、FTP、SMTP)的网络操作,其中用于IP层的称为RawSocket。

5.1 Socket 编程

在Go语言中编写网络程序时,我们将看不到传统的编码形式。以前我们使用Socket编程时, 会按照如下步骤展开。

  • 建立Socket:使用socket()函数。
  • 绑定Socket:使用bind()函数。
  • 监听:使用listen()函数。或者连接:使用connect()函数。
  • 接受连接:使用accept()函数。
  • 接收:使用receive()函数。或者发送:使用send()函数。

Go语言标准库对此过程进行了抽象和封装。无论我们期望使用什么协议建立什么形式的连接,都只需要调用net.Dial()即可。

  • 5.1.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.tianqiweiqi.com")

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

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

这里我们可以通过以下链接查看协议编号的含义:http://www.iana.org/assignments/protocol-num- bers/protocol-numbers.xml。

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

在成功建立连接后,我们就可以进行数据的发送和接收。发送数据时,使用conn的Write() 成员方法,接收数据时使用Read()方法。

5.1.2 ICMP示例程序

下面我们实现这样一个例子:我们使用ICMP协议向在线的主机发送一个问候,并等待主机返回,具体代码如代码清单5-1所示。

代码清单5-1 icmptest.go

package main

import (
    “net”
    “os” 
    “bytes”
    “fmt”
)

func main() {
    if len(os.Args) != 2 {
        fmt.Println(“Usage: “, os.Args[0], “host”) 
        os.Exit(1)
    }

    service := os.Args[1]

    conn, err := net.Dial(“ip4:icmp”, service) 
    checkError(err)

    var msg [512]byte 
    msg[0] = 8 // echo 
    msg[1] = 0 // code 0 
    msg[2] = 0 // checksum 
    msg[3] = 0 // checksum
    msg[4] = 0 // identifier[0] 
    msg[5] = 13 //identifier[1] 
    msg[6] = 0 // sequence[0] 
    msg[7] = 37 // sequence[1] 
    len := 8

    check := checkSum(msg[0:len]) 
    msg[2] = byte(check >> 8) 
    msg[3] = byte(check & 255)

    _, err = conn.Write(msg[0:len]) 
    checkError(err)

    _, err = conn.Read(msg[0:]) 
    checkError(err)

    fmt.Println(“Got response”)

    if msg[5] == 13 { 
        fmt.Println(“Identifier matches”)
    }

    if msg[7] == 37 { 
        fmt.Println(“Sequence matches”)
    }

    os.Exit(0)

}

func checkSum(msg []byte) uint16 { 
    sum := 0

    // 先假设为偶数

    for n := 1; n <len(msg)-1; n += 2 {
        sum += int(msg[n])*256 + int(msg[n+1])
    }

    sum = (sum >> 16) + (sum & 0xffff) 
    sum += (sum >> 16)
    var answer uint16 = uint16(^sum)
    return answer
}

func checkError(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, “Fatal error: %s”, err.Error()) 
    os.Exit(1)
    }
}

func readFully(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
}

执行结果如下:

$ go build icmptest.go
$ ./icmptest www.tianqiweiqi.com 
Got response
Identifier matches 
Sequence matches

5.1.3 TCP示例程序

下面我们建立TCP链接来实现初步的HTTP协议,通过向网络主机发送HTTP Head请求,读取网络主机返回的信息,具体代码如代码清单5-2所示。

代码清单5-2 simplehttp.go

package main

import (
    “net”
    “os” 
    “bytes” 
    “fmt”
)

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]

    conn, err := net.Dial(“tcp”, service) 
    checkError(err)

    _, err = conn.Write([]byte(“HEAD / HTTP/1.0\r\n\r\n”)) 
    checkError(err)

    result, err := readFully(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)
    }
}

func readFully(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
}

执行这段程序并查看执行结果:

$ go build simplehttp.go
$ ./simplehttp qbox.me:80

HTTP/1.1 301 Moved Permanently 
Server: nginx/1.0.14
Date: Mon, 21 May 2012 03:15:08 GMT
Content-Type: text/html 
Content-Length: 184 
Connection: close 
Location: https://qbox.me/

5.1.4 更丰富的网络通信

实际上,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发送HTTP请求,读取服务器返回的HTTP Head的整个流程也可以使用代码清单5-3所示的实现方式。

代码清单5-3 simplehttp2.go

package main

import (
    “net”
    “os”
    “fmt” 
    “io/ioutil”
)

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

与之前使用Dail()的例子相比,这里有两个不同:

  • net.ResolveTCPAddr(),用于解析地址和端口号;
  • net.DialTCP(),用于建立链接。这两个函数在Dial()中都得到了封装。

此外,net包中还包含了一系列的工具函数,合理地使用这些函数可以更好地保障程序的质量。

验证IP地址有效性的代码如下:

func net.ParseIP()

创建子网掩码的代码如下:

func IPv4Mask(a, b, c, d byte) IPMask

获取默认子网掩码的代码如下:

func (ip IP) DefaultMask() IPMask

根据域名查找IP的代码如下:

func ResolveIPAddr(net, addr string) (*IPAddr, error)
func LookupHost(name string) (cname string, addrs []string, err error);

作者:

喜欢围棋和编程。

 
发布于 分类 编程标签

发表评论

邮箱地址不会被公开。