Wayland是一套display server(Wayland compositor)与client间的通信协议,
而Weston是Wayland compositor的参考实现。
Wayland基于domain socket实现了一套display server与client间通信的库(简单的基于例子的介绍可以参见http://blog.csdn.net/jinzhuojun/article/details/40264449),并且以XML形式定义了一套可扩展通信协议。这个协议分为Wayland核心协议和扩展协议(位于Weston中)。Weston作为Wayland compositor的参考实现,一般和Wayland同步发布。
Wayland协议主要提供了Client端应用与Server端Compositor的通信机制,Weston是Server端Compositor的一个参考实现。Wayland协议中最基础的是提供了一种面向对象的跨进程过程调用的功能,在作用上类似于Android中的Binder。与Binder不同的是,在Wayland中Client和Server底层通过domain socket进行连接。和Binder一样,domain socket支持在进程间传递fd,这为传递graphic buffer和shared memory等提供了基础。Client和Server端一方面各自在消息循环中等待socket上的数据,拿到数据后经过反序列化处理生成本地的函数调用,然后进行调用;另一方面将本地的远端调用请求封装序列化后通过socket发出。另外,由于是基于消息循环的处理模型,意味着这种调用不是同步,但通过等待Server端处理完成后的事件再返回可以达到同步调用的效果。
UNIX Domain Socket是在socket架构上发展起来的用于同一台主机的进程间通讯(IPC)
特点:
1. 它不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等
2. 只是将应用层数据从一个进程拷贝到另一个进程。
工作模式:
SOCK_DGRAM 类似于 UDP
SOCK_STREAM 类似于TCP
用途:
UNIX Domain Socket可用于两个没有亲缘关系的进程,是全双工的,是目前使用最广泛的IPC机制
比如X Window服务器和GUI程序之间就是通过UNIX Domain Socket通讯的
二、工作流程
与网络socket的不同点:
1. address family为AF_UNIX
2. unix domain socket不需要IP和端口,取而代之的是文件路径来表示“网络地址”
原理:
UNIXDomain socket用结构体sockaddr_un表示,是一个socket类型的文件在文件系统中的路径
这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回
UNIX Domain Socket客户端一般要显式调用bind函数,而不象网络socket一样依赖系统自动分配的地址。
客户端bind的socket文件名可以包含客户端的pid,这样服务器就可以区分不同的客户端
工作流程:
服务器端:创建socket—绑定文件(端口)—监听—接受客户端连接—接收/发送数据—…—关闭
客户端: 创建socket—绑定文件(端口)—连接—发送/接收数据—…—关闭
三、阻塞和非阻塞(SOCK_STREAM方式)
读写操作有两种操作方式:阻塞和非阻塞
1.阻塞模式下
阻塞模式下,发送数据方和接收数据方的表现情况如同命名管道
2.非阻塞模式
在send或recv函数的标志参数中设置MSG_DONTWAIT,则发送和接收都会返回。如果没有成功,则返回值为-1,errno为EAGAIN 或 EWOULDBLOCK
四.实例
服务端代码:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#define UNIX_DOMAIN "/data/local/tmp/ndk_cmd"
int main(void) {
int listen_fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (listen_fd < 0) {
perror("cannot create communication socket\n");
return 1;
}
//set server addr_param
struct sockaddr_un srv_addr;
srv_addr.sun_family = AF_UNIX;
strncpy(srv_addr.sun_path, UNIX_DOMAIN, sizeof(srv_addr.sun_path) - 1);
unlink (UNIX_DOMAIN);
//bind sockfd & addr
int ret = bind(listen_fd, (struct sockaddr*) &srv_addr, sizeof(srv_addr));
if (ret == -1) {
perror("cannot bind server socket");
close(listen_fd);
unlink(UNIX_DOMAIN);
return 1;
}
//listen sockfd
ret = listen(listen_fd, 1);
if (ret == -1) {
perror("cannot listen the client connect request");
close(listen_fd);
unlink(UNIX_DOMAIN);
return 1;
}
//have connect request use accept
struct sockaddr_un clt_addr;
int len = sizeof(clt_addr);
int com_fd = accept(listen_fd, (struct sockaddr*) &clt_addr, &len);
if (com_fd < 0) {
perror("cannot accept client connect request");
close(listen_fd);
unlink(UNIX_DOMAIN);
return 1;
}
//read and printf sent client info
printf("/n=====info=====/n");
static char recv_buf[1024];
for (int i = 0; i < 4; i++) {
memset(recv_buf, 0, 1024);
int num = read(com_fd, recv_buf, sizeof(recv_buf));
printf("Message from client (%d)) :%s\n", num, recv_buf);
}
close(com_fd);
close(listen_fd);
unlink(UNIX_DOMAIN);
return 0;
}
客户端代码:
#include <stdio.h>
#include <stddef.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
int main(void) {
//creat unix socket
int connect_fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (connect_fd < 0) {
perror("cannot create communication socket");
return 1;
}
static struct sockaddr_un srv_addr;
srv_addr.sun_family = AF_UNIX;
strcpy(srv_addr.sun_path, "/data/local/tmp/ndk_cmd");
//connect server
int ret = connect(connect_fd, (struct sockaddr*) &srv_addr, sizeof(srv_addr));
if (ret == -1) {
perror("cannot connect to the server");
close(connect_fd);
return 1;
}
char snd_buf[1024];
memset(snd_buf, 0, 1024);
strcpy(snd_buf, "message from client");
//send info server
for (int i = 0; i < 4; i++)
write(connect_fd, snd_buf, sizeof(snd_buf));
close(connect_fd);
return 0;
}
Socket,又称套接字,是Linux跨进程通信(IPC,Inter Process Communication,详情参考:Linux进程间通信方式总结)方式的一种。相比于其他IPC方式,Socket更牛的地方在于,它不仅仅可以做到同一台主机内跨进程通信,它还可以做到不同主机间的跨进程通信。根据通信域的不同可以划分成2种:Unix domain socket 和 Internet domain socket。
main(int argc, char **argv) simple-shm.c
{
struct sigaction sigint;
struct display *display;
struct window *window;
int ret = 0;
display = create_display(); {
display->display = wl_display_connect(NULL);
wl_display_roundtrip(display->display);
}
window = create_window(display, 250, 250);
if (!window)
return 1;
{
xdg_wm_base_get_xdg_surface(display->wm_base,
window->surface);
assert(window->xdg_surface);
xdg_surface_add_listener(window->xdg_surface,
&xdg_surface_listener, window);
}
sigint.sa_handler = signal_int;
sigemptyset(&sigint.sa_mask);
sigint.sa_flags = SA_RESETHAND;
sigaction(SIGINT, &sigint, NULL);
/* Initialise damage to full surface, so the padding gets painted */
wl_surface_damage(window->surface, 0, 0,
window->width, window->height);
if (!window->wait_for_configure)
redraw(window, NULL, 0);
while (running && ret != -1)
ret = wl_display_dispatch(display->display);
fprintf(stderr, "simple-shm exiting\n");
destroy_window(window);
destroy_display(display);
return 0;
}
一、基本工作流程
以Weston自带的例程simple-shm为例,先感受一下Client如何通过Wayland协议和Compositor通信。
1. 连接Server,绑定服务
display->display = wl_display_connect() // 通过socket建立与Server端的连接,得到wl_display。它即代表了Server端的display资源,同时也是代理对象wl_proxy。Client可以通过它来向Server端提交调用请求和接收事件。
display->registry = wl_display_get_registry(display->display) // 申请创建registry,得到代理对象wl_registry。这个对象相当于Client在Server端放的一个用于嗅探资源的Observer。Client通过它得到Server端有哪些Global对象的信息。Server端有一系列的Global对象,如wl_compositor, wl_shm等,串在display->global_list链表里。Global对象在概念上类似于Service服务,因此Server端相当于充当了ServiceManager的角色。
wl_registry_add_listener(display->registry, ®istry_listener,...) // 让Client监听刚才创建的wl_registry代理对象。这样,当Client调用wl_display_get_registry()函数或者有新的Global对象加入到Server端时,Client就会收到event通知。
wl_display_roundtrip() // 等待前面的请求全被Server端处理完,它同步了Client和Server端。这意味着到这个函数返回时,Server端有几个Global对象,回调处理函数registry_handle_global()应该就已经被调用过几次了。
registry_handle_global()中会判断是当前这次event代表何种Global对象,然后调用wl_registry_bind()进行绑定,得到远程服务对象的本地代理对象。这些代理对象类型可以是wl_shm, wl_compositor等,但本质上都是wl_proxy类型。这步的作用类似于Android中的bindService(),它会得到一个远端Service的本地代理。
2. 创建窗口
window->surface = wl_compositor_create_surface() // 通过刚才绑定的wl_compositor服务创建Server端的weston_surface,返回代理对象 wl_surface。
xdg_shell_get_xdg_surface(..., window->surface, ...) // 通过刚才绑定的xdg_shell服务创建Server端的shell_surface,返回代理对象 xdg_surface。有些例子中用的是wl_shell_surface,它和xdg_surface的作用是一样的。xdg_surface是作为wl_shell_surface将来的替代品,但还没进Wayland核心协议。
为什么一个窗口要创建两个surface呢?因为Wayland协议假设Server端对Surface的管理分两个层次。以Weston为例,Compositor只负责合成(代码主要在compositor.c),它相当于Android中的SurfaceFligner,它所看到的主要是weston_surface。而Weston在启动时会加载shell模块(如desktop-shell.so,代码主要在desktop-shell/shell.c),它相当于Android中的WindowManagerService,它所看到的主要是shell_surface。shell_surface在结构上是weston_surface的进一步封装,为了做窗口管理。这样,合成渲染和窗口管理的模块既可以方便地相互访问又保证了较低的耦合度。
3. 分配buffer与绘制
wl_surface_damage() // 告诉Compositor该surface哪块区域是脏的,需要重绘。一开始是整个窗口区域。
redraw() // 接下来调用redraw()开始绘制的循环,这里是双buffer的软件渲染。
window_next_buffer() // 取一个buffer,用作绘制。
create_shm_buffer() // 如果该buffer尚未分配则用之前绑定的wl_shm服务分配一块共享内存。
fd = os_create_anonymous_file() // 为了创建共享内存,先创建一个临时文件作为内存映射的backing file。
mmap(..., fd,...) // 将该文件先映射到Client端的内存空间。
pool = wl_shm_create_pool(..., fd,...) // 通过wl_shm服务创建共享内存池。将刚才的fd作为参数传过去,这样Server端就可以和Client通过这个fd映射到同一块物理内存。
buffer->buffer = wl_shm_pool_create_buffer(pool, ...) // 通过这个共享内存池在Server端分配buffer,返回wl_buffer为其本地代理对象。
wl_buffer_add_listener(buffer->buffer, &buffer_listener,...) // 监听这个buffer代理对象,当Server端不再用这个buffer时,会发送release事件。这样,Client就可以重用这个buffer作下一帧的绘制。
paint_pixels() // Client在buffer上绘制自己的内容。
wl_surface_attach()// 将绘制好的buffer attach到surface上。作用上类似于Android中的updateTexImage(),即把某一个buffer与surface绑定。
wl_surface_damage()// 告诉Server端的Compositor这个surface哪块区域是脏区域,需要重新绘制。
window->callback = wl_surface_frame() // 在Server端创建Frame callback,它会被放在该surface下的frame_callback_list列表中。返回它的代理对象wl_callback。
wl_callback_add_listener(window->callback, &frame_listener, ...) // 监听前面得到的callback代理对象。在Server端Compositor在完成一帧的合成渲染后,会往这些callback对象发done的事件(参考weston_output_repaint())。Client收到后会调用参数中wl_callback_listener中的done事件对应的方法,这里是redraw()函数。这样,就形成了一个循环。
wl_surface_commit() // 在0.99版本后,为了保证原子性及使surface属性的改动顺序无关,Server端对于surface的属性(damage region, input region, opaque region, etc.)都是双buffer的(weston_surface_state)。所以commit前的改动都存在backing buffer中。只有当Client调用wl_surface_commit()时,这些改动才生效。
与Android作个类比,这里的wl_surface对应SF中的Layer,wl_buffer对应GraphicBuffer。Weston对应SF+WMS。一个surface对应用户视角看到的一个窗口。为了使Client和Server并行作业,一般会用多个buffer。和Android比较不同的是,Android是Server端来维护buffer的生命周期,而 Wayland中是Client端来做的。