1. 概述#
關於網路編程其實是一個很龐大的領域,本文只是簡單的演示了如何使用 Golang 的網路包進行通信。
- tcp socket 編程(網路編程的主流,底層基於 tcp/ip)
- b/s 結構的 http 編程(用瀏覽器去訪問網站的時候,使用的就是 http 協議,而 http 底層也是用 tcp socket 實現的)
tcp socket 編程:
- 伺服器處理流程:
- 監聽端口
- 接受客戶端的 tcp 請求,建立與客戶端的連接
- 創建 goroutine,處理該連接的請求
- 客戶端處理流程:
- 建立與伺服器的連接
- 發送請求數據,接受伺服器返回的結果
- 關閉連接
2. 互聯網分層模型#
互聯網的邏輯實現被分為幾層,每一層都有自己負責的內容,就像一棟建築一樣,每一層都需要下層的支持。我們平常接觸到的大多是最上面的一層,它是經過層層封裝,然後以最友好的方式呈現在我們面前。
3. 幾個網路核心包#
3.1 net#
net.Dial () 方法用於客戶端連接網路
net.Listen () 方法用於伺服器接受網路連接
3.2 net/http#
net/http 提供了用於開發強大的 Web 伺服端和客戶端的函數功能
http.Get () 和 https.Get () 方法作為客戶端可以用來發送 HTTP 和 HTTPS 請求
http.ListenAndServe () 方法可以用來創建 Web 伺服器,並且指定伺服器監聽的 IP 地址和 TCP 端口號,然後在該方法中處理傳入的請求
3.3 http.RoundTripper#
可以把 RoundTripper 看成是 http.Client 的中間件
使用場景:
- 快取 http responses(若快取存在直接從快取中取)
- 設置適當的 HTTP headers
- Rate limiting
4. Go 實現 DNS 查詢#
DNS(域名系統)的作用是將 IP 地址轉換為域名,或者將域名轉換為 IP 地址
package main
import (
"fmt"
"net"
"os"
)
func main() {
arguments := os.Args
if len(arguments) == 1 {
fmt.Println("Please provide an argument!")
return
}
input := arguments[1]
IPaddress := net.ParseIP(input) //函數net.ParseIP將傳入的字符串解析為IP地址
if IPaddress == nil {
IPs, err := lookHostname(input)
if err == nil {
for _, singleIP := range IPs {
fmt.Println(singleIP)
}
}
} else {
hosts, err := lookIP(input)
if err == nil {
for _, hostname := range hosts {
fmt.Println(hostname)
}
}
}
}
func lookIP(address string) ([]string, error) {
hosts, err := net.LookupAddr(address) //返回與傳入的IP地址相匹配的主機列表(/etc/hosts)
if err != nil {
return nil, err
}
return hosts, nil
}
func lookHostname(hostname string) ([]string, error) {
IPs, err := net.LookupHost(hostname) //返回與傳入的主機名相匹配的IP地址列表
if err != nil {
return nil, err
}
return IPs, nil
}
5. Go 實現 Web 功能#
可以用 Go 的標準庫函數來實現一個 web 伺服器
需要真正強大的 web 伺服器的話,還是建議用 apache 或 nginx 這樣的 web 伺服器
5.1 Web 伺服器#
package main
import (
"fmt"
"net/http"
"os"
"time"
)
func main() {
PORT := ":8001"
arguments := os.Args
if len(arguments) == 1 {
fmt.Println("Using default port number: ", PORT)
}else{
PORT = ":" + arguments[1]
}
http.HandleFunc("/time", timeHandler) //把一個URL路由與一個處理函數關聯起來
http.HandleFunc("/", myHandler)
err := http.ListenAndServe(PORT, nil)
if err != nil {
fmt.Println(err)
return
}
}
func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Serving: %s\n", r.URL.Path)
fmt.Printf("Served: %s\n", r.Host)
}
func timeHandler(w http.ResponseWriter, r *http.Request) {
t := time.Now().Format(time.RFC1123)
Body := "The current time is:" //動態輸出
fmt.Fprintf(w, "<h1 align=\"center\">%s</h1>", Body)
fmt.Fprintf(w, "<h2 align=\"center\">%s</h2>\n", t)
fmt.Fprintf(w, "Serving: %s\n", r.URL.Path)
fmt.Fprintf(w, "Served time for: %s\n", r.Host)
}
5.2 Web 客戶端#
package main
import (
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"os"
"path/filepath"
"strings"
"time"
)
func main() {
if len(os.Args) != 2 {
fmt.Printf("Usage: %s URL\n", filepath.Base(os.Args[0]))
return
}
URL, err := url.Parse(os.Args[1]) //解析傳入的URL
if err != nil {
fmt.Println("Error in parsing:", err)
return
}
c := &http.Client{
Timeout: 15 * time.Second,
}
request, err := http.NewRequest("GET", URL.String(), nil)
if err != nil {
fmt.Println("Get:", err)
return
}
httpData, err := c.Do(request)
if err != nil {
fmt.Println("Error in Do():", err)
return
}
fmt.Println("Status code:", httpData.Status)
header, _ := httputil.DumpResponse(httpData, false)
fmt.Println(string(header))
contentType := httpData.Header.Get("Content-Type")
characterSet := strings.SplitAfter(contentType, "charset=")
if len(characterSet) > 1 {
fmt.Println("Character Set:", characterSet[1])
}
if httpData.ContentLength == -1 {
fmt.Println("ContentLength is unknown!")
} else {
fmt.Println("ContentLength:", httpData.ContentLength)
}
length := 0
var buffer [1024]byte
r := httpData.Body
for {
n, err := r.Read(buffer[0:])
if err != nil {
fmt.Println(err)
break
}
length = length + n
}
fmt.Println("Calculated response data length:", length)
}
6. Go 實現 TCP 功能#
6.1 TCP 伺服器#
package main
import(
"bufio"
"fmt"
"net"
"os"
"strings"
"time"
)
func main() {
arguments := os.Args
if len(arguments) == 1 {
fmt.Println("Please provide port number")
return
}
PORT := ":" + arguments[1]
l, err := net.Listen("tcp" , PORT)
if err != nil {
fmt.Println(err)
return
}
defer l.Close()
c, err := l.Accept()
if err != nil {
fmt.Println(err)
return
}
for {
netData, err := bufio.NewReader(c).ReadString('\n')
if err != nil {
fmt.Println(err)
return
}
if strings.TrimSpace(string(netData)) == "STOP" {
fmt.Println("Exiting TCP server!")
return
}
fmt.Print("-> ", string(netData))
t := time.Now()
myTime := t.Format(time.RFC3339) + "\n"
c.Write([]byte(myTime))
}
}
6.2 TCP 客戶端#
package main
import (
"bufio"
"fmt"
"net"
"os"
"strings"
)
func main(){
arguments := os.Args
if len(arguments) == 1 {
fmt.Println("Please provide host:port.")
return
}
CONNECT := arguments[1]
c, err := net.Dial("tcp", CONNECT)
if err != nil {
fmt.Println(err)
return
}
for {
reader := bufio.NewReader(os.Stdin)
fmt.Print(">> ")
text, _ := reader.ReadString('\n')
fmt.Fprintf(c, text + "\n")
message, _ := bufio.NewReader(c).ReadString('\n')
fmt.Print("->: " + message)
if strings.TrimSpace(string(text)) == "STOP" {
fmt.Println("TCP client exiting...")
return
}
}
}
7. Go 實現 UDP 功能#
7.1 UDP 伺服器#
package main
import (
"fmt"
"net"
)
func main() {
// 創建監聽
socket, err := net.ListenUDP("udp4", &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 8080,
})
if err != nil {
fmt.Println("監聽失敗!", err)
return
}
defer socket.Close()
for {
// 循環讀取數據
data := make([]byte, 4096)
read, remoteAddr, err := socket.ReadFromUDP(data)
if err != nil {
fmt.Println("讀取數據失敗!", err)
continue
}
fmt.Println(read, remoteAddr)
fmt.Printf("%s\n\n", data)
// 發送數據
senddata := []byte("hello client!")
_, err = socket.WriteToUDP(senddata, remoteAddr)
if err != nil {
return
fmt.Println("發送數據失敗!", err)
}
}
}
7.2 UDP 客戶端#
package main
import (
"fmt"
"net"
)
func main() {
// 創建連接
socket, err := net.DialUDP("udp4", nil, &net.UDPAddr{
IP: net.IPv4(192, 168, 110, 110),
Port: 8080,
})
if err != nil {
fmt.Println("連接失敗!", err)
return
}
defer socket.Close()
// 發送數據
senddata := []byte("hello server!")
_, err = socket.Write(senddata)
if err != nil {
fmt.Println("發送數據失敗!", err)
return
}
// 接收數據
data := make([]byte, 4096)
read, remoteAddr, err := socket.ReadFromUDP(data)
if err != nil {
fmt.Println("讀取數據失敗!", err)
return
}
fmt.Println(read, remoteAddr)
fmt.Printf("%s\n", data)
}
8. 總結#
本文主要介紹了 Go 版本的 web 伺服端、客戶端,TCP 伺服端、客戶端和 UDP 伺服端、客戶端,還介紹了網路分層的概念,GO 版本的 DNS 查詢和幾個網路包。