如何通过Guile连接WebSocket?零基础Socket用户技术求助
别担心,从零开始搞WebSocket确实容易摸不着头脑,尤其是Guile本身没自带WebSocket支持的情况下。我来一步步帮你拆解怎么用Guile实现和wss://node2.wsninja.io的连接~
先搞懂核心前提:WebSocket≠普通HTTP请求
WebSocket是先通过HTTP协议升级握手建立连接,之后切换成双向的帧通信模式,所以直接用普通HTTP请求模块完全行不通,得手动处理握手流程+后续的WebSocket帧编码/解码。
步骤1:建立加密TCP连接(wss协议必备)
因为你用的是wss://(WebSocket over TLS),所以必须先创建TCP套接字,再封装TLS加密层,否则连不上服务器。
(use-modules (ice-9 rdelim) (rnrs bytevectors) (gnutls) (srfi srfi-1) (srfi srfi-27)) ;; 初始化随机数生成器(后续生成掩码要用) (random-source-randomize! default-random-source) ;; 1. 获取服务器地址信息 (define addrinfo (getaddrinfo "node2.wsninja.io" "443" AI_NUMERICSERV)) (define addr (addrinfo:addr (car addrinfo))) ;; 2. 创建TCP套接字 (define sock (socket PF_INET SOCK_STREAM 0)) ;; 3. 连接服务器并封装TLS层 (connect sock addr) (define session (make-session connection-end/client)) (set-session-default-priority! session) (set-session-transport-fd! session (fileno sock)) (handshake session)
步骤2:发送WebSocket握手请求
握手请求是特殊的HTTP GET请求,必须包含Upgrade: websocket、Connection: Upgrade、Sec-WebSocket-Key等头部,服务器验证后会返回101 Switching Protocols响应。
;; 生成随机的Sec-WebSocket-Key(Base64编码的16字节随机数) (define (make-websocket-key) (base64-encode (random-bytevector 16))) ;; 构造握手请求 (define key (make-websocket-key)) (define handshake-request (string-append "GET / HTTP/1.1\r\n" "Host: node2.wsninja.io\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Key: " key "\r\n" "Sec-WebSocket-Version: 13\r\n\r\n")) ;; 发送请求并检查响应 (send-session-data session (string->utf8 handshake-request)) (define status-line (read-line session)) (if (string-prefix? "HTTP/1.1 101" status-line) (display "WebSocket握手成功!\n") (error "握手失败,服务器响应:" status-line))
步骤3:编码/解码WebSocket帧
握手成功后,必须按照WebSocket帧格式收发数据——客户端发送的帧必须加掩码,服务器返回的帧不需要。
编码文本帧(客户端发送消息用)
;; 辅助函数:用掩码处理消息字节 (define (mask-bytevector bv mask) (let ([len (bytevector-length bv)]) (do ([i 0 (1+ i)]) ((= i len) bv) (bytevector-u8-set! bv i (bitwise-xor (bytevector-u8-ref bv i) (bytevector-u8-ref mask (modulo i 4))))))) ;; 编码文本帧 (define (encode-websocket-text-frame msg) (let* ([msg-bytes (string->utf8 msg)] [len (bytevector-length msg-bytes)] [mask (random-bytevector 4)] [header (make-bytevector (if (< len 126) 2 6))]) ;; 设置FIN=1(结束帧)、Opcode=1(文本帧) (bytevector-u8-set! header 0 #x81) ;; 设置掩码位=1 + 消息长度 (if (< len 126) (bytevector-u8-set! header 1 (bitwise-ior #x80 len)) (begin (bytevector-u8-set! header 1 #xFE) (bytevector-u16-set! header 2 len (endianness big)))) (bytevector-append header mask (mask-bytevector msg-bytes mask))))
解码文本帧(接收服务器响应用)
(define (decode-websocket-frame session) (let* ([byte1 (get-u8 session)] [opcode (bitwise-and byte1 #x0F)] ; 只处理文本帧(Opcode=1) [byte2 (get-u8 session)] [masked? (bitwise-bit-set? byte2 7)] [len (bitwise-and byte2 #x7F)] ;; 读取实际消息长度 [actual-len (cond [(= len 126) (get-u16 session (endianness big))] [(= len 127) (get-u64 session (endianness big))] [else len])] [mask (if masked? (get-bytevector-n session 4) #f)] [payload (get-bytevector-n session actual-len)]) ;; 解掩码(服务器返回的帧可能不带掩码) (when masked? (mask-bytevector payload mask)) (utf8->string payload)))
完整测试代码
把上面的逻辑整合起来,加上资源清理:
(use-modules (ice-9 rdelim) (rnrs bytevectors) (gnutls) (srfi srfi-1) (srfi srfi-27)) (random-source-randomize! default-random-source) (define (make-websocket-key) (base64-encode (random-bytevector 16))) (define (mask-bytevector bv mask) (let ([len (bytevector-length bv)]) (do ([i 0 (1+ i)]) ((= i len) bv) (bytevector-u8-set! bv i (bitwise-xor (bytevector-u8-ref bv i) (bytevector-u8-ref mask (modulo i 4))))))) (define (encode-websocket-text-frame msg) (let* ([msg-bytes (string->utf8 msg)] [len (bytevector-length msg-bytes)] [mask (random-bytevector 4)] [header (make-bytevector (if (< len 126) 2 6))]) (bytevector-u8-set! header 0 #x81) (if (< len 126) (bytevector-u8-set! header 1 (bitwise-ior #x80 len)) (begin (bytevector-u8-set! header 1 #xFE) (bytevector-u16-set! header 2 len (endianness big)))) (bytevector-append header mask (mask-bytevector msg-bytes mask)))) (define (decode-websocket-frame session) (let* ([byte1 (get-u8 session)] [opcode (bitwise-and byte1 #x0F)] [byte2 (get-u8 session)] [masked? (bitwise-bit-set? byte2 7)] [len (bitwise-and byte2 #x7F)] [actual-len (cond [(= len 126) (get-u16 session (endianness big))] [(= len 127) (get-u64 session (endianness big))] [else len])] [mask (if masked? (get-bytevector-n session 4) #f)] [payload (get-bytevector-n session actual-len)]) (when masked? (mask-bytevector payload mask)) (utf8->string payload))) (define (main) (let* ([addrinfo (getaddrinfo "node2.wsninja.io" "443" AI_NUMERICSERV)] [addr (addrinfo:addr (car addrinfo))] [sock (socket PF_INET SOCK_STREAM 0)]) (dynamic-wind (lambda () #t) (lambda () (connect sock addr) (let ([session (make-session connection-end/client)]) (set-session-default-priority! session) (set-session-transport-fd! session (fileno sock)) (handshake session) ;; 发送握手请求 (let* ([key (make-websocket-key)] [handshake-req (string-append "GET / HTTP/1.1\r\n" "Host: node2.wsninja.io\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Key: " key "\r\n" "Sec-WebSocket-Version: 13\r\n\r\n")]) (send-session-data session (string->utf8 handshake-req)) (let ([status-line (read-line session)]) (if (string-prefix? "HTTP/1.1 101" status-line) (begin (display "WebSocket握手成功!\n") ;; 发送测试消息 (let ([msg "Hello from Guile!"]) (send-session-data session (encode-websocket-text-frame msg)) (display "已发送消息: ") (display msg) (newline) ;; 接收响应 (let ([resp (decode-websocket-frame session)]) (display "收到响应: ") (display resp) (newline)))) (error "握手失败,状态行:" status-line))))) (lambda () (close sock))))) (main)
关键注意事项
- TLS层不能少:
wss://协议必须用TLS加密,直接连TCP套接字会失败。 - 帧格式要严格:客户端发送的帧必须加掩码,否则服务器会拒绝接收。
- 错误处理要补充:上面的代码是简化版,实际使用要处理连接失败、帧解码错误等情况。
内容的提问来源于stack exchange,提问作者Jaft
相关产品推荐
相关产品推荐

