2.4 实现 Lib v0.1

package lib

import (
    "bufio"
    "log"
    "net"
    "os"
    "os/signal"
    "syscall"
)

// 如果 err != nil,输出错误日志并退出进程
func FatalNotNil(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

// 如果 err != nil,输出错误日志
func LogNotNil(err error) {
    if err != nil {
        log.Println(err)
    }
}

// 输出消息到日志
func LogMessage(msg ...any) {
    log.Println(msg...)
}

// 输出连接建立与关闭消息到日志,包内私有方法,外部不能调用
func logConn() func() {
    log.Println("已连接")
    return func() {
        log.Println("已断开连接")
    }
}

// 接收连接另一侧发送的消息,输出消息到日志
func HandleConnection(conn net.Conn) {
    // 连接建立和断开时,分别输出日志
    defer logConn()()
    // 从当前方法返回时,关闭连接
    defer conn.Close()

    // 设置如何处理接收到的字节流,bufio.ScanLines 为逐行扫描的方式把字节流分割为消息流
    scanner := bufio.NewScanner(conn)
    scanner.Split(bufio.ScanLines)

    // 循环解析消息,每当解析出一条消息后,scan() 返回 true
    for scanner.Scan() {
        // 返回解析出的消息字节 slice
        bytes := scanner.Bytes()
        // 消息内容不为空,则输出到日志
        if len(bytes) > 0 {
            LogMessage(string(bytes))
        }
    }

    // 如果解析消息遇到错误,则输出错误到日志
    LogNotNil(scanner.Err())
}

// 信号监听处理器
func SignalHandler() {
    // 创建信号 channel
    sigchan := make(chan os.Signal, 1)

    // 注册要监听哪些信号
    signal.Notify(sigchan, os.Interrupt) // ctrl+c
    signal.Notify(sigchan, syscall.SIGTERM) // kill

    // 一直阻塞,直到收到信号,恢复执行并退出进程
    <-sigchan
    // 退出进程
    defer os.Exit(0)
}

关键字 defer 可以修饰函数调用,用来推迟该函数的执行,直到执行 defer 语句的上层函数返回。上方的程序有三处使用了 defer,如:

func HandleConnection(conn net.Conn) {
    // 连接建立和断开时,分别输出日志
    defer logConn()()
    // 从当前方法返回时,关闭连接
    defer conn.Close()

    // 省略...
}

首先执行 logConn(),立刻输出“已连接”并返回一个匿名函数,该匿名函数被 defer 修饰,推迟到 HandleConnection 函数返回时调用。同样地,conn.Close 被 defer 修饰,也推迟到 HandleConnection 函数返回时调用。被 defer 修饰的函数,按照 defer 语句出现顺序逆序执行。也就是说,在 HandleConnection 函数返回时,先执行 conn.Close(),再执行 logConn() 返回的匿名函数。

一般情况,不管使用资源的代码如何执行,资源在使用完成后必须释放,defer 是一种不常见但是非常有效的释放资源的方法。

关于 defer 可以看 Effective Go: Defer

defer 示例可以看 Tour: DeferGo by Example: Defer

SignalHandler 函数中使用到了信道 (channel),信道是连接并发协程的管道,你可以在一个协程中向信道写入值,然后从另一个协程中读取值。通过 signal.Notify 向系统注册关心的信号,并提供 chan os.Signal 类型参数 sigchan,当信号出现时,会向 sigchan channel 写入信号值 (sigchan <- signal),此时 <-sigchan 可以读取到该值,并从阻塞状态中恢复。

关于 channel 可以看 Effective Go: Channels

channel 示例可以看 Tour: ChannelsGo by Example: Channels