中文 | English
Chez Scheme 的网络编程库,提供 Socket FFI 绑定、Scheme 风格的 Port 封装,以及基于 call/cc 的异步 I/O 调度器。
- Socket Port 封装 - 使用标准 Scheme I/O 函数 (
get-line,put-string,read,write等) - 统一调度器接口 - 支持两种模式,使用相同 API:
- 协程式 (
coroutine) - 基于call/cc的协作式多任务,适合 I/O 密集型 - 并行式 (
parallel) - 基于 OS 线程的真并行,适合 CPU 密集型
- 协程式 (
- 跨平台 I/O 多路复用 - 支持
epoll(Linux)、kqueue(macOS/BSD)、poll(通用) - UDP 支持 - 完整的 UDP 数据报 socket API(同步和异步)
- Unix Domain Socket - 高效的本地进程间通信
- 信号处理 - 将 Unix 信号转换为可等待的事件
- 子进程管理 - 创建和管理子进程
- 异步 DNS - 非阻塞的 DNS 解析
- 异步文件 I/O - 非阻塞的文件操作
- 定时器支持 -
async-sleep、set-timeout、set-interval - 自动资源管理 -
dynamic-wind确保连接正确关闭 - 详细错误处理 - 自定义 condition 类型,支持
guard
- Chez Scheme 9.5 或更高版本
- Unix-like 系统(Linux、macOS、BSD)
git clone https://github.com/user/chez-socket.git
cd chez-socketcd tests
scheme --libdirs ../lib:. run-all-tests.ss如果看到 "All tests passed!",说明安装成功。
最简单的方式是在运行 Scheme 时通过 --libdirs 参数指定库路径:
# 运行脚本
scheme --libdirs /path/to/chez-socket/lib your-script.ss
# 交互式 REPL
scheme --libdirs /path/to/chez-socket/lib
> (import (socket port))
> (import (async scheduler))将库路径添加到 CHEZSCHEMELIBDIRS 环境变量:
# 添加到 ~/.bashrc 或 ~/.zshrc
export CHEZSCHEMELIBDIRS="/path/to/chez-socket/lib:$CHEZSCHEMELIBDIRS"
# 或在当前会话中临时设置
export CHEZSCHEMELIBDIRS="/path/to/chez-socket/lib"
scheme在 Scheme 代码开头动态添加库路径:
#!/usr/bin/env scheme-script
;; 添加库路径
(library-directories
(cons "/path/to/chez-socket/lib"
(library-directories)))
;; 然后正常导入
(import (socket port))
(import (async scheduler))
;; 你的代码...# 找到 Chez Scheme 的库目录
scheme --version # 查看版本
# 创建符号链接(需要 root 权限)
sudo ln -s /path/to/chez-socket/lib/socket /usr/lib/csv<version>/socket
sudo ln -s /path/to/chez-socket/lib/async /usr/lib/csv<version>/async(import (socket port))
;; 连接并发送 HTTP 请求
(with-tcp-text-connection ((in out) "example.com" 80)
(put-string out "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
(flush-output-port out)
(display (get-line in))) ; 显示响应第一行(import (socket port))
(let ([server (open-tcp-server 8080)])
(printf "Server listening on port 8080~n")
(let-values ([(in out host port) (server-socket-accept server)])
(printf "Client connected: ~a:~a~n" host port)
(let ([text-out (transcoded-port out (native-transcoder))])
(put-string text-out "Hello, Client!\n")
(flush-output-port text-out)
(close-port text-out)))
(server-socket-close server))(import (async scheduler)
(async port))
(define sched (make-scheduler)) ; 默认协程式
(spawn
(lambda ()
(with-async-tcp-text-connection ((in out) "example.com" 80)
(put-string out "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
(flush-output-port out)
(display (get-line in)))
(scheduler-stop! sched)))
(scheduler-run! sched) ; 阻塞直到所有任务完成(import (async scheduler))
(define sched (make-scheduler))
(spawn
(lambda ()
;; 异步休眠 1 秒
(async-sleep 1000)
(display "1 second passed\n")
;; 设置 500ms 后执行的回调
(set-timeout
(lambda () (display "Timeout fired!\n"))
500)
;; 每 200ms 执行一次
(let ([cancel (set-interval
(lambda () (display "Tick!\n"))
200)])
(async-sleep 1000)
(cancel)) ; 取消定时器
(scheduler-stop! sched)))
(scheduler-run! sched)(import (socket udp))
;; UDP 服务器
(let ([fd (udp-socket)])
(udp-bind fd 8080)
(let-values ([(data host port) (udp-recvfrom fd)])
(printf "Received from ~a:~a: ~a~n" host port (utf8->string data))
(udp-sendto fd "Pong" host port))
(udp-close fd))
;; UDP 客户端
(let ([fd (udp-socket)])
(udp-sendto fd "Ping" "127.0.0.1" 8080)
(let-values ([(data host port) (udp-recvfrom fd)])
(printf "Response: ~a~n" (utf8->string data)))
(udp-close fd))(import (async udp)
(async coroutine-scheduler))
(define sched (make-coroutine-scheduler))
;; 服务器协程
(spawn
(lambda ()
(let ([fd (udp-socket)])
(udp-bind fd 8080)
(udp-set-nonblocking! fd)
(let-values ([(data host port) (async-udp-recvfrom fd)])
(printf "收到来自 ~a:~a: ~a~n" host port (utf8->string data))
(async-udp-sendto fd "Pong" host port))
(udp-close fd)
(scheduler-stop! sched))))
;; 客户端协程
(spawn
(lambda ()
(let ([fd (udp-socket)])
(udp-set-nonblocking! fd)
(async-udp-sendto fd "Ping" "127.0.0.1" 8080)
(let-values ([(data host port) (async-udp-recvfrom fd)])
(printf "收到回复: ~a~n" (utf8->string data)))
(udp-close fd))))
(scheduler-run! sched)(import (socket unix))
;; 服务器
(let ([fd (unix-stream-socket)])
(unix-unlink "/tmp/my.sock") ; 删除旧文件
(unix-bind fd "/tmp/my.sock")
(unix-listen fd)
(let-values ([(client-fd path) (unix-accept fd)])
(unix-send client-fd "Hello!")
(unix-close client-fd))
(unix-close fd))
;; 客户端
(let ([fd (unix-stream-socket)])
(unix-connect fd "/tmp/my.sock")
(let ([data (unix-recv fd)])
(printf "Received: ~a~n" (utf8->string data)))
(unix-close fd))(import (async signal)
(socket constants))
;; 创建信号处理器
(let ([handler (make-signal-handler (list SIGINT SIGTERM))])
(printf "Waiting for SIGINT or SIGTERM...~n")
(printf "Press Ctrl+C or run: kill -INT ~a~n" (getpid))
;; 获取可等待的文件描述符
(let ([fd (signal-handler-fd handler)])
;; 可以将 fd 注册到 I/O 多路复用器
;; 这里简单地轮询
(let loop ()
(let ([sig-info (signal-try-read handler)])
(if sig-info
(printf "Received signal: ~a~n" (signal-info-signo sig-info))
(begin
(sleep (make-time 'time-duration 100000000 0)) ; 100ms
(loop))))))
(close-signal-handler! handler))(import (async process))
;; 执行命令并读取输出
(let ([proc (spawn-process "ls" '("-la"))])
(let ([stdout (process-stdout proc)])
(let ([text-in (transcoded-port stdout (native-transcoder))])
(let loop ()
(let ([line (get-line text-in)])
(unless (eof-object? line)
(printf "~a~n" line)
(loop))))))
(process-wait proc)
(printf "Exit code: ~a~n" (process-exit-code proc)))
;; 向子进程发送输入
(let ([proc (spawn-process "cat" '())])
(let ([stdin (process-stdin proc)]
[stdout (process-stdout proc)])
(let ([text-out (transcoded-port stdin (native-transcoder))]
[text-in (transcoded-port stdout (native-transcoder))])
(put-string text-out "Hello from parent!\n")
(flush-output-port text-out)
(close-port text-out)
(printf "Child echoed: ~a~n" (get-line text-in))))
(process-wait proc))(import (async blocking thread-pool)
(async blocking dns))
(let ([pool (make-blocking-pool)])
(blocking-pool-start! pool)
;; 同步解析
(let ([results (resolve-sync "example.com")])
(for-each
(lambda (info)
(printf "IP: ~a (family: ~a)~n"
(addr-info-addr info)
(if (= (addr-info-family info) 2) "IPv4" "IPv6")))
results))
(blocking-pool-shutdown! pool))(import (async blocking thread-pool)
(async blocking file))
(let ([pool (make-blocking-pool)])
(blocking-pool-start! pool)
;; 写入文件
(file-write-sync "/tmp/test.txt" (string->utf8 "Hello, World!\n"))
;; 读取文件
(let ([content (file-read-sync "/tmp/test.txt")])
(printf "Content: ~a" (utf8->string content)))
;; 获取文件信息
(let ([info (file-stat-sync "/tmp/test.txt")])
(when info
(printf "Size: ~a bytes~n" (stat-info-size info))
(printf "Type: ~a~n" (stat-info-type info))))
(blocking-pool-shutdown! pool))cd examples
# Echo 服务器(终端 1)
scheme --libdirs ../lib --program echo-server.ss 8080
# Echo 客户端(终端 2)
scheme --libdirs ../lib --program echo-client.ss 127.0.0.1 8080
# HTTP 服务器
scheme --libdirs ../lib --program simple-server.ss 8080
# 访问 http://localhost:8080/
# 并行 Echo 服务器(使用多线程)
scheme --libdirs ../lib --program parallel-echo-server.ss 8080
# 异步示例(交互式)
scheme --libdirs ../lib --program async-example.sschez-socket/
├── lib/
│ ├── socket/ # Socket 库
│ │ ├── constants.ss # 网络常量定义
│ │ ├── ffi.ss # C 函数 FFI 绑定
│ │ ├── addr.ss # 地址结构管理
│ │ ├── platform.ss # 平台检测
│ │ ├── tcp.ss # 底层 TCP API
│ │ ├── udp.ss # UDP API
│ │ ├── unix.ss # Unix Domain Socket API
│ │ └── port.ss # 同步 Port 封装
│ └── async/ # 异步调度器
│ ├── scheduler.ss # 统一调度器接口
│ ├── coroutine-scheduler.ss # 协程式调度器
│ ├── parallel-scheduler.ss # 并行式调度器
│ ├── port.ss # 异步 Port 封装
│ ├── udp.ss # 高级异步 UDP API
│ ├── wakeup.ss # 唤醒机制
│ ├── signal.ss # 信号处理
│ ├── process.ss # 子进程管理
│ └── blocking/ # 阻塞操作线程池
│ ├── thread-pool.ss # 线程池
│ ├── dns.ss # 异步 DNS
│ └── file.ss # 异步文件 I/O
├── examples/ # 示例代码
├── tests/ # 测试套件
├── design/ # 设计文档
└── docs/ # 用户文档
| 功能 | 导入语句 |
|---|---|
| TCP 同步 I/O | (import (socket port)) |
| UDP 同步 | (import (socket udp)) |
| UDP 异步 | (import (async udp)) |
| Unix Socket | (import (socket unix)) |
| 异步调度器 | (import (async scheduler)) |
| 协程调度器 | (import (async coroutine-scheduler)) |
| 异步 TCP | (import (async port)) |
| 信号处理 | (import (async signal)) |
| 子进程 | (import (async process)) |
| 线程池 | (import (async blocking thread-pool)) |
| 异步 DNS | (import (async blocking dns)) |
| 异步文件 | (import (async blocking file)) |
| 网络常量 | (import (socket constants)) |
运行完整的测试套件:
cd tests
scheme --libdirs ../lib:. run-all-tests.ss运行单个测试:
cd tests
scheme --libdirs ../lib:. test-udp.ss # UDP 测试
scheme --libdirs ../lib:. test-unix.ss # Unix Socket 测试
scheme --libdirs ../lib:. test-timer.ss # 定时器测试
scheme --libdirs ../lib:. test-process.ss # 子进程测试
scheme --libdirs ../lib:. test-dns.ss # DNS 测试
scheme --libdirs ../lib:. test-file.ss # 文件 I/O 测试
scheme --libdirs ../lib:. test-signal.ss # 信号处理测试| 模块 | 测试数 | 说明 |
|---|---|---|
| UDP(同步) | 10 | socket 创建、绑定、发送/接收 |
| UDP(异步) | 12 | 异步 sendto/recvfrom、echo、多消息 |
| Unix Socket | 10 | 流式/数据报、连接、通信 |
| Timer | 7 | sleep、timeout、interval |
| Process | 15 | 进程创建、stdin/stdout、信号 |
| Thread Pool | 13 | 线程池管理、任务提交 |
| DNS | 17 | 同步/异步解析 |
| File I/O | 19 | 读写、stat、异步操作 |
| Signal | 12 | 信号处理器、信号读取 |
| Signal + Poll | 14 | pipe handler、poll 后端集成 |
| I/O Backend | 18 | epoll、kqueue、poll 后端 |
| 总计 | 147 |
| 平台 | 状态 | I/O 后端 | 信号处理 |
|---|---|---|---|
| Linux | 完全支持 | epoll | signalfd |
| macOS | 支持 | kqueue | kqueue EVFILT_SIGNAL |
| OpenBSD | 支持 | kqueue | kqueue EVFILT_SIGNAL |
| FreeBSD | 支持 | kqueue | kqueue EVFILT_SIGNAL |
| 其他 Unix | 支持 | poll | self-pipe trick |
| Windows | 暂不支持 | - | - |
MIT License