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

        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);
}
        
阅读更多
想对作者说点什么?
相关热词

博主推荐

换一批

没有更多推荐了,返回首页