如何擴(kuò)展 wayland 協(xié)議
為了能夠擴(kuò)展 wayland 協(xié)議,首先需要理解 wayland 協(xié)議,并且知道怎么樣在server和client端實(shí)現(xiàn)協(xié)議中定義的接口。看了一堆文檔,試著按照自己的理解來整理文檔,并動(dòng)手寫簡(jiǎn)單的代碼來加深理解。【希望一個(gè)月之后再讀這篇文章不會(huì)覺得是一坨shit】
wayland 協(xié)議是什么
wayland核心協(xié)議是一個(gè) xml 文件,如果我們安裝了 wayland 開發(fā)包,這個(gè)文件在一般在系統(tǒng)的 /usr/share/wayland/wayland.xml。核心協(xié)議的內(nèi)容有限,不滿足我們平常對(duì)窗口的一些操作,所以為了實(shí)現(xiàn)一些窗口管理的功能,還有很多擴(kuò)展的協(xié)議,比如 xdg-shell 就是為了實(shí)現(xiàn)桌面窗口而擴(kuò)展的協(xié)議。協(xié)議有穩(wěn)定版本和不穩(wěn)定版本,在這篇文檔中我們主要看 /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml,這是一個(gè)穩(wěn)定的版本,它的 xdg_surface 對(duì)象有一個(gè) request 接口是 set_window_geometry,從注釋來看,如果實(shí)現(xiàn)這個(gè)接口,應(yīng)該能滿足我們的需求。
可以通過 wayland-scanner 工具解析這個(gè) xml 生成Server、Client的頭文件以及 glue code一個(gè) C 文件。
wayland-scanner server-header < xdg-shell.xml > xdg_shell_server_header.h
wayland-scanner client-header < xdg-shell.xml > xdg_shell_client_header.h
wayland-scanner private-code < xdg-shell.xml > xdg_shell_protocol.c
上面這張圖是網(wǎng)上找到的,開發(fā)的時(shí)候基本上就是這么個(gè)流程。寫一個(gè)server.c 文件,用到生成的 server-protocol.h 和 protocol.c ,編譯命令:
gcc -o server server.c xdg_shell_protocol.c -lwayland-server
編譯Client程序:
gcc -o xdg_client xdg_client.c xdg_shell_protocol.c -lwayland-client
理解 wayland 協(xié)議
wayland 協(xié)議其實(shí)就是我們預(yù)先定義好一個(gè) object 的接口,包括它的 request 和 event,Server實(shí)現(xiàn) request 接口,Client實(shí)現(xiàn)對(duì) event 的監(jiān)聽和響應(yīng)。當(dāng)Client把對(duì)這個(gè)對(duì)象的 request 封裝成消息發(fā)給Server,Server收到消息后,根據(jù)對(duì)象id的和操作碼執(zhí)行對(duì)應(yīng)的響應(yīng)函數(shù);event 也是類似的流程,Server把對(duì)這個(gè)對(duì)象的 event 發(fā)到了Client,Client會(huì)作出響應(yīng)。
在Server和Client之間,對(duì)象是一一對(duì)應(yīng)的,互相知道這個(gè)對(duì)象的狀態(tài)。Client是 wl_proxy,與之對(duì)應(yīng)的,在Server就會(huì)有一個(gè) wl_resource。Server程序需要知道這個(gè) resource 屬于哪個(gè)Client程序。這個(gè)對(duì)象之間映射就是 wayland 的 wl_map 來維護(hù)的。
struct wl_map {
struct wl_array client_entries;
struct wl_array server_entries;
uint32_t side;
uint32_t free_list;
};
side 分WL_MAP_CLIENT_SIDE 和 WL_MAP_SERVER_SIDE兩種,表明當(dāng)前map保存的是Client還是 Server對(duì)象。
wayland Server運(yùn)行之后,一般會(huì)有多個(gè) wayland Client程序連接。每當(dāng)Client程序調(diào)用 wl_display_connect 連接到wayland, 對(duì)應(yīng)就會(huì)為它維護(hù)一個(gè) wl_map結(jié)構(gòu):display->objects[代碼wayland-client.c ];如果Server監(jiān)聽到Client連接,在 wl_client_create 的時(shí)候,也會(huì)為之創(chuàng)建一個(gè) wl_map:client->objects[代碼wayland-server.c]。
在映射表中,Client proxy 對(duì)象從 0 開始,Server resource 對(duì)象從 0xff000000 開始存放,display->objects只用了 client_entries,client->objects 只用了 server_entries。wl_map在Client和Server端各有一個(gè),它們分別存了wl_proxy和wl_resource的數(shù)組,且是一一對(duì)應(yīng)的。這些對(duì)象在這個(gè)數(shù)組中的索引作為它們的id。這樣,參數(shù)中的對(duì)象只要傳id,這個(gè)id被傳到目的地后會(huì)通過查找這個(gè)wl_map表來得到本地相應(yīng)的對(duì)象。
映射表創(chuàng)建后,就可以插入數(shù)據(jù)了。比如我們客戶端創(chuàng)建wl_proxy,設(shè)置interface等信息,然后將該wl_proxy插入到display->objects的wl_map中,返回值為id,其實(shí)就是在wl_map中數(shù)組中的索引值。這個(gè)值是會(huì)被發(fā)到Server端的,這樣Server端就可以創(chuàng)建 wl_resource,并把它插入到Server端的wl_map數(shù)組的相同索引值的位置。這樣邏輯上,就創(chuàng)建了wl_proxy和wl_resource的映射關(guān)系。以后,Client和Server間要相互引用對(duì)象只要傳這個(gè)id就可以了。
上面這張圖被多次引用,它很清楚的描述了Server和Client之間的對(duì)象映射和事件調(diào)用。
object
為了Server和Client之間的通信,第一步就是創(chuàng)建對(duì)象,wayland中默認(rèn)第一個(gè)對(duì)象就是 wl_display,object id 為1,這個(gè)對(duì)象之外的所有其他對(duì)象,都是需要通信來創(chuàng)建的。
在 wayland 中,wl_display 是第一個(gè)對(duì)象,wl_registry 是第二個(gè)對(duì)象,因?yàn)橛辛?wl_registry 之后,我們才能注冊(cè)綁定其他所有的 global object。在運(yùn)行Server程序前,開啟 WAYLAND_DEBUG=1,可以看到Client連接后,Server的 debug 如下:
[1008582.564] wl_display@1.get_registry(new id wl_registry@2)
[1008582.581] -> wl_registry@2.global(1, "wl_output", 1)
[1008582.654] wl_registry@2.bind(1, "wl_output", 1, new id [unknown]@4)
request 和 event
在生成的 glue code代碼中,主要定義了一些對(duì)象,以及這寫對(duì)象的request和event接口。Server頭文件中定義 request 的接口結(jié)構(gòu),因?yàn)镾erver需要響應(yīng)Client請(qǐng)求,所以我們?cè)陂_發(fā)Server程序時(shí)需要實(shí)現(xiàn) request接口;在Client頭文件中定義 event 的 listener,因?yàn)镃lient需要監(jiān)聽Server傳過來的事件,執(zhí)行對(duì)應(yīng)的回調(diào)。所以在開發(fā)Client程序時(shí),需要去 add_listener,實(shí)現(xiàn)收到事件之后要做的工作。
以 xdg_surface 為例,它在生成的xdg_shell_protocol.c 文件中定義如下:
WL_PRIVATE const struct wl_interface xdg_surface_interface = {
"xdg_surface", 3,
5, xdg_surface_requests,
1, xdg_surface_events,
};
這個(gè) xdg_surface_interface 是一個(gè)全局變量,數(shù)據(jù)類型是 wl_interface。這個(gè)結(jié)構(gòu)中成員組成:name、version、request個(gè)數(shù)、request簽名、event個(gè)數(shù)以及event簽名。也就是說 xdg_surface 這個(gè)對(duì)象有 5 個(gè)請(qǐng)求和 1 個(gè)事件。
request 和 event 簽名是 wl_message 結(jié)構(gòu)的,不管是 request 和 event 都會(huì)被封裝成 MESSAGE 在 server 和 client 之間傳遞。
static const struct wl_message xdg_surface_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "get_toplevel", "n", xdg_shell_types + 7 },
{ "get_popup", "n?oo", xdg_shell_types + 8 },
{ "**set_window_geometry**", "iiii", xdg_shell_types + 0 },
{ "ack_configure", "u", xdg_shell_types + 0 },
};
message
當(dāng)Client程序拿到一個(gè)對(duì)象了,就可以給Server發(fā)對(duì)這個(gè)對(duì)象的request,Server收到這個(gè)請(qǐng)求去執(zhí)行對(duì)應(yīng)的工作。比如Client的窗口中有個(gè)entry,我們輸入文字時(shí),窗口內(nèi)容需要更新,那么就可以在Client調(diào)用 wl_surface_damage 告知Server這塊區(qū)域無效需要重新繪制了,其實(shí) wl_surface_damage 這個(gè)函數(shù)其實(shí)就是把就是wl_surface 提供的一個(gè) request“damage”封裝成一條操作消息,通過 socket從Client發(fā)到Server,Server收到了這條消息,解析message,找到操作碼去執(zhí)行對(duì)應(yīng)的操作;同樣Server也會(huì)給Client發(fā)event,Client監(jiān)聽到event去執(zhí)行對(duì)應(yīng)的回調(diào)。
對(duì)于一條 message,我們需要了解它的基本結(jié)構(gòu):
object id + messeage size + opcode + 其他各個(gè)參數(shù)組成
其中 opcode 就是我們請(qǐng)求或者事件的操作碼,這個(gè)碼其實(shí)是由協(xié)議文件 xml 中它的出現(xiàn)順序決定的(從0開始計(jì)數(shù)),比如 damage 在 wl_surface_request 中是第三個(gè),它的 opcode 就是 2,下面這張圖來自 wayland protocol book:
其實(shí)我想看 set_window_geometry 的,但是不知道怎么樣打印出上面的數(shù)據(jù)。xdg_surface 的 set_window_geometry 是第4個(gè),所以它的 opcode 應(yīng)該是 3,在生成的客戶端頭文件中定義:#define XDG_SURFACE_SET_WINDOW_GEOMETRY 3
協(xié)議消息打包
Client請(qǐng)求
接下來以我們最關(guān)心的xdg-shell 擴(kuò)展協(xié)議中的 xdg_surface_set_window_geometry 為例來介紹消息是如何從Client發(fā)到Server的。這個(gè)函數(shù)是 wayland-scanner 掃描協(xié)議文件xdg-shell-unstable-v5.xml,為xdg_surface 的請(qǐng)求 set_window_geometry 自動(dòng)生成的供Client使用的函數(shù),函數(shù)里面只是簡(jiǎn)單地執(zhí)行了下面這個(gè)語(yǔ)句:
wl_proxy_marshal((struct wl_proxy *) xdg_surface, XDG_SURFACE_SET_WINDOW_GEOMETRY, x, y, width, height);
wl_proxy_marshal 在我們這幾個(gè)生成文件中找不到定義,它是 wayland 庫(kù)提供的,需要在 wayland 代碼中去找,src/wayland-client.c 文件中,通過層層調(diào)用,在函數(shù) wl_proxy_marshal_array_constructor_versioned 中開始構(gòu)建 message 并發(fā)送:
構(gòu)建 wl_closure 消息:
closure = wl_closure_marshal(&proxy->object, opcode, args, message);
發(fā)送消息:
wl_closure_send(closure, proxy->display->connection)
Server和Client的通信通過 socket 來實(shí)現(xiàn),由于這部分跟我們擴(kuò)展協(xié)議暫時(shí)沒什么關(guān)系,所以沒有深入去看,感興趣可以自己去看代碼。附錄簡(jiǎn)單介紹了通信機(jī)制。
Server事件
流程其實(shí)差不多,只不過Server的函數(shù)是 post_event。比如創(chuàng)建對(duì)象時(shí)發(fā)出的global信號(hào):
wl_resource_post_event(resource,
WL_REGISTRY_GLOBAL,
global->name,
global->interface->name,
global->version);
協(xié)議消息解包
消息解包就是把上面的 marshal 過程再 demarshal,因?yàn)樵诖虬⒌臅r(shí)候知道 object id,interface的接口簽名以及 opcode,這樣就能根據(jù)這幾個(gè)信息從 interface 中解析得到參數(shù)格式,從而把一整條消息解析出來。比如 set_window_geometry,從接口定義中找到它的 wl_message 格式:
{ "set_window_geometry", "iiii", xdg_shell_unstable_v5_types + 0 },
這是一條 wl_message 結(jié)構(gòu)的數(shù)據(jù):
struct wl_message {
/** Message name */
const char *name;
/** Message signature */
const char *signature;
/** Object argument interfaces */
const struct wl_interface **types;
};
其中消息簽名是”iiii”表示這個(gè) request 的參數(shù)是四個(gè)整形數(shù)據(jù)。第三個(gè)成員 types,需要從數(shù)組 xdg_shell_unstable_v5_types 中去找,這里加 0 表示數(shù)組第一條數(shù)據(jù)。這個(gè)請(qǐng)求不需要?jiǎng)?chuàng)建新的對(duì)象,所以簽名為空。如果需要?jiǎng)?chuàng)建新對(duì)象,消息簽名中會(huì)有“n”,表示 new id,types 就是這個(gè)新對(duì)象的接口定義。比如 wl_display_get_registry 需要返回 wl_registry 對(duì)象,Server和Client需要為這個(gè)新對(duì)象達(dá)成共識(shí),好為后面的request、event傳遞打下基礎(chǔ),所以需要提前定好 interface,它的這個(gè) types 就是 &wl_registry_interface。// { "get_registry", "n", wayland_types + 9 },
回到 set_window_geometry,協(xié)議中定義了這個(gè)請(qǐng)求,Client把這個(gè)請(qǐng)求組成 message 發(fā)送給Server。Server socket 監(jiān)聽機(jī)制監(jiān)聽到這條消息后,就會(huì)執(zhí)行 socket 的回調(diào)函數(shù) wl_client_connection_data[見附錄說明]。這個(gè)函數(shù)里面反序列化消息,得到 wl_closure,找到目標(biāo)對(duì)象對(duì)應(yīng)的接口函數(shù),利用 libffi 執(zhí)行 server 端的 implementation 函數(shù)。
如何找到目標(biāo)對(duì)象的接口函數(shù)?這就需要 server 端來實(shí)現(xiàn)了。wayland server 和 client 之間的對(duì)象是一一對(duì)應(yīng)的,對(duì)于每個(gè) object,它的 interface 定義,有幾個(gè) request,有幾個(gè) event,以及各自的參數(shù)是什么,它們相互之間都很清楚。所以每當(dāng)Client申請(qǐng) bind 一個(gè)對(duì)象,Server需要?jiǎng)?chuàng)建一個(gè)資源與之對(duì)應(yīng),如果這個(gè)對(duì)象有 request 需要實(shí)現(xiàn),Server就需要去實(shí)現(xiàn)這些函數(shù)接口并且 set_implementation。
Server代碼簡(jiǎn)述
為了響應(yīng)Client的請(qǐng)求,Server需要?jiǎng)?chuàng)建對(duì)應(yīng)的對(duì)象。首先我們需要去查協(xié)議中對(duì)象的接口定義,再去實(shí)現(xiàn)對(duì)象接口中定義的request。代碼片段1:
display = wl_display_create ();
wl_display_add_socket_auto (display);
wl_global_create (display, &wl_compositor_interface, 3, NULL, &compositor_bind);
wl_global_create (display, &wl_shell_interface, 1, NULL, &shell_bind);
wl_global_create (display, &xdg_wm_base_interface, 1, NULL, &xdg_wm_base_bind);
wl_global_create (display, &wl_seat_interface, 1, NULL, &seat_bind);
每當(dāng)Server調(diào)用 wl_global_create 創(chuàng)建一個(gè)對(duì)象,就會(huì)將 global 事件打包發(fā)給Client,Client收到 global 事件,在回調(diào)函數(shù)中需要 bind 這個(gè)對(duì)象。上面創(chuàng)建 wl_compositor 對(duì)象時(shí),傳遞了 &compositor_bind 函數(shù)指針,也就是說,如果Client執(zhí)行 wl_registry_bind 綁定 wl_compositor對(duì)象,Server就會(huì)執(zhí)行這個(gè) compositor_bind。在綁定的時(shí)候,Server創(chuàng)建與Client對(duì)象相對(duì)應(yīng)的資源,并且設(shè)置它的實(shí)現(xiàn)函數(shù)。比如 wl_compositor,它的 request 有兩個(gè):
static const struct wl_message wl_compositor_requests[] = {
{ "create_surface", "n", wayland_types + 10 },
{ "create_region", "n", wayland_types + 11 },
};
那么Server代碼中就需要定義這兩個(gè)請(qǐng)求對(duì)應(yīng)的處理函數(shù):
static struct wl_compositor_interface compositor_implementation =
{
&compositor_create_surface,
&compositor_create_region
};
在 compositor_bind 中,主要做這兩步工作:
struct wl_resource *resource = wl_resource_create (client, &wl_compositor_interface, 1, id);
wl_resource_set_implementation (resource, &compositor_implementation, NULL, NULL);
如果我們定義的 compositor_create_surface沒有問題,Client調(diào)用 wl_compositor_create_surface 函數(shù)的時(shí)候,Server就能執(zhí)行到 compositor_create_surface,從而打通從Client到Server的請(qǐng)求流程。
但是,由于 wl_compositor,wl_surface 這些對(duì)象要實(shí)現(xiàn)的接口太太多了,所以在測(cè)試代碼中,這些對(duì)象的 request 我就只定義了空的函數(shù)體,保證Client請(qǐng)求能拿到對(duì)象,但是沒有實(shí)際工作。代碼片段如下:
static const struct **wl_output_interface** wl_output_implementation = {
.release = wl_output_handle_release,
};
在綁定對(duì)象時(shí),設(shè)置 request 接口的實(shí)現(xiàn)
struct wl_resource *resource = wl_resource_create (client, &wl_output_implementation, wl_output_interface.version, id);
wl_resource_set_implementation(resource, &wl_output_implementation, client_output, wl_output_handle_resource_destroy);
wayland 中有兩個(gè) wl_output_interface,千萬不要混淆了。其中一個(gè)是wl_interface 類型的變量,指明了 name version request 和 event,在生成的glue code文件中定義:
WL_PRIVATE const struct wl_interface ***wl_output_interface*** = {
"wl_output", 3,
1, wl_output_requests,
4, wl_output_events,
};
另一個(gè)是結(jié)構(gòu)體,成員是待實(shí)現(xiàn)的函數(shù)指針,在生成的server 頭文件中定義:
struct ***wl_output_interface*** {
void (*release)(struct wl_client *client,
struct wl_resource *resource);
};
每個(gè)對(duì)象都會(huì)有這兩個(gè)讓人迷糊的 wl_xxx_interface,一定要注意區(qū)分。結(jié)構(gòu)體 wl_output_interface 里面的函數(shù)指針就對(duì)應(yīng)是這個(gè)對(duì)象需要實(shí)現(xiàn)的 request。
實(shí)現(xiàn)request
qtwayland 無法設(shè)置 Client 窗口的坐標(biāo),理論上來說,可能是下面兩種原因:
1、客戶端程序的 set_window_geometry 沒有給 wayland Server 發(fā)請(qǐng)求
2、雖然客戶端發(fā)了請(qǐng)求,但是 wayland Server 端實(shí)際上沒有實(shí)現(xiàn)它。
很多現(xiàn)有的合成器都沒有實(shí)現(xiàn) set_window_geometry 接口,因?yàn)?wayland 設(shè)計(jì)理念就是這樣。它明確表示不希望客戶端程序自己設(shè)置坐標(biāo),而是覺得客戶端的坐標(biāo)應(yīng)該由合成器去做決定。
我找到了一個(gè)非常非常簡(jiǎn)單的開源合成器代碼,它沒有實(shí)現(xiàn) xdg-shell 的 set_window_geometry 請(qǐng)求。一開始我在運(yùn)行 weston-flower 客戶程序時(shí),不管怎么調(diào)用 xdg_surface_set_window_geometry嘗試設(shè)置坐標(biāo)都無效,一直都顯示在位置0,0.
補(bǔ)充:開源代碼地址:https://github.com/eyelash/tutorials.git
通過修改合成器代碼,實(shí)現(xiàn) xdg-shell 的協(xié)議來達(dá)到設(shè)置客戶端窗口的目標(biāo)。
首先我們需要維護(hù)資源的狀態(tài),可以定義一個(gè)結(jié)構(gòu)體,維護(hù)當(dāng)前 xdg_surface,當(dāng)前坐標(biāo) x,y 等。
第一步:在創(chuàng)建對(duì)象時(shí),綁定我們的實(shí)現(xiàn)接口:
struct surface *surface = wl_resource_get_user_data (_surface);
surface->xdg_surface = wl_resource_create (client, &xdg_surface_interface, 1, id);
wl_resource_set_implementation (surface->xdg_surface, &xdg_surface_implementation, surface, NULL);
這個(gè)surface 就是自定義的結(jié)構(gòu)體,把它設(shè)置為服務(wù)端資源的user_data:resource->data = surface,當(dāng)接收到客戶端請(qǐng)求的時(shí)候,這個(gè)數(shù)據(jù)會(huì)作為 wl_resource 的參數(shù)一起傳遞過來,用于為維護(hù)處于不斷變化中的資源的狀態(tài)。
第二步:定義各個(gè) request 實(shí)現(xiàn):
static struct xdg_surface_interface xdg_surface_implementation = {
&xdg_surface_destroy,
&xdg_surface_get_toplevel,
&xdg_surface_get_popup,
&xdg_surface_set_window_geometry,
&xdg_surface_ack_configure
};
第三步:實(shí)際的實(shí)現(xiàn)接口:
static void xdg_surface_set_window_geometry (struct wl_client *client, struct wl_resource *resource,
int32_t x, int32_t y, int32_t width, int32_t height)
{
struct surface *surface = wl_resource_get_user_data (resource);
surface->x = x;
surface->y = y;
redraw_needed = 1;
}
由于我們想要更新服務(wù)端 resource 的位置,首先需要拿到服務(wù)端維護(hù)的xdg_surface,在綁定實(shí)現(xiàn)接口時(shí),傳入的 surface 參數(shù),可以取出來。redraw_needed 是為了觸發(fā)下一幀畫面時(shí)重新渲染客戶端窗口,要不然不會(huì)更新。
附錄
wayland通信
在開發(fā) wayland Client程序時(shí),第一步工作就是連接到wayland Server拿到資源對(duì)象,一般是wl_display_connect 再wl_display_get_registry,拿到 wl_registry 對(duì)象后,就會(huì)監(jiān)聽 global 信號(hào),通過調(diào)用 wl_registry_add_listener 來監(jiān)聽,注冊(cè)信號(hào)回調(diào)函數(shù)。
Server會(huì)把可用對(duì)象挨個(gè)發(fā)出 global 信號(hào),Client程序在 global 信號(hào)回調(diào)函數(shù)中就可以 wl_registry_bind 這些對(duì)象,生成Client的可用對(duì)象。[參考 weston 的客戶程序代碼 window.c] 但是信號(hào)機(jī)制一般是對(duì)同一個(gè)進(jìn)程來說的,我們可以監(jiān)聽某個(gè)對(duì)象的某個(gè)信號(hào),當(dāng)收到信號(hào)時(shí)執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù);而這里其實(shí)是兩個(gè)程序,Server和Client,這種跨進(jìn)程的信號(hào)不是簡(jiǎn)單地 connect 就可以的,而是需要通過 socket 來傳遞。 要讓兩個(gè)進(jìn)程通過 socket 進(jìn)行函數(shù)調(diào)用,首先需要將調(diào)用抽象成數(shù)據(jù)流的形式,這些信息通過 wl_closure_marshal 寫入 wl_closure 結(jié)構(gòu),再由 serialize_closure 變成數(shù)據(jù)流;等到了目標(biāo)進(jìn)程,從數(shù)據(jù)流中通過 wl_connection_demarshal 轉(zhuǎn)回 wl_closure 結(jié)構(gòu)。
1、wayland server 啟動(dòng)時(shí)會(huì)創(chuàng)建一個(gè) socket(_wl_display_add_socket),并將這個(gè) socket fd 加入 epoll 中,這樣一旦有有Client程序連接,epoll 就會(huì)通知Server,從而執(zhí)行回調(diào)函數(shù) socket_data。Client可能會(huì)有很多,用 epoll 會(huì)比較有效率。
2、當(dāng)有Client程序通過調(diào)用wl_display_connect 連接到 server, 在回調(diào)函數(shù) socket_data 中會(huì)調(diào)用 accept 創(chuàng)建新的 socket fd,緊接著創(chuàng)建 wl_client并在創(chuàng)建 wl_client 的時(shí)候,wl_client_create (display, fd) 將這個(gè) socket fd 加入 epoll,繼續(xù)監(jiān)聽新的 socket,為的是響應(yīng)從Client發(fā)過來的請(qǐng)求,回調(diào)函數(shù)是 wl_client_connect_data。
一個(gè) socket 負(fù)責(zé)監(jiān)聽Client連接,對(duì)于每個(gè)Client還有一個(gè)socket負(fù)責(zé)監(jiān)聽Client的請(qǐng)求。