使用Go?goroutine實現(xiàn)并發(fā)的Clock服務(wù)
網(wǎng)絡(luò)編程是并發(fā)大顯身手的一個領(lǐng)域,由于服務(wù)器是最典型的需要同時處理很多連接的程序,這些連接一般來自于彼此獨立的客戶端。
本文我們會用到go語言的net包,這個包提供編寫一個網(wǎng)絡(luò)客戶端或者服務(wù)器程序的基本組件,無論兩者間通信是使用TCP、UDP或者Unix domain sockets。
我們的第一個例子是一個順序執(zhí)行的時鐘服務(wù)器,它會每隔一秒鐘將當(dāng)前時間寫到客戶端:
// Clock1 is a TCP server that periodically writes the time.
package main
import (
"io"
"log"
"net"
"time"
)
func main() {
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err) // e.g., connection aborted
continue
}
handleConn(conn) // handle one connection at a time
}
}
func handleConn(c net.Conn) {
defer c.Close()
for {
_, err := io.WriteString(c, time.Now().Format("15:04:05\n"))
if err != nil {
return // e.g., client disconnected
}
time.Sleep(1 * time.Second)
}
}Listen函數(shù)創(chuàng)建了一個net.Listener的對象,這個對象會監(jiān)聽一個網(wǎng)絡(luò)端口上到來的連接,在這個例子里我們用的是TCP的localhost:8000端口。listener對象的Accept方法會直接阻塞,直到一個新的連接被創(chuàng)建,然后會返回一個net.Conn對象來表示這個連接。
handleConn函數(shù)會處理一個完整的客戶端連接。在一個for死循環(huán)中,用time.Now()獲取當(dāng)前時刻,然后寫到客戶端。由于net.Conn實現(xiàn)了io.Writer接口,我們可以直接向其寫入內(nèi)容。這個死循環(huán)會一直執(zhí)行,直到寫入失敗。最可能的原因是客戶端主動斷開連接。這種情況下handleConn函數(shù)會用defer調(diào)用關(guān)閉服務(wù)器側(cè)的連接,然后返回到主函數(shù),繼續(xù)等待下一個連接請求。
time.Time.Format方法提供了一種格式化日期和時間信息的方式。它的參數(shù)是一個格式化模板,標(biāo)識如何來格式化時間,而這個格式化模板限定為Mon Jan 2 03:04:05PM 2006 UTC-0700。有8個部分(周幾、月份、一個月的第幾天……)。可以以任意的形式來組合前面這個模板;出現(xiàn)在模板中的部分會作為參考來對時間格式進(jìn)行輸出。
在上面的例子中我們只用到了小時、分鐘和秒。time包里定義了很多標(biāo)準(zhǔn)時間格式,比如time.RFC1123。在進(jìn)行格式化的逆向操作time.Parse時,也會用到同樣的策略。(譯注:這是go語言和其它語言相比比較奇葩的一個地方。你需要記住格式化字符串是1月2日下午3點4分5秒零六年UTC-0700,而不像其它語言那樣Y-m-d H:i:s一樣,當(dāng)然了這里可以用1234567的方式來記憶,倒是也不麻煩。)
為了連接例子里的服務(wù)器,我們需要一個客戶端程序,比如netcat這個工具(nc命令),這個工具可以用來執(zhí)行網(wǎng)絡(luò)連接操作。
$ go build gopl.io/ch8/clock1
$ ./clock1 &
$ nc localhost 8000
13:58:54
13:58:55
13:58:56
13:58:57
^C
客戶端將服務(wù)器發(fā)來的時間顯示了出來,我們用Control+C來中斷客戶端的執(zhí)行,在Unix系統(tǒng)上,你會看到^C這樣的響應(yīng)。如果你的系統(tǒng)沒有裝nc這個工具,你可以用telnet來實現(xiàn)同樣的效果,或者也可以用我們下面的這個用go寫的簡單的telnet程序,用net.Dial就可以簡單地創(chuàng)建一個TCP連接:
// Netcat1 is a read-only TCP client.
package main
import (
"io"
"log"
"net"
"os"
)
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
mustCopy(os.Stdout, conn)
}
func mustCopy(dst io.Writer, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}這個程序會從連接中讀取數(shù)據(jù),并將讀到的內(nèi)容寫到標(biāo)準(zhǔn)輸出中,直到遇到end of file的條件或者發(fā)生錯誤。mustCopy這個函數(shù)我們在本節(jié)的幾個例子中都會用到。讓我們同時運行兩個客戶端來進(jìn)行一個測試,這里可以開兩個終端窗口,下面左邊的是其中的一個的輸出,右邊的是另一個的輸出:
$ go build gopl.io/ch8/netcat1
$ ./netcat1
13:58:54 $ ./netcat1
13:58:55
13:58:56
^C
13:58:57
13:58:58
13:58:59
^C
$ killall clock1
killall命令是一個Unix命令行工具,可以用給定的進(jìn)程名來殺掉所有名字匹配的進(jìn)程。
第二個客戶端必須等待第一個客戶端完成工作,這樣服務(wù)端才能繼續(xù)向后執(zhí)行;因為我們這里的服務(wù)器程序同一時間只能處理一個客戶端連接。我們這里對服務(wù)端程序做一點小改動,使其支持并發(fā):在handleConn函數(shù)調(diào)用的地方增加go關(guān)鍵字,讓每一次handleConn的調(diào)用都進(jìn)入一個獨立的goroutine。
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err) // e.g., connection aborted
continue
}
go handleConn(conn) // handle connections concurrently
}現(xiàn)在多個客戶端可以同時接收到時間了:
$ go build gopl.io/ch8/clock2
$ ./clock2 &
$ go build gopl.io/ch8/netcat1
$ ./netcat1
14:02:54 $ ./netcat1
14:02:55 14:02:55
14:02:56 14:02:56
14:02:57 ^C
14:02:58
14:02:59 $ ./netcat1
14:03:00 14:03:00
14:03:01 14:03:01
^C 14:03:02
^C
$ killall clock2
至此,完成!
到此這篇關(guān)于使用Go goroutine實現(xiàn)并發(fā)的Clock服務(wù)的文章就介紹到這了,更多相關(guān)Go goroutine內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- golang gin 框架 異步同步 goroutine 并發(fā)操作
- Go語言中的并發(fā)goroutine底層原理
- Go并發(fā)編程之goroutine使用正確方法
- Go 并發(fā)編程Goroutine的實現(xiàn)示例
- Golang 語言控制并發(fā) Goroutine的方法
- golang并發(fā)編程中Goroutine 協(xié)程的實現(xiàn)
- Go中Goroutines輕量級并發(fā)的特性及效率探究
- Go語言使用goroutine及通道實現(xiàn)并發(fā)詳解
- Go 控制協(xié)程(goroutine)的并發(fā)數(shù)量
- Go語言使用Goroutine并發(fā)打印的項目實踐

