Libuv源码分析 —— 10. unix域

unix 域

  • Unix 域一种进程间通信的方式,他类似 socket 通信,但是他是基于单主机的。可以说是单机上的 socket 通信。在 libuv 中,unix 域用 uv_pipe_t 表示
  • unix 域的实现和 tcp 的实现类似。都是基于连接的模式。服务器启动等待连接,客户端去连接。 然后服务器逐个摘下连接的节点进行处理。
uv_pipe_t — 管道句柄
  • 管道句柄对Unix上的本地域套接字和Windows上的有名管道提供一个抽象
数据类型
  • uv_pipe_t

    管道句柄类型(结构体具体内容看这里)

API
  • int uv_pipe_init(uv_loop_t* loop, uv_pipe_t* handle, int ipc)

    初始化一个管道句柄。 ipc 参数是一个布尔值指明是否管道将用于在不同进程间传递句柄

  • int uv_pipe_open(uv_pipe_t* handle, uv_file file)

    打开一个已存在的文件描述符或者句柄作为一个管道

  • int uv_pipe_bind(uv_pipe_t* handle, const char* name)

    绑定管道到一个文件路径(Unix)或者名字(Windows)

  • void uv_pipe_connect(uv_connect_t* req, uv_pipe_t* handle, const char* name, uv_connect_cb cb)

    连接到Unix域套接字或者有名管道

  • int uv_pipe_getsockname(const uv_pipe_t* handle, char* buffer, size_t* size)

    获取Unix域套接字或者有名管道的名字

  • int uv_pipe_getpeername(const uv_pipe_t* handle, char* buffer, size_t* size)

    获取被句柄连接的Unix域套接字或者有名管道的名字

example
  • 因为咱们的 unix域 通信过程类似于 socket 通信,所以首先来看一下 socket 通信的基本过程
    Libuv源码分析 —— 10. unix域_第1张图片
服务器端
  • 初始化 uv_pipe_t【源码实现 uv_pipe_init】
    int main() {
        loop = uv_default_loop();
    
        uv_pipe_t server;
        uv_pipe_init(loop, &server, 0);
    }
    
  • 接着创建 socket,然后 bind 这个 socket【源码实现 uv_pipe_bind】
    int main() {
        loop = uv_default_loop();
    
        uv_pipe_t server;
        uv_pipe_init(loop, &server, 0);
    
        int r;
        // 我们把socket命名为echo.sock,意味着它将会在本地文件夹中被创造。对于stream API来说,本地socekt表现得和tcp的socket差不多
        if ((r = uv_pipe_bind(&server, "echo.sock"))) {
            fprintf(stderr, "Bind error %s\n", uv_err_name(r));
            return 1;
        }
    }
    
  • 监听这个 socket,直到有客户端跟他连接【源码实现 uv_listen】
    int main() {
       loop = uv_default_loop();
    
       uv_pipe_t server;
       uv_pipe_init(loop, &server, 0);
    
       int r;
       // 我们把socket命名为echo.sock,意味着它将会在本地文件夹中被创造。对于stream API来说,本地socekt表现得和tcp的socket差不多
       if ((r = uv_pipe_bind(&server, "echo.sock"))) {
           fprintf(stderr, "Bind error %s\n", uv_err_name(r));
           return 1;
       }
    
       // 把 unix 域对应的文件文件描述符设置为 listen 状态。
       // 开启监听请求的到来,连接的最大个数是 128。有连接时的回调是 on_new_connection
       if ((r = uv_listen((uv_stream_t*) &server, 128, on_new_connection))) {
           fprintf(stderr, "Listen error %s\n", uv_err_name(r));
           return 2;
       }
    
       // 启动事件循环
       return uv_run(loop, UV_RUN_DEFAULT);
    }
    
客户端
  • 初始化 uv_pipe_t【源码实现 uv_pipe_t】
  • 跟服务器端连接【源码实现 on_new_connection】
服务器端完整代码
  • 启动了一个服务。同主机的进程可以访问(连接)他。
    #include 
    #include 
    #include 
    #include 
    
    uv_loop_t *loop;
    
    typedef struct {
        uv_write_t req;
        uv_buf_t buf;
    } write_req_t;
    
    void free_write_req(uv_write_t *req) {
        write_req_t *wr = (write_req_t*) req;
        free(wr->buf.base);
        free(wr);
    }
    
    void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
      buf->base = malloc(suggested_size);
      buf->len = suggested_size;
    }
    
    void echo_write(uv_write_t *req, int status) {
        if (status < 0) {
            fprintf(stderr, "Write error %s\n", uv_err_name(status));
        }
        free_write_req(req);
    }
    
    void echo_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) {
        if (nread > 0) {
            write_req_t *req = (write_req_t*) malloc(sizeof(write_req_t));
            req->buf = uv_buf_init(buf->base, nread);
            uv_write((uv_write_t*) req, client, &req->buf, 1, echo_write);
            return;
        }
    
        if (nread < 0) {
            if (nread != UV_EOF)
                fprintf(stderr, "Read error %s\n", uv_err_name(nread));
            uv_close((uv_handle_t*) client, NULL);
        }
    
        free(buf->base);
    }
    
    // 有连接到来时的回调
    void on_new_connection(uv_stream_t *server, int status) {
        if (status == -1) {
            // error!
            return;
        }
        
        // 有连接到来,申请一个结构体表示他
        uv_pipe_t *client = (uv_pipe_t*) malloc(sizeof(uv_pipe_t));
        uv_pipe_init(loop, client, 0);
        
        // 把 accept 返回的 fd 记录到 client,client 是用于和客户端通信的结构体
        if (uv_accept(server, (uv_stream_t*) client) == 0) {
            // 注册读事件,等待客户端发送信息过来,
            // alloc_buffer 分配内存保存客户端的发送过来的信息,
            // echo_read 是回调
            uv_read_start((uv_stream_t*) client, alloc_buffer, echo_read);
        }
        else {
            uv_close((uv_handle_t*) client, NULL);
        }
    }
    
    void remove_sock(int sig) {
        uv_fs_t req;
        // 删除 unix 域对应的路径
        uv_fs_unlink(loop, &req, "echo.sock", NULL);
        // 退出进程
        exit(0);
    }
    
    /*
        本地socket具有确定的名称,而且是以文件系统上的位置来标示的(例如,unix中socket是文件的一种存在形式),
        那么它就可以用来在不相关的进程间完成通信任务
    */
    int main() {
        loop = uv_default_loop();
    
        uv_pipe_t server;
        uv_pipe_init(loop, &server, 0);
        
        // 注册 SIGINT 信号的信号处理函数是 remove_sock
        signal(SIGINT, remove_sock);
    
        int r;
        // 我们把socket命名为echo.sock,意味着它将会在本地文件夹中被创造。对于stream API来说,本地socekt表现得和tcp的socket差不多
        if ((r = uv_pipe_bind(&server, "echo.sock"))) {
            fprintf(stderr, "Bind error %s\n", uv_err_name(r));
            return 1;
        }
        
        // 把 unix 域对应的文件文件描述符设置为 listen 状态。
        // 开启监听请求的到来,连接的最大个数是 128。有连接时的回调是 on_new_connection
        if ((r = uv_listen((uv_stream_t*) &server, 128, on_new_connection))) {
            fprintf(stderr, "Listen error %s\n", uv_err_name(r));
            return 2;
        }
        
        // 启动事件循环
        return uv_run(loop, UV_RUN_DEFAULT);
    }
    

源码分析

uv_pipe_t
  • uv_pipe_t 继承自 handle 和 stream
  • 源码
    struct uv_pipe_t {
      // 句柄[handle]相关参数
      void* data;                             
      uv_loop_t* loop;                            /* 所属事件循环 */
      uv_handle_type type;                        /* handle 类型 */
      uv_close_cb close_cb;                       /* 关闭 handle 时的回调 */
      void* handle_queue[2];                      /* 用于插入事件循环的 handle 队列 */
      union {                                                                     
        int fd;                                                                   
        void* reserved[4];                                                        
      } u; 
      uv_handle_t* next_closing;                  /* 用于插入事件循环的 closing 阶段对应的队列 */
      unsigned int flags;                         /* 各种标记 */
    
    
      // 流[stream]相关参数                                    
      size_t write_queue_size;                    /* 用户写入流的字节大小,流缓存用户的输入,然后等到可写的时候才做真正的写 */                                             
      uv_alloc_cb alloc_cb;                       /* 分配内存的函数,内存由用户定义,主要用来保存读取的数据 */                                           
      uv_read_cb read_cb;                         /* 读取完成时候执行的回调函数 */
      uv_connect_t *connect_req;                  /* 连接成功后,执行 connect_req 的回调(connect_req 在 uv__xxx_connect 中赋值) */                                
      uv_shutdown_t *shutdown_req;                /* 关闭写端的时候,发送完缓存的数据,执行 shutdown_req 的回调(shutdown_req 在 uv_shutdown 的时候赋值) */                                
      uv__io_t io_watcher;                        /* 流对应的 io 观察者,即文件描述符+一个文件描述符事件触发时执行的回调 */                                
      void* write_queue[2];                       /* 流缓存下来的,待写的数据 */                                
      void* write_completed_queue[2];             /* 已经完成了数据写入的队列 */                                
      uv_connection_cb connection_cb;             /* 完成三次握手后,执行的回调 */                                
      int delayed_error;                          /* 操作流时出错码 */                                
      int accepted_fd;                            /* accept 返回的通信 socket 对应的文件描述符 */                                
      void* queued_fds;                           /* 同上,用于缓存更多的通信 socket 对应的文件描述符 */                                
      UV_STREAM_PRIVATE_PLATFORM_FIELDS           /* 目前为空 */
    
      int ipc;                                    /* 标记管道是否能在进程间传递 */
    
      // pipe相关参数
      const char* pipe_fname;                     /* 用于 unix 域通信的文件路径 */
    }
    
uv_pipe_init
  • 初始化 uv_pipe_t 结构体。刚才已经见过 uv_pipe_t 继承于 stream,uv__stream_init 就是初始化 stream(父类)的字段
  • 源码
    int uv_pipe_init(uv_loop_t* loop, uv_pipe_t* handle, int ipc) {
      uv__stream_init(loop, (uv_stream_t*)handle, UV_NAMED_PIPE);
      handle->shutdown_req = NULL;
      handle->connect_req = NULL;
      handle->pipe_fname = NULL;
      handle->ipc = ipc;
      return 0;
    }
    
uv_pipe_bind - 服务器端
  • 申请一个 socket 套接字,绑定 unix 域路径到 socket 中,绑定了路径后,就可以调用 listen 函数开始监听
  • 源码
    // name 是 unix 路径名称
    int uv_pipe_bind(uv_pipe_t* handle, const char* name) {
      struct sockaddr_un saddr;
      const char* pipe_fname;
      int sockfd;
      int err;
    
      pipe_fname = NULL;
    
      if (uv__stream_fd(handle) >= 0)
        return UV_EINVAL;
    
      pipe_fname = uv__strdup(name);
      if (pipe_fname == NULL)
        return UV_ENOMEM;
    
      name = NULL;
    
      // unix 域套接字
      err = uv__socket(AF_UNIX, SOCK_STREAM, 0);
      if (err < 0)
        goto err_socket;
      sockfd = err;
    
      memset(&saddr, 0, sizeof saddr);
      uv__strscpy(saddr.sun_path, pipe_fname, sizeof(saddr.sun_path));
      saddr.sun_family = AF_UNIX;
    
      // 绑定到路径,tcp 是绑定到 ip 和端口
      if (bind(sockfd, (struct sockaddr*)&saddr, sizeof saddr)) {
        err = UV__ERR(errno);
        /* Convert ENOENT to EACCES for compatibility with Windows. */
        if (err == UV_ENOENT)
          err = UV_EACCES;
    
        uv__close(sockfd);
        goto err_socket;
      }
    
      // 已经绑定
      handle->flags |= UV_HANDLE_BOUND;
      handle->pipe_fname = pipe_fname; 
      // 保存 socket fd,用于后面监听
      handle->io_watcher.fd = sockfd;
      return 0;
    
    err_socket:
      uv__free((void*)pipe_fname);
      return err;
    }
    
uv_pipe_listen - 服务器端
  • uv_pipe_listen 执行 listen 函数使得 socket 成为监听型的套接字。然后把 socket 对应 的文件描述符和回调封装成 io 观察者。注册到 libuv。等到有读事件到来(有连接到来)。 就会执行 uv__server_io 函数,摘下对应的客户端节点。最后执行 connection_cb 回调。
  • 源码
    int uv__pipe_listen(uv_pipe_t* handle, int backlog, uv_connection_cb cb) {
      if (uv__stream_fd(handle) == -1)
        return UV_EINVAL;
    
      if (handle->ipc)
        return UV_EINVAL;
    
    #if defined(__MVS__) || defined(__PASE__)
      if (backlog == 0)
        backlog = 1;
      else if (backlog < 0)
        backlog = SOMAXCONN;
    #endif
      // uv__stream_fd(handle)得到 bind 函数中获取的 socket
      if (listen(uv__stream_fd(handle), backlog))
        return UV__ERR(errno);
    
      // 保存回调,有进程调用 connect 的时候时触发,由 uv__server_io 函数触发
      handle->connection_cb = cb;
      // io 观察者的回调,有进程调用 connect 的时候时触发(io 观察者的 fd 在 init 函数里设置了)
      // uv__server_io 中会调用用户提供的 cb
      handle->io_watcher.cb = uv__server_io;
      // 注册 io 观察者到 libuv,等待连接,即读事件到来
      uv__io_start(handle->loop, &handle->io_watcher, POLLIN);
      return 0;
    }
    
uv_pipe_connect - 客户端
  • 源码
    void uv_pipe_connect(uv_connect_t* req,
                        uv_pipe_t* handle,
                        const char* name,
                        uv_connect_cb cb) {
      struct sockaddr_un saddr;
      int new_sock;
      int err;
      int r;
    
      // 判断是否已经有 socket 了,没有的话需要申请一个,见下面
      // #define uv__stream_fd(handle) ((handle)->io_watcher.fd)
      new_sock = (uv__stream_fd(handle) == -1);
    
      // 客户端还没有对应的 socket fd
      if (new_sock) {
        err = uv__socket(AF_UNIX, SOCK_STREAM, 0);
        if (err < 0)
          goto out;
        handle->io_watcher.fd = err;
      }
    
      // 需要连接的服务器信息。主要是 unix 域路径信息
      memset(&saddr, 0, sizeof saddr);
      uv__strscpy(saddr.sun_path, name, sizeof(saddr.sun_path));
      saddr.sun_family = AF_UNIX;
    
      // 连接服务器,unix 域路径是 name
      do {
        r = connect(uv__stream_fd(handle),
                    (struct sockaddr*)&saddr, sizeof saddr);
      }
      while (r == -1 && errno == EINTR);
    
      if (r == -1 && errno != EINPROGRESS) {
        err = UV__ERR(errno);
    #if defined(__CYGWIN__) || defined(__MSYS__)
        if (err == UV_EBADF)
          err = UV_ENOTSOCK;
    #endif
        goto out;
      }
    
      // 忽略错误处理逻辑
      err = 0;
      // 设置 socket 的可读写属
      if (new_sock) {
        err = uv__stream_open((uv_stream_t*)handle,
                              uv__stream_fd(handle),      // #define uv__stream_fd(handle) ((handle)->io_watcher.fd)
                              UV_HANDLE_READABLE | UV_HANDLE_WRITABLE);
      }
    
      // 把 io 观察者注册到 libuv,等到连接成功或者可以发送请求
      if (err == 0)
        uv__io_start(handle->loop, &handle->io_watcher, POLLOUT);
    
    out:
      // 记录错误码,如果有的话
      handle->delayed_error = err;
      // 连接成功时的回调
      handle->connect_req = req;
    
      uv__req_init(handle->loop, req, UV_CONNECT);
      req->handle = (uv_stream_t*)handle;
      req->cb = cb;
      QUEUE_INIT(&req->queue);
    
      // 如果连接出错,在 pending 节点会执行 req 对应的回调。错误码是 delayed_error
      if (err)
        uv__io_feed(handle->loop, &handle->io_watcher);
    }
    

你可能感兴趣的:(Libuv源码解析,libuv)