2021SC@SDUSC-山大智云源码分析(9)

2021SC@SDUSC

目录

Implementatin of a searpc transport based on named pipe(2)

基于named-pipe的searpc-client

searpc-named-pipe-transport-c

创建named-pipe服务器

启动named-pipe服务器

named-pipe监听方法

named-pipe监听到连接后处理通信过程

named-pipe客户端的连接

基于named-pipe的searpc-client

总结


Implementatin of a searpc transport based on named pipe(2)

基于named-pipe的searpc-client

经过前一篇分析,基本分析完成了与searpc客户端相关的方法.在之前的分析中,我们已经知道了,searpc-client的作用手势存放传输函数和socket fd,接下来从这两个方面在总结基于named-pipe的searpc-client的用法.

首先是传输函数,这里的searpc客户端与服务器传输数据的方式是通过named-pipe的,而作用是将收发数据的传输函数自然也应该基于named-pipe.首先通过searpc_create_named_pipe_client方法构造named-pipe客户端,在传输函数searpc_named_pipe_send,中指定该客户端,并通过它收发数据.其余部分与基于socket的searpc-client相同.

其次是socket fd,基于named-pipe的searpc-client显示是不需要socket fd的,但是需要一个用于标识并获取到所使用的named-pipe的标识作为代替,即是ClientTransportData,他是一个包含了named-pipe客户端与服务名的结构体,传输函数通过它获取到named-pipe,并传输数据.

searpc-named-pipe-transport-c

接下来继续分析searpc-named-pipe-transport-c

创建named-pipe服务器

SearpcNamedPipeServer* searpc_create_named_pipe_server(const char *path)
{
    SearpcNamedPipeServer *server = g_malloc0(sizeof(SearpcNamedPipeServer));
    memcpy(server->path, path, strlen(path) + 1);
​
    return server;
}
​
SearpcNamedPipeServer* searpc_create_named_pipe_server_with_threadpool (const char *path, int named_pipe_server_thread_pool_size)
{
    GError *error = NULL;
​
    SearpcNamedPipeServer *server = g_malloc0(sizeof(SearpcNamedPipeServer));
    memcpy(server->path, path, strlen(path) + 1);
    server->named_pipe_server_thread_pool = g_thread_pool_new (handle_named_pipe_client_with_threadpool,
                                                               NULL,
                                                               named_pipe_server_thread_pool_size,
                                                               FALSE,
                                                               &error);
    if (!server->named_pipe_server_thread_pool) {
        if (error) {
            g_warning ("Falied to create named pipe server thread pool : %s\n", error->message);
            g_clear_error (&error);
        } else {
            g_warning ("Falied to create named pipe server thread pool.\n");
        }
        g_free (server);
        return NULL;
    }
​
    return server;
}

上述两个方法都创建了一个NamedPipeServer,并指定了路径,区别在于是否创建了线程池;其中线程池中的线程都指定了handle_named_pipe_client_with_threadpool方法

但是并没有指定pipe_fd

启动named-pipe服务器

int searpc_named_pipe_server_start(SearpcNamedPipeServer *server)
{
#if !defined(WIN32)
    int pipe_fd = socket (AF_UNIX, SOCK_STREAM, 0);
    const char *un_path = server->path;
    if (pipe_fd < 0) {
        g_warning ("Failed to create unix socket fd : %s\n",
                   strerror(errno));
        return -1;
    }
​
    struct sockaddr_un saddr;
    saddr.sun_family = AF_UNIX;
​
    if (strlen(server->path) > sizeof(saddr.sun_path)-1) {
        g_warning ("Unix socket path %s is too long."
                       "Please set or modify UNIX_SOCKET option in ccnet.conf.\n",
                       un_path);
        goto failed;
    }
​
    if (g_file_test (un_path, G_FILE_TEST_EXISTS)) {
        g_message ("socket file exists, delete it anyway\n");
        if (g_unlink (un_path) < 0) {
            g_warning ("delete socket file failed : %s\n", strerror(errno));
            goto failed;
        }
    }
​
    g_strlcpy (saddr.sun_path, un_path, sizeof(saddr.sun_path));
    if (bind(pipe_fd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) {
        g_warning ("failed to bind unix socket fd to %s : %s\n",
                      un_path, strerror(errno));
        goto failed;
    }
​
    if (listen(pipe_fd, 10) < 0) {
        g_warning ("failed to listen to unix socket: %s\n", strerror(errno));
        goto failed;
    }
​
    if (chmod(un_path, 0700) < 0) {
        g_warning ("failed to set permisson for unix socket %s: %s\n",
                      un_path, strerror(errno));
        goto failed;
    }
​
    server->pipe_fd = pipe_fd;
​
#endif // !defined(WIN32)
​
    /* TODO: use glib thread pool */
    pthread_create(&server->listener_thread, NULL, named_pipe_listen, server);
    return 0;
​
#if !defined(WIN32)
failed:
    close(pipe_fd);
    return -1;
#endif
}

该方法的主要作用是创建监听线程listener_thread,并传入方法named_pipe_listen

除此之外,若当前环境不是WIN32(named-pipe仅在win32API下支持),那么是需要用socket fd代替named-pipe fd

named-pipe监听方法

typedef struct {
    SearpcNamedPipe connfd;
} ServerHandlerData;
​
static void* named_pipe_listen(void *arg)
{
    SearpcNamedPipeServer *server = arg;
#if !defined(WIN32)
    while (1) {
        int connfd = accept (server->pipe_fd, NULL, 0);
        ServerHandlerData *data = g_malloc(sizeof(ServerHandlerData));
        data->connfd = connfd;
        if (server->named_pipe_server_thread_pool)
            g_thread_pool_push (server->named_pipe_server_thread_pool, data, NULL);
        else {
            pthread_t handler;
            pthread_attr_t attr;
            pthread_attr_init(&attr);
            pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
            pthread_create(&handler, &attr, handle_named_pipe_client_with_thread, data);
        }
    }
​
#else // !defined(WIN32)
    while (1) {
        HANDLE connfd = INVALID_HANDLE_VALUE;
        BOOL connected = FALSE;
​
        connfd = CreateNamedPipe(
            server->path,             // pipe name
            PIPE_ACCESS_DUPLEX,       // read/write access
            PIPE_TYPE_MESSAGE |       // message type pipe
            PIPE_READMODE_MESSAGE |   // message-read mode
            PIPE_WAIT,                // blocking mode
            PIPE_UNLIMITED_INSTANCES, // max. instances
            kPipeBufSize,             // output buffer size
            kPipeBufSize,             // input buffer size
            0,                        // client time-out
            NULL);                    // default security attribute
​
        if (connfd == INVALID_HANDLE_VALUE) {
            G_WARNING_WITH_LAST_ERROR ("Failed to create named pipe");
            break;
        }
​
        /* listening on this pipe */
        connected = ConnectNamedPipe(connfd, NULL) ?
            TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
​
        if (!connected) {
            G_WARNING_WITH_LAST_ERROR ("failed to ConnectNamedPipe()");
            CloseHandle(connfd);
            break;
        }
​
        /* g_debug ("Accepted a named pipe client\n"); */
​
        ServerHandlerData *data = g_malloc(sizeof(ServerHandlerData));
        data->connfd = connfd;
        if (server->named_pipe_server_thread_pool)
            g_thread_pool_push (server->named_pipe_server_thread_pool, data, NULL);
        else {
            pthread_t handler;
            pthread_attr_t attr;
            pthread_attr_init(&attr);
            pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
            pthread_create(&handler, &attr, handle_named_pipe_client_with_thread, data);
        }
    }
#endif // !defined(WIN32)
    return NULL;
}

首先定义结构体ServerHandlerData,用来存储连接成功后获取的通信描述符

与上面相同,若处于非WIN32环境下,需要使用监听socket的方法监听,即accept方法.

  • accept():接收客户端发起的tcp连接请求,返回一个通信描述符,专门用于与连接成功的客户端进行通信

若可以使用named-pipe,那么创建一个NamedPipe对象,传入named-pipe-server中存放的path然后通过ConnectNamedPipe()方法开始监听.连接成功后将通信描述符放进ServerHandlerData.

然后开始进行通信.若存在线程池,则调用g_thread_pool_push,将通信描述符作为一个任务插入到线程池;否则创建一个新线程,处理通信任务,并指定handle_named_pipe_client_with_thread方法

named-pipe监听到连接后处理通信过程

static void* handle_named_pipe_client_with_thread(void *arg)
{
    named_pipe_client_handler(arg);
​
    return NULL;
}
​
static void handle_named_pipe_client_with_threadpool(void *data, void *user_data)
{
    named_pipe_client_handler(data);
}
​
static void named_pipe_client_handler(void *data)
{
    ServerHandlerData *handler_data = data;
    SearpcNamedPipe connfd = handler_data->connfd;
​
    guint32 len;
    guint32 bufsize = 4096;
    char *buf = g_malloc(bufsize);
​
    g_message ("start to serve on pipe client\n");
​
    while (1) {
        len = 0;
        if (pipe_read_n(connfd, &len, sizeof(guint32)) < 0) {
            g_warning("failed to read rpc request size: %s\n", strerror(errno));
            break;
        }
​
        if (len == 0) {
            /* g_debug("EOF reached, pipe connection lost"); */
            break;
        }
​
        while (bufsize < len) {
            bufsize *= 2;
            buf = realloc(buf, bufsize);
        }
​
        if (pipe_read_n(connfd, buf, len) < 0 || len == 0) {
            g_warning("failed to read rpc request: %s\n", strerror(errno));
            break;
        }
​
        char *service, *body;
        if (request_from_json (buf, len, &service, &body) < 0) {
            break;
        }
​
        gsize ret_len;
        char *ret_str = searpc_server_call_function (service, body, strlen(body), &ret_len);
        g_free (service);
        g_free (body);
​
        len = (guint32)ret_len;
        if (pipe_write_n(connfd, &len, sizeof(guint32)) < 0) {
            g_warning("failed to send rpc response(%s): %s\n", ret_str, strerror(errno));
            g_free (ret_str);
            break;
        }
​
        if (pipe_write_n(connfd, ret_str, ret_len) < 0) {
            g_warning("failed to send rpc response: %s\n", strerror(errno));
            g_free (ret_str);
            break;
        }
​
        g_free (ret_str);
    }
​
#if !defined(WIN32)
    close(connfd);
#else // !defined(WIN32)
    DisconnectNamedPipe(connfd);
    CloseHandle(connfd);
#endif // !defined(WIN32)
    g_free (data);
    g_free (buf);
}

线程池和单个线程都使用了named_pipe_client_handler方法处理通信描述符

首先获取到named-pipe的通信描述符connfd,然后创建缓冲区开始循环收发信息:

  • 通过pipe_read_n读取到json请求并存放到缓冲区

  • 使用request_from_json,将json串转化为service,函数名与参数

  • 调用searpc_server_call_function调用rpc函数

  • 使用pipe_write_n方法写回返回值

在所有过程处理结束后,应该关闭socket或者named-pipe

named-pipe客户端的连接

int searpc_named_pipe_client_connect(SearpcNamedPipeClient *client)
{
#if !defined(WIN32)
    client->pipe_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    struct sockaddr_un servaddr;
    servaddr.sun_family = AF_UNIX;
​
    g_strlcpy (servaddr.sun_path, client->path, sizeof(servaddr.sun_path));
    if (connect(client->pipe_fd, (struct sockaddr *)&servaddr, (socklen_t)sizeof(servaddr)) < 0) {
        g_warning ("pipe client failed to connect to server: %s\n", strerror(errno));
        return -1;
    }
​
#else // !defined(WIN32)
    SearpcNamedPipe pipe_fd;
    pipe_fd = CreateFile(
        client->path,           // pipe name
        GENERIC_READ |          // read and write access
        GENERIC_WRITE,
        0,                      // no sharing
        NULL,                   // default security attributes
        OPEN_EXISTING,          // opens existing pipe
        0,                      // default attributes
        NULL);                  // no template file
​
    if (pipe_fd == INVALID_HANDLE_VALUE) {
        G_WARNING_WITH_LAST_ERROR("Failed to connect to named pipe");
        return -1;
    }
​
    DWORD mode = PIPE_READMODE_MESSAGE;
    if (!SetNamedPipeHandleState(pipe_fd, &mode, NULL, NULL)) {
        G_WARNING_WITH_LAST_ERROR("Failed to set named pipe mode");
        return -1;
    }
​
    client->pipe_fd = pipe_fd;
​
#endif // !defined(WIN32)
​
    /* g_debug ("pipe client connected to server\n"); */
    return 0;
}

对于不支持named-pipe的环境,同样使用socket进行连接

而对于named-pipe,则是创建一个文件用于存放管道相关的信息,并设置到named-pipe客户端的pipe_fd

基于named-pipe的searpc-client

不同于searpc-client,server的核心工作与数据传输过程关系不大,因此与基于socket的searpc-server仅在创建与监听时有所不同

总结

基于命名管道的searpc主要的不同之处在于与数据传输相关的一些方法上使用的named-pipe而非socket

而在使用中,底层的传输方法对用户是透明的,可以根据使用环境选择

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值