CommonLisp UDP库usocket使用心得
本文记录了自己使用usocket创建一个UDP over RPC框架时遇到的一个问题。
当我刚开始做的时候,是这样的流程:
;; server 先bind本地一个port (usocket:socket-connect nil :protocol :datagram :local-host server-host :local-port server-port) -> server-socket ;; 然后server调用receive来监听这个socket (usocket:socket-receive server-socket nil data-max-length) ;; 等待直到有数据 -> return-buffer return-length client-host client-port ;; client 通过connect来连接到server的host port (usocket:socket-connect server-host server-port :protocol :datagram :local-host client-host :local-port client-port) -> client-socket ;; client 监听 server的返回 (usocket:socket-receive client-socket nil data-max-length) ;; 等待直到有数据 -> return-buffer return-length server-host server-port ;; 然后client 发送数据 (usocket:socket-send client-socket buffer (length buffer)) ;; server收到数据后,发送回数据 (usocket:socket-send server-socket buffer (length buffer) :host client-host :port client-port)
采用上面的收发逻辑是可以实现一个UDP服务器和客户端交互的, 然而总觉得怪怪的,因为server和client的非对称性,而UDP协议中两者是“对称”的。
现在比如说有多个节点s1, s2, s3…, 其中每个节点同时要充当server相应其它节点的请求,和充当client在自己有需要时请求其它节点。
按照上面的流程,当其中的一个节点比如s1先充当server bind到一个port后(调用一次 socket-connect
),当它要向s2发送消息时(充当client),又需要调用 socket-connect
指定s2的地址端口和自己的地址端口时,
会产生错误“端口已被使用”(因为两次生成的socket绑定到同一个port),而我需要对于每个节点来说收发都在同一个port上(因为其他节点根据收到你消息的port来给你发信息)。
我之所以陷入这种困境是受这个例子的影响,限制了思维,经过一番研究发现更为合适的流程是下面这样的,而且更为“美观”:
;; server 先bind本地一个port 建立一个socket (usocket:socket-connect nil :protocol :datagram :local-host server-host :local-port server-port) -> server-socket ;; 然后server调用receive来监听这个socket (usocket:socket-receive server-socket nil data-max-length) ;; 等待直到有数据 -> return-buffer return-length client-host client-port ;; client 也先bind到本地一个port 建立一个socket (usocket:socket-connect server-host server-port :protocol :datagram :local-host client-host :local-port client-port) -> client-socket ;; client 监听 socket上的数据 (usocket:socket-receive client-socket nil data-max-length) ;; 等待直到有数据 -> return-buffer return-length server-host server-port ;; 如果client 需要发送数据 (usocket:socket-send client-socket buffer (length buffer) :host server-host :port server-port ) ;; server 收到数据后,发送回数据 (usocket:socket-send server-socket buffer (length buffer) :host client-host :port client-port)
可以看到这个流程是对称的,对比之前的步骤差异就在建立client socket的时候没有绑定到另外一端到哪个地址,而是在发送的时候指定地址,并且同时可以监听这个socket上的消息。
这样可以解决上面举的例子的困境,因为节点充当server或者client的时候其实用的是同一个socket,在上面收发而已。
也就是说,之前的client在创建socket的时候就指定了另一端(server)的方向,和server创建时不指定相冲突,而现在client创建的socket是不指定另一端的,而在发送时指定地址, 该socket同时也能作为server使用,这样端口不冲突。其实从receive/send的角度来考虑会比server/client的方式要清晰。
理清楚其实很简单。可惜我一开始在上面困住了时候看不透,究其原因:
- 自己对于socket网络编程的不清晰(UDP的具体流程和受TCP方式的影响)
- common lisp的usocket库 API说明不清晰,或者设计有些许不合理
- 缺乏common lisp UDP 相关文档示例
故此做个小记。