StreamChannel* stream_channel_new(RedsState *server, uint32_t id) { return g_object_new(TYPE_STREAM_CHANNEL, "spice-server", server, "core-interface", reds_get_core_interface(server), "channel-type", SPICE_CHANNEL_DISPLAY, // TODO this id should be after all qxl devices "id", id, "migration-flags", 0, "handle-acks", TRUE, // TODO sure ?? NULL); }
// give an hint to client that we are sending just streaming // see spice.proto for capability check here if (red_channel_client_test_remote_cap(rcc, SPICE_DISPLAY_CAP_MULTI_CODEC)) { surface_create.flags |= SPICE_SURFACE_FLAGS_STREAMING_MODE; }
channel PortChannel : SpicevmcChannel { client: message { uint8 event; } @declare event = 201; server: message { uint32 name_size; uint8 *name[name_size] @zero_terminated @marshall @nonnull; uint8 opened; } @declare init = 201; message { uint8 event; } @declare event; }; channel WebDAVChannel : PortChannel { };
可以看到 WebDAVChannel 协议是继承 PortChannel 通道协议,而且完全没有新增元素
else if (strcmp(char_device->subtype, SUBTYPE_PORT) == 0) { if (strcmp(char_device->portname, "org.spice-space.webdav.0") == 0) { dev_state = spicevmc_device_connect(reds, char_device, SPICE_CHANNEL_WEBDAV); } else if (strcmp(char_device->portname, "org.spice-space.stream.0") == 0) { dev_state = RED_CHAR_DEVICE(stream_device_connect(reds, char_device)); } else { dev_state = spicevmc_device_connect(reds, char_device, SPICE_CHANNEL_PORT); } }
直接使用 PortChannel 协议进行视频重定向流传输路径
直接把数据插入到主 DisplayChannel 的 视频流里传输
在试验时发现 spice-gtk 里的工具 spicy.c 已经有对特定名称为 org.spice.spicy 的 spiceport 的 传输测试代码,所以测试 portchannel 传输就比较简单了:
<channel type="spiceport"> <source channel="org.spice.spicy"/> <target type="virtio" name="org.spice.spicy" state="disconnected"/> <alias name="channel2"/> <address type="virtio-serial" controller="0" bus="0" port="3"/> </channel>
\\.\Global\org.spice.spicy
#include <iostream> #include <windows.h> //#define SPICE_PORT_NAME L"\\\\.\\Global\\com.troila.newbee.0" #define SPICE_PORT_NAME_SPICY L"\\\\.\\Global\\org.spice.spicy" int main() { HANDLE port_handle = ::CreateFile(SPICE_PORT_NAME_SPICY, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, 0,//FILE_FLAG_OVERLAPPED, NULL); if (port_handle == INVALID_HANDLE_VALUE) { std::cout << "open spice port failed! err:" << ::GetLastError() << std::endl; }else { std::cout << "open spice port successfully" << std::endl; char write_buf[] = "hello troila!\n"; const DWORD buf_size = sizeof(write_buf); char read_buf[buf_size] = {0}; DWORD size = 0; DWORD totalsize = 0; BOOL ret = ::WriteFile(port_handle, (LPCVOID)write_buf, buf_size, &size, NULL); if (ret) { std::cout << "write successfully" << std::endl; } else { std::cout << "write failed! err:" << ::GetLastError() << std::endl; goto end; } size = 0; while (totalsize < buf_size) { ret = ::ReadFile(port_handle, read_buf+totalsize, buf_size-totalsize, &size, NULL); if (!ret) { std::cout << "write failed! err:" << ::GetLastError() << std::endl; goto end; } totalsize += size; std::cout << "read " << size << std::endl; } std::cout << "read content: " << read_buf << std::endl; end: ret = ::CloseHandle(port_handle); std::cout << "close handle ret:" << ret << std::endl; } }
sudo apt install spice-client-gtk
, 启动 spicy
并连接open spice port successfully write successfully read 1 ... read content: hello guest!!! close handle ret:1
** Message: 10:53:14.947: main channel: opened port 0x5561c6fcd270 org.spice.spicy: opened hello troila! port 0x5561c6fcd270 org.spice.spicy: closed
双向传输验证完毕
PortChannel 对象构造过程:
SPICE_SESSION_CHANNEL_NEW
channel-new
的处理函数,按类型与通道名来获知特定名字 portchannel 的建立,并进行初始化port-event
,port-data
的处理函数, port-event
用于获取通道打开关闭事件port-data
来接收数据spice_port_write_async
,spice_port_write_finish
来发送数据