├── README.md ├── RPCServerForPB.md ├── channel.md ├── closure.md ├── interface.md ├── mgo.md ├── multithread.md ├── nil.md ├── reflect.md ├── rpcforgolang.md ├── rpcserver.md ├── standardPB.md └── standardRPC.md /README.md: -------------------------------------------------------------------------------- 1 | blog 2 | ==== 3 | 4 | 在实际做项目中的工作总结,记录自己的成长进步! 5 | -------------------------------------------------------------------------------- /RPCServerForPB.md: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // rpcserver.go 4 | // golang 5 | // 6 | // Created by zhangzhe on 2014-3-25 7 | // 8 | 9 | package rpcserver 10 | 11 | import ( 12 | protorpc "code.google.com/p/protorpc" 13 | "fmt" 14 | "log" 15 | "net" 16 | "net/rpc" 17 | "os" 18 | ) 19 | 20 | type Server struct { 21 | Address string 22 | Network string 23 | Services map[string]interface{} 24 | } 25 | 26 | /************************************ 27 | 函数描述:: 28 | 用于创建Server的工厂方法 29 | 30 | 参数说明: 31 | netwok "TCP" 或 "UDP" 32 | addr 设置 Server IP 33 | ***********************************/ 34 | func NewServer(network string, addr string) *Server { 35 | 36 | rpcserver := new(Server) 37 | rpcserver.init(network, addr) 38 | 39 | return rpcserver 40 | } 41 | 42 | /************************************ 43 | 函数描述:: 44 | 初始化Server 45 | ***********************************/ 46 | func (this *Server) init(network string, addr string) { 47 | 48 | if this.Services == nil { 49 | this.Network = network 50 | this.Address = addr 51 | this.Services = make(map[string]interface{}) 52 | } 53 | } 54 | 55 | /*********************************** 56 | 函数描述:: 57 | 为Server添加服务 58 | 59 | 参数说明: 60 | serverName 服务名称 61 | server 服务实体 62 | ***********************************/ 63 | func (this *Server) AddService(serverName string, server interface{}) { 64 | 65 | _, ok := this.Services[serverName] 66 | if ok { //serverName已经存在 67 | return 68 | } 69 | 70 | this.Services[serverName] = server 71 | } 72 | 73 | /*********************************** 74 | 函数描述:: 75 | 启动服务 76 | ***********************************/ 77 | func (this *Server) StartServer() error { 78 | 79 | if len(this.Services) == 0 { 80 | return errors.New("No Registed Service.") 81 | } 82 | lis, err := net.Listen(this.Network, this.Address) 83 | 84 | if err != nil { 85 | return err 86 | } 87 | defer lis.Close() 88 | 89 | srv := rpc.NewServer() 90 | 91 | //注册服务 92 | for key, value := range this.Services { 93 | if err := srv.RegisterName(key, value); err != nil { 94 | return err 95 | } 96 | } 97 | 98 | for { 99 | conn, err := lis.Accept() 100 | if err != nil { 101 | log.Fatalf("lis.Accept(): %v\n", err) 102 | } 103 | go srv.ServeCodec(protorpc.NewServerCodec(conn)) 104 | } 105 | } 106 | func (this *Server) checkError(err error) { 107 | if err != nil { 108 | fmt.Println("error:", err.Error()) 109 | os.Exit(1) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /channel.md: -------------------------------------------------------------------------------- 1 | ### 一 channel介绍 2 | channel是Go语言在语言级别提供的goroutine间的通信方式。我们可以使用channel在两个或多个goroutine直间传递消息哦。channel是进程内的通信方式,因此通过channel传递对象的过程和用函数时的参数传递行为比较一致,比如也可以传递指针等。如果需要跨进程通信,我们建议用分布式的方法来解决,比如使用socket或者http等通信协议。 3 | channel是类型相关的,也就是说,一个channel只能传递一种类型的值,这个类型需要在声明channel指定。如果对Unix管道有所了解的话,不难理解channel,可以将其认为是一种类型的管道。 4 | 5 | 6 | ### 二 channel类型 7 | 8 | 9 | 1. 缓冲的channel 保证往里放数据‘先于’对应的取数据。说白了就是保证在取的时候里面肯定有数据,否则就因取不到而阻塞。带缓冲的channel相当于信号量。 10 | 11 | // By default channels are _unbuffered_, meaning that they 12 | // will only accept sends (`chan <-`) if there is a 13 | // corresponding receive (`<- chan`) ready to receive the 14 | // sent value. _Buffered channels_ accept a limited 15 | // number of values without a corresponding receiver for 16 | // those values. 17 | 18 | package main 19 | 20 | import "fmt" 21 | 22 | func main() { 23 | 24 | // Here we `make` a channel of strings buffering up to 25 | // 2 values. 26 | messages := make(chan string, 2) 27 | 28 | // Because this channel is buffered, we can send these 29 | // values into the channel without a corresponding 30 | // concurrent receive. 31 | messages <- "buffered" 32 | messages <- "channel" 33 | 34 | // Later we can receive these two values as usual. 35 | fmt.Println(<-messages) 36 | fmt.Println(<-messages) 37 | } 38 | 39 | 40 | 2. 非缓冲的channel 保证取数据‘先于’放数据。同样说白了就是保证放的时候肯定有另外的goroutine在取,否则就因放不进去而阻塞。非缓冲的channel又叫做同步channel 41 | 42 | package main 43 | 44 | import ( 45 | "fmt" 46 | "time" 47 | ) 48 | 49 | var c = make(chan int, 10) 50 | 51 | func Producer() { 52 | 53 | var counter int = 0 54 | 55 | for { 56 | c <- counter 57 | counter++ 58 | time.Sleep(time.Second * 2) 59 | } 60 | 61 | } 62 | 63 | func Consumer() { 64 | 65 | for { 66 | counter := <-c 67 | time.Sleep(time.Second * 3) 68 | fmt.Println("counter:", counter) 69 | } 70 | 71 | } 72 | 73 | func main() { 74 | go Producer() 75 | go Consumer() 76 | 77 | for { 78 | time.Sleep(time.Second * 1000) 79 | } 80 | } 81 | 82 | 83 | 带缓冲的channel 84 | 85 | 86 | var a string 87 | var c = make(chan int, 10) 88 | 89 | func f() { 90 | a = "hello, world" 91 | c <- 0 92 | } 93 | 94 | func main() { 95 | go f() 96 | <-c 97 | print(a) 98 | } 99 | 100 | 在goroutine f()中,赋值先于c<-0。在goroutine main()中,<-c先于print。而对于channel c来说,它是一个缓冲的channel,从而‘放’先于‘取’,所以c<-0先于<-c。所以串起来便是:赋值先于c<-0先于<-c先于print。从而保证了不同的goroutine中对a的读写如期望的顺序发生。 101 | 102 | var a string 103 | var c = make(chan int, 10) 104 | 105 | func f() { 106 | a = "hello, world" 107 | c <- 0 108 | } 109 | 110 | func main() { 111 | go f() 112 | <-c 113 | print(a) 114 | } 115 | 116 | 再来排排‘先于’关系。在f()中,赋值先于<-c,在main()中c<-0先于print,而c是个非缓冲channel,从而‘取’先于‘放’,所以<-c先于c<-0。串起来便是:赋值先于<-c先于c<-0先于print。同样达到了目的。 117 | 118 | 119 | 同步执行 120 | // We can use channels to synchronize execution 121 | // across goroutines. Here's an example of using a 122 | // blocking receive to wait for a goroutine to finish. 123 | 124 | package main 125 | 126 | import "fmt" 127 | import "time" 128 | 129 | // This is the function we'll run in a goroutine. The 130 | // `done` channel will be used to notify another 131 | // goroutine that this function's work is done. 132 | func worker(done chan bool) { 133 | fmt.Print("working...") 134 | time.Sleep(time.Second) 135 | fmt.Println("done") 136 | 137 | // Send a value to notify that we're done. 138 | done <- true 139 | } 140 | 141 | func main() { 142 | 143 | // Start a worker goroutine, giving it the channel to 144 | // notify on. 145 | done := make(chan bool, 1) 146 | go worker(done) 147 | 148 | // Block until we receive a notification from the 149 | // worker on the channel. 150 | <-done 151 | } 152 | 153 | 154 | 155 | 156 | 157 | 3. 单向channel 158 | 159 | 160 | 4. 需要注意的问题 161 | 在使用channel的时候经常使用死循环,应该在调用channel函数时候加上go关键字,看下面的程序: 162 | 163 | package main 164 | 165 | import "fmt" 166 | 167 | func foo() { 168 | defer fmt.Println("World") 169 | fmt.Println("Hello") 170 | } 171 | 172 | func sum(x, y int, c chan int) { 173 | c <- x + y 174 | } 175 | 176 | func main() { 177 | foo() 178 | c := make(chan int) 179 | sum(24, 18, c) 180 | fmt.Println(<-c) 181 | } 182 | 运行时会报错,c是一个无缓冲的Channel,当执行函数sum(24, 18, c)的时候函数会被阻塞,如果没有使用go Channel就一直在阻塞的状态,执行就死循环了。应该改为: 183 | 184 | package main 185 | import "fmt" 186 | 187 | func foo() { 188 | defer fmt.Println("World") 189 | fmt.Println("Hello") 190 | } 191 | 192 | func sum(x, y int, c chan int) { 193 | c <- x + y 194 | } 195 | 196 | func main() { 197 | foo() 198 | c := make(chan int) 199 | go sum(24, 18, c) 200 | fmt.Println(<-c) 201 | } 202 | 203 | 204 | 205 | ### 三 超时问题 206 | 207 | 多线程编程中经常遇到的问题是等待超时问题,Go语言中没有直接可用的方法来避免超时的发生,但是我们可以借助select机制来解决超时等待的问题。 208 | 209 | package main 210 | 211 | import ( 212 | "fmt" 213 | "time" 214 | ) 215 | 216 | func ProduceData(c chan int64) { 217 | 218 | time.Sleep(time.Second * 6) 219 | //time.Sleep(time.Second * 3) 220 | c <- 32 221 | } 222 | func main() { 223 | 224 | //首先实现一个匿名的超时等待函数 225 | timeout := make(chan bool, 1) 226 | defer close(timeout) 227 | 228 | go func() { 229 | time.Sleep(time.Second * 4) 230 | timeout <- true 231 | }() 232 | 233 | c := make(chan int64, 2) 234 | 235 | go ProduceData(c) 236 | 237 | select { 238 | case data := <-c: 239 | fmt.Println("Read a data", data) 240 | case <-timeout: 241 | fmt.Println("timeout......") 242 | } 243 | 244 | } 245 | 246 | ### 四 总结 247 | 248 | 参考: 249 | [http://blog.golang.org/pipelines](http://blog.golang.org/pipelines "http://blog.golang.org/pipelines") 250 | [http://golang.org/doc/effective_go.html#channels](http://golang.org/doc/effective_go.html#channels "http://golang.org/doc/effective_go.html#channels") 251 | -------------------------------------------------------------------------------- /closure.md: -------------------------------------------------------------------------------- 1 | ## 一 函数式编程概论 2 | 3 | 在过去近十年时间里,面向对象编程大行其道,以至于在大学的教育里,老师也只会教给我们两种编程模型,面向过程和面向对象。孰不知,在面向对象思想产生之前,函数式编程已经有了数十年的历史。就让我们回顾这个古老又现代的编程模型,看看究竟是什么魔力将这个概念在21世纪的今天再次拉入我们的视野。 4 | 5 | 随着硬件性能的提升以及编译技术和虚拟机技术的改进,一些曾被性能问题所限制的动态语言开始受到关注,Python、Ruby 和 Lua 等语言都开始在应用中崭露头角。动态语言因其方便快捷的开发方式成为很多人喜爱的编程语言,伴随动态语言的流行,函数式编程也再次进入了我们的视野。 6 | 7 | 究竟什么是函数式编程呢? 8 | 9 | 在维基百科中,对函数式编程有很详细的介绍。Wiki上对Functional Programming的定义: 10 | 11 | > In computer science, functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data. 12 | 13 | 简单地翻译一下,也就是说函数式编程是一种编程模型,他将计算机运算看做是数学中函数的计算,并且避免了状态以及变量的概念。 14 | 15 | ## 二 闭包 16 | 在函数编程中经常用到闭包,闭包是什?它是怎么产生的及用来解决什么问题呢?先给出闭包的字面定义:**闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。**这个从字面上很难理解,特别对于一直使用命令式语言进行编程的程序员们。 17 | 18 | 闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,这些代码在函数被定义后就确定了,不会在执行时发生变化,所以一个函数只有一个实例。闭包在运行时可以有多个实例,**不同的引用环境和相同的函数组合可以产生不同的实例。所谓引用环境是指在程序执行中的某个点所有处于活跃状态的约束所组成的集合。其中的约束是指一个变量的名字和其所代表的对象之间的联系。**那么为什么要把引用环境与函数组合起来呢?这主要是因为在支持嵌套作用域的语言中,有时不能简单直接地确定函数的引用环境。这样的语言一般具有这样的特性: 19 | > 函数是一等公民(First-class value),即函数可以作为另一个函数的返回值或参数,还可以作为一个变量的值。 20 | > 函数可以嵌套定义,即在一个函数内部可以定义另一个函数。 21 | 22 | 23 | 在面向对象编程中,我们把对象传来传去,那在函数式编程中,要做的是把函数传来传去,说成术语,把他叫做高阶函数。在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数: 24 | 25 | > 接受一个或多个函数作为输入 26 | > 输出一个函数 27 | 28 | 在函数式编程中,函数是基本单位,是第一型,他几乎被用作一切,包括最简单的计算,甚至连变量都被计算所取代。 29 | 30 | **闭包小结:** 31 | 函数只是一段可执行代码,编译后就“固化”了,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数了。在函数式编程语言中,函数是一等公民(First class value):第一类对象,我们不需要像命令式语言中那样借助函数指针,委托操作函数,函数可以作为另一个函数的参数或返回值,可以赋给一个变量。函数可以嵌套定义,即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题。如: 32 | 33 | package main 34 | 35 | import ( 36 | "fmt" 37 | ) 38 | 39 | func adder() func(int) int { 40 | sum := 0 41 | innerfunc := func(x int) int { 42 | sum += x 43 | return sum 44 | } 45 | return innerfunc 46 | } 47 | 48 | func main() { 49 | pos, neg := adder(), adder() 50 | for i := 0; i < 10; i++ { 51 | fmt.Println(pos(i), neg(-2*i)) 52 | } 53 | 54 | } 55 | 在这段程序中,函数innerfunc是函数adder的内嵌函数,并且是adder函数的返回值。我们注意到一个问题:内嵌函数innerfunc中引用到外层函数中的局部变量sum,Go会这么处理这个问题呢?先让我们来看看这段代码的运行结果: 56 | 57 | 0 0 58 | 1 -2 59 | 3 -6 60 | 6 -12 61 | 10 -20 62 | 15 -30 63 | 21 -42 64 | 28 -56 65 | 36 -72 66 | 45 -90 67 | 68 | 注意: **Go不能在函数内部显式嵌套定义函数,但是可以定义一个匿名函数。** 如上面所示,我们定义了一个匿名函数对象,然后将其赋值给innerfunc,最后将其作为返回值返回。 69 | 70 | 当用不同的参数调用adder函数得到(pos(i),neg(i))函数时,得到的结果是隔离的,也就是说每次调用adder返回的函数都将生成并保存一个新的局部变量sum。其实这里adder函数返回的就是闭包。 71 | 这个就是Go中的闭包,一个函数和与其相关的引用环境组合而成的实体。一句关于闭包的名言: **对象是附有行为的数据,而闭包是附有数据的行为。** 72 | 73 | ## 三 闭包使用 74 | 75 | 闭包经常用于回调函数,当IO操作(例如从网络获取数据、文件读写)完成的时候,会对获取的数据进行某些操作,这些操作可以交给函数对象处理。 76 | 77 | 除此之外,在一些公共的操作中经常会包含一些差异性的特殊操作,而这些差异性的操作可以用函数来进行封装。看下面的例子: 78 | 79 | package main 80 | 81 | import ( 82 | "errors" 83 | "fmt" 84 | ) 85 | 86 | type Traveser func(ele interface{}) 87 | /* 88 | Process:封装公共切片数组操作 89 | */ 90 | func Process(array interface{}, traveser Traveser) error { 91 | 92 | if array == nil { 93 | return errors.New("nil pointer") 94 | } 95 | var length int //数组的长度 96 | switch array.(type) { 97 | case []int: 98 | length = len(array.([]int)) 99 | case []string: 100 | length = len(array.([]string)) 101 | case []float32: 102 | length = len(array.([]float32)) 103 | default: 104 | return errors.New("error type") 105 | } 106 | if length == 0 { 107 | return errors.New("len is zero.") 108 | } 109 | traveser(array) 110 | return nil 111 | } 112 | /* 113 | 具体操作:升序排序数组元素 114 | */ 115 | func SortByAscending(ele interface{}) { 116 | intSlice, ok := ele.([]int) 117 | if !ok { 118 | return 119 | } 120 | length := len(intSlice) 121 | 122 | for i := 0; i < length-1; i++ { 123 | isChange := false 124 | for j := 0; j < length-1-i; j++ { 125 | 126 | if intSlice[j] > intSlice[j+1] { 127 | isChange = true 128 | intSlice[j], intSlice[j+1] = intSlice[j+1], intSlice[j] 129 | } 130 | } 131 | 132 | if isChange == false { 133 | return 134 | } 135 | 136 | } 137 | } 138 | /* 139 | 具体操作:降序排序数组元素 140 | */ 141 | func SortByDescending(ele interface{}) { 142 | 143 | intSlice, ok := ele.([]int) 144 | if !ok { 145 | return 146 | } 147 | length := len(intSlice) 148 | for i := 0; i < length-1; i++ { 149 | isChange := false 150 | for j := 0; j < length-1-i; j++ { 151 | if intSlice[j] < intSlice[j+1] { 152 | isChange = true 153 | intSlice[j], intSlice[j+1] = intSlice[j+1], intSlice[j] 154 | } 155 | } 156 | 157 | if isChange == false { 158 | return 159 | } 160 | 161 | } 162 | } 163 | 164 | func main() { 165 | 166 | intSlice := make([]int, 0) 167 | intSlice = append(intSlice, 3, 1, 4, 2) 168 | 169 | Process(intSlice, SortByDescending) 170 | fmt.Println(intSlice) //[4 3 2 1] 171 | Process(intSlice, SortByAscending) 172 | fmt.Println(intSlice) //[1 2 3 4] 173 | 174 | stringSlice := make([]string, 0) 175 | stringSlice = append(stringSlice, "hello", "world", "china") 176 | 177 | /* 178 | 具体操作:使用匿名函数封装输出操作 179 | */ 180 | Process(stringSlice, func(elem interface{}) { 181 | 182 | if slice, ok := elem.([]string); ok { 183 | for index, value := range slice { 184 | fmt.Println("index:", index, " value:", value) 185 | } 186 | } 187 | }) 188 | floatSlice := make([]float32, 0) 189 | floatSlice = append(floatSlice, 1.2, 3.4, 2.4) 190 | 191 | /* 192 | 具体操作:使用匿名函数封装自定义操作 193 | */ 194 | Process(floatSlice, func(elem interface{}) { 195 | 196 | if slice, ok := elem.([]float32); ok { 197 | for index, value := range slice { 198 | slice[index] = value * 2 199 | } 200 | } 201 | }) 202 | fmt.Println(floatSlice) //[2.4 6.8 4.8] 203 | } 204 | 205 | 输出结果: 206 | 207 | [4 3 2 1] 208 | [1 2 3 4] 209 | index: 0 value: hello 210 | index: 1 value: world 211 | index: 2 value: china 212 | [2.4 6.8 4.8] 213 | 214 | 在上面的例子中,Process函数负责对切片(数组)数据进行操作,在操作切片(数组)时候,首先要做一些参数检测,例如指针是否为空、数组长度是否大于0等。这些是操作数据的公共操作。具体针对数据可以有自己特殊的操作,包括排序(升序、降序)、输出等。针对这些特殊的操作可以使用函数对象来进行封装。 215 | 再看下面的例子,这个例子没什么实际意义,只是为了说明闭包的使用方式。 216 | 217 | 218 | package main 219 | 220 | import ( 221 | "fmt" 222 | ) 223 | 224 | type FilterFunc func(ele interface{}) interface{} 225 | 226 | /* 227 | 公共操作:对数据进行特殊操作 228 | */ 229 | func Data(arr interface{}, filterFunc FilterFunc) interface{} { 230 | 231 | slice := make([]int, 0) 232 | array, _ := arr.([]int) 233 | 234 | for _, value := range array { 235 | 236 | integer, ok := filterFunc(value).(int) 237 | if ok { 238 | slice = append(slice, integer) 239 | } 240 | 241 | } 242 | return slice 243 | } 244 | /* 245 | 具体操作:奇数变偶数(这里可以不使用接口类型,直接使用int类型) 246 | */ 247 | func EvenFilter(ele interface{}) interface{} { 248 | 249 | integer, ok := ele.(int) 250 | if ok { 251 | if integer%2 == 1 { 252 | integer = integer + 1 253 | } 254 | } 255 | return integer 256 | } 257 | /* 258 | 具体操作:偶数变奇数(这里可以不使用接口类型,直接使用int类型) 259 | */ 260 | func OddFilter(ele interface{}) interface{} { 261 | 262 | integer, ok := ele.(int) 263 | 264 | if ok { 265 | if integer%2 != 1 { 266 | integer = integer + 1 267 | } 268 | } 269 | 270 | return integer 271 | } 272 | 273 | func main() { 274 | sliceEven := make([]int, 0) 275 | sliceEven = append(sliceEven, 1, 2, 3, 4, 5) 276 | sliceEven = Data(sliceEven, EvenFilter).([]int) 277 | fmt.Println(sliceEven) //[2 2 4 4 6] 278 | 279 | sliceOdd := make([]int, 0) 280 | sliceOdd = append(sliceOdd, 1, 2, 3, 4, 5) 281 | sliceOdd = Data(sliceOdd, OddFilter).([]int) 282 | fmt.Println(sliceOdd) //[1 3 3 5 5] 283 | 284 | } 285 | 输出结果: 286 | 287 | [2 2 4 4 6] 288 | [1 3 3 5 5] 289 | 290 | Data作为公共函数,然后分别定义了两个具体的特殊函数:偶数和奇数的过滤器,定义具体的操作。 291 | 292 | 293 | ## 四 总结 294 | 上面例子中闭包的使用有点类似于面向对象设计模式中的模版模式,在模版模式中是在父类中定义公共的行为执行序列,然后子类通过重载父类的方法来实现特定的操作,而在Go语言中我们使用闭包实现了同样的效果。 295 | 其实理解闭包最方便的方法就是将闭包函数看成一个类,一个闭包函数调用就是实例化一个类(在Objective-c中闭包就是用类来实现的),然后就可以从类的角度看出哪些是“全局变量”,哪些是“局部变量”。例如在第一个例子中,pos和neg分别实例化了两个“闭包类”,在这个“闭包类”中有个“闭包全局变量”sum。所以这样就很好理解返回的结果了。 296 | 297 | 298 | 参考: 299 | [http://www.ibm.com/developerworks/cn/linux/l-cn-closure/index.html](http://www.ibm.com/developerworks/cn/linux/l-cn-closure/index.html "http://www.ibm.com/developerworks/cn/linux/l-cn-closure/index.html") 300 | [http://www.cnblogs.com/kym/archive/2011/03/07/1976519.html](http://www.cnblogs.com/kym/archive/2011/03/07/1976519.html "http://www.cnblogs.com/kym/archive/2011/03/07/1976519.html") 301 | -------------------------------------------------------------------------------- /interface.md: -------------------------------------------------------------------------------- 1 | ## 一 接口概述 2 | 3 | 如果说gorountine和channel是支撑起Go语言的并发模型的基石,让Go语言在如今集群化与多核化的时代成为一道亮丽的风景,那么接口是Go语言整个类型系列的基石,让Go语言在基础编程哲学的探索上达到前所未有的高度。 4 | 5 | Go语言在编程哲学上是变革派,而不是改良派。这不是因为Go语言有gorountine和channel,而更重要的是因为Go语言的类型系统,更是因为Go语言的接口。Go语言的编程哲学因为有接口而趋于完美。 6 | 7 | `C++`,`Java` 使用"侵入式"接口,主要表现在实现类需要明确声明自己实现了某个接口。这种强制性的接口继承方式是面向对象编程思想发展过程中一个遭受相当多质疑的特性。 8 | 9 | **Go语言采用的是“非侵入式接口",Go语言的接口有其独到之处:只要类型T的公开方法完全满足接口I的要求,就可以把类型T的对象用在需要接口I的地方,所谓类型T的公开方法完全满足接口I的要求,也即是类型T实现了接口I所规定的一组成员。** `Structural Typing`,有人也把它看作是一种`静态的Duck Typing`。 10 | 11 | **Go 是静态类型的。每一个变量有一个静态的类型** ,也就是说,有一个已知类型并且在编译时就确定下来了 12 | 13 | type MyInt int 14 | var i int 15 | var j MyInt 16 | 17 | 那么 i 的类型为 int 而 j 的类型为 MyInt。即使变量 i 和 j 有相同的底层类型,它们仍然是有不同的静态类型的。未经转换是不能相互赋值的。 18 | 19 | 20 | ## 二 接口类型 21 | 22 | **在类型中有一个重要的类别就是接口类型,表达了固定的一个方法集合。** 一个接口变量可以存储任意实际值(非接口),只要这个值实现了接口的方法。 23 | 24 | type Reader interface { 25 | Read(p []byte) (n int, err os.Error) 26 | } 27 | 28 | // Writer 是包裹了基础 Write 方法的接口。 29 | type Writer interface { 30 | Write(p []byte) (n int, err os.Error) 31 | } 32 | 33 | var r io.Reader 34 | r = os.Stdin 35 | r = bufio.NewReader(r) 36 | r = new(bytes.Buffer) 37 | 38 | 39 | **有一个事情是一定要明确的,不论 r 保存了什么值,r 的类型总是 io.Reader,Go 是静态类型,而 r 的静态类型是 io.Reader。** 40 | **接口类型的一个极端重要的例子是空接口:interface{},它表示空的方法集合,由于任何值都有零个或者多个方法,所以任何值都可以满足它。** 41 | 42 | 也有人说 Go 的接口是动态类型的,不过这是一种误解。 43 | 它们是静态类型的:接口类型的变量总是有着相同的静态类型,这个值总是满足空接口,只是存储在接口变量中的值运行时可能被改变。 44 | 45 | 对于所有这些都必须严谨的对待,因为反射和接口密切相关。 46 | 47 | 48 | ## 三 接口的特色 49 | 50 | 接口类型的变量存储了两个内容:赋值给变量实际的值和这个值的类型描述。更准确的说,值是底层实现了接口的实际数据项目,而类型描述了这个项目完整的类型。例如下面, 51 | 52 | var r io.Reader 53 | tty, err = os.OpenFile("/dev/tty", os.O_RDWR, 0) 54 | if err != nil { return nil, err } 55 | r = tty 56 | 57 | 用模式的形式来表达 r 包含了的是 ** (value, type)** 对,如 (tty, *os.File)。 58 | **注意: 类型 `*os.File` 除了 Read 方法还实现了其他方法:尽管接口值仅仅提供了访问 Read 方法的可能(即通过r 只能访问Read方法),但是内部包含了这个值的完整的类型信息(反射的依据)。** 59 | 60 | 这也就是为什么可以这样做: 61 | 62 | var w io.Writer 63 | w = r.(io.Writer) //接口查询 64 | 65 | 在这个赋值中的断言是一个类型断言:它断言了 r 内部的条目同时也实现了 io.Writer,因此可以赋值它到 w。在赋值之后,w 将会包含 (tty, *os.File),跟在 r 中保存的一致。 66 | **接口的静态类型决定了哪个方法可以通过接口变量调用,即便内部实际的值可能有一个更大的方法集。** 67 | 68 | 接下来,可以这样做: 69 | 70 | var empty interface{} 71 | empty = w 72 | 而空接口值 e 也将包含同样的 (tty, *os.File)。这很方便:**空接口可以保存任何值同时保留关于那个值的所有信息。** 73 | 74 | **注:这里无需类型断言,因为 w 肯定满足空接口的。在上面的个例子中,将一个值从 Reader 变为 Writer,由于 Writer 的方法不是 Reader 的子集,所以就必须明确使用类型断言。** 75 | 76 | **一个很重要的细节是接口内部的总是 (value, 实际类型) 的格式,而不会有 (value, 接口类型) 的格式。接口不能保存接口值。** 77 | 78 | 79 | ## 四 接口赋值 80 | 81 | 接口赋值在Go语言中分为两种情况: 82 | 1.将对象实例赋值给接口 83 | 2.将一个接口赋值给另外一个接口 84 | 85 | * 将对象实例赋值给接口 86 | 看下面的例子: 87 | 88 | 89 | package main 90 | 91 | import ( 92 | "fmt" 93 | ) 94 | 95 | type LesssAdder interface { 96 | Less(b Integer) bool 97 | Add(b Integer) 98 | } 99 | 100 | type Integer int 101 | 102 | func (a Integer) Less(b Integer) bool { 103 | return a < b 104 | } 105 | 106 | func (a *Integer) Add(b Integer) { 107 | *a += b 108 | } 109 | 110 | func main() { 111 | 112 | var a Integer = 1 113 | var b LesssAdder = &a 114 | fmt.Println(b) 115 | 116 | //var c LesssAdder = a 117 | //Error:Integer does not implement LesssAdder 118 | //(Add method has pointer receiver) 119 | } 120 | 121 | go语言可以根据下面的函数: 122 | 123 | func (a Integer) Less(b Integer) bool 124 | 自动生成一个新的Less()方法 125 | 126 | func (a *Integer) Less(b Integer) bool 127 | 这样,类型*Integer就既存在Less()方法,也存在Add()方法,满足LessAdder接口。 128 | 而根据 129 | 130 | func (a *Integer) Add(b Integer) 131 | 132 | 这个函数无法生成以下成员方法: 133 | 134 | func(a Integer) Add(b Integer) { 135 | (&a).Add(b) 136 | } 137 | 因为(&a).Add()改变的只是函数参数a,对外部实际要操作的对象并无影响(值传递),这不符合用户的预期。所以Go语言不会自动为其生成该函数。因此类型Integer只存在Less()方法,缺少Add()方法,不满足LessAddr接口。(可以这样去理解:指针类型的对象函数是可读可写的,非指针类型的对象函数是只读的) 138 | 139 | * 将一个接口赋值给另外一个接口 140 | 在Go语言中,只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就等同的,可以相互赋值。 141 | 如果A接口的方法列表是接口B的方法列表的子集,那么接口B可以赋值给接口A,但是反过来是不行的,无法通过编译。 142 | 143 | ## 五 接口查询 144 | 145 | 接口查询是否成功,要在运行期才能够确定。他不像接口的赋值,编译器只需要通过静态类型检查即可判断赋值是否可行。 146 | 147 | var file1 Writer = ... 148 | if file5,ok := file1.(two.IStream);ok { 149 | ... 150 | } 151 | 这个if语句检查file1接口指向的对象实例是否实现了two.IStream接口,如果实现了,则执行特定的代码。 152 | 153 | 在Go语言中,你可以询问它指向的对象是否是某个类型,比如, 154 | 155 | var file1 Writer = ... 156 | if file6,ok := file1.(*File);ok { 157 | ... 158 | } 159 | 这个if语句判断file1接口指向的对象实例是否是*File类型,如果是则执行特定的代码。 160 | 161 | slice := make([]int, 0) 162 | slice = append(slice, 1, 2, 3) 163 | 164 | var I interface{} = slice 165 | 166 | 167 | if res, ok := I.([]int);ok { 168 | fmt.Println(res) //[1 2 3] 169 | } 170 | 这个if语句判断接口I所指向的对象是否是[]int类型,如果是的话输出切片中的元素。 171 | 172 | func Sort(array interface{}, traveser Traveser) error { 173 | 174 | if array == nil { 175 | return errors.New("nil pointer") 176 | } 177 | var length int //数组的长度 178 | switch array.(type) { 179 | case []int: 180 | length = len(array.([]int)) 181 | case []string: 182 | length = len(array.([]string)) 183 | case []float32: 184 | length = len(array.([]float32)) 185 | 186 | default: 187 | return errors.New("error type") 188 | } 189 | 190 | if length == 0 { 191 | return errors.New("len is zero.") 192 | } 193 | 194 | traveser(array) 195 | 196 | return nil 197 | } 198 | 199 | 通过使用.(type)方法可以利用switch来判断接口存储的类型。 200 | 201 | **小结:** 202 | 203 | **查询接口所指向的对象是否为某个类型的这种用法可以认为是接口查询的一个特例。** 接口是对一组类型的公共特性的抽象,所以查询接口与查询具体类型区别好比是下面这两句问话的区别: 204 | > 你是医生么? 205 | > 是。 206 | > 你是莫莫莫 207 | > 是 208 | 209 | 第一句问话查询的是一个群体,是查询接口;而第二个问句已经到了具体的个体,是查询具体类型。 210 | 211 | 除此之外利用反射也可以进行类型查询,会在反射中做详细介绍。 212 | 213 | 214 | 参考: 215 | 216 | * [http://mikespook.com/2011/09/%E5%8F%8D%E5%B0%84%E7%9A%84%E8%A7%84%E5%88%99/](http://mikespook.com/2011/09/%E5%8F%8D%E5%B0%84%E7%9A%84%E8%A7%84%E5%88%99/ "http://mikespook.com/2011/09/%E5%8F%8D%E5%B0%84%E7%9A%84%E8%A7%84%E5%88%99/") 217 | * [http://research.swtch.com/interfaces](http://research.swtch.com/interfaces "http://research.swtch.com/interfaces") 218 | -------------------------------------------------------------------------------- /mgo.md: -------------------------------------------------------------------------------- 1 | **// 2 | // main.go 3 | // golang 4 | // 5 | // Created by zhangzhe on 2014-3-25 6 | //** 7 | 8 | package main 9 | 10 | import ( 11 | 12 | "fmt" 13 | "labix.org/v2/mgo" 14 | "labix.org/v2/mgo/bson" 15 | ) 16 | 17 | type Person struct { 18 | ID bson.ObjectId `_id` 19 | NAME string 20 | PHONE string 21 | } 22 | type Men struct { 23 | Persons []Person 24 | } 25 | const ( 26 | URL = "192.168.2.175:27017" 27 | ) 28 | func main() { 29 | 30 | session, err := mgo.Dial(URL) //连接数据库 31 | if err != nil { 32 | panic(err) 33 | } 34 | defer session.Close() 35 | session.SetMode(mgo.Monotonic, true) 36 | 37 | db := session.DB("mydb") //数据库名称 38 | collection := db.C("person") //如果该集合已经存在的话,则直接返回 39 | 40 | 41 | //*****集合中元素数目******** 42 | countNum, err := collection.Count() 43 | if err != nil { 44 | panic(err) 45 | } 46 | fmt.Println("Things objects count: ", countNum) 47 | 48 | //*******插入元素******* 49 | temp := &Person{ 50 | ID: bson.NewObjectId(), 51 | PHONE: "18811577546", 52 | NAME: "zhangzheHero", 53 | } 54 | 55 | //一次可以插入多个对象 插入两个Person对象 56 | err = collection.Insert(&Person{"Ale", "+55 53 8116 9639"}, temp) 57 | if err != nil { 58 | panic(err) 59 | } 60 | fmt.Println(temp.ID.Hex()) //example: 532fcfeff03bde6dc4000001 61 | fmt.Println(temp.ID.String()) //example:IdHex("532fcfeff03bde6dc4000001") 62 | 63 | //*****查询单条数据******* 64 | result := Person{} 65 | err = collection.Find(bson.M{"phone": "456"}).One(&result) 66 | fmt.Println("Phone:", result.NAME, result.PHONE) 67 | 68 | //*****查询多条数据******* 69 | var personAll Men //存放结果 70 | iter := collection.Find(nil).Iter() 71 | for iter.Next(&result) { 72 | fmt.Printf("Result: %v\n", result.NAME) 73 | personAll.Persons = append(personAll.Persons, result) 74 | } 75 | 76 | //*****或者 查询多条数据***************** 77 | var PersonALLNew Men 78 | iterNew := collection.Find(nil).Iter() 79 | err = iterNew.All(&PersonALLNew.Persons) 80 | if err != nil { 81 | panic(err) 82 | } 83 | for _, p := range PersonALLNew.Persons { 84 | fmt.Println(p) 85 | fmt.Println(p.ID) 86 | } 87 | 88 | 89 | //*******更新数据********** 90 | err = collection.Update(bson.M{"name": "ccc"}, bson.M{"$set": bson.M{"name": "ddd"}}) 91 | err = collection.Update(bson.M{"name": "ddd"}, bson.M{"$set": bson.M{"phone": "12345678"}}) 92 | err = collection.Update(bson.M{"name": "aaa"}, bson.M{"phone": "1245", "name": "bbb"}) 93 | 94 | //******删除数据************ 95 | _, err = collection.RemoveAll(bson.M{"name": "Ale"}) 96 | 97 | } 98 | -------------------------------------------------------------------------------- /multithread.md: -------------------------------------------------------------------------------- 1 | ###一 多线程编程概述 2 | 3 | 从资源的利用角度看,使用多线程的原因主要有两个:**IO阻塞和多CPU**。 4 | 5 | 当前线程进行IO处理的时候,会被阻塞释放CPU等待IO操作完成,由于IO操纵(磁盘IO或者网络IO)通常都需要较长的时间,这时CPU可以调度其它的线程进行处理。理想的系统Load是既没有进程(线程)等待也没有CPU空闲,利用多线程IO阻塞与执行交替进行,可最大限度的利用CPU资源。 6 | 7 | 使用多线程的另外一个原因是服务器有多个CPU,在这个连手机都是八核的时代,除了最低配置的虚拟机,一般数据中心的服务器至少16核CPU,要想最大限度的使用这些CPU,必须启动多线程。 8 | 9 | **启动线程数 = [任务执行时间/(任务执行时间-IO等待时间)] x CPU 内核数** 10 | 最佳启动线程数和CPU内核数量成正比,和IO阻塞时间成反比。如果任务都是CPU计算型任务,那么线程最多不超过CPU内核数,因为启动再多线程,CPU也来不及调度;相反如果任务需要等待磁盘操作,网络响应,那么启动多线程有助于提高任务并发度,提高系统吞吐能力,改善系统性能。 11 | 12 | 多线程意味着程序在运行时有多个执行上下文,对应着多个调用栈。每个进程在运行时,都有自己的调用栈和堆,有一个完整的上下文,而操作系统在调用进程的时候,会保存被调度进程的上下文环境,等该进程获得时间片后,再恢复该进程在上下文到系统中。 13 | 14 | 多线程安全主要手段:将对象设计为无状态对象、使用局部对象、并发访问资源时使用锁 15 | 16 | ###二 并发实现模型 17 | 18 | 1. **多进程** 19 | 多进程是在操作系统层面进行并发的基本模式。同时也是开销最大的模式。在Linux平台上,很多工具链正式采用这种模式在工作。比如某个Web服务器,它会有专门的进程负责网络端口的监听和链接的管理,还会有专门的进程负责事务和运算。这种的好处在于简单、进程间无不影响,坏处在于系统的开销大,因为所有的进程都是由内核管理的。 20 | 2. **多线程** 21 | 多线程在大多数的系统上都属于系统层面的开发模式,也是我们使用最多最有效的一种是模式。它比多进程的开销小很多,但是其开销依旧比较大,且在高并发模式下,效率会有影响。 22 | 3. **基于回调的非阻塞/异步IO** 23 | 这种架构的诞生实际上来源于多线程模式的危机。在很多高并发服务器开发实践中,使用多线程模式会很快耗尽服务器内存和CPU资源。而这种模式通过事件驱动的方式使用异步IO,使服务器持续运转,且尽可能的减少对线程的使用,降低开销,它目前在Node.js中得到了很好的实践。但是使用这种模式编程比多线程要复杂,因为它把流程做了分割,对于问题本身的反应不够自然。 24 | 4. **协程** 25 | 协程的本质上是一种用户态线程,不需要操作系统来进行抢占式的调度,且在真正的实现中寄予于线程中,因此,系统的开销很小,可以有效的提高线程的任务并发性,而避免多线程的缺点。使用协程的优点是编程简单,结构清晰;缺点是需要语言的支持,如果不支持,则需要用户在程序中自行实现调度器。目前,原生支持协程的语言还很少(个人认为IOS中的GCD也是一种协程的实现,它本质上也是一种用户态的线程)。 26 | 27 | ###三 Go语言中协程 28 | 29 | **协程的最大优势是其轻量级,可以轻松地创建上百万个而不会导致系统资源耗竭,而线程和进程通常最多也不能超过1万个,这也是协程也叫轻量级线程的原因。** 30 | Go语言在语言级别支持轻量级线程,叫goroutine。Go语言标准库提供的所有系统调用操作(当然也包括同步IO操作),都会出让CPU给其他goroutine。这让事情变得非常简单,让轻量级线程的切换管理不依赖于系统的线程和进程,也不依赖于CPU的核心数量。 31 | 32 | goroutine是Go语言中的轻量级线程的实现,由Go运行时(runtime)管理。在一个函数调用前加上go关键字,这次调用就会有一个新的goroutine中并发执行,当被调用的函数返回时,这个goroutine也自动结束了。需要注意的是,如果这个函数有返回值,那么这个返回值被丢弃。下面的程序: 33 | 34 | package main 35 | 36 | import "fmt" 37 | 38 | func f(from string) { 39 | for i := 0; i < 3; i++ { 40 | fmt.Println(from, ":", i) 41 | } 42 | } 43 | 44 | func main() { 45 | 46 | // Suppose we have a function call `f(s)`. Here's how 47 | // we'd call that in the usual way, running it 48 | // synchronously. 49 | f("direct") 50 | 51 | // To invoke this function in a goroutine, use 52 | // `go f(s)`. This new goroutine will execute 53 | // concurrently with the calling one. 54 | go f("goroutine") 55 | 56 | // You can also start a goroutine for an anonymous 57 | // function call. 58 | go func(msg string) { 59 | fmt.Println(msg) 60 | }("going") 61 | 62 | // Our two goroutines are running asynchronously in 63 | // separate goroutines now, so execution falls through 64 | // to here. This `Scanln` code requires we press a key 65 | // before the program exits. 66 | var input string 67 | 68 | fmt.Scanln(&input) 69 | fmt.Println("done") 70 | } 71 | 72 | 输出结果: 73 | 74 | go run goroutines.go 75 | direct : 0 76 | direct : 1 77 | direct : 2 78 | goroutine : 0 79 | going 80 | goroutine : 1 81 | goroutine : 2 82 | 83 | done 84 | 85 | 通过输出结果发现,不加go关键字的函数执行时同步的。下面的两个函数在调用语句前都加了go关键字,这两个函数的执行就会放到一个新的协程中去,从而实现异步的执行。 86 | 87 | 由上面的例子,我们可以看到在Go中使用多线程是如此的简单。 88 | 89 | ###四 Go语言协程间通信 90 | 并发编程的难度在于协调,而协调是通过交流。在工程上有两种并发通信的模型:**共享数据(内存)和消息**。 91 | 共享内存是指多个并发单元分别保持对同一个数据的引用,实现对数据的共享。被共享的数据可能有多种形式,比如内存数据块、磁盘文件、网络数据等,实际在工程应用中最常见的是内存。 92 | 首先,来看一个通过共享内存来实现线程间通信的例子 93 | 94 | package main 95 | 96 | import ( 97 | "fmt" 98 | "runtime" 99 | "sync" 100 | ) 101 | 102 | var counter int = 0 103 | //线程执行时候,加锁的原因是为了保证线程函数执行的原子性 104 | func Count(lock *sync.Mutex) { 105 | 106 | lock.Lock() 107 | counter++ 108 | fmt.Print(counter) 109 | lock.Unlock() 110 | } 111 | 112 | func main() { 113 | 114 | lock := &sync.Mutex{} 115 | 116 | for i := 0; i < 10; i++ { 117 | go Count(lock) 118 | } 119 | 120 | for { 121 | lock.Lock() 122 | c := counter 123 | lock.Unlock() 124 | runtime.Gosched() 125 | if c >= 10 { 126 | break 127 | } 128 | } 129 | } 130 | 131 | 注:使用Gosched()函数可以控制goroutine主动出让时间片给其他gorountine 132 | 133 | 134 | **在Go语言中使用的是后者,Go中的多线程通信:不要通过共享内存来通信,而是通过通信来共享内存。(Do not communicate by sharing memory; instead, share memory by communicating.)** 135 | 136 | // _Channels_ are the pipes that connect concurrent 137 | // goroutines. You can send values into channels from one 138 | // goroutine and receive those values into another 139 | // goroutine. 140 | 141 | package main 142 | 143 | import "fmt" 144 | 145 | func main() { 146 | 147 | // Create a new channel with `make(chan val-type)`. 148 | // Channels are typed by the values they convey. 149 | messages := make(chan string) 150 | 151 | // _Send_ a value into a channel using the `channel <-` 152 | // syntax. Here we send `"ping"` to the `messages` 153 | // channel we made above, from a new goroutine. 154 | go func() { messages <- "ping" }() 155 | 156 | // The `<-channel` syntax _receives_ a value from the 157 | // channel. Here we'll receive the `"ping"` message 158 | // we sent above and print it out. 159 | msg := <-messages 160 | fmt.Println(msg) 161 | } 162 | 首先创建了一个无缓冲的同步类型的字符串类型channel,在匿名函数中存入一个值"ping",此处程序会阻塞,直到执行<-message时,匿名函数才会返回,此时整个程序结束。 163 | 164 | 通过上面的例子我们发现,我们通过channel不仅实现了两个协程之间的通信,而且还传递了数据。这就是Go语言中并发编程的实现如此的简单和优雅,下一节将重点讲解channel的使用。 -------------------------------------------------------------------------------- /nil.md: -------------------------------------------------------------------------------- 1 | 2 | ## 一 接口与nil 3 | 前面讲解了go语言中接口的基本使用方法,下面将说一说nil在接口中的使用。 4 | 从上面一节我们知道在底层,interface作为两个成员实现:一个类型和一个值。该值被称为接口的动态值, 它是一个任意的具体值,而该接口的类型则为该值的类型。对于 int 值3, 一个接口值示意性地包含(int, 3)。 5 | 6 | 只有在内部值和类型都未设置时(nil, nil),一个接口的值才为 nil。特别是,一个 nil 接口将总是拥有一个 nil 类型。若我们在一个接口值中存储一个 int 类型的指针,则内部类型将为 int,无论该指针的值是什么,这样的接口值会是非 nil 的,即使在该指针的内部值为 nil,形如(*int, nil)。 7 | 8 | 下面是一个错误的错误返回方式: 9 | 10 | 11 | func returnsError() error { 12 | var p *MyError = nil 13 | if bad() { 14 | p = ErrBad 15 | } 16 | 17 | return p // Will always return a non-nil error. 18 | } 19 | 这里 p 返回的是一个有效值(非nil),值为 nil。 20 | 21 | 因此,下面判断错误的代码会有问题: 22 | 23 | func main() { 24 | if err := returnsError(); err != nil { 25 | panic(nil) 26 | } 27 | } 28 | 上面的if判断永远为真,因为resturnsError返回的值的类型不为nil。 29 | 30 | 针对 returnsError 的问题,可以这样处理(不建议的方式): 31 | 32 | func main() { 33 | if err := returnsError(); err.(*MyError) != nil { 34 | panic(nil) 35 | } 36 | } 37 | 在判断前先将err转型为*MyError,然后再判断err的值。 38 | 类似的C语言空字符串可以这样判断: 39 | 40 | 41 | bool IsEmptyStr(const char* str) { 42 | return !(str && str[0] != '\0'); 43 | } 44 | 45 | 但是Go语言中标准的错误返回方式不是returnsError这样。 46 | 下面是改进的returnsError: 47 | 48 | func returnsError() error { 49 | 50 | var p *MyError = nil 51 | if bad() { 52 | return nil 53 | } 54 | return p // Will always return a non-nil error. 55 | } 56 | 57 | ## 二 小结 58 | 在处理错误返回值的时候,一定要将正常的错误值转换为 nil。 59 | 60 | 参考: 61 | [http://my.oschina.net/chai2010/blog/117923](http://my.oschina.net/chai2010/blog/117923 "http://my.oschina.net/chai2010/blog/117923") 62 | [http://golang.org/doc/faq#nil_error](http://golang.org/doc/faq#nil_error "http://golang.org/doc/faq#nil_error") 63 | -------------------------------------------------------------------------------- /reflect.md: -------------------------------------------------------------------------------- 1 | ###一 反射的规则 2 | 反射是程序运行时检查其所拥有的结构,尤其是类型的一种能力;这是元编程的一种形式。它同时也是造成混淆的重要来源。 3 | 4 | 每个语言的反射模型都不同(同时许多语言根本不支持反射)。本节将试图明确解释在 Go 中的反射是如何工作的。 5 | 6 | ###1. 从接口值到反射对象的反射 7 | **在基本的层面上,反射只是一个检查存储在接口变量中的类型和值的算法。**在 reflect 包中有两个类型需要了解:**Type** 和 **Value**。这两个类型使得可以访问接口变量的内容,还有两个简单的函数,**reflect.TypeOf** 和 **reflect.ValueOf,从接口值中分别获取 reflect.Type 和 reflect.Value。**(**注:从 reflect.Value 也很容易能够获得 reflect.Type,不过这里让 Value 和 Type 在概念上是分离的**) 8 | 9 | 从 TypeOf 开始: 10 | 11 | package main 12 | 13 | import ( 14 | "fmt" 15 | "reflect" 16 | ) 17 | 18 | func main() { 19 | var x float64 = 3.4 20 | fmt.Println("type:", reflect.TypeOf(x)) 21 | } 22 | 这个程序打印 `type: float64` 23 | 接口在哪里呢,读者可能会对此有疑虑,看起来程序传递了一个 float64 类型的变量 x,而不是一个接口值,到 reflect.TypeOf。但是,它确实就在那里:如同 godoc 报告的那样,reflect.TypeOf 的声明包含了空接口: 24 | 25 | // TypeOf 返回 interface{} 中的值反射的类型。 26 | func TypeOf(i interface{}) Type 27 | **当调用 reflect.TypeOf(x) 的时候,x 首先存储于一个作为参数传递的空接口中;reflect.TypeOf 解包这个空接口来还原类型信息。** 28 | 29 | reflect.ValueOf 函数,当然就是还原那个值(从这里开始将会略过那些概念示例,而聚焦于可执行的代码): 30 | 31 | var x float64 = 3.4 32 | fmt.Println("value:", reflect.ValueOf(x)) 33 | 打印 34 | 35 | value: 36 | 除了reflect.Type 和 reflect.Value外,都有许多方法用于检查和操作它们。一个重要的例子是 Value 有一个 Type 方法返回 reflect.Value 的 Type。另一个是 Type 和 Value 都有 Kind 方法返回一个常量来表示类型:Uint、Float64、Slice 等等。同样 Value 有叫做 Int 和 Float 的方法可以获取存储在内部的值(跟 int64 和 float64 一样): 37 | 38 | var x float64 = 3.4 39 | v := reflect.ValueOf(x) 40 | fmt.Println("type:", v.Type()) 41 | fmt.Println("kind is float64:", v.Kind() == reflect.Float64) 42 | fmt.Println("value:", v.Float()) 43 | 打印 44 | 45 | type: float64 46 | kind is float64: true 47 | value: 3.4 48 | 49 | 同时也有类似 SetInt 和 SetFloat 的方法,不过在使用它们之前需要理解可设置性,这部分的主题在下面的第三条军规中讨论。 50 | 51 | 反射库有着若干特性值得特别说明。 52 | 53 | 54 | 55 | - 为了保持 API 的简洁,“获取者”和“设置者”用 Value 的最宽泛的类型来处理值:例如,int64 可用于所有带符号整数。也就是说 Value 的 Int 方法返回一个 int64,而 SetInt 值接受一个 int64;所以可能必须转换到实际的类型: 56 | 57 | var x uint8 = 'x' 58 | v := reflect.ValueOf(x) 59 | fmt.Println("type:", v.Type()) // uint8. 60 | fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true. 61 | x = uint8(v.Uint()) // v.Uint 返回一个 uint64. 62 | 63 | - 反射对象的 Kind 描述了底层类型,而不是静态类型。如果一个反射对象包含了用户定义的整数类型的值,就像 64 | 65 | type MyInt int 66 | var x MyInt = 7 67 | v := reflect.ValueOf(x)‘ 68 | 69 | v 的 Kind 仍然是 reflect.Int,尽管 x 的静态类型是 MyInt,而不是 int。换句话说,Kind 无法从 MyInt 中区分 int,而 Type 可以。 70 | 71 | 72 | ###2. 从反射对象到接口值的反射 73 | 74 | 如同物理中的反射,在 Go 中的反射也存在它自己的镜像。 75 | 76 | 从 reflect.Value 可以使用 Interface 方法还原接口值; 77 | 此方法可以高效地打包类型和值信息到接口表达中,并返回这个结果: 78 | 79 | // Interface 以 interface{} 返回 v 的值。 80 | func (v Value) Interface() interface{} 81 | 82 | 可以这样作为结果 83 | 84 | y := v.Interface().(float64) // y 将为类型 float64。 85 | fmt.Println(y) 86 | 87 | 通过反射对象 v 可以打印 float64 的表达值。 88 | 89 | 然而,还可以做得更好。`fmt.Println`,`fmt.Printf` 等其他所有传递一个空接口值作为参数的函数,在 fmt 包内部解包的方式就像之前的例子这样。因此正确的打印 reflect.Value 的内容的方法就是将 Interface 方法的结果进行格式化打印(formatted print routine). 90 | 91 | fmt.Println(v.Interface()) 92 | 93 | 为什么不是 fmt.Println(v)?因为 v 是一个 reflect.Value;这里希望获得的是它保存的实际的值。 94 | 95 | 由于值是 float64,如果需要的话,甚至可以使用浮点格式化: 96 | 97 | fmt.Printf("value is %7.1e\n", v.Interface()) 98 | 99 | 输出: `3.4e+00` 100 | 再次强调,**对于 v.Interface() 无需类型断言其为 float64;空接口值在内部有实际值的类型信息,而 Printf 会发现它。** 101 | 简单来说,Interface 方法是 ValueOf 函数的镜像,除了返回值总是静态类型 interface{}。 102 | 回顾:**反射可以从接口值到反射对象,也可以反过来。** 103 | 104 | ###3. 为了修改反射对象,其值必须可设置 105 | 106 | var x float64 = 3.4 107 | v := reflect.ValueOf(x) 108 | v.SetFloat(7.1) // Error: will panic. 109 | 110 | 如果运行这个代码,它报出神秘的 panic 消息 111 | 112 | panic: reflect.Value.SetFloat using unaddressable value 113 | 114 | 问题不在于值 7.1 不能地址化;在于 v 不可设置。**设置性是反射值的一个属性,并不是所有的反射值有此特性。** 115 | 116 | Value的 CanSet 方法提供了值的设置性;在这个例子中, 117 | 118 | var x float64 = 3.4 119 | v := reflect.ValueOf(x) 120 | fmt.Println("settability of v:" , v.CanSet()) 121 | 打印 122 | 123 | settability of v: false 124 | 对不可设置值调用 Set 方法会有错误。 125 | 126 | **但是什么是设置性?** 127 | 设置性有一点点像地址化,但是更严格。这是用于创建反射对象的时候,能够修改实际存储的属性。设置性用于决定反射对象是否保存原始项目。当这样 128 | 129 | var x float64 = 3.4 130 | v := reflect.ValueOf(x) 131 | 就传递了一个 x 的副本到 reflect.ValueOf,所以接口值作为 reflect.ValueOf 参数创建了 x 的副本,而不是 x 本身。因此,如果语句 132 | 133 | v.SetFloat(7.1) 134 | 允许执行,虽然 v 看起来是从 x 创建的,它也无法更新 x。反之,如果在反射值内部允许更新 x 的副本,那么 x 本身不会收到影响。这会造成混淆,并且毫无意义,因此这是非法的,而设置性是用于解决这个问题的属性。 135 | 136 | 这很神奇?其实不是。这实际上是一个常见的非同寻常的情况。考虑传递 x 到函数: 137 | 138 | f(x) 139 | 由于传递的是 x 的值的副本,而不是 x 本身,所以并不期望 f 可以修改 x。如果想要 f 直接修改 x,必须向函数传递 x 的地址(也就是,指向 x 的指针): 140 | 141 | f(&x) 142 | 这是清晰且熟悉的,而反射通过同样的途径工作。如果希望通过反射来修改 x,必须向反射库提供一个希望修改的值的指针。 143 | 144 | 来试试吧。首先像平常那样初始化 x,然后创建指向它的反射值,叫做 p。 145 | 146 | var x float64 = 3.4 147 | p := reflect.ValueOf(&x) // 注意:获取 X 的地址。 148 | fmt.Println("type of p:", p.Type()) 149 | fmt.Println("settability of p:" , p.CanSet()) 150 | 这样输出为 151 | 152 | type of p: *float64 153 | settability of p: false 154 | 反射对象 p 并不是可设置的,而且我们也不希望设置 p,实际上是 *p。为了获得 p 指向的内容,调用值上的 Elem 方法,从指针间接指向,然后保存反射值的结果叫做 v: 155 | 156 | v := p.Elem() 157 | fmt.Println("settability of v:" , v.CanSet()) 158 | 现在 v 是可设置的反射对象,如同示例的输出, 159 | 160 | settability of v: true 161 | 而由于它来自 x,最终可以使用 v.SetFloat 来修改 x 的值: 162 | 163 | v.SetFloat(7.1) 164 | fmt.Println(v.Interface()) 165 | fmt.Println(x) 166 | 得到期望的输出 167 | 168 | 7.1 169 | 7.1 170 | 反射可能很难理解,但是语言做了它应该做的,尽管底层的实现被反射的 Type 和 Value 隐藏了。务必记得反射值需要某些内容的地址来修改它指向的东西。 171 | 172 | ### 二结构体 173 | 174 | 在之前的例子中 v 本身不是指针,它只是从一个指针中获取的。这种情况更加常见的是当使用反射修改结构体的字段的时候。也就是当有结构体的地址的时候,可以修改它的字段。 175 | 176 | 这里有一个分析结构值 t 的简单例子。由于希望对结构体进行修改,所以从它的地址创建了反射对象。设置了 typeOfT 为其类型,然后用直白的方法调用来遍历其字段(参考 reflect 包了解更多信息)。注意从结构类型中解析了字段名字,但是字段本身是原始的 reflect.Value 对象。 177 | 178 | type T struct { 179 | A int 180 | B string 181 | } 182 | t := T{23, "skidoo"} 183 | s := reflect.ValueOf(&t).Elem() 184 | typeOfT := s.Type() 185 | for i := 0; i < s.NumField(); i++ { 186 | f := s.Field(i) 187 | fmt.Printf("%d: %s %s = %v\n", i, 188 | typeOfT.Field(i).Name, f.Type(), f.Interface()) 189 | } 190 | 程序输出: 191 | 192 | 0: A int = 23 193 | 1: B string = skidoo 194 | 还有一个关于设置性的要点:**T 的字段名要大写(可导出),因为只有可导出的字段是可设置的。** 195 | 196 | 由于 s 包含可设置的反射对象,所以可以修改结构体的字段。 197 | 198 | s.Field(0).SetInt(77) 199 | s.Field(1).SetString("Sunset Strip") 200 | fmt.Println("t is now", t) 201 | 这里是结果: 202 | 203 | t is now {77 Sunset Strip} 204 | 如果修改程序使得 s 创建于 t,而不是 &t,调用 SetInt 和 SetString 会失败,因为 t 的字段不可设置。 205 | 206 | ###三 总结 207 | 208 | 反射的规则如下: 209 | 从接口值到反射对象的反射 210 | 从反射对象到接口值的反射 211 | 为了修改反射对象,其值必须可设置 212 | 一旦理解了 Go 中的反射的这些规则,就会变得容易使用了,虽然它仍然很微妙。这是一个强大的工具,除非真得有必要,否则应当避免使用或小心使用。 213 | 214 | 还有大量的关于反射的内容没有涉及到——channel 上的发送和接收、分配内存、使用 slice 和 map、调用方法和函数。 215 | 216 | 事例代码: 217 | [https://github.com/ZhangzheBJUT/GoProject/blob/master/reflect/main.go](https://github.com/ZhangzheBJUT/GoProject/blob/master/reflect/main.go "https://github.com/ZhangzheBJUT/GoProject/blob/master/reflect/main.go") 218 | 219 | 参考: 220 | [http://blog.golang.org/laws-of-reflection](http://blog.golang.org/laws-of-reflection "http://blog.golang.org/laws-of-reflection") -------------------------------------------------------------------------------- /rpcforgolang.md: -------------------------------------------------------------------------------- 1 | 2 | 关于Protocol Buffers在c++,java和Python方面的使用大家可以参考官方文档: 3 | [https://developers.google.com/protocol-buffers/docs/tutorials ](https://developers.google.com/protocol-buffers/docs/tutorials "https://developers.google.com/protocol-buffers/docs/tutorials ") 4 | 5 | 下面着重讲一下Protocol Buffers在golang方面的使用情况。 6 | 7 | 8 | **一 Protocol Buffers 的安装** 9 | 10 | 11 | 1. 安装标准的protocolbuf 12 | 从这里下载 protocolbuf [https://code.google.com/p/protobuf/downloads/list](https://code.google.com/p/protobuf/downloads/list "https://code.google.com/p/protobuf/downloads/list") 这里我们选择 protobuf-2.5.0.tar.bz2 13 | 经过解压安装后会安装到/usr/local/bin/protoc目录下 14 | 15 | cd protobuf-2.5.0 16 | ./configure 17 | make 18 | make check 19 | make install 20 | 21 | 2. 安装针对go环境到goprotobuf 22 | 23 | go get code.google.com/p/goprotobuf/{proto,protoc-gen-go} 24 | 25 | 安装成功后,可以在 `$GO_PATH/bin`下找到 `protoc-gen-go` 这个程序,那么就可以实 26 | 用`protoc-gen-go` 进行go语言的proto文件的自动生成了。 27 | 28 | 29 | 30 | 31 | **二 编辑和编译源文件** 32 | 33 | 编辑源文件 test.proto 34 | 35 | package example; 36 | 37 | message Test { 38 | required string label = 1; 39 | optional int32 type = 2 [default=77]; 40 | repeated int64 reps = 3; 41 | } 42 | 43 | 编译源文件 44 | 45 | $ protoc -I=/home/XXXX/applications/protocobuffer 46 | -- go_out=/home/XXXX/applications/protocobuffer/go 47 | --cpp_out=/home/XXXX/applications/protocobuffer/c++ 48 | /home/XXXX/applications/protocobuffer/test.proto 49 | 50 | 51 | 52 | **-I 表示.proto文件所在的文件夹** 53 | **`go_out` 表示要生成golang语言的代码并存放到指的文件夹中** 54 | **`cpp_out` 表示要生成C语言的代码并存放到指定的文件夹中** 55 | **最后一个参数到文件路径表示要编译到消息定义文件存放路径** 56 | 57 | 生成后 `test.pb.go `文件,生成到代码为: 58 | 59 | 60 | // Code generated by protoc-gen-go. 61 | // source: test.proto 62 | // DO NOT EDIT! 63 | 64 | package example 65 | 66 | import proto "code.google.com/p/goprotobuf/proto" 67 | import json "encoding/json" 68 | import math "math" 69 | 70 | // Reference proto, json, and math imports to suppress error if they are not otherwise used. 71 | var _ = proto.Marshal 72 | var _ = &json.SyntaxError{} 73 | var _ = math.Inf 74 | 75 | type Test struct { 76 | Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"` 77 | Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"` 78 | Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"` 79 | XXX_unrecognized []byte `json:"-"` 80 | } 81 | 82 | func (m *Test) Reset() { *m = Test{} } 83 | func (m *Test) String() string { return proto.CompactTextString(m) } 84 | func (*Test) ProtoMessage() {} 85 | 86 | const Default_Test_Type int32 = 77 87 | 88 | func (m *Test) GetLabel() string { 89 | if m != nil && m.Label != nil { 90 | return *m.Label 91 | } 92 | return "" 93 | } 94 | 95 | func (m *Test) GetType() int32 { 96 | if m != nil && m.Type != nil { 97 | return *m.Type 98 | } 99 | return Default_Test_Type 100 | } 101 | 102 | func (m *Test) GetReps() []int64 { 103 | if m != nil { 104 | return m.Reps 105 | } 106 | return nil 107 | } 108 | 109 | func init() { 110 | } 111 | 112 | 113 | 114 | 115 | 116 | **三 编码和解码** 117 | 118 | Google Protocol Buffer 的使用和原理 119 | 参见: [http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/ ](http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/ "http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/ ") 120 | 121 | 122 | **四 支持RPC版本的ProtocolBuf** 123 | 124 | 安装方法和go ProtocolBuf差不多,首先安装go,然后去安装官方提供的标准protocolbuf 最后运行如下的命令: 125 | 126 | `go get code.google.com/p/protorpc` 127 | `go get code.google.com/p/protorpc/protoc-gen-go` 128 | 129 | 详细安装和使用方法参加: 130 | [**http://godoc.org/code.google.com/p/protorpc**](http://godoc.org/code.google.com/p/protorpc "http://godoc.org/code.google.com/p/protorpc") -------------------------------------------------------------------------------- /rpcserver.md: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // rpcserver.go 4 | // golang 5 | // 6 | // Created by zhangzhe on 2014-3-25 7 | // 8 | 9 | package rpcserver 10 | 11 | import ( 12 | protorpc "code.google.com/p/protorpc" 13 | "fmt" 14 | "log" 15 | "net" 16 | "net/rpc" 17 | "os" 18 | ) 19 | 20 | type Server struct { 21 | Address string 22 | Network string 23 | Services map[string]interface{} 24 | } 25 | 26 | /************************************ 27 | 函数描述:: 28 | 用于创建Server的工厂方法 29 | 30 | 参数说明: 31 | netwok "TCP" 或 "UDP" 32 | addr 设置 Server IP 33 | ***********************************/ 34 | func NewServer(network string, addr string) *Server { 35 | 36 | rpcserver := new(Server) 37 | rpcserver.init(network, addr) 38 | 39 | return rpcserver 40 | } 41 | 42 | /************************************ 43 | 函数描述:: 44 | 初始化Server 45 | ***********************************/ 46 | func (this *Server) init(network string, addr string) { 47 | 48 | if this.Services == nil { 49 | this.Network = network 50 | this.Address = addr 51 | this.Services = make(map[string]interface{}) 52 | } 53 | } 54 | 55 | /*********************************** 56 | 函数描述:: 57 | 为Server添加服务 58 | 59 | 参数说明: 60 | serverName 服务名称 61 | server 服务实体 62 | ***********************************/ 63 | func (this *Server) AddService(serverName string, server interface{}) { 64 | 65 | _, ok := this.Services[serverName] 66 | if ok { //serverName已经存在 67 | return 68 | } 69 | 70 | this.Services[serverName] = server 71 | } 72 | 73 | /*********************************** 74 | 函数描述:: 75 | 启动服务 76 | ***********************************/ 77 | func (this *Server) StartServer() error { 78 | 79 | lis, err := net.Listen(this.Network, this.Address) 80 | 81 | if err != nil { 82 | return err 83 | } 84 | defer lis.Close() 85 | 86 | srv := rpc.NewServer() 87 | 88 | //注册服务 89 | for key, value := range this.Services { 90 | 91 | if err := srv.RegisterName(key, value); err != nil { 92 | return err 93 | } 94 | } 95 | 96 | for { 97 | conn, err := lis.Accept() 98 | if err != nil { 99 | log.Fatalf("lis.Accept(): %v\n", err) 100 | } 101 | go srv.ServeCodec(protorpc.NewServerCodec(conn)) 102 | } 103 | } 104 | func (this *Server) checkError(err error) { 105 | if err != nil { 106 | fmt.Println("error:", err.Error()) 107 | os.Exit(1) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /standardPB.md: -------------------------------------------------------------------------------- 1 | 2 | 关于Protocol Buffers在c++,java和Python方面的使用大家可以参考官方文档: 3 | [https://developers.google.com/protocol-buffers/docs/tutorials ](https://developers.google.com/protocol-buffers/docs/tutorials "https://developers.google.com/protocol-buffers/docs/tutorials ") 4 | 5 | 下面着重讲一下Protocol Buffers在golang方面的使用情况。 6 | 7 | 8 | **一 Protocol Buffers 的安装** 9 | 10 | 11 | 1. 安装标准的protocolbuf 12 | 从这里下载 protocolbuf [https://code.google.com/p/protobuf/downloads/list](https://code.google.com/p/protobuf/downloads/list "https://code.google.com/p/protobuf/downloads/list") 这里我们选择 protobuf-2.5.0.tar.bz2 13 | 经过解压安装后会安装到/usr/local/bin/protoc目录下 14 | 15 | cd protobuf-2.5.0 16 | ./configure 17 | make 18 | make check 19 | make install 20 | 21 | 2. 安装针对go环境到goprotobuf 22 | 23 | go get code.google.com/p/goprotobuf/{proto,protoc-gen-go} 24 | 25 | 安装成功后,可以在 `$GO_PATH/bin`下找到 `protoc-gen-go` 这个程序,那么就可以实 26 | 用`protoc-gen-go` 进行go语言的proto文件的自动生成了。 27 | 28 | 29 | 30 | 31 | **二 编辑和编译源文件** 32 | 33 | 编辑源文件 test.proto 34 | 35 | package example; 36 | 37 | message Test { 38 | required string label = 1; 39 | optional int32 type = 2 [default=77]; 40 | repeated int64 reps = 3; 41 | } 42 | 43 | 编译源文件 44 | 45 | $ protoc -I=/home/XXXX/applications/protocobuffer 46 | -- go_out=/home/XXXX/applications/protocobuffer/go 47 | --cpp_out=/home/XXXX/applications/protocobuffer/c++ 48 | /home/XXXX/applications/protocobuffer/test.proto 49 | 50 | 51 | 52 | **-I 表示.proto文件所在的文件夹** 53 | **`go_out` 表示要生成golang语言的代码并存放到指的文件夹中** 54 | **`cpp_out` 表示要生成C语言的代码并存放到指定的文件夹中** 55 | **最后一个参数到文件路径表示要编译到消息定义文件存放路径** 56 | 57 | 生成后 `test.pb.go `文件,生成到代码为: 58 | 59 | 60 | // Code generated by protoc-gen-go. 61 | // source: test.proto 62 | // DO NOT EDIT! 63 | 64 | package example 65 | 66 | import proto "code.google.com/p/goprotobuf/proto" 67 | import json "encoding/json" 68 | import math "math" 69 | 70 | // Reference proto, json, and math imports to suppress error if they are not otherwise used. 71 | var _ = proto.Marshal 72 | var _ = &json.SyntaxError{} 73 | var _ = math.Inf 74 | 75 | type Test struct { 76 | Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"` 77 | Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"` 78 | Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"` 79 | XXX_unrecognized []byte `json:"-"` 80 | } 81 | 82 | func (m *Test) Reset() { *m = Test{} } 83 | func (m *Test) String() string { return proto.CompactTextString(m) } 84 | func (*Test) ProtoMessage() {} 85 | 86 | const Default_Test_Type int32 = 77 87 | 88 | func (m *Test) GetLabel() string { 89 | if m != nil && m.Label != nil { 90 | return *m.Label 91 | } 92 | return "" 93 | } 94 | 95 | func (m *Test) GetType() int32 { 96 | if m != nil && m.Type != nil { 97 | return *m.Type 98 | } 99 | return Default_Test_Type 100 | } 101 | 102 | func (m *Test) GetReps() []int64 { 103 | if m != nil { 104 | return m.Reps 105 | } 106 | return nil 107 | } 108 | 109 | func init() { 110 | } 111 | 112 | 113 | 114 | 115 | 116 | **三 编码和解码** 117 | 118 | Google Protocol Buffer 的使用和原理 119 | 参见: [http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/ ](http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/ "http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/ ") 120 | 121 | 122 | **四 支持RPC版本的ProtocolBuf** 123 | 124 | 安装方法和go ProtocolBuf差不多,首先安装go,然后去安装官方提供的标准protocolbuf 最后运行如下的命令: 125 | 126 | `go get code.google.com/p/protorpc` 127 | `go get code.google.com/p/protorpc/protoc-gen-go` 128 | 129 | 详细安装和使用方法参加: 130 | [**http://godoc.org/code.google.com/p/protorpc**](http://godoc.org/code.google.com/p/protorpc "http://godoc.org/code.google.com/p/protorpc") -------------------------------------------------------------------------------- /standardRPC.md: -------------------------------------------------------------------------------- 1 | **// 2 | // main.go 3 | // golang 4 | // 5 | // Created by zhangzhe on 2014-2-20 6 | //** 7 | 8 | ###一 HTTP RPC 9 | 10 | 服务端代码 11 | 12 | package main 13 | 14 | import ( 15 | "errors" 16 | "fmt" 17 | "net/http" 18 | "net/rpc" 19 | ) 20 | 21 | const ( 22 | URL = "192.168.2.172:12981" 23 | ) 24 | 25 | type Args struct { 26 | A, B int 27 | } 28 | 29 | type Quotient struct { 30 | Quo, Rem int 31 | } 32 | 33 | type Arith int 34 | 35 | func (t *Arith) Multiply(args *Args, reply *int) error { 36 | *reply = args.A * args.B 37 | return nil 38 | } 39 | func (t *Arith) Divide(args *Args, quo *Quotient) error{ 40 | if args.B == 0 { 41 | return errors.New("divide by zero!") 42 | } 43 | 44 | quo.Quo = args.A / args.B 45 | quo.Rem = args.A % args.B 46 | 47 | return nil 48 | } 49 | func main() { 50 | 51 | arith := new(Arith) 52 | rpc.Register(arith) 53 | rpc.HandleHTTP() 54 | 55 | err := http.ListenAndServe(URL, nil) 56 | if err != nil { 57 | fmt.Println(err.Error()) 58 | } 59 | } 60 | 61 | 62 | 客户端代码 63 | 64 | package main 65 | 66 | import ( 67 | "fmt" 68 | "net/rpc” 69 | ) 70 | 71 | const ( 72 | URL = "192.168.2.172:12982" 73 | ) 74 | 75 | func main() { 76 | 77 | client, err := rpc.DialHTTP("tcp", URL) 78 | if err != nil { 79 | fmt.Println(err.Error()) 80 | } 81 | 82 | args := Args{2, 4} 83 | var reply int 84 | err = client.Call("Arith.Multiply", &args, &reply) 85 | 86 | if err != nil { 87 | fmt.Println(err.Error()) 88 | } else { 89 | fmt.Println(reply) 90 | } 91 | } 92 | 93 | ### 二 JSON-RPC 94 | 95 | 96 | 服务器端代码 97 | 98 | package main 99 | 100 | import ( 101 | "errors" 102 | "fmt" 103 | "net" 104 | "net/rpc" 105 | "net/rpc/jsonrpc" 106 | ) 107 | 108 | const ( 109 | URL= "192.168.2.172:12981" 110 | ) 111 | 112 | type Args struct { 113 | A, B int 114 | } 115 | type Quotient struct { 116 | Quo, Rem int 117 | } 118 | 119 | type Arith int 120 | 121 | func (t *Arith) Multiply(args *Args, reply *int) error { 122 | *reply = args.A * args.B 123 | return nil 124 | } 125 | func (t *Arith) Divide(args *Args, quo *Quotient) error { 126 | if args.B == 0 { 127 | return errors.New("divide by zero!") 128 | } 129 | 130 | quo.Quo = args.A / args.B 131 | quo.Rem = args.A % args.B 132 | 133 | return nil 134 | } 135 | func main() { 136 | 137 | arith := new(Arith) 138 | rpc.Register(arith) 139 | 140 | tcpAddr, err := net.ResolveTCPAddr("tcp", URL) 141 | if err != nil { 142 | fmt.Println(err) 143 | } 144 | listener, err := net.ListenTCP("tcp", tcpAddr) 145 | 146 | for { 147 | conn, err := listener.Accept() 148 | if err != nil { 149 | continue 150 | } 151 | go jsonrpc.ServeConn(conn) 152 | } 153 | } 154 | 155 | 客户端代码 156 | 157 | package main 158 | 159 | import ( 160 | "fmt" 161 | "net/rpc” 162 | ) 163 | 164 | const ( 165 | URL = "192.168.2.172:12982" 166 | ) 167 | 168 | func main() { 169 | 170 | client, err := jsonrpc.Dial("tcp", URL) 171 | defer client.Close() 172 | 173 | if err != nil { 174 | fmt.Println(err) 175 | } 176 | 177 | args := Args{7, 2} 178 | var reply int 179 | err = client.Call("Arith.Multiply", &args, &reply) 180 | if err != nil { 181 | fmt.Println(err) 182 | } 183 | fmt.Println(reply) 184 | } 185 | --------------------------------------------------------------------------------