Go语言编程笔记--网络编程

Go语言标准库里提供的net包,支持基于IP层TCP/UDP层更高层面(如HTTP、FTP、SMTP)的网络操作

其中用于IP层的称为RawSocket

1 socket 编程

传统的socket编程步骤:

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

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

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

protocol-number协议编号的含义

Dial()函数支持如下几种网络协议:

"tcp"、
"tcp4"(仅限IPv4)、
"tcp6"(仅限IPv6)、
"udp"、
"udp4"(仅限IPv4)、
"udp6"(仅限IPv6)、
"ip"、
"ip4"(仅限IPv4)
"ip6"(仅限IPv6)。

成功建立连接后,我们就可以进行数据的发送和接收。

发送数据时,使用conn的Write() 接收数据时使用Read()方法。

代码实例:

icmptest


package main

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

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]
	length := 8
	check := checkSum(msg[0:length])
	msg[2] = byte(check >> 8)
	msg[3] = byte(check & 255)
	_, err = conn.Write(msg[0:length])
	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
	}

tcptest


package main
import (
	"io"
	"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
}

2 HTTP 编程

HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最为广泛的一种网络 协议,定义了客户端和服务端之间请求与响应的传输标准

Go语言标准库内建提供了net/http包,涵盖了HTTP客户端和服务端的具体实现。使用 net/http包,我们可以很方便地编写HTTP客户端或服务端的程序

1)HTTP客户端 –基本请求方法

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() (等价于http.DefaultClient.Get()


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

码请求一个网站首页并将其网页内容打印到标准输出流中

http.Post()

要以POST的方式发送数据,也很简单,只需调用http.Post()方法并依次传递下面的3个参数即可:

 请求的目标 URL
 将要 POST 数据的资源类型(MIMEType)
 数据的比特流([]byte形式)
resp, err := http.Post("http://example.com/upload", "image/jpeg", &imageDataBuf)
if err != nil {
 // 处理错误
 return
}

if resp.StatusCode != http.StatusOK {
 // 处理错误
 return
}
// ... 

http.PostForm() 表单提交

实现了标准编码格式application/x-www-form-urlencoded 的表单提交

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

http.Head() 只请求目标 URL 的头部信息

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

(*http.Client).Do() HTTP 请求需要更多的定制信息

 设定自定义的"User-Agent",而不是默认的 "Go http package"
 传递 Cookie 

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) 

2 HTTP客户端 http.Client 高级封装

http.Client类型结构


type Client struct {
     // Transport用于确定HTTP请求的创建机制。
     // 如果为空,将会使用DefaultTransport
     Transport RoundTripper
     
     // CheckRedirect定义重定向策略。
     // 如果CheckRedirect不为空,客户端将在跟踪HTTP重定向前调用该函数。
     // 两个参数req和via分别为即将发起的请求和已经发起的所有请求,最早的
     // 已发起请求在最前面。
     // 如果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 

(1)自定义 http.Transport

type Transport struct {
     // Proxy指定用于针对特定请求返回代理的函数。
     // 如果该函数返回一个非空的错误,请求将终止并返回该错误。
     // 如果Proxy为空或者返回一个空的URL指针,将不使用代理
     Proxy func(*Request) (*url.URL, error)
     
     // Dial指定用于创建TCP连接的dail()函数。
     // 如果Dial为空,将默认使用net.Dial()函数
     Dial func(net, addr string) (c net.Conn, err error)
     
     // TLSClientConfig指定用于tls.Client的TLS配置。
     // 如果为空则使用默认配置
     TLSClientConfig *tls.Config
     
     DisableKeepAlives bool
     
     DisableCompression bool
     
     // 如果MaxIdleConnsPerHost为非零值,它用于控制每个host所需要
     // 保持的最大空闲连接数。如果该值为空,则使用DefaultMaxIdleConnsPerHost
     MaxIdleConnsPerHost int
     // ...
} 

公开数据成员

Proxy func(Request) (url.URL, error)

Proxy 指定了一个代理方法,该方法接受一个 *Request 类型的请求实例作为参数并返回
一个最终的 HTTP 代理。如果 Proxy 未指定或者返回的 *URL 为零值,将不会有代理被启用。

Dial func(net, addr string) (c net.Conn, err error)

Dial 指定具体的dial()方法来创建 TCP 连接。如果不指定,默认将使用 net.Dial() 方法。

TLSClientConfig *tls.Config

SSL连接专用,TLSClientConfig 指定 tls.Client 所用的 TLS 配置信息,如果不指定,
也会使用默认的配置。

DisableKeepAlives bool

是否取消长连接,默认值为 false,即启用长连接。

DisableCompression bool

是否取消压缩(GZip),默认值为 false,即启用压缩。

MaxIdleConnsPerHost int

指定与每个请求的目标主机之间的最大非活跃连接(keep-alive)数量。如果不指定,默认使
用 DefaultMaxIdleConnsPerHost 的常量值

公开的成员方法

 func(t *Transport) CloseIdleConnections()。该方法用于关闭所有非活跃的
连接。

 func(t *Transport) RegisterProtocol(scheme string, rt RoundTripper)。
该方法可用于注册并启用一个新的传输协议,比如 WebSocket 的传输协议标准(ws),或
者 FTP、File 协议等。

 func(t *Transport) RoundTrip(req *Request) (resp *Response, err error)。
用于实现 http.RoundTripper 接口

实例:

tr := &http.Transport{
 TLSClientConfig: &tls.Config{RootCAs: pool},
 DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://example.com") 


(2) 灵活的 http.RoundTripper 接口


type RoundTripper interface {
 // RoundTrip执行一个单一的HTTP事务,返回相应的响应信息。
 // RoundTrip函数的实现不应试图去理解响应的内容。如果RoundTrip得到一个响应,
 // 无论该响应的HTTP状态码如何,都应将返回的err设置为nil。非空的err
 // 只意味着没有成功获取到响应。
 // 类似地,RoundTrip也不应试图处理更高级别的协议,比如重定向、认证和
 // Cookie等。
 //
 // RoundTrip不应修改请求内容, 除非了是为了理解Body内容。每一个请求
 // 的URL和Header域都应被正确初始化
 RoundTrip(*Request) (*Response, error)
} 

在默认的 http.Transport 之上包一层 Transport 并实现 RoundTrip() 方法


package main
import(
 "net/http"
)

type OurCustomTransport struct {
     Transport http.RoundTripper
}

func (t *OurCustomTransport) transport() http.RoundTripper {
     if t.Transport != nil {
         return t.Transport
     }
     return http.DefaultTransport
}

func (t *OurCustomTransport) RoundTrip(req *http.Request) (*http.Response, error) {
     // 处理一些事情 ...
     // 发起HTTP请求
     // 添加一些域到req.Header中
     return t.transport().RoundTrip(req)
}

func (t *OurCustomTransport) Client() *http.Client {
     return &http.Client{Transport: t}
}

func main() {
     t := &OurCustomTransport{
    //...
    }
     c := t.Client()
     resp, err := c.Get("http://example.com")
     // ...
} 

3 HTTP服务端

处理 HTTP 请求

net/http 包提供的http.ListenAndServe() 方法,可以在指定的地址进行监听, 开启一个HTTP

func ListenAndServe(addr string, handler Handler) error 
     
     自定义 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请求

net/http 包还提供 http.ListenAndServeTLS() 方法,用于处理 HTTPS 连接请求

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

服务器上必须存在包含证书和与之匹配的私钥的相关文件,比如certFile对应SSL证书 文件存放路径,keyFile对应证书私钥文件路径

如果证书是由证书颁发机构签署的,certFile 参数指定的路径必须是存放在服务器上的经由CA认证过的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")) 


3 RPC 编程

RPC(Remote Procedure Call,远程过程调用)是一种通过网络从远程计算机程序上请求服 务,而不需要了解底层网络细节的应用程序通信协议。

RPC 采用客户端—服务器(Client/Server)CS的工作模式,请求程序就是一个客户端(Client), 而服务提供程序就是一个服务器(Server)

(1) Go 中 RPC的的支持

net/rpc包允许 RPC 客户端程序通过网络或是其他 I/O 连接调用一个远端对象的公开方法 (必须是大写字母开头、可外部调用的)

RPC 服务端,可将一个对象注册为可访问的服务, 之后该对象的公开方法就能够以远程的方式提供访问。

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

RPC 服务端注册可远程对象的条件

 必须是在对象外部可公开调用的方法(首字母大写);
 必须有两个参数,且参数的类型都必须是包外部可以访问的类型或者是Go内建支持的类
型;
 第二个参数必须是一个指针;
 方法必须返回一个error类型的值

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

类型T、T1 和 T2 默认会使用 Go 内置的 encoding/gob 包进行编码解码
  • 建立链接

在 RPC 客户端,Go 的 net/rpc 包提供了便利的 rpc.Dial()rpc.DialHTTP() 方法 来与指定的 RPC 服务端建立连接

  • 接收处理结果

在建立连接之后,Go 的 net/rpc 包允许我们使用同步或者 异步的方式接收 RPC 服务端的处理结果

调用 RPC 客户端的 Call() 方法则进行同步处理,这 时候客户端程序按顺序执行,只有接收完 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 客户端的 Go() 方法时,则可以进行异步处理,RPC 客户端程序无需等待服务 端的结果即可执行后面的程序,而当接收到 RPC 服务端的处理结果时,再对其进行相应的处理

quotient := new(Quotient)
divCall := client.Go("Arith.Divide", args, &quotient, nil)
replyCall := <-divCall.Done 
  • Gob简介

Gob 是 Go 的一个序列化数据结构的编码解码工具,在 Go 标准库中内置encoding/gob包 以供使用。

Gob 是二进制编码的数据流,并且 Gob 流是可以自解释的, 它在保证高效率的同时,也具备完整的表达能力。

在 Go 的net/rpc包中,传输数据所需要用到的编码解码器,默认就是 Gob

  • RPC 提供的编码解码器接口
type ClientCodec interface {
     WriteRequest(*Request, interface{}) error
     ReadResponseHeader(*Response) error
     ReadResponseBody(interface{}) error
     Close() error
} 


type ServerCodec interface {
     ReadRequestHeader(*Request) error
     ReadRequestBody(interface{}) error
     WriteResponse(*Response, interface{}) error
     Close() error
} 

通过实现上述接口,我们可以自定义数据传输前后的编码解码方式,而不仅仅局限于 Gob。 同样,可以自定义RPC 服务端和客户端的交互行为

net/rpc/json包,就是一套实现了rpc.ClientCodec和rpc.ServerCodec接口的 JSON-RPC 模块

(2)JSON 处理 encoding/json 标准库

JSON (JavaScript Object Notation)是一种比XML更轻量级的数据交换格式,在易于人们阅 读和编写的同时,也易于程序解析和生成。

表现为键/值对集合的文本描述形式

为较为理想的、跨平台跨语言数据交换语言

更多关于JSON

编码为 JSON 格式json.Marshal()

func Marshal(v interface{}) ([]byte, error) 

type Book struct {
     Title string
     Authors []string
     Publisher string
     IsPublished bool
     Price float
} 


gobook := Book{
     "Go语言编程",
     ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan",
     "XuDaoli"],
     "ituring.com.cn",
     true,
     9.99
} 


# 使用  json.Marshal() 编码 

b, err := json.Marshal(gobook) 
 

如果编码成功,err 将赋于零值 nil,变量b 将会是一个进行JSON格式化之后的[]byte类型

Go语言的大多数数据类型都可以转化为有效的JSON文本,但channelcomplex函数这几种类型除外

在Go中,JSON转化前后的数据类型映射如下。

 布尔值转化为JSON后还是布尔类型。

 浮点数和整型会被转化为JSON里边的常规数字。

 字符串将以UTF-8编码转化输出为Unicode字符集的字符串,特殊字符比如<将会被转义为
\u003c。

 数组和切片会转化为JSON里边的数组,但[]byte类型的值将会被转化为 Base64 编码后
的字符串,slice类型的零值会被转化为 null。

 结构体会转化为JSON对象,并且只有结构体里边以大写字母开头的可被导出的字段才会
被转化输出,而这些可导出的字段会作为JSON对象的字符串索引。

 转化一个map类型的数据结构时,该数据的类型必须是 map[string]T(T可以是
encoding/json 包支持的任意数据类型)。

解码JSON 数据 – json.Unmarhsal()

func Unmarshal(data []byte, v interface{}) error 

要解码一段JSON数据,首先需要在Go中创建一个目标类型的实例对象,用于存放解码后 的值


var book Book 

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


book := Book{
 "Go语言编程",
 ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan",
 "XuDaoli"],
 "ituring.com.cn",
 true,
 9.99
} 

解码未知结构的JSON数据

每一个类型其实都至少实现了一个空接口 (即通用类型)

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

 JSON中的布尔值将会转换为Go中的bool类型;
 数值会被转换为Go中的float64类型;
 字符串转换后还是string类型;
 JSON数组会转换为[]interface{}类型;
 JSON对象会转换为map[string]interface{}类型;
 null值会转换为nil。

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) 

gobook, ok := r.(map[string]interface{}) 


json.Unmarshal() 函数将一个JSON对象解码到空接口r中,最终r将会是一个键值对的 map[string]interface{}


map[string]interface{}{
 "Title": "Go语言编程",
 "Authors": ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan",
 "XuDaoli"],
 "Publisher": "ituring.com.cn",
 "IsPublished": true,
 "Price": 9.99,
 "Sales": 1000000
} 

JSON的流式读写

encoding/json 包还提供DecoderEncoder两个类型,用于支持JSON数据的流式读写

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

使用Decoder 和Encoder对数据流进行处理可以应用得更为广泛些,比如读写 HTTP 连接WebSocket或文件

Buy me a 肥仔水!