筆記 [Go] — TCP Server

Leo Lin
5 min readJun 15, 2018

從底層開始了解並且實作簡單的 TCP Server

Write to connection

首先看到第11行, net.Listen(“tcp”, “:8080”) 會回傳一個 Listener.

Listener 有三個方法, 分別是 Accept, Close 和 Addr.

  • Accept() (Conn, error) : 等待並且回傳一個 connection.
  • Close() error : 關閉 Listener.
  • Addr() Addr : 回傳 Listener 監聽的位置.

接著看到第15行, defer li.Close() 會讓程式自動幫我們處理關閉的動作, 但是寫這一行的動機並不是怕忘記在最後面關閉它所以先寫, 而是如果寫在後面 ( 例如將 li.Close() 寫在第29行之後 ), 我們的程式可能在中間的迴圈就噴錯了, 在這個情形, Listener 就不會被關閉, 因為 li.Close() 沒有被執行, 所以我們才需要使用 defer li.Close() 讓 Go 自動幫我們處理.

進入 for 迴圈, 我們的程式會因為第18行一直等待新的 connection 被建立, 當有新的 connection 被建立的時候, 才會往下執行後面的程式碼.

Conn 有以下幾種方法.

  • Read(b []byte) (n int, err error)
  • Write(b []byte) (n int, err error)
  • Close() error
  • LocalAddr() Addr
  • RemoteAddr() Addr
  • SetDeadline(t time.Time) error
  • SetReadDeadline(t time.Time) error
  • SetWriteDeadline(t time.Time) error

我們先把 Write(b []byte) (n int, err error) 挑出來對照

// io.WriteStringfunc WriteString(w Writer, s string) (n int, err error) {
if sw, ok := w.(stringWriter); ok {
return sw.WriteString(s)
}
return w.Write([]byte(s))
}
// fmt.Fprintlnfunc Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintln(a)
// 這邊的 p.buf 是 buffer, type buffer []byte
n, err = w.Write(p.buf)
p.free()
return
}
// fmt.Fprintffunc Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
// 這邊的 p.buf 是 buffer, type buffer []byte
n, err = w.Write(p.buf)
p.free()
return
}

比對一下後會發現, io.WriteString, fmt.Fprintln, fmt.Fprintf 剛好都會吃一個 Writer 的輸入, 並且輸出 (n int, err error), 因此我們可以使用以上三種方法來將東西寫入 Conn.

實際跑跑看上面這份程式碼, 並且用 telnet 測試.

Read from connection

在23行可以看到當一個 connection 被建立之後, 會以一個獨立的 process 去跑 handle 這個 function, 因此原本的程式不會被 block 住, 這種做法是因為, 當有很多人同時連上我們的伺服器, 伺服器應該要能夠讓所有人都成功地進行溝通, 而不是只有第一個連上的.

實際執行後長這樣子, 用三個 terminal 去打伺服器做測試, 可以看到 Server 可以同時處理很多個連接, 並且取得透過 Conn 傳過來的訊息.

總結

這是一個比較底層的範例, 如果要做 HTTP Server 的話其實 Go 有提供很方便的做法, 有興趣的話可以參考這一篇.

--

--