客户端与服务之间通过数据交换来通信。因为数据可能是高度结构化的,所以在传输前必须进行序列化。这一章将研究序列化基础并介绍一些 Go API 提供的序列化技术。
简介
客户端与服务器需要通过消息来交换信息。TCP 与 UDP 是消息传递的两种机制,在这两种机制之上就需要有合适的协议来约定传输的内容的含义。
在网络上,消息被当作字节序列来传输,它们是没有结构的,仅仅只是一串字节流。我们将在下一章讨论定义消息与协议涉及到的的各种问题。本章,我们只重点关注消息的一个方面- 被传输的数据
程序通常构造一个复杂的数据结构来保存其自身当前的状态。在与远程的客户端或服务的交互中,程序会通过网络将这样的数据结构传输到 -应用程序所在的地址空间之外的地方
编程语言使用的结构化的数据类型有
- 记录/结构
- 可变记录
- 数组 - 固定大小或可变大小
- 字符串 - 固定大小或可变大小
- 表 - 例如:记录构成的数组
- 非线程结构,比如
o 循环链表
o 二叉树
o 含有其他对象引用的对象
IP,TCP 或者 UDP 网络包并不知道这些数据类型的含义,它们只是字节序列的载体。因此,写入网络包
的时候,应用需要将要传输的(有类型的)数据 序列化
成字节流,反之,读取网络包
的时候,应用需要将字节流反序列化
成合适的数据结构,这两个操作被分别称为编组
和解组
。
例如:考虑发送如下这样一个由两列可变长度字符串构成的可变长度的表格
fred | programmer |
---|---|
liping | analyst |
sureerat | manager |
这可以通过多种方式来完成。比如:假设知道数据是一个未知行数的两列表格,那么编组形式可能是:
可变长度的事物都可以通过用一个
“非法”的终结值
,比如对于字符串来说的'\0',来间接获得它们的长度
3
fred\0
programmer\0
liping\0
analyst\0
sureerat\0
manager\0
假设知道数据是一个三行两列且每列长度分别是 8 或 10 的表格,那么序列化的结果可能是:
这些格式中的任意一种都是可行的 - 但是消息交换协议必须指定使用哪一种(格式),或者约定在运行期再做决定
交互协议
前一小节总结了在数据序列化过程中可能遇到的各种问题。而在实际操作中,需要考虑的细节还更多一些,例如:先考虑下面这个问题,如何将下面这个表编组成流
3
4 fred
10 programmer
6 liping
7 analyst
8 sureerat
7 manager
许多问题冒出来了。例如:这个表格可能有多少行?- 即我们需要多大的整数来表示表格的大小,如果它只有 255 行或者更少,那么一个字节就够了,如果更大一些,就可能需要 short,integer 或者 long 来表示了。对于字符串的长度也存在同样的问题,对字符本身来说,它们属于哪种字符集? 7 位的 ASCII?16 位的 Unicode?字符集的问题将会在后面的章节里详细讨论。
上面的序列化是不透明的
或者被称为隐式的
,如果采用这种格式来编组数据,那么序列化后的数据中没有包含任何指示它应该被如何解组的信息。为了正确的解组,解组的一端需要精确的知晓编组的方式。如果数据的行数以 8 位整型数的方式编组,却以 16 位整型的方式解组,那么接收者将得到错误的解码结果。比如接受者尝试将 3 与 4 当作 16 位整型解组,在后续的程序运行的时候肯定会失败。
自描述数据
自描述数据在最终的结果数据中附带了类型信息,例如,前面提到的数据可能被编码为:
table
uint8 3
uint 2
string
uint8 4
[]byte fred
string
uint8 10
[]byte programmer
string
uint8 6
[]byte liping
string
uint8 7
[]byte analyst
string
uint8 8
[]byte sureerat
string
uint8 7
[]byte manager
当然,实际使用的编码方式不会如此啰嗦。小整数可能被用作类型标记,并且整个数据编码后的字节数组会尽量的小(XML 是一个反例)。原则就是编组器会在序列化后的数据中包含类型信息。解组器知道类型生成的规则,并使用此规则重组出正确的数据结构。
抽象语法表示法
抽象语法表示法/1(ASN.1)最初出现在 1984 年,它是一个为电信行业设计的复杂标准,Go的标准包 asn1
实现了它的一个子集,它可以将复杂的数据结构序列化成自描述的数据。在当前的网络系统中,它主要用于对认证系统中普遍使用的 X.509 证书的编码。Go 对 ASN.1的支持主要是 X.509 证书的读写上。
以下两个函数用以对数据的编、解组
func Marshal(val interface{}) ([]byte, os.Error)
func Unmarshal(val interface{}, b []byte) (rest []byte, err os.Error)
前一个将数据值编组成序列化的字节数组,后一个将其解组出来,需要对 interface
类型的参数进行更多的类型检查。编组时,我们只需要传递某个类型的变量的值即可,解组它,则需要一个与被序列化过的数据匹配的确定类型的变量,我们将在后面讨论这部分的细节。除了有确定类型的变量外,我们同时需要保证那个变量的内存已经被分配,以使被解组后的数据能有实际被写入的地址。
我们将举一个整数编、解组的小例子。在这个例子中。我们先将一个整数传递给 Marshal
得到一个字节数组,然后又将此数组解组成一个整数。
/*ASN.1*/
package main
import (
"encoding/asn1"
"fmt"
"os"
)
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error:%s", err.Error())
os.Exit(1)
}
}
func main() {
mdata,err := asn1.Marshal(13)
checkError(err)
var n int
_,err = asn1.Unmarshal(mdata, &n)
checkError(err)
fmt.Println("After marshal/unmarshal:", n)
}
当然,被解组后的值,是 13After marshal/unmarshal: 13
一旦我们越过了这个小关卡,事情开始变得复杂。为了管理更复杂的数据类型,我们需要更深入的了解 ASN.1 支持的数据类型,以及 Go 是如何支持 ASN.1 的。
任何序列化方法都只能处理某些数据类型,而对其他的数据类型无能为力。因此为了评估类似 ASN.1 等序列化方案的可行性,你必须先将要在程序中使用的数据类型与它们支持的数据类型做个比较,下面是 ASN.1 支持的数据类型,它们来自于http://www.obj-sys.com/asn1tutorial/node4.html
简单数据类型有
:
- BOOLEAN:两态变量值
- INTEGER:表征整型变量值
- BIT STRING:表征任意长度的二进制数据
- OCT STRING:表征长度是 8 的倍数的二进制数据
- NULL:指示一个没有有效数据的序列
- OBJECT IDENTIFIER:命名信息对象
- REAL:表征一个 real 变量值
- ENUMERATED:表征一个至少有三个状态的变量值
- CHARACTER STRING:表征一个字符串值
字符串可以来自于确定的字符集
- NumericString: 0,1,2,3,4,5,6,7,8,9, 与空格(space)
- PrintableString: 大、小写字母,数字,空格,省略号,左、右小括号,加号,逗号,连字符,句号,斜线,冒号,等号,问号
- TeletexString(T61String): CCITT 的 Teletex 字符集中的 T61,空格和删除(delete)
- VideotexString:CCITT 的 Videotex 字符集中的 T.100 与 T.101, 空格和删除(delete)
- VisibleString (ISO646String):国际 ASCII 中的打印字符集和空格
- IA5String:国际字母表 5(国际 ASCII)
- GraphicString:所有被注册的 G 集和空格
最后,以下是结构化的类型:
- SEQUENCE:表征不同类型变量构成的有序集合
- SEQUENCE OF: 表征相同类型的变量构成的有序集合
- SET: 表征不同类型的变量构成的无序集合
- SET OF:表征相同类型的变量构成的有序集合
- CHOICE:从一个不同类型构成的特定集合中选出一个类型
- SELECTION: 从一个特定的 CHOICE 类型中选取一个组件类型
- ANY:启用一个用以指定类型的应用. 注意:ANY 是一个弃用的 ASN.1 结构类型,它被
x.680 的 Open Type 所替代
不是以上所有的类型、可能的值都被 Go 支持,在 Go 'asn1'包文档中定义的规则如下:
Go 在实现上,为 ASN.1 添加了一些约束。例如 ASN.1 允许任意大小的整数,而GO 只允许最大为 64 位有符号整数能表示的值.另一方面,Go 区分有符号类型与无符号类型,而在 ASN.1则没有分别.因此传递一个大于 int64 最大值能表示的 uint64 的值,则可能会失败。
同理,ASN.1 允许多个不同的字符集,而 Go 只支持
PrintableString
和IA5String(ASCII)
. ASN.1不支持 Unicode 字符(它需要 BMPString ASN.1 扩展),连 Go 中的基本 Unicode 字符集它都不支持,如果应用程序需要传输 Unicode 字符,则可能需要类似 UTF-7 的编码。有关编码的内容将会在后边字符集相关的章节来讨论。我们已经看到,整型的值很容易被编、解组。类似的 boolean 与 real 等基本类型处理手法也类似。由 ASCII 字符构成的字符串也很容易。但当处理 "hello \u00bc"这种含有 '¼'这个非ASCII 字符的字符串,则会出现错误:“ASN.1 结构错误:PrintableString 包含非法字符”。以下的代码仅在处理由可打印字符(printable characters)构成的字符串时,工作良好。
s := "hello"
mdata,_ := asn1.Marshal(s)
var newstr string
asn1.Unmarshal(mdata, &newstr)
ASN.1 还包含一些未在上边列表中出现的“有用的类型(useful types)”, 比如 UTC 时间类型
,GO 支持此 UTC 时间类型。就是说你可以用一种特有的类型来传递时间值。ASN.1 不支持指针,Go 中却有指向时间值的指针。比如函数 GetLocalTime 返回*time.Time。asn1 包编组这个 time 结构,也使用这个包解组到一个 time.Time 对象指针中。代码如下
t := time.LocalTime()
mdata,err := asn1.Marshal(t)
var newtime = new(time.Time)
_,err1 := asn1.Unmarshal(&newtime, mdata)
LocalTime
与 new
函数都返回的是time.Time 类型的指针,GO 将内部对这些特殊类型进行处理*。
除了 time 这种特殊情况外,你可能要编、解组结构类型
。除了上面提到的 Time 结构外,其他的结构 Go 还是很好处理的。类以 new 的操作将会创建指针,因此在编、解组之前,你需要解引用它。通常,Go 会随需自动对指针进行解引用,但是下面这个例子并不是这么个情况。对于类型 T,以下两种方式均可
//using variables
var t1 T
t1 = ...
madata1,_ := asn1.Marshal(t)
var newT1 T
asn1.Unmarshal(&newT1, madata1)
//using pointers
var t2 = new(T)
*t2 = ...
mdata2,_ := asn1.Marshal(*t2)
var newT2 = new(T)
asn1.Unmarshal(newT2, mdata2)
恰当地的使用指针与变量能让代码工作得更好。
结构的所有字段必须是公共的,即字段名必须以大写字母开头。Go 内部实际是使用 reflect包
来编、解组结构,因此 reflect 包必须能访问所有的字段。比如下面这个类型是不能被编组的:
type T struct {
Field1 int
field2 int //not exportable
}
ASN.1 只处理数据类型,它并不关心结构字段的名字。因此只要对应的字段类型相同那么下面的 T1 类型将可以被解、解组到 T2 类型中。
type T1 struct {
F1 int
F2 string
}
type T2 {
FF1 int
FF2 string
}
不仅每个字段的类型必须匹配,而且字段数目也要相等,下面两个类型将不能互编、解码:
type T1 struct {
F1 int
}
type T2 {
F1 int
F2 string //too many fields
}
ASN.1 日期查询服务客户端与服务器
现在(最后)让我们使用 ASN.1 来跨网络传输数据
我们可以使用上一章的技术来编写一个将当前时间作为 ASN.Time 类型时间来传送的 TCP服务器。服务器是:
/*ASN1 DaytimeServer*/
package main
import (
"encoding/asn1"
"fmt"
"net"
"os"
"time"
)
func main() {
service := ":1200"
tcpAddr,err := net.ResolveTCPAddr("tcp", service)
checkError(err)
listener,err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
for {
conn,err := listener.Accept()
if err != nil {
continue
}
daytime := time.Now()
//Ignore return network errors
mdata,_ := asn1.Marshal(daytime)
conn.Write(mdata)
conn.Close() //we're finished
}
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error:%s", err.Error())
os.Exit(1)
}
}
它可以被编译为一个诸如名为 ASN1DaytimeServer
的可执行程序,运行它不需要任何实际参数,(启动后)它将等待来自客户端的连接,当有新连接后它会将当前时间当作 ASN.1 字符串传回给客户端.
客户端代码是
/*ASN1 DaytimeClient*/
package main
import (
"bytes"
"encoding/asn1"
"fmt"
"io"
"net"
"os"
"time"
)
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)
result,err := readFully(conn)
checkError(err)
var newtime time.Time
_,err1 := asn1.Unmarshal(result, &newtime)
checkError(err1)
fmt.Println("After marshal/unmarshal:", newtime.String())
}
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
}
连接字符串形如:localhost:1200
。它将读取应答 TCP 包然后将 ASN.1 内容解码成字符串并输出。
我们应当注意,无论是客户端还是服务器都不兼容前一章介绍的基于文本的客户端与服务器。此地的客户端与服务器交换的是 ASN.1 编码的数据值,而非文本串。
JSON
JSON
全称是 JavaScript Object Notation
,它是一种应用于 JavaScript系统之间传递数据的轻量级格式。它使用基于文本的格式,因为足够通用,现在已经成为了多种编程语言采用的通用的序列化方法了。
JSON 序列化对象
,数组
和基本值
。基本值包括:字符串,数字,布尔值和 NULL 值。数组是逗号分割的一组值的列表,可以用来表示各种编程语言中的数组、向量、列表或者序列。它们由方括号来界定,对象则由一个包含在大括号中的"field: values"
对构成的列表来表示。
例如.前面提到过的雇员表可以被编码成如下的一个雇员对象的数组.
[
{Name: fred, Occupation: programmer},
{Name: liping, Occupation: analyst},
{Name: sureerat, Occupation: manager}
]
JSON
没有为类似日期这这样的复杂数据类型提供特别的格式支持,不区分各种数字类型,也没有递归类型等。JSON 是一个非常简单但却十分有用的语言,尽管他基于文本的格式在字符传递上开销过多,但是却很适合人类阅读和使用。
从 Go JSON 包
的规范文档可知,JSON 包将在编组
时使用以下类型相关的默认编码方法:
- 布尔值被编码为 JSON 的布尔值。
- 浮点数与整数被编码为 JSON 的数字值。
- 字符串被编码为 JSON 的字符串,每一个非法的 UTF-8 序列将会被 UTF8 替换符
U+FFFD 替换。 - 数组与 Slice 会被编码为 JSON 数组,但是[]byte 是会被编码为 base64 字符串。
- 结构体被编码为 JSON 对象。每一个结构体字段被编码为此对象的对应成员,默认
情况下对象的 key 的名字是对应结构体字段名的小写。如果此字段含有 tag,则此 tag将是最终对象 key 的名字。 - map 值被编码为 JSON 对象,此 map 的 key 的类型必须是 string;map 的 key 直接被当作 JSON 对象的 key。
- 指针值被编码为指针所指向的值(注意:此处只允许出现树(tree),而不允许出现图
(graph)!)。空指针被编码为空 JSON 对象. - 接口值被编码为接口实际包含的值。空接口被编码为空 JSON 对象。
- 程道,复数,函数不能被编码为 JSON 格式。如果尝试这样做,Marshal 将会返回一个 InvalidTypeError 错误。
- JSON 不能表示环形数据结构。Go 的 Marshal 函数也不处理它们,将一个环形结构传递给 Marshal 将会导致死循环。
将 JSON 数据存入文件的示例如下:
/*SaveJSON*/
package main
import (
"encoding/json"
"fmt"
"os"
)
type Person struct {
Name Name
Email []Email
}
type Name struct {
Family string
Personal string
}
type Email struct {
Kind string
Address string
}
func main() {
person := Person{
Name:Name{Family:"Newmarch", Personal:"Jan"},
Email:[]Email{Email{Kind: "home", Address: "jan@newmarch.name"},
Email{Kind: "work", Address: "j.newmarch@boxhill.edu.au"}}}
saveJSON("person.json", person)
}
func saveJSON(fileName string, key interface{}) {
outFile, err :=os.Create(fileName)
checkError(err)
encoder := json.NewEncoder(outFile)
err = encoder.Encode(key)
checkError(err)
outFile.Close()
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error:%s", err.Error())
os.Exit(1)
}
}
可以这样将之重新加载到内存中:
/*LoadJSON*/
package main
import (
"encoding/json"
"fmt"
"os"
)
type Person struct {
Name Name
Email []Email
}
type Name struct {
Family string
Personal string
}
type Email struct {
Kind string
Address string
}
func(p Person) String() string {
s := p.Name.Personal+ " "+ p.Name.Family
for _, v := range p.Email{
s+= "\n"+ v.Kind+ ": "+ v.Address
}
return s
}
func main() {
var person Person
loadJSON("person.json", &person)
fmt.Println("Person", person.String())
}
func loadJSON(fileName string, key interface{}) {
inFile, err := os.Open(fileName)
checkError(err)
decoder := json.NewDecoder(inFile)
err = decoder.Decode(key)
checkError(err)
inFile.Close()
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error:%s", err.Error())
os.Exit(1)
}
}
被序列化后的结果如:(经过了美化处理)
Person Jan Newmarch
home: jan@newmarch.name
work: j.newmarch@boxhill.edu.au
客户端与服务器
一个将 person 数据收发 10 次的客户端
/** JSON EchoClient*/
package main
import (
"encoding/json"
"fmt"
"net"
"os"
"bytes"
"io"
)
type Person struct {
Name Name
Email []Email
}
type Name struct {
Family string
Personal string
}
type Email struct {
Kind string
Address string
}
func(p Person) String() string {
s := p.Name.Personal+ " "+ p.Name.Family
for _, v := range p.Email{
s+= "\n"+ v.Kind+ ": "+ v.Address
}
return s
}
func main() {
person := person := Person{
Name: Name{Family: "Newmarch", Personal: "Jan"},
Email: []Email{Email{Kind: "home", Address: "jan@newmarch.name"},
Email{Kind: "work", Address: "j.newmarch@boxhill.edu.au"}}}
if len(os.Args) != 2 {
fmt.Println("Usage: ", os.Args[0], "host:port")
os.Exit(1)
}
service := os.Args[1]
conn, err := net.Dial("tcp", service)
checkError(err)
encoder := json.NewEncoder(conn)
decoder := json.NewDecoder(conn)
for n := 0; n < 10; n++ {
encoder.Encode(person)
var newPerson Person
decoder.Decode(&newPerson)
fmt.Println(newPerson.String())
}
os.Exit(0)
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error:%s", err.Error())
os.Exit(1)
}
}
funcreadFully(conn net. readFully(conn net.Conn) ([]byte, error) { , 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
}
对应的服务器
/** JSON EchoServer*/
package main
import (
"encoding/json"
"fmt"
"net"
"os"
)
type Person struct {
Name Name
Email []Email
}
type Name struct {
Family string
Personal string
}
type Email struct {
Kind string
Address string
}
func(p Person) String() string {
s := p.Name.Personal+ " "+ p.Name.Family
for _, v := range p.Email{
s+= "\n"+ v.Kind+ ": "+ v.Address
}
return s
}
func main() {
service := "0.0.0.0:1200"
tcpAddr, err := net.ResolveTCPAddr("tcp", service)
checkError(err)
listener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
for{
conn, err := listener.Accept()
if err != nil {
continue
}
encoder := json.NewEncoder(conn)
decoder := json.NewDecoder(conn)
for n := 0; n < 10; n++ {
var person Person
decoder.Decode(&person)
fmt.Println(person.String())
encoder.Encode(person)
}
conn.Close() //we're finished
}
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error:%s", err.Error())
os.Exit(1)
}
}
gob 包
gob
是 Go 中特有的序列化技术。它只能编码 Go 的数据类型,目前它不支持其他语言,反之亦然。它支持除 interface,function,channel 外的所有的 Go 数据类型。它支持任何类型和任何大小的整数,还有字符串和布尔值,结构,数组与切片。目前它在处理 ring 等环型数据结构方面还存在一些问题,但假以时日,将会得到改善。
Go 将类型信息编码到序列化后的表单中,在扩展性方面这远比对应的 X.509 序列化方法要好。而同时与将类型信息包含在表单中的 XML 文档相比,则更加高效。对于每个数据,类型信息只包含一次。当然,包含的是字段名称这样的信息。
包含类型信息使得 Gob 在编、解组操作上,当 marshaler 与 unmarshaler 不同或者有变化时,具有相当高的健壮性。例如,如下这个结构:
struct T {
a int
b int
}
可以被编组并随需解组到不同的结构中
struct T {
b int
a int
}
此处变更了字段的顺序.它也可以处理缺少字段(值将被忽略)或多出字段(此字段原样保持)的情况。它也可以处理指针类型,因此上边的结构可以被解组到下面的结构中.
struct T {
*a int
**b int
}
在一定程度上,它也可以强制执行类型转换,比如 int 字段被扩展成为 int 字段被扩展成为 字段被扩展成为 int64。而对于不兼容类型,比如 int 与 uint,就无能为力了.
为了使用 gob 编组一个数据值,首先你得创建 Encoder。它使用 Writer 作为参数,编组操作会将最终结果写入此流中。encoder
有个 Encode 方法
,它执行将值编组成流的操作。此方法可以在多份数据上被调用多次。但是对于每一种数据类型 ,类型信息却只会被写入一次.
你将使用Decoder
来执行解组序列化后的数据流的操作。它持有一个 Reader 参数,每次读取都将返回一个解组后的数据值。
将 gob 序列化后的数据存入文件的示例程序如下:
/* SaveGob
*/
package main
import (
"fmt"
"os"
"encoding/gob"
)
type Person struct{
Name Name
Email[]Email
}
type Name struct{
Family string string
Personalstring
}
type Email struct{
Kind string string
Addressstring
}
func main() {
person := Person{
Name: Name{Family: "Newmarch", Personal: "Jan"},
Email: []Email{Email{Kind: "home", Address: "jan@newmarch.name"},
Email{Kind: "work", Address: "j.newmarch@boxhill.edu.au"}}}
saveGob("person.gob", person)
}
func saveGob(fileName string, key interface{}) {
outFile, err := os.Create(fileName)
checkError(err)
encoder := gob.NewEncoder(outFile)
err = encoder.Encode(key)
checkError(err)
outFile.Close()
}
func checkError(err error) {
if err != nil {
fmt.Println("Fatal error ", err.Error())
os.Exit(1)
}
}
将之重新加载回内存的操作如下: 将之重新加载回内存的操作如下:
/* LoadGob
*/
package main
import (
"fmt"
"os"
"encoding/gob"
)
type Person struct{
Name Name
Email[]Email
}
type Name struct{
Family string string
Personalstring
}
type Email struct{
Kind string string
Addressstring
}
func main() {
var person Person
loadGob("person.gob", &person)
fmt.Println("Person", person.String())
}
func loadGob(fileName string, key interface{}) {
inFile, err := os.Open(fileName)
checkError(err)
decoder := gob.NewDecoder(inFile)
err = decoder.Decode(key)
checkError(err)
inFile.Close()
}
func checkError(err error) {
if err != nil {
fmt.Println("Fatal error ", err.Error())
os.Exit(1)
}
}
一个客户端与服务器的例子
一个将 person 数据收发 10 次的客户端
/** Gob EchoClient*/
package main
import (
"encoding/gob"
"fmt"
"net"
"os"
"bytes"
"io"
)
type Person struct {
Name Name
Email []Email
}
type Name struct {
Family string
Personal string
}
type Email struct {
Kind string
Address string
}
func(p Person) String() string {
s := p.Name.Personal+ " "+ p.Name.Family
for _, v := range p.Email{
s+= "\n"+ v.Kind+ ": "+ v.Address
}
return s
}
func main() {
person := person := Person{
Name: Name{Family: "Newmarch", Personal: "Jan"},
Email: []Email{Email{Kind: "home", Address: "jan@newmarch.name"},
Email{Kind: "work", Address: "j.newmarch@boxhill.edu.au"}}}
if len(os.Args) != 2 {
fmt.Println("Usage: ", os.Args[0], "host:port")
os.Exit(1)
}
service := os.Args[1]
conn, err := net.Dial("tcp", service)
checkError(err)
encoder := gob.NewEncoder(conn)
decoder := gob.NewDecoder(conn)
for n := 0; n < 10; n++ {
encoder.Encode(person)
var newPerson Person
decoder.Decode(&newPerson)
fmt.Println(newPerson.String())
}
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. readFully(conn net.Conn) ([]byte, error) { , 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
}
对应的服务器:
/** Gob EchoServer*/
package main
import (
"encoding/gob"
"fmt"
"net"
"os"
)
type Person struct {
Name Name
Email []Email
}
type Name struct {
Family string
Personal string
}
type Email struct {
Kind string
Address string
}
func(p Person) String() string {
s := p.Name.Personal+ " "+ p.Name.Family
for _, v := range p.Email{
s+= "\n"+ v.Kind+ ": "+ v.Address
}
return s
}
func main() {
service := "0.0.0.0:1200"
tcpAddr, err := net.ResolveTCPAddr("tcp", service)
checkError(err)
listener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
for{
conn, err := listener.Accept()
if err != nil {
continue
}
encoder := gob.NewEncoder(conn)
decoder := gob.NewDecoder(conn)
for n := 0; n < 10; n++ {
var person Person
decoder.Decode(&person)
fmt.Println(person.String())
encoder.Encode(person)
}
conn.Close() //we're finished
}
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error:%s", err.Error())
os.Exit(1)
}
}
将二进制数据编码为字符串
以前,传输 8-bit 数据总是会出现各种问题。它通常由充满噪声的串行线来输入,因此会出错。因为第 8 个比特位可以被用来做数字检验,所以 7-bit 的传输要值得信任一些。例如在“偶数奇偶校检”模式下,为了让一个字节中出现偶数个1,校检位可以被设置1 或 0,这将可侦测每个字节中的单个 bit位出现的错误.
ASCII 是一种7-bit 字符集。很多比‘奇偶检验’精巧的模式被开发出来,但是本质上都是 但是本质上都是将 8-bit 二进制数据转化成7-bit ASCII格式。本质上 8-bit 数据是 7-bit 数据的延伸。
在 HTTP 的请求与应答中,二进制数据常被转化为 ASCII 的形式。这使得通过一个简单的文本阅读器来检视 HTTP消息变得容易,而不需要担心 8-bit 字节造成的显示乱码的问题!
一个通用的格式是 Base64,Go 支持包括 base64 在内的多种 binary-to-text 格式.
两个编、解码 Base64
的主要函数:
func NewEncoder(enc *Encoding, w io.Writer) io.WriteCloser
func NewDecoder(enc *Encoding, r io.Reader) io.Reader
一个用以演示编解码 8位二进制数的简单程序如下:
/*
Base64
*/
package main
import (
"bytes"
"encoding/base64"
"fmt"
)
func main() {
eightBitData := []byte{1, 2, 3, 4, 5, 6, 7, 8}
bb := &bytes.Buffer{}
encoder := base64.NewEncoder(base64.StdEncoding, bb)
encoder.Write(eightBitData)
encoder.Close()
fmt.Println(bb)
dbuf := make([]byte, 12)
decoder := base64.NewDecoder(base64.StdEncoding, bb)
decoder.Read(dbuf)
for _, ch := range dbuf {
fmt.Print(ch)
}
}