spice client 和 spice server 通信機制

作者:“達沃時代”    原文鏈接:http://www.cnblogs.com/D-Tec/archive/2013/04/19/3030129.html

〇、基本原理

目前的Channel類型枚舉值定義如下:

複製代碼
enum {
    SPICE_CHANNEL_MAIN = 1,
    SPICE_CHANNEL_DISPLAY,
    SPICE_CHANNEL_INPUTS,
    SPICE_CHANNEL_CURSOR,
    SPICE_CHANNEL_PLAYBACK,
    SPICE_CHANNEL_RECORD,
    SPICE_CHANNEL_TUNNEL, // 沒定義USE_TUNNEL,則此Channel無效

    SPICE_END_CHANNEL
};
複製代碼


每個Channel就是客戶端與服務端一個的網絡連接。

客戶端將每個Channel實現爲一個單獨的線程,實現方式是定義一個以單獨線程運轉的RedChannel基類,然後從此基類中派生所需要的具體功能類,客戶端Channel類包括:RedClient、DisplayChannel、CursorChannel、InputsChannel、PlaybackChannel、RecordChannel、TunnelChannel。

服務端Channel的工作相對複雜一點,部分Channel工作在Qemu主線程,另一部分在工作在libspice的單獨線程中,服務端的網絡模型參見文檔02。

客戶端啓動後會首先與服務器建立連接,此連接即爲MAIN_CHANNEL,MAIN_CHANNEL建立起來之後,客戶端首先向服務器發送查詢命令,請求服務器支持的Channel類型,然後客戶端對所有支持的Channel一一創建對應的Channel類實例,每個實例都會開啓自己的工作線程並向服務端發起連接請求,建立網絡連接,下面是大致的工作流程及相關代碼:

1、客戶端首先會與服務器之間建立一個MAIN_CHANNEL,客戶端一側表現爲RedClient,此Channel由客戶端主動發起創建:

     Application構造函數初始化時會初始化其RedClient成員_client,RedClient初始化時就會啓動RedChannel類的start函數來創建工作線程,

     具體的網絡連接操作在工作線程的主循環內部完成,具體由RedChannelBase基類實現的connect、link兩個函數來實現網絡連接的建立和Channel link的建立。工作線程的主循環只處理簡單的幾個事件,其餘消息處理交給其成員ProcessLoop的消息循環來完成。

2、MAIN_CHANNEL建立後,客戶端RedClient進入其ProcessLoop的消息循環,等待服務端消息。服務端完成MAIN_CHANNEL相關工作的初始化(主要就是向Qemu註冊一個網絡消息處理結點,具體參見select網絡模型中關於Watch的講解)後,會通過此Channel發送SPICE_MSG_MAIN_INIT消息,告知客戶端可以進行後續的初始化工作了。

3、客戶端執行初始化工作後,會發送消息請求支持的Channel列表,服務端將註冊(參見下面Server端Channel實現)的Channel列表發送給客戶端,並通知客戶端創建Channel。

4、客戶端其餘Channel的創建都通過一個統一的接口,從工廠類中生產各個Channel類實例。參見具體實現如下(略去部分檢查代碼):

複製代碼
void RedClient::create_channel(uint32_t type, uint32_t id)

{

    ChannelFactory* factory = find_factory(type);

    RedChannel* channel = factory->construct(*this, id);

    _channels.push_back(channel);

 

    channel->start();   // 啓動RedChannel類的工作線程

    channel->connect(); // 激活condition

 

    _migrate.add_channel(new MigChannel(type, id, channel->get_common_caps(), channel->get_caps()));

}
複製代碼


建立Channel的詳細步驟及消息傳遞流程如下:(s表示server代碼,c表示client代碼)

s: reds_init_net, 創建listen socket,增加監聽watch_add(reds_accept)

c: RedChannel::run-》RedChannelBase::connect() -》RedPeer::connect_unsecure發送連接請求,等待服務端accept

s: reds_accept-》reds_accept_connection,

            reds_handle_new_link-》obj->done = reds_handle_read_header_done,

                            async_read_handler-》cb_read 堵塞,等待客戶端發送link消息

c: RedChannelBase::link() -》link_mess.channel_type = _type,send 發送link請求,recive等待

s: obj->done -》reds_handle_read_header_done

            -》(read_header完了,繼續從流中讀取數據)obj->done = reds_handle_read_link_done,async_read_handler

            -》cb_read,obj->done(reds_handle_read_link_done) -》   obj->done = reds_handle_ticket,async_read_handler

            -》cb_read,obj->done(reds_handle_ticket) {

                case channel_type of SPICE_CHANNEL_MAIN:reds_handle_main_link

                otherwise:reds_handle_other_links

              }        

 

c->s: SPICE_CHANNEL_MAIN s: reds_handle_main_link,此過程的詳細步驟如上

s->c: SPICE_MSG_MAIN_INIT, c: handle_init

c->s: SPICE_MSGC_MAIN_ATTACH_CHANNELS s: reds_send_channels

s->c: SPICE_MSG_MAIN_CHANNELS_LIST c: handle_channels

 

一、Server端 Channel 實現

1、服務端註冊Channel。

服務端最關鍵的數據結構爲RedsState,又有一個別名叫SpiceServer,Server端會維護一個全局的RedsState變量,用來存儲全局數據。該全局數據結構在CoreInterface初始化時由Qemu負責發起創建,並通過VDI接口將此對象傳遞給libspice。

RedsState中一個數據成員爲Channel* channels,Server端通過此變量來維護一個Channel鏈表,所有Server端支持的Channel都需要通過reds_register_channel註冊到此鏈表。除了InputChannle是在spice_server_init中註冊的外(即:在CoreInterface初始化時註冊的),其餘Channel都是在Qemu進行虛擬設備初始化時,通過調用spice_server_add_interface函數註冊VDI時註冊的,列舉如下:

// spice_server_init

inputs_init 中註冊:SPICE_CHANNEL_INPUTS   

// spice_server_add_interface(SPICE_INTERFACE_QXL)

red_dispatcher_init 中註冊:SPICE_CHANNEL_DISPLAY、SPICE_CHANNEL_CURSOR

// spice_server_add_interface(SPICE_INTERFACE_PLAYBACK)                

snd_attach_playback 中註冊:SPICE_CHANNEL_PLAYBACK     

// spice_server_add_interface(SPICE_INTERFACE_RECORD)      

snd_attach_record 中註冊:SPICE_CHANNEL_RECORD 

// spice_server_add_interface(SPICE_INTERFACE_NET_WIRE)            

red_tunnel_attach 中註冊:SPICE_CHANNEL_TUNNEL                 

所謂註冊Channel,就是初始化一個Channel對象,然後將其插入到RedsState的channels鏈表中供後續的訪問處理。Channel結構定義如下:

複製代碼
typedef struct Channel {

    struct Channel *next;

    uint32_t type;

    uint32_t id;

    int num_common_caps;

    uint32_t *common_caps;

    int num_caps;

    uint32_t *caps;

    void (*link)(struct Channel *, RedsStreamContext *peer, int migration, int num_common_caps,

                 uint32_t *common_caps, int num_caps, uint32_t *caps);

    void (*shutdown)(struct Channel *);

    void (*migrate)(struct Channel *);

    void *data;

} Channel;
複製代碼


Channel註冊主要是初始化Channel的數據和三個回調函數:link、shutdown、migrate,用來對Channel進行操作。其中數據成員type就是最開始我們列出的枚舉值,用以標識當前Channel類型。下面是Spice中Display Channel初始化的代碼:

   

複製代碼
reds_channel = spice_new0(Channel, 1);

    reds_channel->type = SPICE_CHANNEL_DISPLAY;

    reds_channel->id = qxl->id;

    reds_channel->link = red_dispatcher_set_peer;

    reds_channel->shutdown = red_dispatcher_shutdown_peer;

    reds_channel->migrate = red_dispatcher_migrate;

    reds_channel->data = dispatcher;

reds_register_channel(reds_channel);
複製代碼


2、Channel的三個回調函數:link、shutdown、migrate,其中link是在客戶端與服務端建立Channel連接的時候被調用的,下面對其進行詳細解說

    首先,客戶端Channel的工作線程啓動後,發起link()操作,服務端在reds_handle_new_link中響應,最終會調用各個註冊的Channel的link()函數完成link操作。這個過程中,Server端利用了一種異步消息處理機制,主要是通過一個結構加一個處理函數來實現,簡單介紹一下此消息處理機制:

   AsyncRead是此消息處理機制的一個非常重要的輔助結構體,定義如下:

複製代碼
   typedef struct AsyncRead {

    RedsStreamContext *peer; // 用來處理網絡數據流的數據對象

    void *opaque;  // 傳遞數據用的,通常作爲done函數的參數

    uint8_t *now;  // 數據起始,用來保存接收數據

    uint8_t *end;  // 數據結束,通常用來計算數據長度

    void (*done)(void *opaque); // 消息處理函數

    void (*error)(void *opaque, int err); // 錯誤處理函數

   } AsyncRead;
複製代碼


   // 異步消息處理函數,最主要的參數就是data,是一個AsyncRead指針(省略異常處理,具體異常處理方法參考源代碼)

  

複製代碼
 static void async_read_handler(int fd, int event, void *data)

{

    AsyncRead *obj = (AsyncRead *)data;

 

    for (;;) {

        int n = obj->end - obj->now;

        if ((n = obj->peer->cb_read(obj->peer->ctx, obj->now, n)) <= 0) {

           // 異常處理省略……

        } else { // 正常情況處理

            obj->now += n;

            if (obj->now == obj->end) { // 接收數據完畢了就執行done,否則繼續循環接收數據

                async_read_clear_handlers(obj);

                obj->done(obj->opaque);

                return;

            }

        }

    }

}
複製代碼


利用上面的消息處理機制,Server端的reds_handle_new_link函數最終會找到具體的new_link(即:Channel)類型,然後調用此Channel註冊時註冊的的link回調函數。

 

link函數的主要工作

對於不同的Channel,link的工作不同,並且Display、Cursor的實現又與其他Channel有些差異,各個Channel對應的link函數如下:

複製代碼
SPICE_CHANNEL_DISPLAY   red_dispatcher_set_peer

SPICE_CHANNEL_CURSOR        red_dispatcher_set_cursor_peer

SPICE_CHANNEL_INPUTS        inputs_link

SPICE_CHANNEL_PLAYBACK snd_set_playback_peer

SPICE_CHANNEL_RECORD        snd_set_record_peer
複製代碼


link最主要的工作之一就是網絡連接的處理,即選擇網絡事件處理模型,設置網絡事件響應函數等。spice用了兩種模型:select、epoll

 

1)DisplayChannel的link實現:

Display的實現中引入了一個dispatcher,實現了各種對外的接口,接口本身大都不做實際工作,主要負責消息轉發。

QXL_INTERFACE的初始化函數red_dispatcher_init會啓動一個專門的工作線程red_worker_main,此線程有三個職責,其中之一就是接收dispatcher接口轉發過來的消息,並進行實際的消息處理工作。dispatcher和工作線程之間的通信是通過一個socketpair,利用epoll模型機型通信。

此處link函數的實現同樣也是通過dispatcher接口red_dispatcher_set_peer將RED_WORKER_MESSAGE_DISPLAY_CONNECT消息發送給red_worker_main,

red_worker_main中,epoll_wait等待事件,然後調用具體事件處理函數來處理,此處會調用handle_dev_input函數。handle_dev_input函數響應RED_WORKER_MESSAGE_DISPLAY_CONNECT消息,調用handle_new_display_channel進行具體link工作(簡化版本):

複製代碼
static void handle_new_display_channel(RedWorker *worker, RedsStreamContext *peer, int migrate)

{

    DisplayChannel *display_channel;

    size_t stream_buf_size;

 

     // 先斷開原來的Channel連接,所做工作基本與此函數後面的工作相反

    red_disconnect_display((RedChannel *)worker->display_channel);

 

    // __new_channel函數會new一個RedChannel,並用該函數的參數初始化RedChannel的成員

    // RedChannel數據成員比較多,只揀最主要的說明一下:

// struct RedChannel {

//     EventListener listener;     // 事件觸發器,網絡事件處理用

//     spice_parse_channel_func_t parser; // 用途待查,網絡通信解碼器??

//     struct RedWorker *worker; // 用以保存RedWorker指針

//     RedsStreamContext *peer;  // 用以保存RedsStreamContext指針,此指針在響應客戶端連接時創建,網絡通信的主要處理對象

//

//     Ring pipe; // 用途待查明,聲明爲Ring,表明是一個鏈表,而非一個鏈表內的一項。如果聲明爲RingItem,表明其容器爲一個Item

//

//     disconnect_channel_proc disconnect;  // 下面四個是回調函數

//     hold_item_proc hold_item;       // 針對不同的Channel有不同的實現

//     release_item_proc release_item;    // 通過__new_channel函數

//     handle_message_proc handle_message; // 的參數傳入來初始化

// };

// 初始化上述結構後,__new_channel調用

// epoll_ctl(worker->epoll, EPOLL_CTL_ADD, peer->socket, &event)

// 將該channel對應的網絡連接socket加入到red_worker_main的epoll事件監聽// 列表中,以在red_worker_main中處理此Channel的網絡事件

if (!(display_channel = (DisplayChannel *)__new_channel(worker,

 sizeof(*display_channel),SPICE_CHANNEL_DISPLAY, peer,migrate,

 handle_channel_events,

red_disconnect_display,

display_channel_hold_item,

display_channel_release_item,

display_channel_handle_message))) {

        return;

    }

 

    ring_init(&display_channel->palette_cache_lru);

    red_display_init_streams(display_channel);

    red_display_share_stream_buf(display_channel);

    red_display_init_glz_data(display_channel);

    worker->display_channel = display_channel;

 

    on_new_display_channel(worker);

}
複製代碼


2)CursorChannel的link實現:

工作流程與DisplayChannel一樣,通過dispatcher將消息發送給工作線程處理。

消息:RED_WORKER_MESSAGE_CURSOR_CONNECT

處理函數:red_connect_cursor,所做工作基本一樣:

 

複製代碼
static void red_connect_cursor(RedWorker *worker, RedsStreamContext *peer, int migrate)

{

    CursorChannel *channel;

    red_disconnect_cursor((RedChannel *)worker->cursor_channel);

if (!(channel = (CursorChannel *)__new_channel(worker, sizeof(*channel),

           SPICE_CHANNEL_CURSOR, peer, migrate,

handle_channel_events,

red_disconnect_cursor,

cursor_channel_hold_item,

cursor_channel_release_item,

channel_handle_message))) {

        return;

    }

 

    ring_init(&channel->cursor_cache_lru);

    worker->cursor_channel = channel;

    on_new_cursor_channel(worker);

}
複製代碼

3)InputChannel的link實現:

與上述兩者不同的是,InputChannel的網絡處理放在了主循環的select模型中處理:

複製代碼
static void inputs_link(Channel *channel, RedsStreamContext *peer,

int migration,int num_common_caps, uint32_t *common_caps, int num_caps,

uint32_t *caps)

{

    InputsState *inputs_state;

    inputs_state = spice_new0(InputsState, 1);

 

    // 一些初始化……

 

    peer->watch = core->watch_add(peer->socket, SPICE_WATCH_EVENT_READ,

                                  inputs_event, inputs_state);

 

    SpiceMarshaller *m;

    SpiceMsgInputsInit inputs_init;

    m = marshaller_new_for_outgoing(inputs_state, SPICE_MSG_INPUTS_INIT);

    inputs_init.keyboard_modifiers = kbd_get_leds(keyboard);

    spice_marshall_msg_inputs_init(m, &inputs_init);

}
複製代碼


4)PLAYBACK、Record Channel的link實現:

大致流程與Display一樣,但網絡處理與InputChannel一樣,是通過watch_add方式採用select模型。

複製代碼
static void snd_set_record_peer(Channel *channel, RedsStreamContext *peer,

int migration, int num_common_caps, uint32_t *common_caps, int num_caps,

                                uint32_t *caps)

{

    snd_disconnect_channel(worker->connection);

if (!(record_channel = (RecordChannel *)__new_channel(worker,

 sizeof(*record_channel),SPICE_CHANNEL_RECORD,peer,migration,

        snd_record_send,

snd_record_handle_message,

snd_record_on_message_done,

snd_record_cleanup))) {

        goto error_2;

    }

 

    on_new_record_channel(worker);

 

    if (worker->active) {

        spice_server_record_start(st->sin);

    }

    snd_record_send(worker->connection);

    return;

}
複製代碼


3、Channel建立起來之後,服務端開始推送數據到客戶端,同時響應客戶端的請求,這裏涉及到兩大類不同的交互:用戶交互請求、實時數據推送

1)用戶交互請求

用戶交互請求都通過InputChannel進行網絡數據收發處理,上面分析過,InputChannel是通過watch_add的方式利用select模型在主循環中收發網絡信息的。

   InputChannel link建立時,向系統註冊了自己的消息處理函數:

   watch_add(peer->socket, SPICE_WATCH_EVENT_READ, inputs_event,

 inputs_state) // 從第二個參數可以看出,InputChannel只關心讀事件

   網絡事件到達時,系統將調用input_event函數,同時將inputs_state作爲其opaque參數傳入。

   input_event中對於輸入、輸出事件分別調用handle_incoming、handle_outgoing來進行處理,因此inputs_state比較重要的成員包括InComingHandler和OutGoinghandler,以及負責網絡通信的RedsStreamContext。對於InputChannel來說,OutGoinghandler目前是沒有用的。

   InComingHandler中最主要的成員是handle_message函數,此處對應於inputs_handle_input,主要響應客戶端發來的鍵盤鼠標消息:

複製代碼
static void inputs_handle_input(void *opaque, size_t size, uint32_t type, void *message)

{

    InputsState *state = (InputsState *)opaque;

    uint8_t *buf = (uint8_t *)message;

    SpiceMarshaller *m;

 

    switch (type) {

    case SPICE_MSGC_INPUTS_KEY_DOWN:    

    case SPICE_MSGC_INPUTS_KEY_UP:

    case SPICE_MSGC_INPUTS_MOUSE_MOTION:

    case SPICE_MSGC_INPUTS_MOUSE_POSITION:

    case SPICE_MSGC_INPUTS_MOUSE_PRESS:

    case SPICE_MSGC_INPUTS_MOUSE_RELEASE:

    case SPICE_MSGC_INPUTS_KEY_MODIFIERS:

    case SPICE_MSGC_DISCONNECTING:

        break;

    default:

        red_printf("unexpected type %d", type);

    }

}
複製代碼


   2)實時數據推送

   主要是音頻、視頻數據從Server端實時推送到Client。

   視頻包括兩部分:Screen、Cursor

   音頻包括兩部分:Playback、Record(Record屬於Client -> Server)  

   Server端視頻部分涉及到三大模塊:Qxl Qemu device、Qxl Guest OS driver、Channel處理線程red_worker_man

   關於Qxl Device、Qxl Driver將單獨分析,此處只關注Channel部分,即red_worker_main的工作。

   Server端音頻部分相對簡單一些,應該全部都在音頻設備中完成:(這裏只關注了ac97,其他音頻設備工作機制類似)

   AUD_register_card() ->

   audio_init() ->qemu_new_timer (vm_clock, audio_timer, s)

// 注意,audio_init 調用qemu_new_timer只是初始化glob_audio_state的timer,而不

// 是立即執行audio_timer函數,因爲此時glob_audio_state這個關鍵的全局數據對象

// 還沒有初始化完,audio_timer函數執行時所需要的很多信息要等glob_audio_state

// 初始化完後才能正常訪問。

   ac97_on_reset() -> ac97_on_reset() -> mixer_reset() -> reset_voices() ->

        {open_voice(這裏註冊VDI接口), AUD_set_active_out/in}

->audio_reset_timer() 激活timer

 

   AUD_register_card 和 ac97_on_reset 是在ac97_initfn函數中順序調用的,即:

    AUD_register_card():先做初始化,主要是初始化全局對象,glob_audio_state

    ac97_on_reset() :然後開始幹活,包括註冊VDI,註冊/激活timer等

 

   audio_timer() -> audio_run() --> audio_run_out() --> >pcm_ops->run_out --> line_out_run/line_in_run

  (注意,timer函數本身不是一個循環,而是被循環調用,關於timer工作機制參照文檔02)

 

   具體的執行部分在line_out_run/line_in_run  

   音頻、視頻的工作線程:red_worker_main、line_out_run/line_in_run

      1) 音頻輸出:line_out_run

      音頻部分會調用spice的幾個接口:

void spice_server_playback_start(SpicePlaybackInstance *sin);

void spice_server_playback_stop(SpicePlaybackInstance *sin);

void spice_server_playback_get_buffer(SpicePlaybackInstance *sin, uint32_t **samples, uint32_t *nsamples);

void spice_server_playback_put_samples(SpicePlaybackInstance *sin, uint32_t *samples);

AUD_set_active_out 時會調用 spice_server_playback_start

main_loop循環執行timer,timer調用line_out_run函數,此函數大致流程如下:

複製代碼
static int line_out_run (HWVoiceOut *hw, int live)

{

    SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw);

    decr = rate_get_samples (&hw->info, &out->rate);

 

    while (samples) {

        if (!out->frame) {

            spice_server_playback_get_buffer (&out->sin, &out->frame, &out->fsize); // 從spice中獲取緩衝區

            out->fpos = out->frame;

        }

        if (out->frame) {

            hw->clip (out->fpos, hw->mix_buf + rpos, len); // 虛擬聲卡捕獲音頻數據應該是放在mix_buf中

            out->fsize -= len;

            out->fpos  += len;

            if (out->fsize == 0) {

                spice_server_playback_put_samples (&out->sin, out->frame);

// 往緩衝區寫數據,實際會將數據發送到客戶端

                out->frame = out->fpos = NULL;

            }

        }

        rpos = (rpos + len) % hw->samples;

        samples -= len;

    }

}
複製代碼


    2) 視頻輸出:

    A、red_worker_main (on spice server)

   

複製代碼
 void *red_worker_main(void *arg)

{

    RedWorker worker;

 

    red_init(&worker, (WorkerInitData *)arg);

    red_init_quic(&worker);

    red_init_lz(&worker);

    red_init_jpeg(&worker);

    red_init_zlib(&worker);

    worker.epoll_timeout = INF_EPOLL_WAIT;

    for (;;) {

        num_events = epoll_wait(worker.epoll, events, MAX_EPOLL_SOURCES, worker.epoll_timeout);

        red_handle_streams_timout(&worker);

 

        if (worker.display_channel && worker.display_channel->glz_dict) {

              red_display_handle_glz_drawables_to_free(worker.display_channel);

        }

 

for (event = events, end = event + num_events; event < end; event++) {

            EventListener *evt_listener = (EventListener *)event->data.ptr;

 

            if (evt_listener->refs > 1) {

                evt_listener->action(evt_listener, event->events);

                if (--evt_listener->refs) {

                    continue;

                }

            }

            free(evt_listener); // refs == 0 , release it!

        }

 

        if (worker.running) {

            int ring_is_empty;

// 處理Cursor command,從虛擬設備中獲取數據

            red_process_cursor(&worker, MAX_PIPE_SIZE, &ring_is_empty);

// 處理Display command,從虛擬設備中獲取實時數據

            red_process_commands(&worker, MAX_PIPE_SIZE,

&ring_is_empty);

}

        red_push(&worker); // 往客戶端push顯示命令

    }

    red_printf("exit");

    return 0;

}
複製代碼


二、客戶端Channel實現

1、Channel類介紹

客戶端Channel通過基類RedChannel進行了基本功能的封裝,該基類的繼承層也非常深:

RedChannel: public RedChannelBase: public RedPeer: protected EventSources::Socket: public EventSource

其他具體的Channel則派生於RedChannel,各基類的功能函數如下(豎排):

RedChannel:public RedChannelBase:  public RedPeer:  protected EventSources::Socket:public EventSource

start         get_type        connect_unsecure     get_socket() = 0       action

connect       get_id          connect_secure  

disconnect    connect         disconnect

recive                        do_send

post_message                  send

get_message_handler           recive

worker_main                     

Message處理:_message_handler

run

可以簡單區分一下每個基類的職責:

RedChannel:開啓工作線程,分發、處理消息

RedChannelBase:維護type、id信息,連接網絡

RedPeer:網絡數據收發處理

EventSources::Socket:Socket類型的事件虛基類

EventSources:事件資源公共基類

 

2、消息處理

客戶端內部實現了消息處理機制,不看懂消息處理機制,無法真正理解Client的執行流程,下面是消息處理實現相關的類:

基類:

複製代碼
class RedChannel::MessageHandler {

public:

    MessageHandler() {}

    virtual ~MessageHandler() {}

    virtual void handle_message(RedPeer::CompoundInMessage& message) = 0;

};
複製代碼


消息模板:

複製代碼
template <class HandlerClass, unsigned int channel_id>

class MessageHandlerImp: public RedChannel::MessageHandler {

public:

    MessageHandlerImp(HandlerClass& obj);

    ~MessageHandlerImp() { delete [] _handlers; };

    virtual void handle_message(RedPeer::CompoundInMessage& message);

    typedef void (HandlerClass::*Handler)(RedPeer::InMessage* message);

    void set_handler(unsigned int id, Handler handler);

 

private:

    HandlerClass& _obj;

    unsigned int _max_messages;

    spice_parse_channel_func_t _parser;

    Handler *_handlers;

};
複製代碼


消息實例:

複製代碼
class MainChannelLoop: public MessageHandlerImp<RedClient, SPICE_CHANNEL_MAIN> {

public:

    MainChannelLoop(RedClient& client): MessageHandlerImp<RedClient, SPICE_CHANNEL_MAIN>(client) {}

};
複製代碼


模板類中封裝了消息處理的公共函數,對於不同的Channel,需要實例化自己的消息處理類,以MAIN_CHANNLE爲例,看看Client的Channel建立及消息處理機制實現。Client中MAIN_CHANNEL通過RedClient類進行封裝,其建立及消息機制初始化過程如下:

1) Application默認構造函數執行時,構造其RedClient成員_client

2)實例化基類的 RedChannel::_message_handler

       RedClient::RedClient(Application& application)

        : RedChannel(*this, SPICE_CHANNEL_MAIN,0, new MainChannelLoop(*this)) …

3)初始化MessageHandler的回調函數

MainChannelLoop* message_loop =

             static_cast<MainChannelLoop*>(get_message_handler());

      message_loop->set_handler(MSG_MIGRATE, &RedClient::handle_migrate);

      message_loop->set_handler(MSG_LIST, &RedClient::handle_channels);

       ……

4) 啓動工作線程

start(){

            _worker = new Thread(RedChannel::worker_main, this);

            ……

      }

5) 工作線程中處理消息

工作線程起來後會wait condition,RedClient的condition由LoginDialog的handle_connect函數調用application().connect來激活。

注:LoginDialog是用CEGUI庫寫的界面,button 響應函數的寫法如下:

    add_bottom_button(wnd, // 父窗

res_get_string(STR_BUTTON_CONNECT),                         CEGUI::Event::Subscriber(&LoginDialog::handle_connect, this),//響應函數

        x_pos);

工作線程詳細執行流程:

複製代碼
RedChannel::worker_main(){

        RedChannel::run(){

       // 這裏用了condition 相關的pthread庫函數來進行信號控制

// pthread_cond_signal、pthread_cond_wait……

       // 工作線程啓動後會等待RedClient的connect、disconnect、quit等操作

       // RedClient的connect會pthread_cond_signal(cond)

       _action_cond.wait --》 pthread_cond_wait(cond) // 等吧

 

       // _action 用來標識等來的是個什麼操作,一共有三個:

// CONNECT_ACTION、DISCONNECT_ACTION、QUIT_ACTION

       // 後面兩個是做一些清理工作,主要是第一個:CONNECT_ACTION

       case _action of CONNECT_ACTION:{

          RedChannelBase::connect // 調用基類的connect,進行網絡連接

          _marshallers = spice_message_marshallers_get // 初始化marshallers

          on_connect --> _migrate.add_channel()

          on_event {send_messages,recive_messages}

       

          // ProcessLoop 消息循環

          _loop.add_socket(*this); // --> add_event, 往_loop中的

// _event_sources增加event,以進行監聽……

// 注意了:上面使用了EventSources中的socket類,用於網絡事件的監聽

// 具體方法參見代碼:

// HANDLE event = CreateEvent(NULL, FALSE, FALSE, NULL); // 創建一個Event,然後:

// WSAEventSelect(socket.get_socket(), event,FD_READ | FD_WRITE | FD_CLOSE)

// 上面這個函數將event 與 socket綁定,當socket上有事件被激活時,event也將被激活

 

       _loop.run() {

           _event_sources.wait_events(){

            DWORD wait_res = MsgWaitForMultipleObjectsEx(_handles.size(),  &_handles[0], timeout_ms,                                                 QS_ALLINPUT, 0); // 此函數用來等待事件,對於同步事件,此函數返回前會修改事件狀態

                       

            int event_index = wait_res - WAIT_OBJECT_0;

            _events[event_index]->action() --> RedChannel::on_event{

               send_messages(){

                get_outgoing_message

                send

               }

               recive_messages(){

                RedPeer::recive()

                on_message_recived()

                _message_handler->handle_message(*(*message)){

                   (_obj.*_handlers[type])(&main_message); // 通過實例調用其成員函數,與SetHandler是所賦的成員函數對象一致

                }

               }

            }

           }   
複製代碼


    3、DisplayChannel、CursorChannel、InputChannel的創建啓動過程

    1)註冊ChannelFactory

複製代碼
void Application::register_channels()

{

    if (_enabled_channels[SPICE_CHANNEL_DISPLAY]) { //註冊channelFactory

        _client.register_channel_factory(DisplayChannel::Factory());

    }

    ……

}
複製代碼


2) 創建:響應服務端發過來的SPICE_MSG_MAIN_CHANNELS_LIST消息,調用handle_channels,創建各個channel

複製代碼
void RedClient::handle_channels(RedPeer::InMessage* message)

{

    SpiceMsgChannels *init = (SpiceMsgChannels *)message->data();

    SpiceChannelId* channels = init->channels;

    for (unsigned int i = 0; i < init->num_of_channels; i++) {

        create_channel(channels[i].type, channels[i].id);

    }

}
複製代碼


3)啓動:與RedClient類似,這裏在create_channel中,創建channel後,調用start函數啓動channel主線程worker_main

複製代碼
void RedClient::create_channel(uint32_t type, uint32_t id)

{

    ChannelFactory* factory = find_factory(type);

    RedChannel* channel = factory->construct(*this, id);

    _channels.push_back(channel);

    channel->start();   // 創建工作線程

    channel->connect(); // 工作線程會等待CONNECT_ACTION condition來激活線程

_migrate.add_channel(new MigChannel(type, id, channel->get_common_caps(), channel->get_caps()));

}
複製代碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章