Go 基础语法小结(3) 😘 | 青训营

70 阅读10分钟

Additional

主要内容

  • 异常
  • 测试
  • 数据库
  • Golang 生态简介

异常处理

  • Go语言中相比于使用异常处理错误,Go语言鼓励使用返回错误值的方式来处理错误。函数通常会返回一个额外的error类型的值,以指示函数是否成功执行。这种错误处理方式使得代码更加清晰和可控,避免了异常处理带来的复杂性和性能开销。
package main

import (
	"errors"
	"fmt"
)

func divide(a, b float64) (float64, error) {
	if b == 0 {
		return 0, errors.New("division by zero")
	}
	return a / b, nil
}

func main() {
	result, err := divide(10, 0)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	fmt.Println("Result:", result)
}
  • panic() 和 recover()
    • panic() 用于手动抛出恐慌,终止程序继续运行
    • recover() 函数通常用于在发生 panic 后进行错误处理和恢复,以避免程序异常终止。它只能在延迟函数(deferred function)中使用,并且只有在发生 panic 时才会生效。
func handlePanic(a , b int){
	if a != b {
		panic("no equal")
	}
	defer func() {
		if r := recover(); r != nil{
			println(r)
		}
	}()
}

测试

Go语言内置了一个轻量级的测试框架,可以用于编写和运行单元测试。测试代码通常位于与被测试代码相同的包中,只有_test.go为后缀的文件会被自动识别为可测试文件。

package main

import "testing"

func Add(a, b int) int {
	return a + b
}

func TestAdd(t *testing.T) {
	result := Add(2, 3)
	if result != 5 {
		t.Errorf("Expected 5, but got %d", result)
	}
}

数据库

在Go语言中,可以使用第三方库来连接和操作数据库。以下是一个使用database/sql库连接到MySQL数据库的示例:

  1. 连接数据库
  2. 测试连接
  3. 执行查询
  4. 处理查询结果
func main() {
	// 连接数据库
	db, err := sql.Open("mysql", "username:password@tcp(localhost:3306)/database_name")
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()

	// 测试连接
	err = db.Ping()
	if err != nil {
		panic(err.Error())
	}

	fmt.Println("Connected to the database!")

	// 执行查询
	rows, err := db.Query("SELECT * FROM users")
	if err != nil {
		panic(err.Error())
	}
	defer rows.Close()

	// 处理查询结果
	for rows.Next() {
		var id int
		var name string
		err = rows.Scan(&id, &name)
		if err != nil {
			panic(err.Error())
		}
		fmt.Println("ID:", id, "Name:", name)
	}

	if err = rows.Err(); err != nil {
		panic(err.Error())
	}
}

使用database/sql库和go-sql-driver/mysql驱动程序来连接到MySQL数据库。首先,我们使用sql.Open()函数打开数据库连接,并在最后使用defer db.Close()关闭连接。然后,我们使用db.Ping()方法测试连接是否成功。

使用db.Query()方法执行一个查询,并使用rows.Next()rows.Scan()迭代和扫描查询结果。最后,我们使用rows.Err()检查是否有任何错误。

Golang 生态简介

以下是目前Golang生态中一些常用的开源框架、库和工具链:

  1. Web框架:

    • Gin:轻量级的Web框架,提供快速构建高性能的Web应用程序。
    • Echo:简单而高性能的Web框架,适用于构建RESTful API和微服务。
  2. 数据库和ORM:

    • GORM:简单而强大的ORM库,用于操作关系型数据库。
    • go-sqlmock:用于在单元测试中模拟SQL数据库的库。
  3. 消息队列:

    • RabbitMQ:功能强大的开源消息队列系统,用于实现可靠的消息传递。
    • NSQ:实时分布式消息平台,用于大规模系统的实时消息处理。
  4. 缓存:

    • Redis:高性能的键值存储数据库,用于缓存和数据存储。
    • Memcached:分布式内存对象缓存系统,用于缓存常用数据。
  5. DevOps工具链:

    • Docker:用于容器化应用程序的开源平台,实现应用程序的快速部署和可移植性。
    • Kubernetes:用于自动化部署、扩展和管理容器化应用程序的开源容器编排平台。
    • Jenkins:用于持续集成和持续交付的开源自动化工具。
  6. 云原生:

    • GKE(Google Kubernetes Engine):Google Cloud提供的托管Kubernetes服务。
    • AWS Lambda:亚马逊云提供的无服务器计算服务,用于构建和运行无服务器应用程序。
  7. RPC框架:

    • Thrift:可扩展的跨语言服务开发框架,支持多种编程语言和多种数据传输协议。
    • Micro:用于构建微服务的分布式系统开发框架,提供服务发现、负载均衡、消息传递等功能。
  8. 日志和监控:

    • Zap:快速、结构化的日志记录库,用于高性能日志记录。
    • Prometheus:开源的监控和警报系统,用于收集和存储指标数据。
  9. 测试和断言:

    • GoConvey:针对Go语言的测试框架,提供更具表达力的测试语法和实时测试结果反馈。
    • Testify:功能丰富的测试工具包,提供断言和测试套件等功能。
  10. 安全和认证:

    • JWT-Go:用于处理JSON Web令牌(JWT)的库,用于身份验证和授权。
    • Casbin:强大的访问控制库,支持基于角色的访问控制(RBAC)和访问控制列表(ACL)等模型。

Web

主要内容

  • Socket
  • Http client
  • Http server

Socket (套接字)

Socket 是一种用于网络通信的编程接口,它提供了一种机制,使得应用程序能够通过网络进行数据的发送和接收。

Socket 编程允许应用程序通过网络传输层协议进行数据的发送和接收。它提供了一组接口和方法,使得应用程序能够创建、连接、发送和接收数据,以实现网络通信的功能。

传输层协议

在计算机网络中,网络传输层协议是指在网络中负责数据传输的协议。它位于网络协议栈的第四层,上面是应用层,下面是网络层。

传输层的作用主要负责的是进程间通信,包括对应用层(解)复用,可靠性传输,拥塞控制和流量控制等

传输层协议通过网络层协议(IP,BGP) 等进行路由寻找到网络中的主机和设备。它使用 IP 地址和端口号来唯一标识网络中的应用程序(进程),以便进行数据的发送和接收。

关于协议中详细的字段请参考 RFC 官方文档

TCP & UDP client

常见的网络传输层协议有 TCP(传输控制协议)和 UDP(用户数据报协议)。

TCP 是一种面向连接的、可靠的(三次握手,四次挥手机制)、基于字节流的传输协议。它提供了可靠的数据传输、流量控制、拥塞控制和错误恢复等功能。TCP 通过建立连接、数据传输和断开连接的三次握手和四次挥手过程来确保数据的可靠传输。

UDP 是一种无连接的、不可靠的、基于数据报的传输协议。它提供了简单的数据传输功能,不保证数据的可靠性和顺序性。UDP 适用于对实时性要求较高、数据传输量较小、对可靠性要求不高的应用场景。

  • TCP 客户端发起请求
func TCP_client() {
	// 连接服务器
	conn, err := net.Dial("tcp", "localhost:8080")
	if err != nil {
		fmt.Println("Error connecting:", err.Error())
		return
	}
	defer conn.Close()

	// 发送消息给服务器
	message := "Hello, Server!"
	_, err = conn.Write([]byte(message))
	if err != nil {
		fmt.Println("Error sending message:", err.Error())
		return
	}

	fmt.Println("Message sent to server:", message)
}

  • UDP 客户端发起请求

func UDP_client() {
	serverAddr, err := net.ResolveUDPAddr("udp", "localhost:8080")
	if err != nil {
		log.Fatal("Error resolving UDP address:", err)
	}

	conn, err := net.DialUDP("udp", nil, serverAddr)
	if err != nil {
		log.Fatal("Error connecting to UDP server:", err)
	}

	defer conn.Close()

	message := []byte("Hello, UDP Server!")
	_, err = conn.Write(message)
	if err != nil {
		log.Fatal("Error sending data:", err)
	}

	fmt.Println("Message sent to UDP server:", string(message))
}

Server Socket

  • Golang 中也提供了负责 server 功能的 socket,用于监听指定端口号并在接收到请求时返回一个 socket 对象用于和客户端对话

  • TCP server

func TCP_server() {
	// 监听指定的端口
	listener, err := net.Listen("tcp", "localhost:8080")
	if err != nil {
		fmt.Println("Error listening:", err.Error())
		return
	}
	defer listener.Close()

	fmt.Println("Server listening on localhost:8080")

	// 接受客户端连接
	conn, err := listener.Accept()
	if err != nil {
		fmt.Println("Error accepting connection:", err.Error())
		return
	}
	defer conn.Close()

	// 处理客户端请求
	buffer := make([]byte, 1024)
	n, err := conn.Read(buffer)
	if err != nil {
		fmt.Println("Error reading:", err.Error())
		return
	}

	message := string(buffer[:n])
	fmt.Println("Received message:", message)
}

  • UDP server
func UDP_server() {
	addr, err := net.ResolveUDPAddr("udp", ":8080")
	if err != nil {
		log.Fatal("Error resolving UDP address:", err)
	}

	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		log.Fatal("Error starting UDP server:", err)
	}
	defer conn.Close()

	// 处理客户端请求
	fmt.Println("UDP server listening on port 8080")

	buffer := make([]byte, 1024)
	n, addr, err := conn.ReadFromUDP(buffer)
	if err != nil {
		log.Println("Error reading data:", err)
		return
	}

	message := string(buffer[:n])
	fmt.Println("Received message:", message)
	conn.WriteToUDP([]byte("Message received"), addr)
}

Http client

http协议

http 协议是应用层协议,有多种版本。常用的版本有 HTTP 1.1和 HTTP 2,和基于UDP的 HTTP 3协议

以下介绍 HTTP 1.1协议报文结构:

HTTP 报文是在客户端和服务器之间传输的数据块,它由请求报文和响应报文两种类型组成。结构示意图:

+---------------------------------------------------+
|                      报文首部                      |
+---------------------------------------------------+
|                      空行                         |
+---------------------------------------------------+
|                      报文主体                      |
+---------------------------------------------------+

HTTP 报文的结构可以分为三个部分:

  1. 报文首部(Header):包含了一系列的键值对,用于传递元数据和控制信息,如请求方法、响应状态码、内容类型、缓存控制等。

  2. 空行(Blank Line):报文首部和报文主体之间有一个空行,用于分隔报文首部和报文主体。

  3. 报文主体(Body):可选的,用于传输实际的数据内容,如请求的参数、响应的实体内容等。

对于请求报文,报文首部包含了请求行和请求头字段。请求行包含了请求方法、请求目标和协议版本。请求头字段包含了一系列的键值对,用于传递请求的元数据和控制信息。

对于响应报文,报文首部包含了状态行和响应头字段。状态行包含了协议版本、状态码和状态描述。响应头字段包含了一系列的键值对,用于传递响应的元数据和控制信息。

报文主体根据具体的请求或响应而有所不同,可以包含任意类型的数据,如文本、HTML、JSON、图片等。

发送请求

  • 以下是发送一个http 请求的示例,包括设置 请求路径和方法,请求体,请求头,请求参数,cookie
func example() {
	// 创建一个 HTTP 客户端
	client := &http.Client{}

	// 创建一个 HTTP 请求
	req, err := http.NewRequest("POST", "https://example.com/api", strings.NewReader("request body"))
	if err != nil {
		fmt.Println("Error creating request:", err)
		return
	}

	// 设置 Header
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer token")

	// 设置 Query 参数
	queryParams := url.Values{}
	queryParams.Set("param1", "value1")
	queryParams.Set("param2", "value2")
	req.URL.RawQuery = queryParams.Encode()

	// 设置 Cookie
	cookie := &http.Cookie{
		Name:  "session",
		Value: "abc123",
	}
	req.AddCookie(cookie)

	// 发送请求并获取响应
	resp, err := client.Do(req)
	
	//。。。
}

处理响应

  • 定义 Json 响应体结构

type CommitRecord struct {
	Commit      struct {
		Url       string `json:"url"`
		Committer struct {
			Name  string    `json:"name"`
			Email string    `json:"email"`
			Date  time.Time `json:"date"`
		} `json:"committer"`
		Message string `json:"message"`
	} `json:"commit"`
}

func (c CommitRecord) String() string {
	return fmt.Sprintf("commit:{\n\turl: %s\n\tdate: %s\n\tcommitter: %s\n\tmessage: %s\n}",
		c.Commit.Url, c.Commit.Committer.Date.String(), c.Commit.Committer.Name, c.Commit.Message)
}

  • 一个简单的爬虫示例: 通过 Github API 获取仓库提交信息的请求,包括填写参数和反序列化响应 json
func githubAPI(owner, repo string) {
	client := &http.Client{}
	request, err := http.NewRequest(
		"GET",
		fmt.Sprintf("https://api.github.com/repos/%s/%s/commits", owner, repo),
		nil)
	if err != nil {
		fmt.Println("Error creating request:", err)
		return
	}

	// 填写请求头
	request.Header.Set("Accept", "application/vnd.github+json")
	//request.Header.Set("Authorization", "Bearer token")
	request.Header.Set("X-GitHub-Api-Version", "2022-11-28")

	// 分页参数
	params := url.Values{}
	params.Set("per_page", "5")
	params.Set("page ", "1")
	request.URL.RawQuery = params.Encode()

	// 发送请求
	resp, err := client.Do(request)
	if err != nil {
		fmt.Println("Error sending request:", err)
		return
	}
	defer resp.Body.Close()

	// 读取响应体
	var record []CommitRecord
	err = json.NewDecoder(resp.Body).Decode(&record)
	if err != nil {
		log.Fatal("Error decoding JSON:", err)
	}
	for _, commit := range record {
		println(commit.String())
	}
}
  • 打印结果 (redis/redis 仓库的提交信息)
commit:{
        url: https://api.github.com/repos/redis/redis/git/commits/9b1d4f003de1b141ea850f01e7104e7e5c670620
        date: 2023-07-13 14:45:38 +0000 UTC
        committer: GitHub
        message: Merge pull request #12409 from chayim/ck-hired120

Upgrade to hiredis 1.2.0.
}

Http server

Go 的标准库中实现了一个基本的 Http server 的功能,可用于处理Http请求。

以下是一个最简单的 Hello world 示例

func helloHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, World!")
}

func Serve() {
    // 设置设置路由处理
	http.HandleFunc("/", helloHandler)

	fmt.Println("Starting HTTP server on port 8080...")
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal("Error starting HTTP server:", err)
	}
}