spice客戶端建立連接從 application.cpp 開始,下面整理下spice客戶端和服務端的連接機制。
1. 首先,spice客戶端打開監聽,添加監聽端口;
static int reds_init_net(void)
{
if (spice_port != -1) {
reds->listen_socket = reds_init_socket(spice_addr, spice_port, spice_family);
...
reds->listen_watch = core->watch_add(reds->listen_socket,SPICE_WATCH_EVENT_READ,
reds_accept, NULL);
...
}//初始化監聽服務
if (spice_secure_port != -1) {
reds->secure_listen_socket = reds_init_socket(spice_addr, spice_secure_port,
spice_family);
...
reds->secure_listen_watch = core->**watch_add**(reds->secure_listen_socket,
SPICE_WATCH_EVENT_READ,
reds_accept_ssl_connection, NULL);
...
}
if (spice_listen_socket_fd != -1 ) {
reds->listen_socket = spice_listen_socket_fd;
reds->listen_watch = core->watch_add(reds->listen_socket,SPICE_WATCH_EVENT_READ,
reds_accept, NULL);
...
}
return 0;
}
其中,watch_add 是在qemu中實現的,qemu所做的具體工作就是在一個socket隊列中增加一個新網絡socket,並註冊其對應的網絡事件處理函數reds_accept。當此socket可讀或可寫時,Qemu便調用reds_accept函數來處理網絡事件。
2. application啓動
對應客戶端生成一個_client參數,用來表示客戶端;初始化庫,解析命令行參數,平臺初始化顯示、鍵盤和窗口,初始化監視器、菜單(快捷鍵)和事件偵聽,當一個時間需要實現,會調用process_loop的run()函數。
app->init_remainder();
ret = app->run();
int Application::run()
{
_exit_code = ProcessLoop::run();
...
}
2.processloop::run()
int ProcessLoop::run()
{
_thread = pthread_self();
_started = true;
on_start_running();
...
}//獲取線程ID並執行;
3.調用application::on_start_running()–>connect()
void Application::on_start_running()
{
_foreign_menu.reset(new ForeignMenu(this, _active));
if (_enable_controller) {
_controller.reset(new Controller(this));
return;
}//使能控制器;
#ifdef USE_GUI
if (_gui_mode == GUI_MODE_FULL) {
show_gui();
return;
}
#endif
connect();//執行與服務器端的連接;
}
下面是連接過程;
bool Application::connect(const std::string& host, int port, int sport, const std::string& password)
{
if (_state != DISCONNECTED) {
return false;
}
_client.set_target(host, port, sport);//主機:端口號;
_client.set_password(password);//密碼;
if (!set_channels_security(port, sport)) {
return false;
}//設置通道安全類型;
register_channels();//初始化各種管道寄存器;
connect();//建立連接;
return true;
}
...//建立連接並且進行sll 驗證;
使用RedChannelBase的connect(),進行連接安全類型的設置:
...
RedPeer::connect_unsecure(host, options.unsecure_port);
link(connection_id, password, protocol);
...
ASSERT(options.allow_secure());
RedPeer::connect_secure(options, host);
link(connection_id, password, protocol);
``
檢查連接之後,設置安全連接和非安全連接後調用link()執行連接過程;
void RedChannelBase::link(uint32_t connection_id, const std::string& password, int protocol)
在連接的頭部封裝上連接的信息:
link_mess.connection_id = connection_id;
link_mess.channel_type = _type;
link_mess.channel_id = _id;
link_mess.num_common_caps = get_common_caps().size();
link_mess.num_channel_caps = get_caps().size();
link_mess.caps_offset = sizeof(link_mess);
...
send(buffer, p - buffer);//向服務端發送請求;
delete [] buffer;//信息發送後,刪除buffer,等待服務器迴應;
4. 服務端對服務端進行驗證授權;
調用reds_accept()函數進行對信息進行接收;
static void reds_accept(int fd, int event, void *data)
{
int socket;
...
if (spice_server_add_client(reds, socket, 0) < 0)
close(socket);
}//添加客戶端連接;
SPICE_GNUC_VISIBLE int spice_server_add_client(SpiceServer *s, int socket, int skip_auth)
{
RedLinkInfo *link;
...
link->skip_auth = skip_auth;
reds_handle_new_link(link);
return 0;
}
static void reds_handle_new_link(RedLinkInfo *link)
{
reds_stream_set_async_error_handler(link->stream, reds_handle_link_error);
reds_stream_async_read(link->stream,
(uint8_t *)&link->link_header,
sizeof(SpiceLinkHeader),
reds_handle_read_header_done,
link);
}//異步接收數據,需要客戶端發送數據,填充link->link_header結束;
reds_handle_read_header_done —> reds_handle_read_link_done —>
reds_handle_auth_mechanism —> reds_get_spice_ticket(link)
如果有sasl的情況調用reds_start_auth_sasl(link)進行sasl授權;
reds_handle_ticket —> reds_handle_link(link) —>
ticket是spice的安全機制,作用是驗證有授權的安全鏈接;Ticketing是一個在SPICE服務器端有密碼和有效時間段的組成。時間過期後,這個Ticketing也就過期了。這個Ticketing是加密的。爲了使用加密,服務器生成一個1024位的RSA密鑰並向客戶端發送公鑰(通過RedLinkInfo)。以上這些函數通過對ticket的驗證,完成對連接的授權。授權完成後,開始建立主通道:
static void reds_handle_main_link(RedLinkInfo *link)
{
RedClient *client;
RedsStream *stream;
SpiceLinkMess *link_mess;
uint32_t *caps;
uint32_t connection_id;
MainChannelClient *mcc;
int mig_target = FALSE;
spice_info(NULL);
spice_assert(reds->main_channel);
link_mess = link->link_mess;
if (!reds->allow_multiple_clients) {
reds_disconnect();
}
if (link_mess->connection_id == 0) {
reds_send_link_result(link, SPICE_LINK_ERR_OK);
while((connection_id = rand()) == 0);
mig_target = FALSE;
} else {
// TODO: make sure link_mess->connection_id is the same
// connection id the migration src had (use vmstate to store the connection id)
reds_send_link_result(link, SPICE_LINK_ERR_OK);
connection_id = link_mess->connection_id;
mig_target = TRUE;
}//判斷連接的信息是否是connection_id;
caps = (uint32_t *)((uint8_t *)link_mess + link_mess->caps_offset);
//初始化功能詞數目;
red_client_set_main(client, mcc);//將客戶和連接信息對應;
if (spice_name)
main_channel_push_name(mcc, spice_name);//保存通道名稱;
if (spice_uuid_is_set)
main_channel_push_uuid(mcc, spice_uuid);//保存通道的uuid;
} else {
reds_mig_target_client_add(client);
}
main_channel_client_start_net_test(mcc, !mig_target);//對主通道進行網絡測試;
}
5.服務端進行網絡測試
用來測試通道的網絡情況,並且可以通過這個測試同步客戶端和服務端的時間信息;
void main_channel_client_start_net_test(MainChannelClient *mcc, int test_rate)
{
if (!mcc || mcc->net_test_id) {
return;
}
if (test_rate) {
if (main_channel_client_push_ping(mcc, NET_TEST_WARMUP_BYTES)
&& main_channel_client_push_ping(mcc, 0)
&& main_channel_client_push_ping(mcc, NET_TEST_BYTES)) {
mcc->net_test_id = mcc->ping_id - 2;
mcc->net_test_stage = NET_TEST_STAGE_WARMUP;
}//測試字節大小爲1024X250;
} else {
red_channel_client_start_connectivity_monitoring(&mcc->base, CLIENT_CONNECTIVITY_TIMEOUT);//超時;
}
}
6.客戶端迴應測試請求
receive((uint8_t*)&link_res, sizeof(link_res));
if (link_res != SPICE_LINK_ERR_OK) {
int error_code = (link_res == SPICE_LINK_ERR_PERMISSION_DENIED) ?
SPICEC_ERROR_CODE_CONNECT_FAILED : SPICEC_ERROR_CODE_CONNECT_FAILED;
THROW_ERR(error_code, "connect failed %u", link_res);
}
}
客戶端判斷服務端是否返回了連接錯誤信息;如果沒有返回錯誤信息,就就成功建立連接;