1

CommonLisp UDP库usocket使用心得

 2 years ago
source link: https://imnisen.github.io/udp-usocket.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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的方式要清晰。

理清楚其实很简单。可惜我一开始在上面困住了时候看不透,究其原因:

  1. 自己对于socket网络编程的不清晰(UDP的具体流程和受TCP方式的影响)
  2. common lisp的usocket库 API说明不清晰,或者设计有些许不合理
  3. 缺乏common lisp UDP 相关文档示例

故此做个小记。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK