2016-02-15 03:06:34 +00:00
|
|
|
|
## 8.3. 示例: 并发的Echo服务
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
clock服务器每一个连接都会起一个goroutine。在本节中我们会创建一个echo服务器,这个服务在每个连接中会有多个goroutine。大多数echo服务仅仅会返回他们读取到的内容,就像下面这个简单的handleConn函数所做的一样:
|
2015-12-29 05:43:02 +00:00
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
func handleConn(c net.Conn) {
|
2016-01-21 02:39:06 +00:00
|
|
|
|
io.Copy(c, c) // NOTE: ignoring errors
|
|
|
|
|
c.Close()
|
2015-12-29 05:43:02 +00:00
|
|
|
|
}
|
|
|
|
|
```
|
2016-01-02 13:17:21 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
一个更有意思的echo服务应该模拟一个实际的echo的“回响”,并且一开始要用大写HELLO来表示“声音很大”,之后经过一小段延迟返回一个有所缓和的Hello,然后一个全小写字母的hello表示声音渐渐变小直至消失,像下面这个版本的handleConn(译注:笑看作者脑洞大开):
|
2015-12-29 05:43:02 +00:00
|
|
|
|
|
2016-01-21 02:39:06 +00:00
|
|
|
|
<u><i>gopl.io/ch8/reverb1</i></u>
|
2015-12-29 05:43:02 +00:00
|
|
|
|
```go
|
|
|
|
|
func echo(c net.Conn, shout string, delay time.Duration) {
|
2016-01-21 02:39:06 +00:00
|
|
|
|
fmt.Fprintln(c, "\t", strings.ToUpper(shout))
|
|
|
|
|
time.Sleep(delay)
|
|
|
|
|
fmt.Fprintln(c, "\t", shout)
|
|
|
|
|
time.Sleep(delay)
|
|
|
|
|
fmt.Fprintln(c, "\t", strings.ToLower(shout))
|
2015-12-29 05:43:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func handleConn(c net.Conn) {
|
2016-01-21 02:39:06 +00:00
|
|
|
|
input := bufio.NewScanner(c)
|
|
|
|
|
for input.Scan() {
|
|
|
|
|
echo(c, input.Text(), 1*time.Second)
|
|
|
|
|
}
|
|
|
|
|
// NOTE: ignoring potential errors from input.Err()
|
|
|
|
|
c.Close()
|
2015-12-29 05:43:02 +00:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
我们需要升级我们的客户端程序,这样它就可以发送终端的输入到服务器,并把服务端的返回输出到终端上,这使我们有了使用并发的另一个好机会:
|
2015-12-29 05:43:02 +00:00
|
|
|
|
|
2016-01-21 02:39:06 +00:00
|
|
|
|
<u><i>gopl.io/ch8/netcat2</i></u>
|
2015-12-29 05:43:02 +00:00
|
|
|
|
```go
|
|
|
|
|
func main() {
|
2016-01-21 02:39:06 +00:00
|
|
|
|
conn, err := net.Dial("tcp", "localhost:8000")
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer conn.Close()
|
|
|
|
|
go mustCopy(os.Stdout, conn)
|
|
|
|
|
mustCopy(conn, os.Stdin)
|
2015-12-29 05:43:02 +00:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2018-06-09 16:27:25 +00:00
|
|
|
|
当main goroutine从标准输入流中读取内容并将其发送给服务器时,另一个goroutine会读取并打印服务端的响应。当main goroutine碰到输入终止时,例如,用户在终端中按了Control-D(^D),在windows上是Control-Z,这时程序就会被终止,尽管其它goroutine中还有进行中的任务。(在8.4.1中引入了channels后我们会明白如何让程序等待两边都结束。)
|
2015-12-29 05:43:02 +00:00
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
下面这个会话中,客户端的输入是左对齐的,服务端的响应会用缩进来区别显示。
|
|
|
|
|
客户端会向服务器“喊三次话”:
|
2015-12-29 05:43:02 +00:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
$ go build gopl.io/ch8/reverb1
|
|
|
|
|
$ ./reverb1 &
|
|
|
|
|
$ go build gopl.io/ch8/netcat2
|
|
|
|
|
$ ./netcat2
|
|
|
|
|
Hello?
|
|
|
|
|
HELLO?
|
|
|
|
|
Hello?
|
|
|
|
|
hello?
|
|
|
|
|
Is there anybody there?
|
|
|
|
|
IS THERE ANYBODY THERE?
|
|
|
|
|
Yooo-hooo!
|
|
|
|
|
Is there anybody there?
|
|
|
|
|
is there anybody there?
|
|
|
|
|
YOOO-HOOO!
|
|
|
|
|
Yooo-hooo!
|
|
|
|
|
yooo-hooo!
|
|
|
|
|
^D
|
|
|
|
|
$ killall reverb1
|
|
|
|
|
```
|
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
注意客户端的第三次shout在前一个shout处理完成之前一直没有被处理,这貌似看起来不是特别“现实”。真实世界里的回响应该是会由三次shout的回声组合而成的。为了模拟真实世界的回响,我们需要更多的goroutine来做这件事情。这样我们就再一次地需要go这个关键词了,这次我们用它来调用echo:
|
2015-12-29 05:43:02 +00:00
|
|
|
|
|
2016-01-21 02:39:06 +00:00
|
|
|
|
<u><i>gopl.io/ch8/reverb2</i></u>
|
2015-12-29 05:43:02 +00:00
|
|
|
|
```go
|
|
|
|
|
func handleConn(c net.Conn) {
|
2016-01-21 02:39:06 +00:00
|
|
|
|
input := bufio.NewScanner(c)
|
|
|
|
|
for input.Scan() {
|
|
|
|
|
go echo(c, input.Text(), 1*time.Second)
|
|
|
|
|
}
|
|
|
|
|
// NOTE: ignoring potential errors from input.Err()
|
|
|
|
|
c.Close()
|
2015-12-29 05:43:02 +00:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
go后跟的函数的参数会在go语句自身执行时被求值;因此input.Text()会在main goroutine中被求值。
|
|
|
|
|
现在回响是并发并且会按时间来覆盖掉其它响应了:
|
2015-12-29 05:43:02 +00:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
$ go build gopl.io/ch8/reverb2
|
|
|
|
|
$ ./reverb2 &
|
|
|
|
|
$ ./netcat2
|
|
|
|
|
Is there anybody there?
|
|
|
|
|
IS THERE ANYBODY THERE?
|
|
|
|
|
Yooo-hooo!
|
|
|
|
|
Is there anybody there?
|
|
|
|
|
YOOO-HOOO!
|
|
|
|
|
is there anybody there?
|
|
|
|
|
Yooo-hooo!
|
|
|
|
|
yooo-hooo!
|
|
|
|
|
^D
|
|
|
|
|
$ killall reverb2
|
|
|
|
|
```
|
|
|
|
|
|
2016-02-15 03:06:34 +00:00
|
|
|
|
让服务使用并发不只是处理多个客户端的请求,甚至在处理单个连接时也可能会用到,就像我们上面的两个go关键词的用法。然而在我们使用go关键词的同时,需要慎重地考虑net.Conn中的方法在并发地调用时是否安全,事实上对于大多数类型来说也确实不安全。我们会在下一章中详细地探讨并发安全性。
|