wayland学习(3)-wayland通信机制(server端实现)

原创 2018年04月16日 18:04:13

        wayland的client端和server端的跨进程通信是通过socket实现的。本文首先对server端的socket的生成,绑定,监听进行分析,以wayland的源码中自带的weston代码为例,在server端的main函数中,会调用weston_create_listening_socket,该函数的实现如下:

static int
weston_create_listening_socket(struct wl_display *display, const char *socket_name)
{
	if (socket_name) {
		if (wl_display_add_socket(display, socket_name)) {
			weston_log("fatal: failed to add socket: %m\n");
			return -1;
		}
	} else {
		socket_name = wl_display_add_socket_auto(display);
		if (!socket_name) {
			weston_log("fatal: failed to add socket: %m\n");
			return -1;
		}
	}

	setenv("WAYLAND_DISPLAY", socket_name, 1);

	return 0;
}

        若没有指定socket的名称,会自动生成socket名称,为"wayland-0",随后调用_wl_display_add_socket函数完成socket的生成,绑定,该函数的实现如下:

static int
_wl_display_add_socket(struct wl_display *display, struct wl_socket *s)
{
	socklen_t size;

	s->fd = wl_os_socket_cloexec(PF_LOCAL, SOCK_STREAM, 0);
	if (s->fd < 0) {
		return -1;
	}

	size = offsetof (struct sockaddr_un, sun_path) + strlen(s->addr.sun_path);
	if (bind(s->fd, (struct sockaddr *) &s->addr, size) < 0) {
		wl_log("bind() failed with error: %m\n");
		return -1;
	}

	if (listen(s->fd, 128) < 0) {
		wl_log("listen() failed with error: %m\n");
		return -1;
	}

	s->source = wl_event_loop_add_fd(display->loop, s->fd,
					 WL_EVENT_READABLE,
					 socket_data, display);
	if (s->source == NULL) {
		return -1;
	}

	wl_list_insert(display->socket_list.prev, &s->link);
	return 0;
}

        在该函数的调用中,wl_os_socket_cloexec的作用是生成socket,实现如下:

wl_os_socket_cloexec(int domain, int type, int protocol)
{
	int fd;

	fd = socket(domain, type | SOCK_CLOEXEC, protocol);
	if (fd >= 0)
		return fd;
	if (errno != EINVAL)
		return -1;

	fd = socket(domain, type, protocol);
	return set_cloexec_or_close(fd);
}

        生成的socket的函数为socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC,0),其中PF_LOCAL表示unix系统间的通信,SOCK_STREAM标示我们用的是TCP协议,这样会提供按顺序的、可靠、双向、面向连接的比特流,关于SOCK_CLOEXEC的作用可以参考博客。创建socket完成后,将该socket保存在wl_socket结构体s的成员fd中,接下来需要做的是socket的bind操作,它的参数设置在wl_socket_init_for_display_name中完成,该函数的实现如下所示:

static int
wl_socket_init_for_display_name(struct wl_socket *s, const char *name)
{
	int name_size;
	const char *runtime_dir;

	runtime_dir = getenv("XDG_RUNTIME_DIR");
	if (!runtime_dir) {
		wl_log("error: XDG_RUNTIME_DIR not set in the environment\n");

		/* to prevent programs reporting
		 * "failed to add socket: Success" */
		errno = ENOENT;
		return -1;
	}

	s->addr.sun_family = AF_LOCAL;
	name_size = snprintf(s->addr.sun_path, sizeof s->addr.sun_path,
			     "%s/%s", runtime_dir, name) + 1;

	s->display_name = (s->addr.sun_path + name_size - 1) - strlen(name);

	assert(name_size > 0);
	if (name_size > (int)sizeof s->addr.sun_path) {
		wl_log("error: socket path \"%s/%s\" plus null terminator"
		       " exceeds 108 bytes\n", runtime_dir, name);
		*s->addr.sun_path = 0;
		/* to prevent programs reporting
		 * "failed to add socket: Success" */
		errno = ENAMETOOLONG;
		return -1;
	}

	return 0;
}

        可以看到该socket的addr.sun_path由环境变量XDG_RUNTIME_DIR与display_name指定,在weston中该环境变量为

XDG_RUNTIME_DIR=/run/user/1000

        若没有指定display名称,会在该目录下生成一个wayland-0的文件。下一步需要对该socket文件进行监听,设置请求的最大排队长度为128。接下来调用wl_event_loop_add_fd创建了wl_event_source_fd,它代表一个基于socket fd的事件源,该函数的实现如下所示:

WL_EXPORT struct wl_event_source *
wl_event_loop_add_fd(struct wl_event_loop *loop,
		     int fd, uint32_t mask,
		     wl_event_loop_fd_func_t func,
		     void *data)
{
	struct wl_event_source_fd *source;

	source = malloc(sizeof *source);
	if (source == NULL)
		return NULL;

	source->base.interface = &fd_source_interface;
	source->base.fd = wl_os_dupfd_cloexec(fd, 0);
	source->func = func;
	source->fd = fd;

	return add_source(loop, &source->base, mask, data);
}

        将刚刚监听的fd通过add_source添加到display的loop->epoll_fd上,消息循环在上面等待client的连接。

static struct wl_event_source *
add_source(struct wl_event_loop *loop,
	   struct wl_event_source *source, uint32_t mask, void *data)
{
	struct epoll_event ep;

	if (source->fd < 0) {
		free(source);
		return NULL;
	}

	source->loop = loop;
	source->data = data;
	wl_list_init(&source->link);

	memset(&ep, 0, sizeof ep);
	if (mask & WL_EVENT_READABLE)
		ep.events |= EPOLLIN;
	if (mask & WL_EVENT_WRITABLE)
		ep.events |= EPOLLOUT;
	ep.data.ptr = source;

	if (epoll_ctl(loop->epoll_fd, EPOLL_CTL_ADD, source->fd, &ep) < 0) {
		close(source->fd);
		free(source);
		return NULL;
	}

	return source;
}

        其中epoll_ctl的用法可参考博客,当client端连接到server端的fd时,会调用处理函数wl_event_source_fd_dispatch(),该函数会调用之前注册的回调函数socket_data,其中wl_event_source_fd_dispatch实现如下:

static int
wl_event_source_fd_dispatch(struct wl_event_source *source,
			    struct epoll_event *ep)
{
	struct wl_event_source_fd *fd_source = (struct wl_event_source_fd *) source;
	uint32_t mask;

	mask = 0;
	if (ep->events & EPOLLIN)
		mask |= WL_EVENT_READABLE;
	if (ep->events & EPOLLOUT)
		mask |= WL_EVENT_WRITABLE;
	if (ep->events & EPOLLHUP)
		mask |= WL_EVENT_HANGUP;
	if (ep->events & EPOLLERR)
		mask |= WL_EVENT_ERROR;

	return fd_source->func(fd_source->fd, mask, source->data);
}

        其中socket_data的实现如下所示:

static int
socket_data(int fd, uint32_t mask, void *data)
{
	struct wl_display *display = data;
	struct sockaddr_un name;
	socklen_t length;
	int client_fd;

	length = sizeof name;
	client_fd = wl_os_accept_cloexec(fd, (struct sockaddr *) &name,
					 &length);
	if (client_fd < 0)
		wl_log("failed to accept: %m\n");
	else
		if (!wl_client_create(display, client_fd))
			close(client_fd);

	return 1;
}

        在函数wl_os_accept_cloexec中对client端的连接请求进行处理

int
wl_os_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
	int fd;

#ifdef HAVE_ACCEPT4
	fd = accept4(sockfd, addr, addrlen, SOCK_CLOEXEC);
	if (fd >= 0)
		return fd;
	if (errno != ENOSYS)
		return -1;
#endif

	fd = accept(sockfd, addr, addrlen);
	return set_cloexec_or_close(fd);
}

        紧接着是通过wl_client_create创建client对象,同时将该对象添加到compositor的client list中,那个时候,客户端已经初始化完成。

WL_EXPORT struct wl_client *
wl_client_create(struct wl_display *display, int fd)
{
	struct wl_client *client;
	socklen_t len;

	client = zalloc(sizeof *client);
	if (client == NULL)
		return NULL;

	wl_priv_signal_init(&client->resource_created_signal);
	client->display = display;
	client->source = wl_event_loop_add_fd(display->loop, fd,
					      WL_EVENT_READABLE,
					      wl_client_connection_data, client);

	if (!client->source)
		goto err_client;

	len = sizeof client->ucred;
	if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED,
		       &client->ucred, &len) < 0)
		goto err_source;

	client->connection = wl_connection_create(fd);
	if (client->connection == NULL)
		goto err_source;

	wl_map_init(&client->objects, WL_MAP_SERVER_SIDE);

	if (wl_map_insert_at(&client->objects, 0, 0, NULL) < 0)
		goto err_map;

	wl_priv_signal_init(&client->destroy_signal);
	if (bind_display(client, display) < 0)
		goto err_map;

	wl_list_insert(display->client_list.prev, &client->link);

	wl_priv_signal_emit(&display->create_client_signal, client);

	return client;

err_map:
	wl_map_release(&client->objects);
	wl_connection_destroy(client->connection);
err_source:
	wl_event_source_remove(client->source);
err_client:
	free(client);
	return NULL;
}

        着重看下bind_display函数,它将display与client对象进行绑定,当client端有display相关请求时,会调用client中的相关回调。实现如下:

static int
bind_display(struct wl_client *client, struct wl_display *display)
{
	client->display_resource =
		wl_resource_create(client, &wl_display_interface, 1, 1);
	if (client->display_resource == NULL) {
		/* DON'T send no-memory error to client - it has no
		 * resource to which it could post the event */
		return -1;
	}

	wl_resource_set_implementation(client->display_resource,
				       &display_interface, display,
				       destroy_client_display_resource);
	return 0;
}

        该函数首先创建了一个wl_resource类型对象,将值赋给client->display_resource。实际注册回调是在wl_resource_set_implementation。

WL_EXPORT void
wl_resource_set_implementation(struct wl_resource *resource,
			       const void *implementation,
			       void *data, wl_resource_destroy_func_t destroy)
{
	resource->object.implementation = implementation;
	resource->data = data;
	resource->destroy = destroy;
	resource->dispatcher = NULL;
}    

        其中display_interface封装的为对于request相关回调函数指针。

static const struct wl_display_interface display_interface = {
	display_sync,
	display_get_registry
};

        以上就是在启动wayland的server端时做的相关动作,而server端向client端发送event是通过wl_resource_post_event完成。

WL_EXPORT void
wl_resource_post_event(struct wl_resource *resource, uint32_t opcode, ...)
{
	union wl_argument args[WL_CLOSURE_MAX_ARGS];
	struct wl_object *object = &resource->object;
	va_list ap;

	va_start(ap, opcode);
	wl_argument_from_va_list(object->interface->events[opcode].signature,
				 args, WL_CLOSURE_MAX_ARGS, ap);
	va_end(ap);

	wl_resource_post_event_array(resource, opcode, args);
}

        第4行为可能出现的参数定义一个数组,wayland规定opcode后续的参数个数最多为20个,数组元素类型为wl_argument,该类型是一个联合体,定义如下:

union wl_argument {
	int32_t i;           /**< `int`    */
	uint32_t u;          /**< `uint`   */
	wl_fixed_t f;        /**< `fixed`  */
	const char *s;       /**< `string` */
	struct wl_object *o; /**< `object` */
	uint32_t n;          /**< `new_id` */
	struct wl_array *a;  /**< `array`  */
	int32_t h;           /**< `fd`     */
};

        可以看到可能出现的参数的类型包括int,uint...等八种。第8行所调用的函数va_start是c语言中对不定参数函数所做的处理,主要目的时将参数堆栈地址赋值给ap,通常与va_end配套使用,具体用法和参考va_start说明文档。而第9行函数wl_argument_from_va_list的主要功能是函数传入参数存入args中。具体发送事件在wl_resource_post_event_array中实现,代码如下:

WL_EXPORT void
wl_resource_post_event_array(struct wl_resource *resource, uint32_t opcode,
			     union wl_argument *args)
{
	handle_array(resource, opcode, args, wl_closure_send);
}

        该函数为对handle_array做的封装,handle_array多了一个参数wl_closure_send,该参数为一个函数指针,具体向client端socket发送消息在该函数中实现,该函数的实现如下所示:

int
wl_closure_send(struct wl_closure *closure, struct wl_connection *connection)
{
	int size;
	uint32_t buffer_size;
	uint32_t *buffer;
	int result;

	if (copy_fds_to_connection(closure, connection))
		return -1;

	buffer_size = buffer_size_for_closure(closure);
	buffer = zalloc(buffer_size * sizeof buffer[0]);
	if (buffer == NULL)
		return -1;

	size = serialize_closure(closure, buffer, buffer_size);
	if (size < 0) {
		free(buffer);
		return -1;
	}

	result = wl_connection_write(connection, buffer, size);
	free(buffer);

	return result;
}
        可以看到在该函数中

        handle_array的具体实现如下:

static void
handle_array(struct wl_resource *resource, uint32_t opcode,
	     union wl_argument *args,
	     int (*send_func)(struct wl_closure *, struct wl_connection *))
{
	struct wl_closure *closure;
	struct wl_object *object = &resource->object;

	if (resource->client->error)
		return;

	if (!verify_objects(resource, opcode, args)) {
		resource->client->error = 1;
		return;
	}

	closure = wl_closure_marshal(object, opcode, args,
				     &object->interface->events[opcode]);

	if (closure == NULL) {
		resource->client->error = 1;
		return;
	}

	if (send_func(closure, resource->client->connection))
		resource->client->error = 1;

	log_closure(resource, closure, true);

	wl_closure_destroy(closure);
}
        

颜色英文代码全集

redgreenbluemagentayellowchocolateblackaquamarinelimefuchsiabrassazurebrownbronzedeeppinkalicebluegr...
  • denal
  • denal
  • 2003-03-09 11:10:00
  • 668

Wayland消息队列

主消息队列 调用wl_display_dispath()函数的线程会自动成为主线程,并且拥有主消息队列。 wl_proxy消息队列 Wayland允许创建多个消息队列,使用wl_display_...
  • goodboychina
  • goodboychina
  • 2014-05-18 14:04:15
  • 2215

wayland helloworld (一)

Wayland是linux新一代的窗口系统服务器,将来肯定会替代X Server,学习一下还是很有必要的。如果有win32 gui编程经验的话学习wayland会相对容易点。     Win32...
  • goodboychina
  • goodboychina
  • 2014-05-17 16:52:59
  • 3487

wayland进程间调用

一、基本工作流程 以Weston自带的例程simple-shm为例,先感受一下Client如何通过Wayland协议和Compositor通信。 1. 连接Server,绑定服务 1)   displ...
  • arag2009
  • arag2009
  • 2017-11-15 10:36:32
  • 295

X11 和Wayland的区别,一点感悟

最近一直在找Wayland的资料,想知道他和X11到底有什么区别,有什么改进。 到目前为止,我还没有完完全全搞清楚这两个架构的原理。只能说记录一下这两天的发现。 x11是一个很古老的系统,有了几十...
  • love4Mario
  • love4Mario
  • 2017-07-16 22:57:19
  • 2218

第一个Wayland程序

Wayland的窗口程序没有默认的标题栏,需要手动绘制。
  • goodboychina
  • goodboychina
  • 2014-05-23 14:36:18
  • 1880

Wayland相关名词解释

看了一天Wayland,如果不做个笔记,估计晚上没到家就忘光了(话说越来越健忘了,不知是什么毛病)。 1. Wayland: 是一个叫Wayland compositor的Display serv...
  • coroutines
  • coroutines
  • 2016-01-12 18:38:49
  • 1830

Wayland中的跨进程过程调用浅析

Wayland协议主要提供了Client端应用与Server端Compositor的通信机制,Weston是Server端Compositor的一个参考实现。Wayland协议中最基础的是提供了一种面...
  • ariesjzj
  • ariesjzj
  • 2014-10-20 09:43:53
  • 9734

Wayland 源码解析之代码结构

获取、编译 Wayland 及其依赖库可参考 Wayland 官方网站的 Build 指南:http://wayland.freedesktop.org/building.html。   Wayl...
  • basilc
  • basilc
  • 2012-10-15 21:51:23
  • 3636

Wayland helloworld (四)之窗口显示

Wayland窗口绘制
  • goodboychina
  • goodboychina
  • 2014-05-17 18:05:41
  • 3681
收藏助手
不良信息举报
您举报文章:wayland学习(3)-wayland通信机制(server端实现)
举报原因:
原因补充:

(最多只允许输入30个字)