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);
}
        
————————————————
版权声明:本文为CSDN博主「马剑圣」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/LK1993/article/details/79957866

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值