网络 channels

Warning

警告

The netchan package is being reworked. While it was in earlier versions of Go, it is not in Go 1. It is available in the old/netchan package if you still need it. This chapter describes this old version. Do not use it for new code.

现在netchan包正在重新设计。出于对Go 1之前版本的兼容性考虑,可以在old/netchan下找到它。这一章描述的是旧版本的使用。请不要在新代码中使用它.

Introduction

简介

There are many models for sharing information between communicating processes. One of the more elegant is Hoare's concept of channels. In this, there is no shared memory, so that none of the issues of accessing common memory arise. Instead, one process will send a message along a channel to another process. Channels may be synchronous, or asynchronous, buffered or unbuffered.

关于进程间共享信息有过许多模型。其中较为优美的是Hoare提出的channels模型。在这一模型中,不需要共享内存,因此读取共享内存引起的问题都可以避免。取而代之的是使用channel传递消息:一个进程通过一个channel向另一个进程发送消息,channels可以是同步的,也可以是异步的,可以是带缓冲的,也可以是不带缓冲的。

Go has channels as first order data types in the language. The canonical example of using channels is Erastophene's prime sieve: one goroutine generates integers from 2 upwards. These are pumped into a series of channels that act as sieves. Each filter is distinguished by a different prime, and it removes from its stream each number that is divisible by its prime. So the '2' goroutine filters out even numbers, while the '3' goroutine filters out multiples of 3. The first number that comes out of the current set of filters must be a new prime, and this is used to start a new filter with a new channel.

Go内建channel作为第一等数据类型。一个使用channel的经典例子是Erastophene的素数筛选器:使用一个goroutine从2开始生成整数,将这些数字送入一系列作为过滤器的channel,每一个过滤器由一个不同的素数标识,它们把能被自身代表素数整除的数从流中删除,所以“2”goroutine过滤掉所有偶数,“3”goroutine过滤掉所有3的倍数。第一个从这一系列过滤器中走出来的必然是一个新的素数,然后再开启新的channel,用新素数生成一个新的过滤器,循环往复。

The efficacy of many thousands of goroutines communicating by many thousands of channels depends on how well the implementation of these primitives is done. Go is designed to optimise these, so this type of program is feasible.

大量goroutine之间通过channel通信的效率取决于原语设计的好坏。Go天生为此优化,所以这种程序是可行的

Go also supports distributed channels using the netchan package. But network communications are thousands of times slower than channel communications on a single computer. Running a sieve on a network over TCP would be ludicrously slow. Nevertheless, it gives a programming option that may be useful in many situations.

Go也通过netchan包支持分布式channel。但是网络间channel通信的效率远比单一电脑上channel间通信的效率低。在网络上通过TCP协议运行一个筛选器更是慢的可怕。然而,这还是给程序员多了一个选择,而且这在某些情况下十分有用。

Go's network channel model is somewhat similar in concept to the RPC model: a server creates channels and registers them with the network channel API. A client does a lookup for channels on a server. At this point both sides have a shared channel over which they can communicate. Note that communication is one-way: if you want to send information both ways, open two channels one for each direction.

Go的网络channel模型某种意义上和RPC模型类似:服务器创建channel然后用网络channel API注册它们,客户端在服务器上查询channel。这样服务器和客户端就有了一个可以相互通信的共享channel。注意这种通信是单向的,如果你想要双向发送信息,为每个方向单独创建一个channel。

Channel server

服务器端Channel

In order to make a channel visible to clients, you need to export it. This is done by creating an exporter using NewExporter with no parameters. The server then calls ListenAndServe to lsiten and handle responses. This takes two parameters, the first being the underlying transport mechanism such as "tcp" and the second being the network listening address (usually just a port number.

要让一个channel对客户端可见,你需要导出它。这可以通过不带参数NewExporter创建一个新的导出,之后服务器调用ListenAndServe监听、处理请求。ListenAndServe带有两个参数,第一个是底层的传输机制,比如“tcp”;第二个是监听地址(通常只是一个端口号)。

For each channel, the server creates a normal local channel and then calls Export to bind this to the network channel. At the time of export, the direction of communication must be specified. Clients search for channels by name, which is a string. This is specified to the exporter.

对每个channel,服务器创建一个普通的本地channel,然后调用Export将它绑定到网络channel上。在导出的同时,必须指定通信的方向。客户端可以通过名字(一个字符串)搜寻channel。

The server then uses the local channels in the normal way, reading or writing on them. We illustrate with an "echo" server which reads lines and sends them back. It needs two channels for this. The channel that the client writes to we name "echo-out". On the server side this is a read channel. Similarly, the channel that the client reads from we call "echo-in", which is a write channel to the server.

服务器之后可以像使用本地channel一样,读取或是写入数据。我们以一个“echo”服务器为例,它读入文本,再原样发送回去。它需要两个channel,我们把客户端用来写入数据的channel叫做“echo-out”,在服务器端,用它来读取数据。相似的,客户端用来读取数据的channel叫做“echo-in”,服务器往里写入数据。

The server program is

服务器程序如下

  1. /* EchoServer
  2. */
  3. package main
  4. import (
  5. "fmt"
  6. "os"
  7. "old/netchan"
  8. )
  9. func main() {
  10. // exporter, err := netchan.NewExporter("tcp", ":2345")
  11. exporter := netchan.NewExporter()
  12. err := exporter.ListenAndServe("tcp", ":2345")
  13. checkError(err)
  14. echoIn := make(chan string)
  15. echoOut := make(chan string)
  16. exporter.Export("echo-in", echoIn, netchan.Send)
  17. exporter.Export("echo-out", echoOut, netchan.Recv)
  18. for {
  19. fmt.Println("Getting from echoOut")
  20. s, ok := <-echoOut
  21. if !ok {
  22. fmt.Printf("Read from channel failed")
  23. os.Exit(1)
  24. }
  25. fmt.Println("received", s)
  26. fmt.Println("Sending back to echoIn")
  27. echoIn <- s
  28. fmt.Println("Sent to echoIn")
  29. }
  30. }
  31. func checkError(err error) {
  32. if err != nil {
  33. fmt.Println("Fatal error ", err.Error())
  34. os.Exit(1)
  35. }
  36. }

Note: at the time of writing, the server will sometimes fail with an error message "netchan export: error encoding client response". This is logged as Issue 1805

注意:在这篇教程写下的时候,服务器可能会收到“netchan export: error encoding client response”的错误消息,这个问题被登记为Issue 1805。

Channel client

客户端Channel

In order to find an exported channel, the client must import it. This is created using Import which takes a protocol and a network service address of "host:port". This is then used to import a network channel by name and bind it to a local channel. Note that channel variables are references, so you do not need to pass their addresses to functions that change them.

为了找到导出的channel,客户端必须导入它。使用Import方法可以完成,它接受两个参数:协议名和形如“host:port”网络服务地址。之后就能通过名字导入,并绑定到本地channel上。注意,channel变量是引用变量,不用向改变他们的函数传递channel的地址。

The following client gets two channels to and from the echo server, and then writes and reads ten messages:

以下客户端使用两个channel向/从服务器发送、接受消息,然后写入并读取收到的十条信息。

  1. /* EchoClient
  2. */
  3. package main
  4. import (
  5. "fmt"
  6. "old/netchan"
  7. "os"
  8. )
  9. func main() {
  10. if len(os.Args) != 2 {
  11. fmt.Println("Usage: ", os.Args[0], "host:port")
  12. os.Exit(1)
  13. }
  14. service := os.Args[1]
  15. importer, err := netchan.Import("tcp", service)
  16. checkError(err)
  17. fmt.Println("Got importer")
  18. echoIn := make(chan string)
  19. importer.Import("echo-in", echoIn, netchan.Recv, 1)
  20. fmt.Println("Imported in")
  21. echoOut := make(chan string)
  22. importer.Import("echo-out", echoOut, netchan.Send, 1)
  23. fmt.Println("Imported out")
  24. for n := 0; n < 10; n++ {
  25. echoOut <- "hello "
  26. s, ok := <-echoIn
  27. if !ok {
  28. fmt.Println("Read failure")
  29. break
  30. }
  31. fmt.Println(s, n)
  32. }
  33. close(echoOut)
  34. os.Exit(0)
  35. }
  36. func checkError(err error) {
  37. if err != nil {
  38. fmt.Println("Fatal error ", err.Error())
  39. os.Exit(1)
  40. }
  41. }

Handling Timeouts

处理超时

Because these channels use the network, there is alwasy the possibility of network errors leading to timeouts. Andrew Gerrand points out a solution using timeouts: "[Set up a timeout channel.] We can then use a select statement to receive from either ch or timeout. If nothing arrives on ch after one second, the timeout case is selected and the attempt to read from ch is abandoned."

因为channel使用网络通信,存在因为网络错误导致超时的可能性。Andrew Gerrand提出了一个办法timeouts: "[Set up a timeout channel.]我们可以使用select语句从ch或是timeout接受信息。如果超过1秒钟ch没有收到信息,timeout通道被选择,放弃从ch获取信息。

  1. timeout := make(chan bool, 1)
  2. go func() {
  3. time.Sleep(1e9) // one second
  4. timeout <- true
  5. }()
  6. select {
  7. case <- ch:
  8. // a read from ch has occurred
  9. case <- timeout:
  10. // the read from ch has timed out
  11. }
  12.  

Channels of channels

传递channel的channel

The online Go tutorial at http://golang.org/doc/go_tutorial.html has an example of multiplexing, where channels of channels are used. The idea is that instread of sharing one channel, a new communicator is given their own channel to have a privagye conversation. That is, a client is sent a channel from a server through a shared channel, and uses that private channel.

在线Go指导(http://golang.org/doc/go_tutorial.html)中展示了一个有点复杂的例子,其中使用了传递channel的channel。这个方法避免了总是使用共享channel,新的进程被赋予他们自己的channel进行私有交流。即,客户端通过共享channel从服务器端获得一个channel,之后使用这个私有channel进行通信。

This doesn't work directly with network channels: a channel cannot be sent over a network channel. So we have to be a little more indirect. Each time a client connects to a server, the server builds new network channels and exports them with new names. Then it sends the names of these new channels to the client which imports them. It uses these new channels for communicaiton.

然而这对网络channel不起作用,网络channel不能发送channel,所以我们要稍微绕点弯路。每次客户端连接服务器,服务器建立一个新的channel,然后用新名字导出他们。之后向导入它们的客户端发送这些新channel的名字。最后使用这些新channel进行通信。

A server is

服务器代码如下

  1. /* EchoChanServer
  2. */
  3. package main
  4. import (
  5. "fmt"
  6. "os"
  7. "old/netchan"
  8. "strconv"
  9. )
  10. var count int = 0
  11. func main() {
  12. exporter := netchan.NewExporter()
  13. err := exporter.ListenAndServe("tcp", ":2345")
  14. checkError(err)
  15. echo := make(chan string)
  16. exporter.Export("echo", echo, netchan.Send)
  17. for {
  18. sCount := strconv.Itoa(count)
  19. lock := make(chan string)
  20. go handleSession(exporter, sCount, lock)
  21. <-lock
  22. echo <- sCount
  23. count++
  24. exporter.Drain(-1)
  25. }
  26. }
  27. func handleSession(exporter *netchan.Exporter, sCount string, lock chan string) {
  28. echoIn := make(chan string)
  29. exporter.Export("echoIn"+sCount, echoIn, netchan.Send)
  30. echoOut := make(chan string)
  31. exporter.Export("echoOut"+sCount, echoOut, netchan.Recv)
  32. fmt.Println("made " + "echoOut" + sCount)
  33. lock <- "done"
  34. for {
  35. s := <-echoOut
  36. echoIn <- s
  37. }
  38. // should unexport net channels
  39. }
  40. func checkError(err error) {
  41. if err != nil {
  42. fmt.Println("Fatal error ", err.Error())
  43. os.Exit(1)
  44. }
  45. }

and a client is

客户端代码如下

  1. /* EchoChanClient
  2. */
  3. package main
  4. import (
  5. "fmt"
  6. "old/netchan"
  7. "os"
  8. )
  9. func main() {
  10. if len(os.Args) != 2 {
  11. fmt.Println("Usage: ", os.Args[0], "host:port")
  12. os.Exit(1)
  13. }
  14. service := os.Args[1]
  15. importer, err := netchan.Import("tcp", service)
  16. checkError(err)
  17. fmt.Println("Got importer")
  18. echo := make(chan string)
  19. importer.Import("echo", echo, netchan.Recv, 1)
  20. fmt.Println("Imported in")
  21. count := <-echo
  22. fmt.Println(count)
  23. echoIn := make(chan string)
  24. importer.Import("echoIn"+count, echoIn, netchan.Recv, 1)
  25. echoOut := make(chan string)
  26. importer.Import("echoOut"+count, echoOut, netchan.Send, 1)
  27. for n := 1; n < 10; n++ {
  28. echoOut <- "hello "
  29. s := <-echoIn
  30. fmt.Println(s, n)
  31. }
  32. close(echoOut)
  33. os.Exit(0)
  34. }
  35. func checkError(err error) {
  36. if err != nil {
  37. fmt.Println("Fatal error ", err.Error())
  38. os.Exit(1)
  39. }
  40. }

Conclusion

总结

Network channels are a distributed analogue of local channels. They behave approximately the same, but due to limitations of the model some things have to be done a little differently.

网络channel是本地channel的分布式模拟。它们表现的近乎相同,但是由于模型的限制,存在一些不同。

Copyright Jan Newmarch, jan@newmarch.name

版权所有 Jan Newmarch, jan@newmarch.name

If you like this book, please contribute using Flattr

or donate using PayPal

如果你喜欢这本书,请通过Flattr支持

或是使用Paypal捐助,

网络 channels - 图1