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")
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, "ient, 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文本
,但channel
、complex
和函数
这几种类型除外
。
在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
包还提供Decoder
和Encoder
两个类型,用于支持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或文件
等