From 690413356504ae5a731ef44c048877b0c723dfbb Mon Sep 17 00:00:00 2001 From: 4566704 <4566704@qq.com> Date: Sun, 20 Sep 2026 12:25:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(listen):=20=E8=87=AA=20go-hua=20=E8=BF=81?= =?UTF-8?q?=E5=85=A5=E7=9B=91=E5=90=AC=E5=99=A8=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - net.Listener 统一封装与 Close 解除 Accept 阻塞, 附测试、例程与 README --- examples/listen/main.go | 47 +++++++++++++++++++++++ listen/README.md | 28 ++++++++++++++ listen/listen.go | 40 ++++++++++++++++++++ listen/listen_test.go | 82 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 197 insertions(+) create mode 100644 examples/listen/main.go create mode 100644 listen/README.md create mode 100644 listen/listen.go create mode 100644 listen/listen_test.go diff --git a/examples/listen/main.go b/examples/listen/main.go new file mode 100644 index 0000000..47d5ce0 --- /dev/null +++ b/examples/listen/main.go @@ -0,0 +1,47 @@ +// listen 包示例:TCP 监听封装(NewListen/Accept/Close/Addr) +package main + +import ( + "bufio" + "fmt" + "net" + + "git.zeroonesoft.cn/golib/zogo/listen" +) + +func main() { + // 创建 TCP 监听(端口 0 表示由系统分配) + l, err := listen.NewListen("tcp", "127.0.0.1:0") + if err != nil { + panic(err) + } + defer l.Close() + fmt.Println("监听地址:", l.Addr()) + + // echo 服务 + go func() { + for { + conn, err := l.Accept() + if err != nil { + return + } + go func(c net.Conn) { + defer c.Close() + scanner := bufio.NewScanner(c) + for scanner.Scan() { + fmt.Fprintf(c, "echo: %s\n", scanner.Text()) + } + }(conn) + } + }() + + // 客户端连接测试 + conn, err := net.Dial("tcp", l.Addr().String()) + if err != nil { + panic(err) + } + defer conn.Close() + fmt.Fprintln(conn, "hello go-hua") + line, _ := bufio.NewReader(conn).ReadString('\n') + fmt.Print("收到:", line) +} diff --git a/listen/README.md b/listen/README.md new file mode 100644 index 0000000..52f7fee --- /dev/null +++ b/listen/README.md @@ -0,0 +1,28 @@ +# listen + +标准库 `net.Listener` 的简单封装,提供统一的监听器接口与关闭语义。 + +> 迁移自 go-hua/listen,代码保持原样。 + +## 用法 + +```go +import "git.zeroonesoft.cn/golib/zogo/listen" + +l, err := listen.NewListen("tcp", ":9000") +if err != nil { ... } +defer l.Close() + +for { + c, err := l.Accept() // Close 后 Accept 立即解除阻塞返回错误 + if err != nil { break } + go handle(c) +} +``` + +完整可运行例程:[examples/listen/main.go](../examples/listen/main.go) + +## 注意 + +- `Close` 后阻塞中的 `Accept` 会立即返回错误,无需额外唤醒逻辑。 +- 新代码若无需扩展点,直接用标准库 `net.Listen` 亦可。 diff --git a/listen/listen.go b/listen/listen.go new file mode 100644 index 0000000..0c0ca0b --- /dev/null +++ b/listen/listen.go @@ -0,0 +1,40 @@ +// Package listen 对标准库 net.Listener 的简单封装,提供统一的监听器接口。 +package listen + +import ( + "net" +) + +// Listener 对 net.Listener 的封装 +type Listener struct { + listener net.Listener +} + +// NewListen 创建监听器 +// network 如 "tcp",address 如 "127.0.0.1:8080" +func NewListen(network, address string) (*Listener, error) { + listener, err := net.Listen(network, address) + if err != nil { + return nil, err + } + l := new(Listener) + l.listener = listener + return l, nil +} + +// Accept waits for and returns the next connection to the listener. +func (l *Listener) Accept() (net.Conn, error) { + conn, err := l.listener.Accept() + return conn, err +} + +// Close closes the listener. +// Any blocked Accept operations will be unblocked and return errors. +func (l *Listener) Close() error { + return l.listener.Close() +} + +// Addr returns the listener's network address. +func (l *Listener) Addr() net.Addr { + return l.listener.Addr() +} diff --git a/listen/listen_test.go b/listen/listen_test.go new file mode 100644 index 0000000..f3ed14e --- /dev/null +++ b/listen/listen_test.go @@ -0,0 +1,82 @@ +package listen + +import ( + "bufio" + "net" + "testing" + "time" +) + +func TestNewListenAndEcho(t *testing.T) { + l, err := NewListen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("NewListen: %v", err) + } + defer l.Close() + + if l.Addr() == nil { + t.Fatal("Addr 不应为 nil") + } + + go func() { + for { + conn, err := l.Accept() + if err != nil { + return + } + go func(c net.Conn) { + defer c.Close() + scanner := bufio.NewScanner(c) + for scanner.Scan() { + c.Write(append([]byte("echo:"), scanner.Bytes()...)) + c.Write([]byte("\n")) + } + }(conn) + } + }() + + time.Sleep(50 * time.Millisecond) + + conn, err := net.Dial("tcp", l.Addr().String()) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer conn.Close() + + conn.Write([]byte("hello\n")) + conn.SetReadDeadline(time.Now().Add(2 * time.Second)) + line, err := bufio.NewReader(conn).ReadString('\n') + if err != nil { + t.Fatalf("ReadString: %v", err) + } + if line != "echo:hello\n" { + t.Errorf("echo = %q, want echo:hello\\n", line) + } +} + +func TestCloseUnblocksAccept(t *testing.T) { + l, err := NewListen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("NewListen: %v", err) + } + + accepted := make(chan error, 1) + go func() { + _, err := l.Accept() + accepted <- err + }() + + time.Sleep(50 * time.Millisecond) + if err := l.Close(); err != nil { + t.Fatalf("Close: %v", err) + } + + select { + case err := <-accepted: + if err == nil { + t.Error("Close 后 Accept 应返回错误") + } + case <-time.After(2 * time.Second): + t.Error("Close 未解除 Accept 阻塞") + } +}