声明:内容来自《Go语言编程》

[TOC]

1 TCP

1.1 TCP Server

package main

import (
    "bufio"
    "fmt"
    "io"
    "net"
)

func main() {
    server, err := net.Listen("tcp", "127.0.0.1:5000")
    if err != nil {
        fmt.Println("Error:", err.Error())
    }
    for {
        conn, err := server.Accept()
        if err != nil {
            fmt.Println("Error:", err.Error())

        }
        go func(c net.Conn) {
            defer c.Close()
            for {
                reader := bufio.NewReader(c)
                var receive [128]byte
                n, err := reader.Read(receive[:])
                if err != nil {
                    if err != io.EOF {
                        fmt.Println("Error:", err)

                    }
                    break
                }
                fmt.Println("receive:", string(receive[:n]))
                c.Write([]byte("你好"))
            }

        }(conn)
    }

}

1.2 TCP Client

func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:5000")
    if err != nil {
        fmt.Println("Error:", err.Error())
    }
    defer conn.Close()
    _, err = conn.Write([]byte("hello"))
    if err != nil {
        fmt.Println("Error:", err.Error())
    }
    response := [128]byte{}
    n, err := conn.Read(response[:])
    if err != nil {
        fmt.Println("Error:", err.Error())
    }
    fmt.Println("response:", string(response[:n]))

}

2 UDP

2.1 UDP Server

func main() {
    server, err := net.ListenUDP("udp", &net.UDPAddr{
        IP:   net.IPv4(0, 0, 0, 0),
        Port: 5000,
    })
    if err != nil {
        fmt.Println("Error:", err.Error())
    }
    defer server.Close()
    for {
        var data [1024]byte
        n, addr, err := server.ReadFromUDP(data[:])
        if err != nil {
            fmt.Println("Error:", err.Error())

        }
        fmt.Printf("data:%v addr:%v count:%v\n", string(data[:n]), addr, n)
        _, err = server.WriteToUDP(data[:n], addr) // 发送数据
        if err != nil {
            fmt.Println("write to udp failed, err:", err)
            continue
        }
    }

}

3 HTTP

3.1 HTTP Server

func ListenAndServe(addr string, handler Handler) error

该方法用于在指定的 TCP 网络地址 addr 进行监听,然后调用服务端处理程序来处理传入的连 接请求。该方法有两个参数:第一个参数 addr 即监听地址;第二个参数表示服务端处理程序, 通常为空,这意味着服务端调用 http.DefaultServeMux 进行处理,而服务端编写的业务逻 辑处理程序 http.Handle() 或 http.HandleFunc() 默认注入 http.DefaultServeMux 中。

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
        defer request.Body.Close()
        if request.Method == "GET" {
            fmt.Fprint(writer, "Hello", request.RemoteAddr)
            return
        }
        if request.Method == "POST" {
            request.ParseForm() //这里需要先解析一下,才能拿到结果
            params := request.PostForm
            for k, v := range params {
                fmt.Println(k,v)
            }
            return
        }
    })
    err := http.ListenAndServe("0.0.0.0:5000", nil)
    if err != nil {
        fmt.Println("Error:", err.Error())
        return
    }
}

3.2 HTTP Client

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
)

func MyGetNoParams(url_ string) (responseBody string, err error) {
    resp, err := http.Get(url_)
    if err != nil {
        fmt.Println("Error:", err)
        return "", err
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error:", err)
        return "", err
    }
    return string(body), nil
}

func MyGetParams(url_ string) (responseBody string, err error) {
    params := url.Values{}
    params.Set("wd", "hello")
    u, err := url.ParseRequestURI(url_)
    if err != nil {
        fmt.Println("Error:", err)
        return "", err
    }
    fmt.Println(u.String())      //http://www.baidu.com
    u.RawQuery = params.Encode() //将需要携带的参数信息拼接到当前url的尾部
    fmt.Println(u.String())      //http://www.baidu.com?wd=hello
    return MyGetNoParams(u.String())
}

func MyPost(url_ string) {
    params := url.Values{}
    params.Set("wd", "hello")
    resp, err := http.PostForm(url_, params)
    if err!= nil{
        fmt.Println(err)
    }
    defer resp.Body.Close()
    body,err := ioutil.ReadAll(resp.Body)
    if err!= nil{
        fmt.Println(err)
    }
    fmt.Println(string(body))
}

func main() {
    //resp, err := MyGetParams("http://www.baidu.com")
    //if err != nil {
    //    fmt.Println("Error:", err)
    //    return
    //}
    //fmt.Println(resp)
    MyPost("http://127.0.0.1:5000/")
}

4 RPC

go语言进程间通信的方法有两种:HTTP、RPC。

RPC(Remote Procedure Call,远程过程调用)是一种通过网络从远程计算机程序上请求服 务,而不需要了解底层网络细节的应用程序通信协议。RPC协议构建于TCP或UDP,或者是 HTTP 之上,允许开发者直接调用另一台计算机上的程序,而开发者无需额外地为这个调用过程编写网 络通信相关代码,使得开发包括网络分布式程序在内的应用程序更加容易。

RPC 采用客户端—服务器(Client/Server)的工作模式。请求程序就是一个客户端(Client), 而服务提供程序就是一个服务器(Server)。当执行一个远程过程调用时,客户端程序首先发送一 个带有参数的调用信息到服务端,然后等待服务端响应。在服务端,服务进程保持睡眠状态直到 客户端的调用信息到达为止。当一个调用信息到达时,服务端获得进程参数,计算出结果,并向 客户端发送应答信息,然后等待下一个调用。最后,客户端接收来自服务端的应答信息,获得进 程结果,然后调用执行并继续进行。

  • 一个 RPC 服务端可以注册多个不同类型 的对象,但不允许注册同一类型的多个对象。

  • 一个对象中只有满足如下这些条件的方法,才能被 RPC 服务端设置为可供远程访问:

    • 必须是在对象外部可公开调用的方法(首字母大写);

    • 必须有两个参数,且参数的类型都必须是包外部可以访问的类型或者是Go内建支持的类 型;

    • 第二个参数必须是一个指针;

    • 方法必须返回一个error类型的值。

      以上四点可以用一行代码表示:func (t *T) MethodName(argType T1, replyType *T2) error

      该方法(MethodName)的第一个参数表示由 RPC 客户端传入的参数,第二个参数表示要返 回给RPC客户端的结果,该方法最后返回一个 error 类型的值。

4.1 RPC服务端

RPC 服务端可以通过调用 rpc.ServeConn 处理单个连接请求。多数情况下,通过 TCP 或 是 HTTP 在某个网络地址上进行监听来创建该服务是个不错的选择

4.2 RPC客户端

在 RPC 客户端,Go 的 net/rpc 包提供了便利的 rpc.Dial()rpc.DialHTTP() 方法 来与指定的 RPC 服务端建立连接。在建立连接之后,Go 的 net/rpc 包允许我们使用同步或者 异步的方式接收 RPC 服务端的处理结果。调用 RPC 客户端的 Call() 方法则进行同步处理,这 时候客户端程序按顺序执行,只有接收完 RPC 服务端的处理结果之后才可以继续执行后面的程 序。当调用 RPC 客户端的 Go() 方法时,则可以进行异步处理,RPC 客户端程序无需等待服务 端的结果即可执行后面的程序,而当接收到 RPC 服务端的处理结果时,再对其进行相应的处理。 无论是调用 RPC 客户端的 Call() 或者是 Go() 方法,都必须指定要调用的服务及其方法名称, 以及一个客户端传入参数的引用,还有一个用于接收处理结果参数的指针。

如果没有明确指定 RPC 传输过程中使用何种编码解码器,默认将使用 Go 标准库提供的 encoding/gob 包进行数据传输。

4.3 RPC服务端与客户端代码示例

package myRpc

import "errors"

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
}
4.3.1 RPC服务端
package main

import (
    "go_learn/myNet/myRpc"
    "log"
    "net"
    "net/http"
    "net/rpc"
)

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

此时,RPC 服务端注册了一个Arith类型的对象及其公开方法Arith.Multiply()和 Arith.Divide()供 RPC 客户端调用。

4.3.2 RPC客户端

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

package main

import (
    "fmt"
    "go_learn/myNet/myRpc"
    "log"
    "net/rpc"
)

func main() {
    client, err := rpc.DialHTTP("tcp", "127.0.0.1:1234")
    if err != nil {
        log.Fatal("dialing:", err)
    }
    //同步调用程序顺序执行的方式
    args := &myRpc.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)

    //异步调用
    //quotient := new(Quotient)
    //divCall := client.Go("Arith.Divide", args, &quotient, nil)
    //replyCall := <-divCall.Done
}

5 Gob

Gob 是 Go 的一个序列化数据结构的编码解码工具,在 Go 标准库中内置encoding/gob包 以供使用。一个数据结构使用 Gob 进行序列化之后,能够用于网络传输。与 JSON 或 XML 这种 基于文本描述的数据交换语言不同,Gob 是二进制编码的数据流,并且 Gob 流是可以自解释的, 它在保证高效率的同时,也具备完整的表达能力。

由于 Gob 仅局 限于使用 Go 语言开发的程序,这意味着我们只能用 Go 的 RPC 实现进程间通信,我们用 Go 编写的 RPC 服务端(或客户端),可能更希望它是通用的,与语言无关的,无 论是Python 、 Java 或其他编程语言实现的 RPC 客户端,均可与之通信。

6 JSON处理

  • Go语言的大多数数据类型都可以转化为有效的JSON文本,但channel、complex和函数这几种 类型除外。
  • 如果转化前的数据结构中出现指针,那么将会转化指针所指向的值,如果指针指向的是零值, 那么null将作为转化后的结果输出。
  • 字符串将以UTF-8编码转化输出为Unicode字符集的字符串,特殊字符比如<将会被转义为 \u003c
  • 如果JSON中的字段在Go目标类型中不存在,json.Unmarshal()函数在解码过程中会丢弃 该字段。
  • 目标类型中不可被导出的私有字段(非首 字母大写)将不会受到解码转化的影响。

6.1 解码未知结构的JSON数据

空接口是通用类型。如果要解码一段未知结构的JSON,只需将这段JSON数据解码输出到一个空接口即可

6.2 JSON的流式读写

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

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