从LISP的套接字中读取c++结构
Reading C++ structs from socket in LISP
我们有一个定义为通过网络传输的c++类的应用协议。我想连接到以这种格式发送数据的服务器。我想用lisp写一个客户端(最好是sbcl)来与这个服务器通信。我更喜欢用纯lisp编写,而不是使用CFFI来包装c++ dll。示例结构看起来像这样:
class Header
{
public:
int MsgType;
uint64_t Length;
}
class SampleMsg
{
public:
Header MsgHeader;
char Field1[256];
bool Field2;
double Field3;
SomeOtherClass Field4;
}
我想知道如何在lisp中映射这些结构体,使它们与二进制兼容,以及如何读写这些结构体。是否有比在结构中打包/拆包每个字段更简单的方法?
例如,在c#中,您可以像下面这样映射二进制结构并直接从字节数组中读取它:
[StructLayout(LayoutKind.Sequential)]
public struct Header
{
public int MsgType;
public ulong Length;
}
[StructLayout(LayoutKind.Sequential)]
public struct SampleMsg
{
public:
public Header MsgHeader;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string Field1;
public bool Field2;
public double Field3;
public SomeOtherClass Field4;
}
如果类似的方法在lisp中是可能的,那将是理想的。如果没有,我愿意做一些管道,只要它是可控的。
编辑:尝试Svante的建议:
(ql:quickload "userial")
(in-package :sb-bsd-sockets)
(defun read-buffer (host port)
(let ((socket (make-instance 'inet-socket :type :stream :protocol :tcp)))
(socket-connect socket host port)
(let ((buf (socket-receive socket nil 1024 :element-type '(unsigned-byte 8))))
(socket-close socket)
buf)))
(defstruct header
msg-type
length)
(userial:make-slot-serializer (:header header (make-header))
:int64 msg-type
:uint64 length)
(defvar *buffer*)
(defvar *b*)
(setq *buffer* (read-buffer #(10 1 2 75) 5003))
(setq *b* (make-array 2048 :element-type '(unsigned-byte 8) :fill-pointer 0 :adjustable t))
(map 'vector #'(lambda (x) (vector-push x *b*)) *buffer*)
(setf (fill-pointer *b*) 0)
此时,*b*
的值是这样的:#(7 0 0 0 0 0 0 0 176 2 0 0 0 0 0 0 45 71 253 83 0 0 0 0 165 30 11 11 0 0 0 ...)
。第一个7对应于msg类型,它应该是7。长度应该是688(176 + 2*256)。
现在我知道了(userial:with-buffer *b* (userial:unserialize :header))
。这给了我
#S(HEADER :MSG-TYPE 504403158265495552 :LENGTH 12682699500628738048)
#(7 0 0 0 0 0 0 0 176 2 0 0 0 0 0 0)
似乎是一个顺序问题。如何解决这个问题?我找不到任何方法来处理userial lib中的端序。
EDIT2:
最终放弃了使用,并写了这些(以下实用通用Lisp书):
(defun read-64 (buf)
(let ((u 0))
(setf (ldb (byte 8 56) u) (aref buf 7))
(setf (ldb (byte 8 48) u) (aref buf 6))
(setf (ldb (byte 8 40) u) (aref buf 5))
(setf (ldb (byte 8 32) u) (aref buf 4))
(setf (ldb (byte 8 24) u) (aref buf 3))
(setf (ldb (byte 8 16) u) (aref buf 2))
(setf (ldb (byte 8 8) u) (aref buf 1))
(setf (ldb (byte 8 0) u) (aref buf 0))
u))
(defun read-32 (buf)
(let ((u 0))
(setf (ldb (byte 8 24) u) (aref buf 3))
(setf (ldb (byte 8 16) u) (aref buf 2))
(setf (ldb (byte 8 8) u) (aref buf 1))
(setf (ldb (byte 8 0) u) (aref buf 0))
u))
(defun read-16 (buf)
(let ((u 0))
(setf (ldb (byte 8 8) u) (aref buf 1))
(setf (ldb (byte 8 0) u) (aref buf 0))
u))
现在我可以写(read-uint64 (subseq *buffer* 8 16))
来获得msg的长度。谢谢你的帮助。
您可以使用Quicklisp提供的userial
。
然而,我将非常努力地寻找一种方法来消除保持两个定义位置同步的需要(一个在c++,一个在Lisp)。
编辑:这是我的想法。我只做了一些非常肤浅的测试,所以不能保证。特别是,我还没有测试过c++输出,您很可能需要为对齐进行大量调整。
(defstruct header
msg-type
length)
;; Msg-type might be best handled with an enum unserializer:
;; (make-enum-unserializer :msg-type (:foo :bar)), but I don't know
;; what your values are.
(defstruct sample-msg
msg-header
field-1
field-2
field-3
field-4)
;; You might need to use a different serializer for msg-type for
;; alignment.
(make-slot-serializer (:header header (make-header))
:int msg-type
:uint64 length)
(make-vector-serializer :vector-256-char :uint8 256)
;; I have no idea how a boolean is serialized and aligned on the C++
;; side, so I'll just use :boolean for field-3 here as a first
;; attempt.
(make-slot-serializer (:sample-msg sample-msg (make-sample-msg))
:header msg-header
:vector-256-char field-1
:boolean field-2
:float64 field-3
:some-other field-4)
;; You can serialize and unserialize now:
(serialize :sample-msg some-sample-msg)
(rewind-buffer)
(unserialize :sample-msg)
;; Userial operates on an adjustable vector with fill-pointer in the
;; special variable *buffer*, so you'll need to fill that with content
;; from wherever you read that from.
(with-buffer (read-my-content)
(unserialize :sample-msg))
- 理解boost::asio-async_read在无需读取内容时的行为
- 使用新行和不使用新行读取文件
- 读取文件并输入到矢量中
- 用c++从输入文件中读取另一行
- 读取文件的最后一行并输入到链接列表时出错
- 在进程中对同一管道进行读取和写入时C++管道出现问题
- 无法找到/读取配置文件.conf-FileIOException
- 如何使用Luacneneneba API正确读取字符串和表参数
- C++将文本文件中的数据读取到结构数组中
- 正在将csv文件读取为双精度矢量
- 为什么 sscanf 无法从一个字符串中读取uint64_t和字符?
- 为什么在读取文件大小时文件IO速度会发生变化
- 正在读取二进制文件(is_open)
- 如何在c++中从文本文件中逐行读取整数
- SSH通过/sbin/SSH无法读取RSA密钥文件(从控制台运行)
- 独立读取-修改-写入顺序
- 从文本文件中读取时钟时间和事件时间并进行处理
- 如何从文本文件中读取值和数组
- 为什么文件名被设置为一个点,而不是在读取矢量中的文件名时
- 一种在C++中读取TXT配置文件的简单方法