Go语言学习总结

Go语言学习总结

一.初识Go语言
      贝尔实验室的肯·汤普逊和Plan 9项目原班人马加入了Google。在Google,他们创造了Go语言。到了2008年5月,Google发现了Go语言的巨大潜力,从而开始全力支持这个项目,让这批人可以全身心投入Go语言的设计和开发工作中。Go语言的第一个版本在2009年11月正式对外发布,并在此后的两年内快速迭代,发展迅猛。第一个正式版本的Go语言于2012年3月28日正式发布,让Go语言迎来了第一个引人瞩目的里程碑。

Go语言是开源的,使用BSD授权协议。

       Go语言的语言特性包括:

 1自动垃圾回收

 2更丰富的内置类型

 3函数多返回值

 4错误处理

 5匿名函数和闭包

 6类型和接口

 7并发编程

 8反射

 9语言交互性

              Go语言的网站资源有:

                     http://golang.org
                     http://code.google.com/p/go/
                     http://github.com/wonderfo/wonderfogo/wiki
                     https://studygolang.com/

二.       顺序编程
   变量的申明:

var v1 int

var v2 string

var v3 [10]int  // 数组

var v4 []int   // 数组切片

var v5 struct {

    f int

}

var v6 *int  // 指针

var v7 map[string]int  // map,key为string类型,value为int类型

var v8 func(a int) int  // 定义一个函数   


也可以将var提取出来

var(

       var v1 int

var v2 string

       …

)

 

变量初始化

var v1 int = 10 // 正确的使用方式1 

var v2 = 10 // 正确的使用方式2,编译器可以自动推导出v2的类型

v3 := 10  // 正确的使用方式3,编译器可以自动推导出v3的类型

出现在:=左侧的变量不应该是已经被声明过的,否则会导致编译错误,比如下面这个

写法:

var i int

i := 2

 

变量赋值

var v10 int

v10 = 123

i, j = j, i  // 交换I , j  的值  Go语言的多重赋值特性

 

匿名变量

func GetName() (firstName, lastName, nickName string) {

return "May", "Chan", "Chibi Maruko"

}

若只想获得nickName,则函数调用语句可以用如下方式编写:

_, _, nickName := GetName()

 

常量定义

const Pi float64 = 3.14159265358979323846 

const zero = 0.0            // 无类型浮点常量

const (

    size int64 = 1024

    eof = -1                // 无类型整型常量

const u, v float32 = 0, 3   // u = 0.0, v = 3.0,常量的多重赋值

const a, b, c = 3, 4, "foo" // a = 3, b = 4, c ="foo", 无类型整型和字符串常量

 

由于常量的赋值是一个编译期行为,所以右值不能出现任何需要运行期才能得出结果的表达

式,比如试图以如下方式定义常量就会导致编译错误:

const Home = os.GetEnv("HOME")

 

预定义常量

Go语言预定义了这些常量:true、false和iota。

iota比较特殊,可以被认为是一个可被编译器修改的常量,在每一个const关键字出现时被

重置为0,然后在下一个const出现之前,每出现一次iota,其所代表的数字会自动增1。

 

const (          // iota被重设为0

    c0 = iota  // c0 == 0

    c1 = iota // c1 == 1

    c2 = iota // c2 == 2 

)  

 

const (

    Sunday = iota

    Monday

    Tuesday

    Wednesday

    Thursday

    Friday

    Saturday

    numberOfDays        // 这个常量没有导出 

同Go语言的其他符号(symbol)一样,以大写字母开头的常量在包外可见。

 

                     类型

                     Go语言内置以下这些基础类型: 

                 布尔类型:bool。

                 整型:int8、byte、int16、int、uint、uintptr等。

                  浮点类型:float32、float64。

                 复数类型:complex64、complex128。

                 字符串:string。

                 字符类型:rune。

                 错误类型:error。

 

此外,Go语言也支持以下这些复合类型:

                  指针(pointer)

                  数组(array)

                  切片(slice)

 字典(map)

                  通道(chan)

                  结构体(struct)

                  接口(interface)      

 

需要注意的是,int和int32在Go语言里被认为是两种不同的类型,编译器也不会帮你自动

做类型转换,比如以下的例子会有编译错误:

var value2 int32

value1 := 64  // value1将会被自动推导为int类型

value2 = value1 // 编译错误     

 

2. 数值运算

Go语言支持下面的常规整数运算:+、、*、/和%。加减乘除就不详细解释了,需要说下的

是,% 和在C语言中一样是求余运算,比如:

5 % 3 // 结果为:2

 

3. 比较运算

Go语言支持以下的几种比较运算符:>、<、==、>=、<=和!=。这一点与大多数其他语言相

同,与C语言完全一致。

 

两个不同类型的整型数不能直接比较,比如int8类型的数和int类型的数不能直接比较 。 比如:

var i int32

var j int64

i, j = 1, 2

if i == j {   // 编译错误

   fmt.Println("i and j are equal.") 

}  

 

复数类型

复数实际上由两个实数(浮点数)构成,一个表示实部(real),一个表示

虚部(imag)。

1. 复数表示

var value1 complex64 

// 由2个float32构成的复数类型

value1 = 3.2 + 12i

value2 := 3.2 + 12i   // value2是complex128类型

value3 := complex(3.2, 12)  // value3结果同 value2

2. 实部与虚部

对于一个复数z = complex(x, y),就可以通过Go语言内置函数real(z)获得该复数的实部,也就是x,通过imag(z)获得该复数的虚部,也就是y。

 

字符串

var str string  // 声明一个字符串变量

str = "Hello world" // 字符串赋值

ch := str[0]  // 取字符串的第一个字符

 

字符串的内容可以用类似于数组下标的方式获取,但与数组不同,字符串的内容不能在初始化后被修改,比如以下的例子:

str := "Hello world" // 字符串也支持声明时进行初始化的做法

str[0] = 'X' // 编译错误

 

x + y 字符串连接"Hello" + "123"   // 结果为Hello123

len(s) 字符串长度len("Hello")      // 结果为5

s[i] 取字符"Hello" [1]       // 结果为'e'

 

2. 字符串遍历

Go语言支持两种方式遍历字符串。一种是以字节数组的方式遍历:

str := "Hello,世界"

n := len(str)

for i := 0; i < n; i++ {

   ch := str[i] // 依据下标取字符串中的字符,类型为byte

   fmt.Println(i, ch)

}

 

另一种是以Unicode字符遍历:

str := "Hello,世界"

for i, ch := range str {

   fmt.Println(i, ch) //ch的类型为rune

}

 

字符类型

在Go语言中支持两个字符类型,一个是byte(实际上是uint8的别名),代表UTF-8字符串的单个字节的值;另一个是rune,代表单个Unicode字符。

Go语言的多数API都假设字符串为UTF-8编码。尽管Unicode字符在标

准库中有支持,但实际上较少使用。

 

数组

数组就是指一系列同一类型数据的集合。

以下为一些常规的数组声明方法:

[32]byte  // 长度为32的数组,每个元素为一个字节

[2*N] struct { x, y int32 } // 复杂类型数组

[1000] *float64  // 指针数组

[3][5]int  // 二维数组

[2][2][2]float64  // 等同于[2]([2]([2]float64))

 

在Go语言中,数组长度在定义后就不可更改。

元素遍历

for i := 0; i < len(array); i++ {

   fmt.Println("Element", i, "of array is",array[i]) 

}‘’

 

for i, v := range array {

   fmt.Println("Array element[", i, "]=", v) 

 

 

在Go语言中数组是一个值类型(valuetype)。所有的值类型变量在赋值

和作为参数传递时都将产生一次复制动作。如果将数组作为函数的参数类型,则在函数调用时该参数将发生数据复制。因此,在函数体中无法修改传入的数组的内容,因为函数内操作的只是所传入数组的一个副本。

 

数组切片(slice)

数组切片的数据结构可以抽象为以下3个变量:

 一个指向原生数组的指针;

 数组切片中的元素个数;

 数组切片已分配的存储空间。

 

 // 先定义一个数组

 var myArray [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

 // 基于数组创建一个数组切片

 var mySlice []int = myArray[:5]

 

Go语言支持用myArray[first:last]这样的方式来基于数组生成一

个数组切片

 

基于myArray的所有元素创建数组切片:

mySlice = myArray[:]

基于myArray的前5个元素创建数组切片:

mySlice = myArray[:5] 

基于从第5个元素开始的所有元素创建数组切片:

mySlice = myArray[5:] 

 

//创建一个初始元素个数为5的数组切片,元素初始值为0:

mySlice1 := make([]int, 5) 

创建一个初始元素个数为5的数组切片,元素初始值为0,并预留10个元素的存储空间:

mySlice2 := make([]int, 5, 10) 

直接创建并初始化包含5个元素的数组切片:

mySlice3 := []int{1, 2, 3, 4, 5} 

 

与数组相比,数组切片多了一个存储能力(capacity)的概念. cap()函数返回的是数组切片分配的空间大小,而len()函数返回的是数组切片中当前所存储的元素个数

 

append()

mySlice = append(mySlice, 1, 2, 3)

mySlice2 := []int{8, 9, 10}

// 给mySlice后面添加另一个数组切片

mySlice = append(mySlice, mySlice2...)

 

oldSlice := []int{1, 2, 3, 4, 5}

newSlice := oldSlice[:3] // 基于oldSlice的前3个元素构建新数组切片

 

copy()

slice1 := []int{1, 2, 3, 4, 5}  

slice2 := []int{5, 4, 3} 

 

copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中

copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置

 

map

map是一堆键值对的未排序集合.

var myMap map[string] PersonInfo //声明

myMap = make(map[string] PersonInfo)  //创建

 

myMap = make(map[string] PersonInfo,100) //100为存储能力

 

myMap = map[string] PersonInfo{

"1234":PersonInfo{"1", "Jack", "Room 101,..."},

}

myMap["1234"] = PersonInfo{"1","Jack", "Room 101,..."}

delete(myMap, "1234")  //元素删除

 

value, ok :=myMap["1234"]  //元素查找

if ok { // 找到了

   // 处理找到的value 

}

 

流程控制

Go语言支持如下的几种流程控制语句:

 条件语句,对应的关键字为if、else和else if;

 选择语句,对应的关键字为switch、case和select

 循环语句,对应的关键字为for和range;

 跳转语句,对应的关键字为goto。

Go语言还添加了如下关键字:break、continue和fallthrough(执行本case的下一个case)。

 

循环语句

sum := 0

for i := 0; i < 10; i++ {

   sum += i

 

sum := 0

for { //无限循环

   sum++ 

   if sum > 100 {

       break

   }

}

 

Go语言中的for循环与C语言一样,都允许在循环条件中定义和初始化变量,唯一的区别是,Go语言不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多个变量。

 

在Go语言中,函数的基本组成为:关键字func、函数归属者(可选)、函数名、参数列表、返回值、函数体和返回语句。

 

不定参数类型

不定参数是指函数传入的参数个数为不定数量。为了做到这点,首先需要将函数定义为接受不定参数类型:

func myfunc(args ...int) {

   for _, arg := range args {

     fmt.Println(arg)

   } 

 

如果你希望传任意类型,可以指定类型为interface{}。下面是Go语言标准库中fmt.Printf()的函数原型:

func Printf(format string, args...interface{}) {

 

// ... 

 

多返回值

func (file *File) Read(b []byte) (nint, err Error)

 

匿名函数与闭包

 

匿名函数是指不需要定义函数名的一种函数实现方式

f := func(x, y int) int {

   return x + y 

}

f()

 

Go的匿名函数是一个闭包

 

错误处理

Go语言引入了一个关于错误处理的标准模式,即error接口,该接口的定义如下:

type error interface {

   Error() string

 

Defer (相当于Java的finally)

defer func() {

   // 做你复杂的清理工作

} () 

 

panic() (当在一个函数执行过程中调用panic()函数时,正常的函数执行流程将立即终止,但函数中之前使用defer关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致逐层向上执行panic流程,直至所属的goroutine中所有正在执行的函数被终止。错误信息将被报

告,包括在调用panic()函数时传入的参数,这个过程称为错误处理流程。)

recover()(用于终止错误处理流程。一般情况下,recover()应该在一个使用defer关键字的函数中执行以有效截取错误处理流程,有点类似于catch)

defer func() {

   if r := recover(); r != nil {

        log.Printf("Runtime error caught:%v", r)

   } 

}()

 

 

三.面向对象编程
       语言中的大多数类型都是值语义,并且都可以包含对应的操作方法。在需要

的时候,你可以给任何类型(包括内置类型)“增加”新方法。而在实现某个接口时,无需从该接口继承,只需要实现该接口要求的所有方法即可。任何类型都可以被Any类型引用。Any类型就是空接口,即interface{}。

为类型添加方法

type Integer int

func (a Integer) Less(b Integer) bool {

    return a < b

}

func (a *Integer) Add(b Integer) {

    *a += b

}

值语义和引用语义

Go语言中的大多数类型都基于值语义,包括:

 基本类型,如byte、int、bool、float32、float64和string等;

 复合类型,如数组(array)、结构体(struct)和指针(pointer)等。

Go语言中类型的值语义表现得非常彻底。我们之所以这么说,是因为数组。

type slice struct {

    first *T

    len int

    cap int

}

因为数组切片内部是指向数组的指针,所以可以改变所指向的数组元素并不奇怪。数组切片

类型本身的赋值仍然是值语义。

channel和map类似,本质上是一个指针。将它们设计为引用类型而不是统一的值类型的原因是,完整复制一个channel或map并不是常规需求。

结构体

type Rect struct {

    x, y float64

    width, height float64

}

func (r *Rect) Area() float64 {

    return r.width * r.height

}

rect1 := new(Rect)

rect2 := &Rect{}

rect3 := &Rect{0, 0, 100, 200}

rect4 := &Rect{width: 100, height: 200}

在Go语言中没有构造函数的概念,对象的创建通常交由一个全局的创建函数来完成,以

NewXXX来命名,表示“构造函数”:

func NewRect(x, y, width, height float64) *Rect {

    return &Rect{x, y,width, height}

}

匿名组合

type Base struct {

    Name string

}

func (base *Base) Foo() { ... }

func (base *Base) Bar() { ... }

type Foo struct {

    Base

    ...

}

func (foo *Foo) Bar() {

    foo.Base.Bar()

    ...

}

可见性

Go语言对关键字的增加非常吝啬,其中没有private、protected、public这样的关键字。要使某个符号对其他包(package)可见(即可以访问),需要将该符号定义为以大写字母开头

非侵入式接口

在Go语言中,一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口,

接口赋值并不要求两个接口必须等价。如果接口A的方法列表是接口B的方法列表的子集,那么接口B可以赋值给接口A

接口查询

var file1 Writer = ...

if file5, ok := file1.(two.IStream); ok {

    ...

}

类型查询

var v1 interface{} = ...

switch v := v1.(type) {

    case int:    // 现在v的类型是int

    case string: // 现在v的类型是string

...

}

接口组合

type ReadWriter interface {

    Reader

    Writer

}

Any类型

由于Go语言中任何对象实例都满足空接口interface{},所以interface{}看起来像是可以指向任何对象的Any类型,如下:

var v1 interface{} = 1      // 将int类型赋值给interface{}

var v2 interface{} = "abc"   // 将string类型赋值给interface{}

var v3 interface{} = &v2    // 将*interface{}

var v4 interface{} = struct{ X int }{1}

var v5 interface{} = &struct{ X int }{1}

四.       并发编程
       多进程。多进程是在操作系统层面进行并发的基本模式

多线程。多线程在大部分操作系统上都属于系统层面的并发模式,也是我们使用最多的最有效的一种模式

基于回调的非阻塞/异步IO。

协程。协程(Coroutine)本质上是一种用户态线程,不需要操作系统来进行抢占式调度,且在真正的实现中寄存于线程中,因此,系统开销极小,可以有效提高线程的任务并发性,而避免多线程的缺点。

执行体是个抽象的概念,在操作系统层面有多个概念与之对应,比如操作系统自己掌管的进程(process)、进程内的线程(thread)以及进程内的协程(coroutine,也叫轻量级线程)。与传统的系统级线程和进程相比,协程的最大优势在于其“轻量级”,可以轻松创建上百万个而不会导致系统资源衰竭,而线程和进程通常最多也不能超过1万个。这也是协程也叫轻量级线程的原因。   

goroutine是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。

package main

import"fmt"

func Add(x, yint) {

    z := x + y

    fmt.Println(z) 

}  

func main() {

for i := 0; i < 10; i++ {

go Add(i, i)

    } 

并发通信

并发编程的难度在于协调,而协调就要通过交流

在工程上,有两种最常见的并发通信模型:共享数据和消息。

func Count(lock*sync.Mutex) {

lock.Lock()

counter++

fmt.Println(z)

lock.Unlock() 

}  

func main() {

    lock := &sync.Mutex{}

for i := 0; i < 10; i++ {

go Count(lock)

    }

for {

        lock.Lock()

        c := counter

        lock.Unlock()

        runtime.Gosched()

if c >= 10 {

break

        }

    } 

}

channel

channel是Go语言在语言级别提供的goroutine间的通信方式。我们可以使用channel在两个或多个goroutine之间传递消息。channel是进程内的通信方式,因此通过channel传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针等。如果需要跨进程通信,我们建议用分布式系统的方法来解决,比如使用Socket或者HTTP等通信协议。

func Count(chchan int) {

    ch <- 1 

    fmt.Println("Counting")

}

func main() {

    chs := make([]chan int, 10)

for i := 0; i < 10; i++ {

        chs[i] = make(chan int)

             goCount(chs[i])

    }

for _, ch := range(chs) {

<-ch

    } 

}

我们通过ch <- 1语句向对应的channel中写入一个数据。在这个channel被读取前,这个操作是阻塞的。

channel的语法

var chanNamechan ElementType //一般channel的声明形式

var ch chan int

var mmap[string] chan bool

ch := make(chanint)

ch <- value//往channel写数据

value := <-ch//从channel里读数据

c := make(chanint, 1024) //1024为缓冲

for i := range c{ //遍历channel里面的数据

    fmt.Println("Received:", i)

}

select

select的用法与switch语言非常类似,由select开始一个新的选择块,每个选择条件由case语句来描述。与switch语句可以选择任何可使用相等比较的条件相比,select有比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作,大致的结构如下:

select {

case <-chan1: // 如果chan1成功读到数据,则进行该case处理语句

case chan2 <- 1:// 如果成功向chan2写入数据,则进行该case处理语句

default: // 如果上面都没有成功,则进入default处理流程

}

超时机制

Go语言没有提供直接的超时处理机制,但我们可以利用select机制。虽然select机制不是专为超时而设计的,却能很方便地解决超时问题。   

在Go语言中channel本身也是一个原生类型,与map之类的类型地位一样,因此channel本身在定义后也可以通过channel来传递。       

单向channel

顾名思义,单向channel只能用于发送或者接收数据。channel本身必然是同时支持读写的,否则根本没法用。假如一个channel真的只能读,那么肯定只会是空的,因为你没机会往里面写数据。同理,如果一个channel只允许写,即使写进去了,也没有丝毫意义,因为没有机会读取里面的数据。所谓的单向channel概念,其实只是对channel的一种使用限制。

var ch1 chan int// ch1是一个正常的channel,不是单向的

var ch2chan<- float64// ch2是单向channel,只用于写float64数据

var ch3<-chan int // ch3是单向channel,只用于读取int数据

关闭channel

关闭channel非常简单,直接使用Go语言内置的close()函数即可:

close(ch)

如何判断一个channel是否已经被关闭?我们可以在读取的时候使用多重返回值的方式:x, ok := <-ch

在Go语言升级到默认支持多CPU的某个版本之前,我们可以先通过设置环境变量GOMAXPROCS的值来控制使用多少个CPU核心。具体操作方法是通过直接设置环境变量GOMAXPROCS的值,或者在代码中启动goroutine之前先调用以下这个语句以设置使用16个CPU核心: runtime.GOMAXPROCS(16)

出让时间片

我们可以在每个goroutine中控制何时主动出让时间片给其他goroutine,这可以使用runtime包中的Gosched()函数实现。

同步锁

sync.Mutex 当一个goroutine获得了Mutex后,其他goroutine就只能乖乖等到这个goroutine释放该Mutex

sync.RWMutex。是经典的单写多读模型。在读锁占用的情况下,会阻止写,但不阻止读,写锁阻止读和写

全局唯一性操作

var oncesync.Once  

once.Do(setup) //setup方法只会被调用一次

 

五.       网咯编程

Dial()函数

func Dial(net,addr string) (Conn, error)

conn, err :=net.Dial("tcp", "192.168.0.10:2100")

conn, err :=net.Dial("udp", "192.168.0.12:975")

conn, err :=net.Dial("ip4:icmp", "www.baidu.com")

conn, err :=net.Dial("ip4:1", "10.0.0.3")

在成功建立连接后,我们就可以进行数据的发送和接收。发送数据时,使用conn的Write()成员方法,接收数据时使用Read()方法。’

实际上,Dial()函数是对DialTCP()、DialUDP()、DialIP()和DialUnix()的封装。我

们也可以直接调用这些函数,它们的功能是一致的。这些函数的原型如下:

func DialTCP(netstring, laddr, raddr *TCPAddr) (c *TCPConn, err error)

func DialUDP(netstring, laddr, raddr *UDPAddr) (c *UDPConn, err error)

funcDialIP(netProto string, laddr, raddr *IPAddr) (*IPConn, error)

funcDialUnix(net string, laddr, raddr *UnixAddr) (c *UnixConn, err error)

此外,net包中还包含了一系列的工具函数,合理地使用这些函数可以更好地保障程序的质量。

验证IP地址有效性的代码如下:

funcnet.ParseIP()

创建子网掩码的代码如下:

func IPv4Mask(a,b, c, d byte) IPMask

获取默认子网掩码的代码如下:

func (ip IP)DefaultMask() IPMask

根据域名查找IP的代码如下:

func ResolveIPAddr(net,addr string) (*IPAddr, error)

funcLookupHost(name string) (cname string, addrs []string, err error);

net/http包的Client类型提供了如下几个方法,让我们可以用最简洁的方式实现 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)

高级封装

type Clientstruct {

// 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     

}

在Go语言标准库中,http.Client类型包含了3个公开数据成员:

TransportRoundTripper

CheckRedirectfunc(req *Request, via []*Request) error

Jar CookieJar

自定义http.Transport

type Transportstruct {

// 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

MaxIdleConnsPerHostint

// ...

}

灵活的http.RoundTripper 接口

我们知道 HTTP Client是可以自定义的,而 http.Client 定义的第一个公开成员就是一个 http.Transport 类型的实例,且该成员所对应的类型必须实现

http.RoundTripper接口。下面我们来看看 http.RoundTripper 接口的具体定义:

typeRoundTripper interface {

// RoundTrip执行一个单一的HTTP事务,返回相应的响应信息。

// RoundTrip函数的实现不应试图去理解响应的内容。如果RoundTrip得到一个响应, 无论该响应的HTTP状态码如何,都应将返回的err设置为nil。非

//空的err 只意味着没有成功获取到响应。

// 类似地,RoundTrip也不应试图处理更高级别的协议,比如重定向、认证和

// Cookie等。 RoundTrip不应修改请求内容, 除非了是为了理解Body内容。//每一个请求的URL和Header域都应被正确初始化

    RoundTrip(*Request) (*Response, error)     

HTTP服务端

funcListenAndServe(addr string, handler Handler) error 

该方法用于在指定的 TCP 网络地址 addr 进行监听,然后调用服务端处理程序来处理传入的连接请求。

s :=&http.Server{

    Addr:           ":8080",

    Handler:        myHandler,    

    ReadTimeout:    10 * time.Second,    

    WriteTimeout:   10 * time.Second,     

    MaxHeaderBytes: 1 << 20,     

}

log.Fatal(s.ListenAndServe())

RPC编程

在 RPC 服务端,可将一个对象注册为可访问的服务,

之后该对象的公开方法就能够以远程的方式提供访问。一个 RPC 服务端可以注册多个不同类型的对象,但不允许注册同一类型的多个对象。

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

该方法(MethodName)的第一个参数表示由 RPC 客户端传入的参数,第二个参数表示要返回给RPC客户端的结果,该方法最后返回一个 error 类型的值。

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

调用 RPC 客户端的 Call() 方法则进行同步处理,当调用 RPC 客户端的 Go() 方法时,则可以进行异步处理,

err =client.Call("Arith.Multiply", args, &reply)

Gob简介

Gob 是二进制编码的数据流,并且 Gob 流是可以自解释的,它在保证高效率的同时,也具备完整的表达能力。由于 Gob 仅局限于使用 Go 语言开发的程序,这意味着我们只能用 Go 的 RPC 实现进程间通信。

JSON处理

func Marshal(vinterface{}) ([]byte, error)

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

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

funcNewDecoder(r io.Reader) *Decoder

funcNewEncoder(w io.Writer) *Encoder

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

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

六.       安全编程

采用单密钥的加密算法,我们称为对称加密

哈希算法是一种从任意数据中创建固定长度摘要信息的办法。一般我们要求,对于不同的数据,要求产生的摘要信息也是唯一的。常见的哈希算法包括MD5、SHA-1等。

TestString:="Hi,pandaman!"

    Md5Inst:=md5.New()

    Md5Inst.Write([]byte(TestString))

    Result:=Md5Inst.Sum([]byte(""))

fmt.Printf("%x\n\n",Result)

Sha1Inst:=sha1.New()

Sha1Inst.Write([]byte(TestString))

Result=Sha1Inst.Sum([]byte(""))

fmt.Printf("%x\n\n",Result)

数字签名,是指用于标记数字文件拥有者、创造者、分发者身份的字符串。数字签名拥有标记文件身份、分发的不可抵赖性等作用。

数字证书中包含了银行的公钥,有了公钥之后,网银就可以用公钥加密我们

提供给银行的信息,这样只有银行才能用对应的私钥得到我们的信息,确保安全。

SSL协议由两层组成,上层协议包括SSL握手协议、更改密码规格协议、警报协议,下层协议包括SSL记录协议。

SSL握手协议建立在SSL记录协议之上,在实际的数据传输开始前,用于在客户与服务器之间进行“握手”。“握手”是一个协商过程。这个协议使得客户和服务器能够互相鉴别身份,协商加密算法。在任何数据传输之前,必须先进行“握手”。在“握手”完成之后,才能进行SSL记录协议,它的主要功能是为高层协议提供数据封装、压缩、添加MAC、加密等支持。

SSL/TLS的工作方式。

(1) 在浏览器中输入HTTPS协议的网址,如https://qbox.me。

(2) 服务器向浏览器返回证书,浏览器检查该证书的合法性

(3) 验证合法性

(4) 浏览器使用证书中的公钥加密一个随机对称密钥,并将加密后的密钥和使用密钥加密后的请求URL一起发送到服务器。

(5) 服务器用私钥解密随机对称密钥,并用获取的密钥解密加密的请求URL。

(6) 服务器把用户请求的网页用密钥加密,并返回给用户。

(7) 用户浏览器用密钥解密服务器发来的网页数据,并将其显示出来。

上述过程都是依赖于SSL/TLS层实现的。在实际开发中,SSL/TLS的实现和工作原理比较复杂,但基本流程与上面的过程一致。

七.       工程管理

Gotool的功能非常强大,我们可以查看一下它的功能说明,具体如下所示:

$ go help  //Go is a tool formanaging Go source code.

Usage: go command [arguments]

The commands are:

    build       compile packages and dependencies

    clean       remove object files

    doc        run godoc on package sources

    fix         run go tool fix on packages

    fmt         run gofmt on package sources

    get         download and install packages anddependencies

    install     compile and install packages and dependencies

    list        list packages

    run         compile and run Go program

    test        test packages

    tool        run specified go tool

    version     print Go version

    vet         run go tool vet on packages

Additional help topics:

    gopath      GOPATH environment variable

    packages    description of package lists

    remote      remote import path syntax

    testflag    description of testing flags

    testfunc    description of testing functions

简而言之,Gotool可以帮你完成以下这几类工作:

 代码格式化

 代码质量分析和修复

 单元测试与性能测试

 工程构建

 代码文档的提取和展示

 依赖包管理

 执行其他的包含指令,比如6g等

GOPATH这个环境变量是在讨论工程组织之前必须提到的内容。Gotool的大部分功能其实已经不再针对当前目录,而是针对包名,于是如何才能定位到对应的源代码就落到了GOPATH身上。

Go的单元测试函数分为两类:功能测试函数和性能测试函数,分别为以Test和Benchmark为函数名前缀并以*testing.T为单一参数的函数。

一个标准的Go语言工程包含以下几个目录:src、pkg和bin。目录src用于包含所有的源代码,是Gotool一个强制的规则,而pkg和bin则无需手动创建,如果必要Gotool在构建过程中会自动创建这些目录。

八.       进阶话题

反射

Type主要表达的是被反射的这个变量本身的类型信息,而Value则为该变量实例本身的信息。

var x float64 = 3.4

fmt.Println("type:", reflect.TypeOf(x))

var x float64 = 3.4

v := reflect.ValueOf(x)

fmt.Println("type:", v.Type())

fmt.Println("kind is float64:", v.Kind() ==reflect.Float64)

fmt.Println("value:", v.Float())

类型Type中有一个成员函数CanSet(),其返回值为bool类型。如果你在注意到这个函数之前就直接设置了值,很有可能会收到一些看起来像异常的错误处理消息。

ar x float64 =3.4

p :=reflect.ValueOf(&x) // 注意:得到X的地址 如果是x,则不能修改其值

fmt.Println("typeof p:", p.Type())

fmt.Println("settabilityof p:" , p.CanSet())

v := p.Elem()

fmt.Println("settabilityof v:" , v.CanSet())

v.SetFloat(7.1)

fmt.Println(v.Interface())

fmt.Println(x)

对结构的反射操作

type T struct {

    A int

    B string

}

t := T{203,"mh203"}

s :=reflect.ValueOf(&t).Elem()

typeOfT :=s.Type()

for i := 0; i< s.NumField(); i++ {

    f := s.Field(i)

    fmt.Printf("%d: %s %s = %v\n", i,

        typeOfT.Field(i).Name, f.Type(),f.Interface())

}

语言交互性Cgo

package main

import"fmt"

/*

#include<stdlib.h>

*/

import"C"

func Random()int {

return int(C.random())

}

func Seed(i int){

    C.srandom(C.uint(i))

}

func main() {

    Seed(100)

    fmt.Println("Random:", Random())

}

协程,也有人称之为轻量级线程,具备以下几个特点。

1能够在单一的系统线程中模拟多个任务的并发执行。

2在一个特定的时间,只有一个任务在运行,即并非真正地并行。

3被动的任务调度方式,即任务没有主动抢占时间片的说法。当一个任务正在执行时,外部没有办法中止它。要进行任务切换,只能通过由该任务自身调用yield()来主动出让CPU使用权。

4每个协程都有自己的堆栈和局部变量。每个协程都包含3种运行状态:挂起、运行和停止。停止通常表示该协程已经执行完成(包括遇到问题后明确退出执行的情况),挂起则表示该协程尚未执行完成,但出让了时间片,以后

有机会时会由调度器继续执行。

通信机制

我们已经知道,channel是推荐的goroutine之间的通信方式。而实际上,“通信”这个术语并不太适用。从根本上来说,channel只是一个数据结构,可以被写入数据,也可以被读取数据。所谓的发送数据到channel,或者从channel读取数据,说白了就是对一个数据结构的操作,仅此而已。

常用包介绍

本节我们介绍Go语言标准库里使用频率相对较高的一些包。熟悉了这些包后,使用Go语言开发一些常规的程序将会事半功倍。

fmt。它实现了格式化的输入输出操作,其中的fmt.Printf()和fmt.Println()是开发者使用最为频繁的函数。

io。它实现了一系列非平台相关的IO相关接口和实现,比如提供了对os中系统相关的IO功能的封装。我们在进行流式读写(比如读写文件)时,通常会用到该包。

bufio。它在io的基础上提供了缓存功能。在具备了缓存功能后,bufio可以比较方便地提供ReadLine之类的操作。

strconv。本包提供字符串与基本数据类型互转的能力。

os。本包提供了对操作系统功能的非平台相关访问接口。接口为Unix风格。提供的功能包括文件操作、进程管理、信号和用户账号等。

sync。它提供了基本的同步原语。在多个goroutine访问共享资源的时候,需要使用sync中提供的锁机制。

flag。它提供命令行参数的规则定义和传入参数解析的功能。绝大部分的命令行程序都需要用到这个包。

encoding/json。JSON目前广泛用做网络程序中的通信格式。本包提供了对JSON的基本支持,比如从一个对象序列化为JSON字符串,或者从JSON字符串反序列化出一个具体的对象等。

http。它是一个强大而易用的包,也是Golang语言是一门“互联网语言”的最好佐证。通过http包,只需要数行代码,即可实现一个爬虫或者一个Web服务器,这在传统语言中是无法想象的。

猜你喜欢

转载自blog.csdn.net/wen942467928/article/details/78652521