socket编程

传统的socket编程步骤:
(1) 建立Socket:使用socket()函数。
(2) 绑定Socket:使用bind()函数。
(3) 监听:使用listen()函数。或者连接:使用connect()函数。
(4) 接受连接:使用accept()函数。
(5) 接收:使用recv()函数。或者发送:使用send()函数。
Go语言标准库对此过程进行了抽象和封装。无论我们期望使用什么协议建立什么形式的连接,都只需要调用net.Dial()即可。

客户端

  1. //Dial()函数原型
  2. func Dial(net, addr string) (Conn, error)

其中net参数是网络协议的名字,addr参数是IP地址或域名,而端口号以“:”的形式跟随在地址或域名的后面,端口号可选。如果连接成功,返回连接对象,否则返回error。

  1. //示例:
  2. conn, err := net.Dial("tcp4", "127.0.0.1:16666")
  3. if err != nil {
  4. fmt.Println("Dial error: ", err)
  5. }

实际上,Dial()函数是对DialTCP()、DialUDP()、DialIP()和DialUnix()的封装。我们也可以直接调用这些函数,它们的功能是一致的。这些函数的原型如下:

  1. func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err error)
  2. func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err error)
  3. func DialIP(netProto string, laddr, raddr *IPAddr) (*IPConn, error)
  4. func DialUnix(net string, laddr, raddr *UnixAddr) (c *UnixConn, err error)

服务端

  1. addr, err := net.ResolveTCPAddr("tcp4", "127.0.0.1:16666")
  2. if err != nil {
  3. fmt.Println("ResolveTCPAddr err: ", err)
  4. }
  5. listen, err := net.ListenTCP("tcp", addr)
  6. if err != nil {
  7. fmt.Println("ListenTCP err: ", err)
  8. }
  9. conn, err := listen.AcceptTCP()
  10. if err != nil {
  11. fmt.Println("AcceptTCP err: ", err)
  12. }

HTTP编程

net/http包的Client类型提供了如下几个方法,让我们可以用最简洁的方式实现 HTTP 请求:

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

使用 net/http 包提供的 http.ListenAndServe() 方法,可以在指定的地址进行监听,开启一个HTTP,服务端该方法的原型如下:

  1. func ListenAndServe(addr string, handler Handler) error

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

  1. http.Handle("/foo", fooHandler)
  2. http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
  3. fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
  4. })
  5. log.Fatal(http.ListenAndServe(":8080", nil))
  6. //如果想更多地控制服务端的行为,可以自定义 http.Server,代码如下:
  7. s := &http.Server{
  8. Addr: ":8080",
  9. Handler: myHandler,
  10. ReadTimeout: 10 * time.Second,
  11. WriteTimeout: 10 * time.Second,
  12. MaxHeaderBytes: 1 << 20,
  13. }
  14. log.Fatal(s.ListenAndServe())
  15. // 这就是用Go实现的一个最简短的hello world服务器.
  16. package main
  17. import "net/http"
  18. func main() {
  19. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  20. w.Write([]byte(`hello world`))
  21. })
  22. http.ListenAndServe(":3000", nil) // <-今天讲的就是这个ListenAndServe是如何工作的
  23. }
  24. //net/http 包还提供 http.ListenAndServeTLS() 方法,用于处理 HTTPS 连接请求:
  25. func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error

ListenAndServeTLS() 和 ListenAndServe()的行为一致,区别在于只处理HTTPS请求。此外,服务器上必须存在包含证书和与之匹配的私钥的相关文件,如certFile对应SSL证书文件存放路径,keyFile对应证书私钥文件路径。如果证书是由证书颁发机构签署的,certFile参数指定的路径必须是存放在服务器上的经由CA认证过的SSL证书。

  1. //开启 SSL 监听服务也很简单,如下列代码所示:
  2. http.Handle("/foo", fooHandler)
  3. http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
  4. fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
  5. })
  6. log.Fatal(http.ListenAndServeTLS(":10443", "cert.pem", "key.pem", nil))
  7. 或者是:
  8. ss := &http.Server{
  9. Addr: ":10443",
  10. Handler: myHandler,
  11. ReadTimeout: 10 * time.Second,
  12. WriteTimeout: 10 * time.Second,
  13. MaxHeaderBytes: 1 << 20,
  14. }
  15. log.Fatal(ss.ListenAndServeTLS("cert.pem", "key.pem"))

RPC编程

RPC(Remote Procedure Call,远程过程调用)是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络细节的应用程序通信协议。RPC协议构建于TCP或UDP,或者是 HTTP 之上,允许开发者直接调用另一台计算机上的程序,而开发者无需额外地为这个调用过程编写网络通信相关代码,使得开发包括网络分布式程序在内的应用程序更加容易。
RPC 采用客户端—服务器(Client/Server)的工作模式。请求程序就是一个客户端(Client),而服务提供程序就是一个服务器(Server)。当执行一个远程过程调用时,客户端程序首先发送一个带有参数的调用信息到服务端,然后等待服务端响应。在服务端,服务进程保持睡眠状态直到客户端的调用信息到达为止。当一个调用信息到达时,服务端获得进程参数,计算出结果,并向客户端发送应答信息,然后等待下一个调用。最后,客户端接收来自服务端的应答信息,获得进程结果,然后调用执行并继续进行。
net/rpc包允许 RPC 客户端程序通过网络或是其他 I/O 连接调用一个远端对象的公开方法(必须是大写字母开头、可外部调用的)。在 RPC 服务端,可将一个对象注册为可访问的服务,之后该对象的公开方法就能够以远程的方式提供访问。一个 RPC 服务端可以注册多个不同类型的对象,但不允许注册同一类型的多个对象。
一个对象中只有满足如下这些条件的方法,才能被 RPC 服务端设置为可供远程访问:

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

以上4个条件,可以简单地用如下一行代码表示:
func (t T) MethodName(argType T1, replyType T2) error
在上面这行代码中,类型T、T1 和 T2 默认会使用 Go 内置的 encoding/gob 包进行编码解码。该方法的第一个参数表示由 RPC 客户端传入的参数,第二个参数表示要返回给RPC客户端的结果,该方法最后返回一个 error 类型的值。

  1. // 服务器端
  2. package server
  3. type Args struct {
  4. A, B int
  5. }
  6. type Quotient struct {
  7. Quo, Rem int
  8. }
  9. type Arith int
  10. func (t *Arith) Multiply(args *Args, reply *int) error {
  11. *reply = args.A * args.B
  12. return nil
  13. }
  14. func (t *Arith) Divide(args *Args, quo *Quotient) error {
  15. if args.B == 0 {
  16. return errors.New("divide by zero")
  17. }
  18. quo.Quo = args.A / args.B
  19. quo.Rem = args.A % args.B
  20. return nil
  21. }
  22. func main() {
  23. arith := new(Arith) //服务内容
  24. rpc.Register(arith) //注册服务
  25. rpc.HandleHTTP() //开启服务
  26. l, e := net.Listen("tcp", ":1234") //端口监听
  27. if e != nil {
  28. log.Fatal("listen error:", e)
  29. }
  30. go http.Serve(l, nil) //启动HTTP服务
  31. }
  32. // 客户端
  33. package client
  34. func main() {
  35. client, err := rpc.DialHTTP("tcp", serverAddress + ":1234") //连接服务RPC
  36. if err != nil {
  37. log.Fatal("dialing:", err)
  38. }
  39. 然后,客户端可以执行远程调用:
  40. // Synchronous call
  41. args := &server.Args{7,8}
  42. var reply int
  43. err = client.Call("Arith.Multiply", args, &reply) //同步调用RPC服务
  44. if err != nil {
  45. log.Fatal("arith error:", err)
  46. }
  47. fmt.Printf("Arith: %d*%d=%d", args.A, args.B, reply)
  48. 或:
  49. // Asynchronous call
  50. quotient := new(Quotient)
  51. divCall := client.Go("Arith.Divide", args, quotient, nil) //异步调用RPC服务
  52. replyCall := <-divCall.Done // will be equal to divCall
  53. // check errors, print, etc.

JSON

Go语言内建对JSON的支持。使用Go语言内置的encoding/json 标准库。

  1. //使用json.Marshal()函数可以对一组数据进行JSON格式的编码。json.Marshal()函数的声明如下:
  2. func Marshal(v interface{}) ([]byte, error)
  3. //假如有如下一个Book类型的结构体:
  4. type Book struct {
  5. Title string
  6. Authors []string
  7. Publisher string
  8. IsPublished bool
  9. Price float
  10. }
  11. //并且有如下一个 Book 类型的实例对象:
  12. gobook := Book{
  13. "Go语言编程",
  14. ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan",
  15. "XuDaoli"],
  16. "ituring.com.cn",
  17. true,
  18. 9.99
  19. }
  20. //然后,我们可以使用 json.Marshal() 函数将gobook实例生成一段JSON格式的文本:
  21. b, err := json.Marshal(gobook)
  22. //如果编码成功,err 将赋于零值 nil,变量b 将会是一个进行JSON格式化之后的[]byte 类型:
  23. b == []byte(`{
  24. "Title": "Go语言编程",
  25. "Authors": ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan",
  26. "XuDaoli"],
  27. "Publisher": "ituring.com.cn",
  28. "IsPublished": true,
  29. "Price": 9.99
  30. }`)

当调用json.Marshal(gobook)语句时,会递归遍历gobook对象,如果发现gobook这个数据结构实现了json.Marshaler接口且包含有效的值,Marshal()就会调用其MarshalJSON()方法将该数据结构生成 JSON 格式的文本。
Go语言的大多数数据类型都可以转化为有效的JSON文本,但channel、complex和函数这几种类型除外。
如果转化前的数据结构中出现指针,那么将会转化指针所指向的值,如果指针指向的是零值,那么null将作为转化后的结果输出。
在Go中,JSON转化前后的数据类型映射如下。

  • 布尔值转化为JSON后还是布尔类型。
  • 浮点数和整型会被转化为JSON里边的常规数字。
  • 字符串将以UTF-8编码转化输出为Unicode字符集的字符串,特殊字符比如<将会被转义为\u003c。
  • 数组和切片会转化为JSON里边的数组,但[]byte类型的值将会被转化为 Base64 编码后的字符串,slice类型的零值会被转化为 null。
  • 结构体会转化为JSON对象,并且只有结构体里边以大写字母开头的可被导出的字段才会被转化输出,而这些可导出的字段会作为JSON对象的字符串索引。
  • 转化一个map类型的数据结构时,该数据的类型必须是 map[string]T(T可以是encoding/json 包支持的任意数据类型)。
    1. //可以使用json.Unmarshal()函数将JSON格式的文本解码为Go里边预期的数据结构。json.Unmarshal()函数的原型如下:
    2. func Unmarshal(data []byte, v interface{}) error
    3. //该函数的第一个参数是输入,即JSON格式的文本(比特序列),第二个参数表示目标输出容器,用于存放解码后的值。要解码一段JSON数据,首先需要在Go中创建一个目标类型的实例对象,用于存放解码后的值:
    4. var book Book
    5. //然后调用 json.Unmarshal() 函数,将 []byte 类型的JSON数据作为第一个参数传入,将book实例变量的指针作为第二个参数传入:
    6. err := json.Unmarshal(b, &book)
    7. //如果 b 是一个有效的JSON数据并能和 book 结构对应起来,那么JSON解码后的值将会一一存放到book结构体中。解码成功后的 book 数据如下:
    8. book := Book{
    9. "Go语言编程",
    10. ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan",
    11. "XuDaoli"],
    12. "ituring.com.cn",
    13. true,
    14. 9.99
    15. }