2015-12-18 06:49:31 +00:00
|
|
|
|
## 8.3. 示例: 併發的Echo服務
|
2015-12-09 07:45:11 +00:00
|
|
|
|
|
2015-12-29 08:15:56 +00:00
|
|
|
|
clock服務器每一個連接都會起一個goroutine。在本節中我們會創建一個echo服務器,這個服務在每個連接中會有多個goroutine。大多數echo服務僅僅會返迴他們讀取到的內容,就像下面這個簡單的handleConn函數所做的一樣:
|
2015-12-29 05:43:02 +00:00
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
func handleConn(c net.Conn) {
|
|
|
|
|
io.Copy(c, c) // NOTE: ignoring errors
|
|
|
|
|
c.Close()
|
|
|
|
|
}
|
|
|
|
|
```
|
2016-01-02 13:17:21 +00:00
|
|
|
|
|
2015-12-29 08:15:56 +00:00
|
|
|
|
一個更有意思的echo服務應該模擬一個實際的echo的“迴響”,併且一開始要用大寫HELLO來表示“聲音很大”,之後經過一小段延遲返迴一個有所緩和的Hello,然後一個全小寫字母的hello表示聲音漸漸變小直至消失,像下面這個版本的handleConn(譯註:笑看作者腦洞大開):
|
2015-12-29 05:43:02 +00:00
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
gopl.io/ch8/reverb1
|
|
|
|
|
func echo(c net.Conn, shout string, delay time.Duration) {
|
|
|
|
|
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))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func handleConn(c net.Conn) {
|
|
|
|
|
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 08:15:56 +00:00
|
|
|
|
我們需要陞級我們的客戶端程序,這樣它就可以發送終端的輸入到服務器,併把服務端的返迴輸出到終端上,這使我們有了使用併發的另一個好機會:
|
2015-12-29 05:43:02 +00:00
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
gopl.io/ch8/netcat2
|
|
|
|
|
func main() {
|
|
|
|
|
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 08:15:56 +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
|
|
|
|
|
2015-12-29 08:15:56 +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
|
|
|
|
|
```
|
|
|
|
|
|
2015-12-29 08:15:56 +00:00
|
|
|
|
註意客戶端的第三次shout在前一個shout處理完成之前一直沒有被處理,這貌似看起來不是特别“現實”。眞實世界里的迴響應該是會由三次shout的迴聲組合而成的。爲了模擬眞實世界的迴響,我們需要更多的goroutine來做這件事情。這樣我們就再一次地需要go這個關鍵詞了,這次我們用它來調用echo:
|
2015-12-29 05:43:02 +00:00
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
gopl.io/ch8/reverb2
|
|
|
|
|
func handleConn(c net.Conn) {
|
|
|
|
|
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 08:15:56 +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
|
|
|
|
|
```
|
|
|
|
|
|
2015-12-29 08:15:56 +00:00
|
|
|
|
讓服務使用併發不隻是處理多個客戶端的請求,甚至在處理單個連接時也可能會用到,就像我們上面的兩個go關鍵詞的用法。然而在我們使用go關鍵詞的同時,需要慎重地考慮net.Conn中的方法在併發地調用時是否安全,事實上對於大多數類型來説也確實不安全。我們會在下一章中詳細地探討併發安全性。
|