给大家讲个我试图用common lisp 写一个小工具的搞笑经历

我今年新入职了一家用go做后端的游戏公司。游戏公司嘛,大家都懂。一般都是用protobuf 序列化协议然后在网络上和前端进行通信。本人是一个还未毕业的后端小白,所以和前端联调的时候,难免要点时间去解决一些小bug。

因为最近老是要前端的同事发协议过来帮我做测试(前端同事也很忙的,喊他的时候他未必有时间),所以我就想写一个工具,专门在和前端同事做对接之前先用工具给后端发一下协议,看看代码哪里还有隐藏的bug没找到,起码流程先不能出问题。

后端其实有测试的工具,但是go需要先编译才能运行。而且公司喜欢用svn我每次拉代码都会把我自己的测试代码给覆盖,超级不爽。

所以我就想自己写个工具,最好语言有repl,这样我写一段,然后就可以吧代码发送给解释器让他运行。这种语言我知道的就有python 和 lisp。 于是我傻白甜的选择了common lisp。

python 那玩意好用是好用,但是我已经学过了,就没啥兴趣了(所以直接排除了)。而且我是用emacs 的,不如就趁这次机会把common lisp学了。以后也好hack自己的emacs。正好又看见github上有人已经写了cl-protobuf的序列化库。那行,就决定是你了 common lisp。

然后我就吭哧吭哧的吧common lisp cookbook 给看完了(没看完其实,有些觉得不重要的就没看 :stuck_out_tongue_winking_eye:

然后我去看了下后端有关通信那块的流程,发现用的gonet 配合的是 LengthFieldBasedFrameCodec(这个玩意会自动帮你编码和解码协议 原理大概就是每次发之前,自动给你的二进制流前面加上协议长度信息头,然后每次接收就先读协议头的长度信息,在一次性把协议读出来方便你反序列化)

既然这样的话,那我的工具也需要这种自动组装协议帧的LengthFieldBasedFrameCodec. 这事简单,我那时候写课设的时候用skynet + lua 就已经干过这个事了。不就是在协议序列化的数组前面加点私货嘛。

然后我大概看了下gonet 里面是怎么实现这个LengthFieldBasedFrameCodec的(大概就是你可以指定协议头用来表示长度信息的字节长度, 然后它会根据这个长度一个同样长度的byte数组, 最后计算你的协议长度,然后把这个长度转换到这个byte数组里面去)。

嗯!这个时候就遇到问题了。我没搞过这种把数字的字节搬到数组里面的事。而且我的机器上common lisp 一个integer 最大正数大概就是62个bits 差不多8个字节吧,最大负数大概也是62个bits. 这一组合 我去 128bit?

所以我就卡在了,到底要怎么把common lisp 里面的integer给序列化到二进制数组里面去。协议部分不是问题,因为cl-protobuf 自己有序列化成二进制数组的函数,不用我操心。这个integer他不止是长度的问题,他还分有符号和无符号的问题。这两个我都没啥头绪。不知道论坛里热心,可爱,声音又好听的大佬们有没有办法解决下快秃头的我。 :eyes:

2 个赞

个人建议是去读 TAOCP Vol 1.

但是由于大部分人都挺菜的,就算了。

另外, Common Lisp 的 integer 是无限大的,你说的 62 bit 的是 fixnum。因为这个原因,Common Lisp 整 bin arith 之类的也很麻烦。

而且我实在没兴趣在这里讲算法。所以我告訴你有个现成的库叫 bit-smasher 可以用。

CL-USER> (bit-smasher:bits<- 255)
#*11111111
CL-USER> (bit-smasher:bits<- -255) ;; 你要自己检查一下, bit-smasher 不保证对负数的行为一致
#*00000001
CL-USER> (bit-smasher:bits<- 23) ;; endianness 也要你自己看
#*00010111
CL-USER> (bit-smasher:bits<- (+ 123 most-positive-fixnum)) ;; 对大于 fixnum size 的 integer 也能用
#*0001000000000000000000000000000000000000000000000000000001111010
2 个赞

主要,昨天没找到操作字节的函数。而且那个integer太牛了,所以有点犯难。昨天看了下intbytes库的实现,大概有点思绪了 :stuck_out_tongue_winking_eye: TAOCP 这种书不是读后面忘前面吗 :crazy_face:

感谢大佬,已经能正常把uint给转换成对应大小的二进制数组了。 先创建对应宽度的字节数组 buf

(setf buf (make-array 8 :element-type '(unsigned-byte 8)  :initial-element 0))

然后用bit-smasher 把integer给转成字节数组octs,最后倒着把octs里面的东西给拷贝到buf里面去,这样我的协议头的长度部分就有了。

(defmethod put-uint64 ((obj binary) buf uint64)
  (declare (type (array (unsigned-byte 8) (8)) buf))
  (let* ((octs (bit-smasher:octets<- uint64))
         (buf-last-idx (- (length buf) 1))
         (octs-last-idx (- (length octs) 1))
         )

	(loop for i from 0 below (length octs)
          do
             (setf (aref buf (- buf-last-idx i))
                   (aref octs (- octs-last-idx i)))
          )
    )
  )

办法好像比较笨,不知道有没有更聪明的办法。 :thinking:

1 个赞

非计算机专业,TAOCP自打买来之后连塑封都还没拆开过……

我一样非计算机的,前三卷看完都觉得很有用,越是用小众编程语言越需要这套书的内容,看完可以无压力自己造基础轮子

我也买了中英文加起来近10本,但是0.001本都没看完。。。:rofl::rofl:

技术/真理掌握在少数人手里,Common Lisp 走起