go语言网络编程

网 络 编 程

Socket 编程

(1) 建立Socket:使用socket()函数。
(2) 绑定Socket:使用bind()函数。
(3) 监听:使用listen()函数。或者连接:使用connect()函数。
(4) 接受连接:使用accept()函数。
(5) 接收:使用receive()函数。或者发送:使用send()函数。
无论我们期望使用什么协议建立什么形式的连接,都只需要调用net.Dial()即可。

Dial()函数

//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")

ICMP示例程序

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.baidu.com
Got response
Identifier matches
Sequence matches

TCP示例程序

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/

更丰富的网络通信

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请求可以使用以下代码实现:

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()中都得到了封装。

//验证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);

HTTP 编程

HTTP客户端

net/http包的Client类型提供了如下几个方法:

func (c *Client) Get(url string) (r *Response, err error)
func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err
error)
func (c *Client) PostForm(url string, data url.Values) (r *Response, err error)
func (c *Client) Head(url string) (r *Response, err error)
func (c *Client) Do(req *Request) (resp *Response, err error)

http.Get()使用:

resp, err := http.Get("http://example.com/")
if err != nil {
// 处理错误 ...
return
}
defer resp.Body.close()
io.Copy(os.Stdout, resp.Body)

http.Post()使用:

resp, err := http.Post("http://example.com/upload", "image/jpeg", &imageDataBuf)
if err != nil {
// 处理错误
return
}
if resp.StatusCode != http.StatusOK {
// 处理错误
return
}
// ...

http.PostForm()使用:

resp, err := http.PostForm("http://example.com/posts", url.Values{"title":{"article title"}, "content": {"article body"}})
if err != nil {
// 处理错误
return
}
// ...

http.Head()使用:

resp, err := http.Head("http://example.com/")

(*http.Client).Do()使用:
如果我们发起的
HTTP 请求需要更多的定制信息,我们希望设定一些自定义的 Http Header 字段,比如:
 设定自定义的”User-Agent”,而不是默认的 “Go http package”
 传递 Cookie
此时可以使用net/http包http.Client对象的Do()方法来实现

req, err := http.NewRequest("GET", "http://example.com", nil)
// ...
req.Header.Add("User-Agent", "Gobook Custom User-Agent")
// ...
client := &http.Client{ //... }
resp, err := client.Do(req)
// ...

高级封装

http.Get()、http.Post()、http.PostForm()和http.Head()方法其实都是在http.DefaultClient的基础上进行调用的。
在net/http包中,的确提供了Client类型。让我们来看一看http.Client类型的结构:

type Client struct {
    // Transport用于确定HTTP请求的创建机制。
    // 如果为空,将会使用DefaultTransport
    Transport RoundTripper
    // CheckRedirect定义重定向策略。
    // 如果CheckRedirect不为空,客户端将在跟踪HTTP重定向前调用该函数。
    // 两个参数reqvia分别为即将发起的请求和已经发起的所有请求,最早的
    // 已发起请求在最前面。
    // 如果CheckRedirect返回错误,客户端将直接返回错误,不会再发起该请求。
    // 如果CheckRedirect为空,Client将采用一种确认策略,将在10个连续
    // 请求后终止
    CheckRedirect func(req *Request, via []*Request) error
    // 如果Jar为空,Cookie将不会在请求中发送,并会
    // 在响应中被忽略
    Jar CookieJar
}

,http.Client类型包含了3个公开数据成员:
Transport RoundTripper
CheckRedirect func(req *Request, via []*Request) error
Jar CookieJar
其中Transport类型必须实现http.RoundTripper接口。Transport指定了执行一个HTTP 请求的运行机制,倘若不指定具体的Transport,默认会使用http.DefaultTransport,这意味着http.Transport也是可以自定义的。net/http包中的http.Transport类型实现了http.RoundTripper接口。
CheckRedirect函数指定处理重定向的策略。当使用 HTTP Client 的Get()或者是Head()方法发送 HTTP 请求时,若响应返回的状态码为 30x (比如 301 / 302 / 303 / 307),HTTP Client 会在遵循跳转规则之前先调用这个CheckRedirect函数。
Jar可用于在 HTTP Client 中设定 Cookie,Jar的类型必须实现了 http.CookieJar 接口,该接口预定义了 SetCookies()和Cookies()两个方法。如果 HTTP Client 中没有设定 Jar,Cookie将被忽略而不会发送到客户端。实际上,我们一般都用 http.SetCookie() 方法来设定Cookie。
创建自定义的 HTTP Client 非常简单,具体代码如下:

client := &http.Client {
    CheckRedirect: redirectPolicyFunc,
}
resp, err := client.Get("http://example.com")
// ...
req, err := http.NewRequest("GET", "http://example.com", nil)
// ...
req.Header.Add("User-Agent", "Our Custom User-Agent")
req.Header.Add("If-None-Match", `W/"TheFileEtag"`)
resp, err := client.Do(req)
// ...

HTTP服务端

处理HTTP请求

开启一个HTTP,服务端该方法的原型如下:

func ListenAndServe(addr string, handler Handler) error

第一个参数 addr 即监听地址;第二个参数表示服务端处理程序,
通常为空,这意味着服务端调用 http.DefaultServeMux 进行处理,而服务端编写的业务逻
辑处理程序 http.Handle() 或 http.HandleFunc() 默认注入 http.DefaultServeMux 中,
具体代码如下:

http.Handle("/foo", fooHandler)
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServe(":8080", nil))

如果想更多地控制服务端的行为,可以自定义 http.Server,代码如下:

s := &http.Server{
    Addr: ":8080",
    Handler: myHandler,
    ReadTimeout: 10 * time.Second,
    WriteTimeout: 10 * time.Second,
    MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())

处理HTTPS请求

处理 HTTPS 连接请求:

func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error

开启 SSL 监听服务也很简单,如下列代码所示:

http.Handle("/foo", fooHandler)
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServeTLS(":10443", "cert.pem", "key.pem", nil))

或者是:

ss := &http.Server{
    Addr: ":10443",
    Handler: myHandler,
    ReadTimeout: 10 * time.Second,
    WriteTimeout: 10 * time.Second,
    MaxHeaderBytes: 1 << 20,
}
log.Fatal(ss.ListenAndServeTLS("cert.pem", "key.pem"))

RPC 编程

在 RPC 服务端,可将一个对象注册为可访问的服务,之后该对象的公开方法就能够以远程的方式提供访问。一个 RPC 服务端可以注册多个不同类型的对象,但不允许注册同一类型的多个对象。
一个对象中只有满足如下这些条件的方法,才能被 RPC 服务端设置为可供远程访问:
 必须是在对象外部可公开调用的方法(首字母大写);
 必须有两个参数,且参数的类型都必须是包外部可以访问的类型或者是Go内建支持的类
型;
 第二个参数必须是一个指针;
 方法必须返回一个error类型的值。
以上4个条件,可以简单地用如下一行代码表示:

func (t *T) MethodName(argType T1, replyType *T2) error

在上面这行代码中,类型T、T1 和 T2 默认会使用 Go 内置的 encoding/gob 包进行编码解码。
该方法(MethodName)的第一个参数表示由 RPC 客户端传入的参数,第二个参数表示要返回给RPC客户端的结果,该方法最后返回一个 error 类型的值。
RPC 服务端和客户端交互的示例程序
服务端代码:

package server
type Args struct {
    A, B int
}
type Quotient struct {
    Quo, Rem int
}
type Arith int
func (t *Arith) Multiply(args *Args, reply *int) error {
    *reply = args.A * args.B
    return nil
}
func (t *Arith) Divide(args *Args, quo *Quotient) error {
    if args.B == 0 {
    return errors.New("divide by zero")
    }
    quo.Quo = args.A / args.B
    quo.Rem = args.A % args.B
    return nil
}

注册服务对象并开启该 RPC 服务的代码如下:

arith := new(Arith)
rpc.Register(arith)
rpc.HandleHTTP()
l, e := net.Listen("tcp", ":1234")
if e != nil {
    log.Fatal("listen error:", e)
}
go http.Serve(l, nil)

RPC 在调用服务端提供的方法之前,必须先与 RPC 服务端建立连接:

client, err := rpc.DialHTTP("tcp", serverAddress + ":1234")
if err != nil {
    log.Fatal("dialing:", err)
}

RPC 客户端调用服务端顺序方法:

args := &server.Args{7,8}
var reply int
err = client.Call("Arith.Multiply", args, &reply)
if err != nil {
log.Fatal("arith error:", err)
}
fmt.Printf("Arith: %d*%d=%d", args.A, args.B, reply)

RPC 客户端调用服务端异步方法:

quotient := new(Quotient)
divCall := client.Go("Arith.Divide", args, "ient, nil)
replyCall := <-divCall.Done

Json

json.Marshal() 函数将gobook实例生成一段JSON格式的文本:

b, err := json.Marshal(gobook)

在Go中,JSON转化前后的数据类型映射如下。
 布尔值转化为JSON后还是布尔类型。
 浮点数和整型会被转化为JSON里边的常规数字。
 字符串将以UTF-8编码转化输出为Unicode字符集的字符串,特殊字符比如<将会被转义为
\u003c。
 数组和切片会转化为JSON里边的数组,但[]byte类型的值将会被转化为 Base64 编码后
的字符串,slice类型的零值会被转化为 null。
 结构体会转化为JSON对象,并且只有结构体里边以大写字母开头的可被导出的字段才会
被转化输出,而这些可导出的字段会作为JSON对象的字符串索引。
 转化一个map类型的数据结构时,该数据的类型必须是 map[string]T(T可以是
encoding/json 包支持的任意数据类型)。

son.Unmarshal()函数将JSON格式的文本解码为Go里边预期的数据结构:
[]byte 类型的JSON数据作为第一个参数传入,将 book实例变量的指针作为第二个参数传入:

err := json.Unmarshal(b, &book)

JSON的流式读写

Go内建的encoding/json 包还提供Decoder和Encoder两个类型,用于支持JSON数据的
流式读写,并提供NewDecoder()和NewEncoder()两个函数来便于具体实现:

func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder
//从标准输入流中读取JSON数据,然后将其解码,但只保留Title字段,再写入到标准输出流中
package main
import (
    "encoding/json"
    "log"
    "os"
)
func main() {
    dec := json.NewDecoder(os.Stdin)
    enc := json.NewEncoder(os.Stdout)
    for {
        var v map[string]interface{}
        if err := dec.Decode(&v); err != nil {
            log.Println(err)
            return
        }
        for k := range v {
            if k != "Title" {
                v[k] = nil, false
            }
        }
        if err := enc.Encode(&v); err != nil {
            log.Println(err)
        }
    }
}

网站开发

网站demo:

package main
import (
"io"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "Hello, world!")
}
func main() {
    http.HandleFunc("/hello", helloHandler)
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err.Error())
    }
}

http.HandleFunc(),该方法用于分发请求.
http.HandleFunc()方法接受两个参数,第一个参数是HTTP请求的目标路径”/hello”,该参数值可以是字符串,也可以是字符串形式的正则表达式,第二个参数指定具体的回调方法,比如helloHandler。

你可能感兴趣的:(go)