feat(listen): 自 go-hua 迁入监听器封装

- net.Listener 统一封装与 Close 解除 Accept 阻塞, 附测试、例程与 README
This commit is contained in:
2026-09-20 12:25:35 +08:00
parent a81e467a31
commit 6904133565
4 changed files with 197 additions and 0 deletions
+47
View File
@@ -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)
}
+28
View File
@@ -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` 亦可。
+40
View File
@@ -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()
}
+82
View File
@@ -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 阻塞")
}
}