xfeng

xfeng

Sporting | Reading | Technology | Recording
github
bilibili

Golang網路程式設計基礎

image

1. 概述#

關於網路編程其實是一個很龐大的領域,本文只是簡單的演示了如何使用 Golang 的網路包進行通信。

  • tcp socket 編程(網路編程的主流,底層基於 tcp/ip)
  • b/s 結構的 http 編程(用瀏覽器去訪問網站的時候,使用的就是 http 協議,而 http 底層也是用 tcp socket 實現的)

tcp socket 編程:

  • 伺服器處理流程:
    • 監聽端口
    • 接受客戶端的 tcp 請求,建立與客戶端的連接
    • 創建 goroutine,處理該連接的請求
  • 客戶端處理流程:
    • 建立與伺服器的連接
    • 發送請求數據,接受伺服器返回的結果
    • 關閉連接

2. 互聯網分層模型#

互聯網的邏輯實現被分為幾層,每一層都有自己負責的內容,就像一棟建築一樣,每一層都需要下層的支持。我們平常接觸到的大多是最上面的一層,它是經過層層封裝,然後以最友好的方式呈現在我們面前。

image

image

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 查詢和幾個網路包。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。