19、Go语言中的数据输入输出与网络服务开发

Go语言中的数据输入输出与网络服务开发

1. Go语言的数据输入输出

在Go语言中,数据输入输出(Data IO)是编程的重要组成部分,涉及多种操作和格式转换。

1.1 流数据的基本操作

在Go里,流数据操作基于 io.Reader io.Writer 接口。下面是一个简单的示例,将一些书籍名称写入缓冲区并输出到标准输出:

package main

import (
    "bytes"
    "fmt"
    "os"
)

func main() {
    var books bytes.Buffer
    books.WriteString("A Tale of Two Cities")
    books.WriteString("Les Miserables")
    books.WriteString("The Call of the Wild")
    books.WriteTo(os.Stdout)
}

也可以将内容写入普通文件,示例代码如下:

package main

import (
    "bytes"
    "fmt"
    "os"
)

func main() {
    var books bytes.Buffer
    books.WriteString("The Great Gatsby\n")
    books.WriteString("1984\n")
    books.WriteString("A Take of Two Cities\n")
    books.WriteString("Les Miserables\n")
    books.WriteString("The Call of the Wild\n")
    file, err := os.Create("./books.txt")
    if err != nil {
        fmt.Println("Unable to create file:", err)
        return
    }
    defer file.Close()
    books.WriteTo(file)
}
1.2 数据编码与解码

Go语言提供了多种数据编码和解码方式,以满足不同的需求,如数据转换、压缩和加密等。这里主要介绍Gob和JSON两种编码格式。

  • Gob二进制编码 :Gob包(https://golang.org/pkg/encoding/gob )可以将复杂的Go数据类型转换为二进制格式。它是自描述的,每个编码的数据项都附带类型描述。以下是一个将书籍切片编码为Gob格式的示例:
package main

import (
    "encoding/gob"
    "fmt"
    "os"
    "time"
)

type Name struct {
    First, Last string
}

type Book struct {
    Title       string
    PageCount   int
    ISBN        string
    Authors     []Name
    Publisher   string
    PublishDate time.Time
}

func main() {
    books := []Book{
        Book{
            Title:       "Leaning Go",
            PageCount:   375,
            ISBN:        "9781784395438",
            Authors:     []Name{{"Vladimir", "Vivien"}},
            Publisher:   "Packt",
            PublishDate: time.Date(2016, time.July, 0, 0, 0, 0, 0, time.UTC),
        },
        Book{
            Title:       "The Go Programming Language",
            PageCount:   380,
            ISBN:        "9780134190440",
            Authors:     []Name{{"Alan", "Donavan"}, {"Brian", "Kernighan"}},
            Publisher:   "Addison-Wesley",
            PublishDate: time.Date(2015, time.October, 26, 0, 0, 0, 0, time.UTC),
        },
    }
    file, err := os.Create("book.dat")
    if err != nil {
        fmt.Println(err)
        return
    }
    enc := gob.NewEncoder(file)
    if err := enc.Encode(books); err != nil {
        fmt.Println(err)
    }
}

解码Gob数据的示例如下:

package main

import (
    "encoding/gob"
    "fmt"
    "os"
    "time"
)

type Name struct {
    First, Last string
}

type Book struct {
    Title       string
    PageCount   int
    ISBN        string
    Authors     []Name
    Publisher   string
    PublishDate time.Time
}

func main() {
    file, err := os.Open("book.dat")
    if err != nil {
        fmt.Println(err)
        return
    }
    var books []Book
    dec := gob.NewDecoder(file)
    if err := dec.Decode(&books); err != nil {
        fmt.Println(err)
        return
    }
}

需要注意的是,目前Gob编码器和解码器API仅在Go语言中可用,编码为Gob的数据只能由Go程序消费。

  • JSON编码 :Go语言的 encoding/json 子包支持JSON格式的数据。JSON编码生成的是明文格式,便于不同语言之间交换复杂的数据结构。以下是将书籍数据编码为JSON格式的示例:
package main

import (
    "encoding/json"
    "fmt"
    "os"
    "time"
)

type Name struct {
    First, Last string
}

type Book struct {
    Title       string
    PageCount   int
    ISBN        string
    Authors     []Name
    Publisher   string
    PublishDate time.Time
}

func main() {
    books := []Book{
        Book{
            Title:       "Leaning Go",
            PageCount:   375,
            ISBN:        "9781784395438",
            Authors:     []Name{{"Vladimir", "Vivien"}},
            Publisher:   "Packt",
            PublishDate: time.Date(2016, time.July, 0, 0, 0, 0, 0, time.UTC),
        },
    }
    file, err := os.Create("book.dat")
    if err != nil {
        fmt.Println(err)
        return
    }
    enc := json.NewEncoder(file)
    if err := enc.Encode(books); err != nil {
        fmt.Println(err)
    }
}

解码JSON数据的示例如下:

package main

import (
    "encoding/json"
    "fmt"
    "os"
    "time"
)

type Name struct {
    First, Last string
}

type Book struct {
    Title       string
    PageCount   int
    ISBN        string
    Authors     []Name
    Publisher   string
    PublishDate time.Time
}

func main() {
    file, err := os.Open("book.dat")
    if err != nil {
        fmt.Println(err)
        return
    }
    var books []Book
    dec := json.NewDecoder(file)
    if err := dec.Decode(&books); err != nil {
        fmt.Println(err)
        return
    }
}

默认情况下,结构体字段名会作为生成的JSON对象的键名。可以使用结构体标签来控制JSON对象键名的映射,示例如下:

type Book struct {
    Title       string      `json:"book_title"`
    PageCount   int         `json:"pages,string"`
    ISBN        string      `json:"-"`
    Authors     []Name      `json:"auths,omniempty"`
    Publisher   string      `json:",omniempty"`
    PublishDate time.Time   `json:"pub_date"`
}

结构体标签的含义如下表所示:
| 标签 | 描述 |
| ---- | ---- |
| Title string \ json:”book_title”` | 将 Title 结构体字段映射到JSON对象键 “book_title” | | PageCount int `json:”pages,string”` | 将 PageCount 结构体字段映射到JSON对象键 “pages” ,并将值输出为字符串而非数字 | | ISBN string `json:”-“` | 编码和解码时跳过 ISBN 字段 | | Authors []Name `json:”auths,omniempty”` | 将 Authors 字段映射到JSON对象键 “auths” ,若值为 nil 则省略该字段 | | Publisher string `json:”,omniempty”` | 将 Publisher 结构体字段名作为JSON对象键名,若字段为空则省略 | | PublishDate time.Time `json:”pub_date”` | 将 PublishDate 字段映射到JSON对象键 “pub_date”` |

此外,JSON包还提供了 Marshaler Unmarshaler 接口,用于自定义编码和解码过程。以下是实现自定义编码的示例:

package main

import (
    "encoding/json"
    "fmt"
    "os"
    "time"
)

type Name struct {
    First, Last string
}

func (n *Name) MarshalJSON() ([]byte, error) {
    return []byte(fmt.Sprintf("\"%s, %s\"", n.Last, n.First)), nil
}

type Book struct {
    Title       string
    PageCount   int
    ISBN        string
    Authors     []Name
    Publisher   string
    PublishDate time.Time
}

func main() {
    books := []Book{
        Book{
            Title:       "Leaning Go",
            PageCount:   375,
            ISBN:        "9781784395438",
            Authors:     []Name{{"Vladimir", "Vivien"}},
            Publisher:   "Packt",
            PublishDate: time.Date(2016, time.July, 0, 0, 0, 0, 0, time.UTC),
        },
    }
    file, err := os.Create("book.dat")
    if err != nil {
        fmt.Println(err)
        return
    }
    enc := json.NewEncoder(file)
    if err := enc.Encode(books); err != nil {
        fmt.Println(err)
    }
}

实现自定义解码的示例如下:

package main

import (
    "encoding/json"
    "fmt"
    "strings"
)

type Name struct {
    First, Last string
}

func (n *Name) UnmarshalJSON(data []byte) error {
    var name string
    err := json.Unmarshal(data, &name)
    if err != nil {
        fmt.Println(err)
        return err
    }
    parts := strings.Split(name, ", ")
    n.Last, n.First = parts[0], parts[1]
    return nil
}
2. 网络服务开发

Go语言因其对创建网络程序的固有支持而广受欢迎,标准库提供了从底层套接字原语到高级服务抽象(如HTTP和RPC)的一系列API。

2.1 net

net 包(https://golang.org/pkg/net )是Go语言所有网络程序的起点,它提供了丰富的API来处理底层网络原语和应用层协议(如HTTP)。网络的每个逻辑组件都由Go类型表示,并且每个类型都提供了大量的方法,支持IPv4和IPv6。

  • 地址表示 net 包使用字符串字面量来表示地址,如 "127.0.0.1" ,地址还可以包含服务端口,用冒号分隔,如 "74.125.21.113:80" 。同时,也支持IPv6地址的字符串字面量表示,如 "::1" "[2607:f8b0:4002:c06::65]:80"

  • net.Conn 类型 net.Conn 接口表示网络中两个节点之间建立的通用连接,它实现了 io.Reader io.Writer 接口,允许连接的节点使用流数据原语交换数据。 net 包提供了特定网络协议的 net.Conn 接口实现,如 IPConn UDPConn TCPConn

  • 建立连接 :客户端程序使用 net.Dial 函数来连接到网络上的主机服务,函数签名如下:

func Dial(network, address string) (Conn, error)

其中, network 参数指定连接的网络协议,可选值包括 tcp tcp4 tcp6 udp udp4 udp6 ip ip4 ip6 unix unixgram unixpacket 等; address 参数指定要连接的主机地址。以下是一个使用 net.Dial 函数连接到HTTP服务器并获取数据的示例:

package main

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

func main() {
    host, port := "www.gutenberg.org", "80"
    addr := net.JoinHostPort(host, port)
    httpRequest := "GET  /cache/epub/16328/pg16328.txt HTTP/1.1\n" +
        "Host: " + host + "\n\n"
    conn, err := net.Dial("tcp", addr)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer conn.Close()
    if _, err = conn.Write([]byte(httpRequest)); err != nil {
        fmt.Println(err)
        return
    }
    file, err := os.Create("beowulf.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer file.Close()
    io.Copy(file, conn)
    fmt.Println("Text copied to file", file.Name())
}
  • 监听连接 :创建服务程序时,第一步通常是宣布服务将使用的端口来监听网络中的传入请求。可以使用 net.Listen 函数来实现,函数签名如下:
func Listen(network, laddr string) (net.Listener, error)

network 参数指定协议,有效值包括 "tcp" "tcp4" "tcp6" "unix" "unixpacket" laddr 参数是服务的本地主机地址。成功调用 net.Listen 函数将返回一个 net.Listener 类型的值,根据 network 参数的值,可能返回 net.TCPListener net.UnixListener

以下是一个简单的网络服务开发流程的mermaid流程图:

graph LR
    A[开始] --> B[选择网络协议]
    B --> C{客户端还是服务器}
    C -- 客户端 --> D[使用net.Dial建立连接]
    C -- 服务器 --> E[使用net.Listen监听连接]
    D --> F[发送和接收数据]
    E --> G[接受客户端连接]
    G --> F
    F --> H[关闭连接]
    H --> I[结束]

综上所述,Go语言在数据输入输出和网络服务开发方面提供了丰富的功能和灵活的API,开发者可以根据具体需求选择合适的方法来实现自己的程序。

2.2 TCP API 服务器

TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在Go里可以借助 net 包创建TCP API服务器。

下面是一个简单的TCP API服务器的示例代码:

package main

import (
    "bufio"
    "fmt"
    "log"
    "net"
)

func handleConnection(conn net.Conn) {
    defer conn.Close()
    reader := bufio.NewReader(conn)
    for {
        message, err := reader.ReadString('\n')
        if err != nil {
            log.Println("Error reading:", err)
            return
        }
        fmt.Print("Received message: ", message)
        _, err = conn.Write([]byte("Message received\n"))
        if err != nil {
            log.Println("Error writing:", err)
            return
        }
    }
}

func main() {
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal("Error listening:", err)
    }
    defer listener.Close()
    fmt.Println("Server listening on port 8080")
    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Println("Error accepting connection:", err)
            continue
        }
        go handleConnection(conn)
    }
}

代码解释如下:
1. main 函数
- 运用 net.Listen 函数在TCP协议的8080端口监听连接。
- 借助 for 循环持续接受客户端的连接请求。
- 为每个客户端连接开启一个新的 goroutine 来处理,避免阻塞其他连接。
2. handleConnection 函数
- 利用 bufio.NewReader 读取客户端发送的消息。
- 把接收到的消息打印出来,并且向客户端发送确认消息。

以下是TCP API服务器处理连接的流程mermaid流程图:

graph LR
    A[开始] --> B[监听端口]
    B --> C{是否有新连接}
    C -- 是 --> D[接受连接]
    C -- 否 --> C
    D --> E[开启新goroutine处理连接]
    E --> F[读取客户端消息]
    F --> G[处理消息]
    G --> H[发送响应给客户端]
    H --> I{是否继续接收消息}
    I -- 是 --> F
    I -- 否 --> J[关闭连接]
    J --> C
2.3 HTTP 包

Go语言的 net/http 包提供了HTTP客户端和服务器的实现,能够轻松创建HTTP服务。

下面是一个简单的HTTP服务器示例:

package main

import (
    "fmt"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    http.HandleFunc("/", helloHandler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

代码解释如下:
1. helloHandler 函数
- 此函数是一个HTTP处理函数,接收 http.ResponseWriter *http.Request 作为参数。
- 借助 fmt.Fprintf 函数向客户端发送 Hello, World! 消息。
2. main 函数
- 利用 http.HandleFunc 函数将 / 路径映射到 helloHandler 处理函数。
- 运用 http.ListenAndServe 函数在8080端口启动HTTP服务器。

HTTP服务器处理请求的流程如下表所示:
| 步骤 | 描述 |
| ---- | ---- |
| 1 | 服务器监听指定端口 |
| 2 | 接收到客户端请求 |
| 3 | 依据请求的路径找到对应的处理函数 |
| 4 | 执行处理函数,生成响应 |
| 5 | 将响应发送给客户端 |

2.4 JSON API 服务器

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,在Web开发中应用广泛。可以结合 net/http 包和 encoding/json 包创建JSON API服务器。

下面是一个简单的JSON API服务器示例:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
)

type Book struct {
    Title  string `json:"title"`
    Author string `json:"author"`
}

func booksHandler(w http.ResponseWriter, r *http.Request) {
    books := []Book{
        {Title: "Go Programming", Author: "John Doe"},
        {Title: "Web Development", Author: "Jane Smith"},
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(books)
}

func main() {
    http.HandleFunc("/books", booksHandler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

代码解释如下:
1. Book 结构体
- 定义了书籍的结构体,包含 Title Author 两个字段。
- 运用 json 标签指定JSON对象的键名。
2. booksHandler 函数
- 创建一个书籍切片。
- 设置响应的 Content-Type application/json
- 利用 json.NewEncoder 将书籍切片编码为JSON格式并发送给客户端。
3. main 函数
- 把 /books 路径映射到 booksHandler 处理函数。
- 在8080端口启动HTTP服务器。

JSON API服务器处理请求的流程mermaid流程图如下:

graph LR
    A[开始] --> B[监听端口]
    B --> C{是否有新请求}
    C -- 是 --> D[接收请求]
    C -- 否 --> C
    D --> E{请求路径是否为/books}
    E -- 是 --> F[生成书籍数据]
    E -- 否 --> G[返回404错误]
    F --> H[将数据编码为JSON格式]
    H --> I[设置响应头]
    I --> J[发送响应给客户端]
    J --> C

总之,Go语言在网络服务开发方面提供了强大且丰富的功能。无论是底层的TCP编程,还是高层的HTTP和JSON API开发,都能借助标准库轻松实现。开发者可以根据具体需求,灵活运用这些功能来构建高效、稳定的网络服务。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值