天天看點

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");

        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;

        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) {

        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;          

    uint32_t u;          

    wl_fixed_t f;        

    const char *s;      

    struct wl_object *o;

    uint32_t n;          

    struct wl_array *a;  

    int32_t h;          

};

        可以看到可能出現的參數的類型包括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