Skip to content

TTalkPro/chez-socket

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Chez Socket

中文 | 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-sleepset-timeoutset-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-socket

验证安装

cd 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

快速开始

同步 TCP 客户端

(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)))  ; 显示响应第一行

同步 TCP 服务器

(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))

异步 I/O(协程式)

(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)

UDP 通信

(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))

异步 UDP

(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)

Unix Domain Socket

(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))

异步 DNS 解析

(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))

异步文件 I/O

(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.ss

目录结构

chez-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

About

A simple socket library of ChezScheme

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages