首页 » Go » 正文

Go语言集训(五):网络编程

网络编程

在Go语言中编写网络程序时,我们看不到传统的那种编码形式。原来使用socket编程是,会按照以下步骤:
1. 建立socket : socket()
2. 绑定socket: bind()
3. 监听: listen() 或者链接:connect()
4. 接受连接: accept()
5. 接收: receive() 或者发送:send
在GO语言中,只需要调动net.Dial()即可。

Dial 函数

func Dial(net, addr string) (Conn, error)
* net 是网络协议的名字
* addr 是IP地址或者域名,端口号以“:”的形式跟随在抵制或者域名的后面,端口号可选。
* 目前支持tcp,tcp4,tcp6,udp,udp4,udp6,ip,ip4,ip6
* 如果ICMP可以使用:ip4:ICMP 或者 ip4:1
成功建立连接后,我们可以进行数据的发送和接收。发送数据时,使用conn的write(),接收是采用Read()

TCP 实例

package main

import (
    "bytes"
    "fmt"
    "io"
    "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]

    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
}

HTTP客户端

Go内置的net/http提供了最简单的http客户端实现,我们不需要借助第三方网络通信库如libcurl就可以直接使用http中用的最多的GET和POST方式请求数据
1. 基本方法
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)
  1. 概要介绍

* http.Get()

resp, err := http.Get("http://www.baidu.com")
if err != nil {
    // handle error
    return
}
defer resp.Body.close()
io.Copy(os.Stdout, resp.Body)
// 上面这段代码请求一个网页首页,并将网页内容打印到标准输出上。
  • http.Post()
resp, err := http.Post("http://www.baidu.com/upload","image/jpeg",&imageDataBuf)
if err != nil {
   //handle error
   return
}
if resp.StatusCode != http.StatusOK {
  //handle error
  return
}
  • http.PostForm()
// 实现了标准编码格式为application/x-www-form-urlencoded的表单提交
resp, err := http.PostForm("http://www.baidu.com/posts", url.Values("title":{"article title"},"content":{"article body"}))
if err != nil {
   //handle error
   return
}
  • http.Head()
//请求目标URL的头部信息
resp, err := http.Head("www.baidu.com")
  • (*http.Client).Do()
//在多数情况下http.Get()和http.PostForm()能够满足需求,但是需要我们发起的HTTP请求需要更多的定制信息。比如传递cookie,设置自定义的“User-Agent”
req, err := http.NewRequest("GET","http://www.baidu.com",nil)
req.Header.Add("User-Agent", "Gobook Custom User-Agent")
client := & http.Client{}
resp, err := client.Do(req)

HTTP服务端

  1. 处理HTTP请求
    func ListenAndServe(add string, handler Handler) error

* addr即坚挺地址
* handle为服务端处理程序。通常为空,意味着调用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.ListenAndServer(":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.ListenAndServer())
  1. 处理HTTPS请求
    func ListenAndServeTLS(add string,certFile string, keyFile string, handler Handler) error

* addr即坚挺地址
* certFile对应SSL证书存放的路径
* keyFile对应证书私钥文件路径
* handle为服务端处理程序。通常为空,意味着调用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.ListenAndServerTLS(":10443","cert.pem", "key.pem",nil))

//如果更多控制服务端的行为,可以自动以http.Server

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

RPC编程

RPC协议构架于TCP或者UDP,或者HTTP上,允许开发者直接调用另一台计算机上的程序,而无需额外调用编写网络通信相关代码。使得开发包括网络分布式程序在内的应用程序更加容易
net/rpc允许RPC客户端通过网络或者IO连接调用一个远端对象的公开方法(必须是大写字母开头、可外部调用的)。在RPC服务端,可将一个对象注册为可访问的服务,之后该对象的公开方法就能能够以远程的方式提供访问。一个RPC服务端可以注册多个不同类型的对象,但不允许注册同一类型的多个对象。
一个对象需要满足以下条件,才能够提供远程访问:
1. 必须是在对象外部可公开调用的方法
2. 必须有两个参数,且参数的类型都必须是包外部可以访问的类型,或者Go内建支持的类型
3. 第二个参数必须是一个指针
4. 方法必须返回一个error类型的值
func (t *T) MethodName(argType T1, replyType *T2) error

在RPC客户端,GO的net/rpc包提供便利的rpc.Dial()和rpc.DialHTTP()方法来于指定RPC服务端建立连接。建立后,go的net/rpc包允许我们使用同步或者异步的方式接收rpc服务端的结果。调用rpc客户端的call方法进行同步处理,这时候客户端程序按顺序执行,只有接收完RPC服务端的处理结果之后,才可能继续执行后面的程序。当调用RPC客户端的Go方法,进行异步处理,RPC客户端无需等待服务端的结果即可执行后面的程序,而当接收到RPC服务端的处理结果时,再对其进行相应的处理。无论是调用RPC客户端的Call还是Go,都必须制定要调用的服务及其方法名称,以及一个客户端传入参数的引用,还有一个用于接收处理结果参数的指针。
如果没有明确制定RPC传输过程中采用何种编码,默认将使用encoding/gob包进行数据传输。

package server

import (
    "log"
    "net"
    "net/http"
    "net/rpc"
)

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, reply *Quotient) error {
    reply.Quo = args.A / args.B
    reply.Rem = args.A % args.B
    return nil
}

// 服务端侦听
func main() {
    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)
}

//客户端连接调用

func client() {
    client, err := rpc.DialHTTP("tcp", serverAddress+":1234")

    args := &server.Args{7, 8}
    var reply int
    err = client.Call("Arith.Multiply", args, &reply)

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

json编解码

空接口是通用类型。如果要解码一段位置结构的JSON,只需要将这段json数据解码输出到一个空接口接口。
* JSON中布尔值会专为Go中bool
* 数值–float64
* 字符串–string
* json数组会转换为[]interface{}
* json对象会转换为map[string]interface{}
* null — nil



func json() { b := []byte(`{ "Title": "Go语言编程", "Authors": ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan", "XuDaoli"], "Publisher": "ituring.com.cn", "IsPublished": true, "Price": 9.99, "Sales": 1000000 }`) var r interface{} err := json.Unmarshal(b, &r) // //map[string]interface{}{ // "Title": "Go语言编程", // "Authors": ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan", // "XuDaoli"], // "Publisher": "ituring.com.cn", // "IsPublished": true, // "Price": 9.99, // "Sales": 1000000 //} }

简单web界面

目录结构:
uploads:上传文件目的地
views: html文件 包含两个list.html 和 upload.html
photoweb.go

//photoweb.go
package main

import (
    "fmt"
    "html/template"
    "io"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "path"
    "runtime/debug"
    "strings"
)

const (
    ListDir      = 0x0001
    UPLOAD_DIR   = "./uploads"
    TEMPLATE_DIR = "./views"
)

var templates = make(map[string]*template.Template)

func init() {
    fileInfoArr, err := ioutil.ReadDir(TEMPLATE_DIR)
    check(err)
    var templateName, templatePath string
    for _, fileInfo := range fileInfoArr {
        templateName = fileInfo.Name()
        if ext := path.Ext(templateName); ext != ".html" {
            continue
        }
        templatePath = TEMPLATE_DIR + "/" + templateName
        log.Println("Loading template:", templatePath)
        t := template.Must(template.ParseFiles(templatePath))
        if strings.Contains(templateName, "upload") {
            fmt.Println("upload....")
            templates["upload"] = t
        }
        if strings.Contains(templateName, "list") {
            fmt.Println("list....")
            templates["list"] = t
        }
    }
}
func check(err error) {
    if err != nil {
        panic(err)
    }
}
func renderHtml(w http.ResponseWriter, tmpl string, locals map[string]interface{}) {
    err := templates[tmpl].Execute(w, locals)
    check(err)
}
func isExists(path string) bool {
    _, err := os.Stat(path)
    if err == nil {
        return true
    }
    return os.IsExist(err)
}
func uploadHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method == "GET" {
        renderHtml(w, "upload", nil)
    }
    if r.Method == "POST" {
        f, h, err := r.FormFile("image")
        check(err)
        filename := h.Filename
        defer f.Close()
        imagePath := UPLOAD_DIR + "/" + filename
        t, err := os.OpenFile(imagePath, os.O_WRONLY|os.O_CREATE, 0666)
        check(err)
        defer t.Close()
        _, err1 := io.Copy(t, f)
        check(err1)
        http.Redirect(w, r, "/view?id="+filename, http.StatusFound)
    }
}
func viewHandler(w http.ResponseWriter, r *http.Request) {
    imageId := r.FormValue("id")
    imagePath := UPLOAD_DIR + "/" + imageId
    if exists := isExists(imagePath); !exists {
        http.NotFound(w, r)
        return
    }
    w.Header().Set("Content-Type", "image")
    http.ServeFile(w, r, imagePath)
}
func listHandler(w http.ResponseWriter, r *http.Request) {
    fileInfoArr, err := ioutil.ReadDir("./uploads")
    check(err)
    locals := make(map[string]interface{})
    images := []string{}
    for _, fileInfo := range fileInfoArr {
        images = append(images, fileInfo.Name())
        fmt.Println(images)
    }
    locals["images"] = images
    renderHtml(w, "list", locals)
}
func safeHandler(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if e, ok := recover().(error); ok {
                http.Error(w, e.Error(), http.StatusInternalServerError)
                // 或者输出自定义的50x错误页面
                // w.WriteHeader(http.StatusInternalServerError)
                // renderHtml(w, "error", e)
                // logging
                log.Println("WARN: panic in %v. - %v", fn, e)
                log.Println(string(debug.Stack()))
            }
        }()
        fn(w, r)
    }
}
func staticDirHandler(mux *http.ServeMux, prefix string, staticDir string, flags int) {
    mux.HandleFunc(prefix, func(w http.ResponseWriter, r *http.Request) {
        file := staticDir + r.URL.Path[len(prefix)-1:]
        if (flags & ListDir) == 0 {
            if exists := isExists(file); !exists {
                http.NotFound(w, r)
                return
            }
        }
        http.ServeFile(w, r, file)
    })
}
func main() {
    mux := http.NewServeMux()
    staticDirHandler(mux, "/assets/", "./public", 0)
    mux.HandleFunc("/", safeHandler(listHandler))
    mux.HandleFunc("/view", safeHandler(viewHandler))
    mux.HandleFunc("/upload", safeHandler(uploadHandler))
    err := http.ListenAndServe(":8080", mux)
    if err != nil {
        log.Fatal("ListenAndServe: ", err.Error())
    }
}

list.html

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>List</title>
</head>
<body>
<ol>
{{range $.images}}
<li><a href="/view?id={{.|urlquery}}">{{.|html}}</a></li>
{{end}}
</ol>
</body>
</html>

upload.html

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Upload</title>
</head>
<body>
<form method="POST" action="/upload" enctype="multipart/form-data">
Choose an image to upload: <input name="image" type="file" />
<input type="submit" value="Upload" />
</form>
</body>
</html>