mysql源码分析——VIO数据结构

本文详细介绍了MySQL中的VIO数据结构,它用于封装网络通信,特别是Socket操作。VIO包含了连接类型、超时设置、本地和远程地址等信息,并提供了读写、错误处理和网络操作的接口。在不同平台下,VIO结构适应性较强,允许自定义操作。文章还展示了如何创建VIO、进行连接、读写操作及关闭连接的流程。通过对VIO的深入理解,有助于更好地掌握MySQL的网络通信机制。
摘要由CSDN通过智能技术生成

一、VIO数据类型

VIO是一个数据结构,在include/violite.h中定义的说明中有一句话“This structure is for every connection on both sides.Note that it has a non-default move assignment operator, so if adding more
members, you’ll need to update operator=.”,它的意思是说这个数据结构可以用来网络连接的服务端和客户端。后面就是说没有默认的操作符如果需要就自己搞一个。这个不重要。VIO主要是用在了网络通信Socket中的数据结构的封装。一般来说,封装的目的是为抽象,抽象的目的是为更有适应性,让程序在不同的平台或者说环境下的不修改或者改动最小。对具体动作的依赖降低,这也是设计原则里,依赖于抽象而不依赖于具体的行为动作。比如在处理不同的写入操作时就只考虑接口的实现,而不用考虑具体的细节的实现。
一般来说,对网络通信的封装有好多种情形,网上也有不少的知名的网络库,这个需要大家认真分析一下。不过MYSQL应该对网络通信的要求没有库的那么高,但一定会有独特一些的要求,下面会分析一下。

二、MySql中的定义

先看一下相关的定义:

typedef Vio Vio;
#define MYSQL_VIO Vio *
#endif

enum enum_vio_type : int {
  /**
    Type of the connection is unknown.
  */
  NO_VIO_TYPE = 0,
  /**
    Used in case of TCP/IP connections.
  */
  VIO_TYPE_TCPIP = 1,
  /**
    Used for Unix Domain socket connections. Unix only.
  */
  VIO_TYPE_SOCKET = 2,
  /**
    Used for named pipe connections. Windows only.
  */
  VIO_TYPE_NAMEDPIPE = 3,
  /**
    Used in case of SSL connections.
  */
  VIO_TYPE_SSL = 4,
  /**
    Used for shared memory connections. Windows only.
  */
  VIO_TYPE_SHARED_MEMORY = 5,
  /**
    Used internally by the prepared statements
  */
  VIO_TYPE_LOCAL = 6,
  /**
    Implicitly used by plugins that doesn't support any other VIO_TYPE.
  */
  VIO_TYPE_PLUGIN = 7,

  FIRST_VIO_TYPE = VIO_TYPE_TCPIP,
  /*
    If a new type is added, please update LAST_VIO_TYPE. In addition, please
    change get_vio_type_name() in vio/vio.c to return correct name for it.
  */
  LAST_VIO_TYPE = VIO_TYPE_PLUGIN
};

这个定义如果学过SOCKET通信的,应该看得很明白。不同的平台的套接字,不同的SSL验证方式。管道和共享内存的定义等等,这没啥。再看一下具体的定义:

struct Vio {
  MYSQL_SOCKET mysql_socket;          /* Instrumented socket */
  bool localhost = {false};           /* Are we from localhost? */
  enum_vio_type type = {NO_VIO_TYPE}; /* Type of connection */

  int read_timeout = {-1};  /* Timeout value (ms) for read ops. */
  int write_timeout = {-1}; /* Timeout value (ms) for write ops. */
  int retry_count = {1};    /* Retry count */
  bool inactive = {false};  /* Connection has been shutdown */

  struct sockaddr_storage local;  /* Local internet address */
  struct sockaddr_storage remote; /* Remote internet address */
  size_t addrLen = {0};           /* Length of remote address */
  char *read_buffer = {nullptr};  /* buffer for vio_read_buff */
  char *read_pos = {nullptr};     /* start of unfetched data in the
                                     read buffer */
  char *read_end = {nullptr};     /* end of unfetched data */

#ifdef USE_PPOLL_IN_VIO
  my_thread_t thread_id = {0};  // Thread PID
  sigset_t signal_mask;         // Signal mask
  /*
    Flag to indicate whether we are in poll or shutdown.
    A true value of flag indicates either the socket
    has called  shutdown or it is sleeping on a poll call.
    False value of this flag means that the socket is
    not sleeping on a poll call.
    This flag provides synchronization between two threads
    one entering vio_io_wait and another entering vio_shutdown
    for the same socket. If the other thread is waiting on poll
    sleep, it wakes up the thread by sending a signal via
    pthread_kill. Also it ensures that no other thread enters in
    to a poll call if it's socket has undergone shutdown.

  */
  std::atomic_flag poll_shutdown_flag = ATOMIC_FLAG_INIT;
#elif defined HAVE_KQUEUE
  int kq_fd = {-1};
  std::atomic_flag kevent_wakeup_flag = ATOMIC_FLAG_INIT;
#endif

#ifdef HAVE_SETNS
  /**
    Socket network namespace.
  */
  char network_namespace[256];
#endif
  /*
     VIO vtable interface to be implemented by VIO's like SSL, Socket,
     Named Pipe, etc.
  */

  /*
     viodelete is responsible for cleaning up the VIO object by freeing
     internal buffers, closing descriptors, handles.
  */
  void (*viodelete)(MYSQL_VIO) = {nullptr};
  int (*vioerrno)(MYSQL_VIO) = {nullptr};
  size_t (*read)(MYSQL_VIO, uchar *, size_t) = {nullptr};
  size_t (*write)(MYSQL_VIO, const uchar *, size_t) = {nullptr};
  int (*timeout)(MYSQL_VIO, uint, bool) = {nullptr};
  int (*viokeepalive)(MYSQL_VIO, bool) = {nullptr};
  int (*fastsend)(MYSQL_VIO) = {nullptr};
  bool (*peer_addr)(MYSQL_VIO, char *, uint16 *, size_t) = {nullptr};
  void (*in_addr)(MYSQL_VIO, struct sockaddr_storage *) = {nullptr};
  bool (*should_retry)(MYSQL_VIO) = {nullptr};
  bool (*was_timeout)(MYSQL_VIO) = {nullptr};
  /*
     vioshutdown is resposnible to shutdown/close the channel, so that no
     further communications can take place, however any related buffers,
     descriptors, handles can remain valid after a shutdown.
  */
  int (*vioshutdown)(MYSQL_VIO) = {nullptr};
  bool (*is_connected)(MYSQL_VIO) = {nullptr};
  bool (*has_data)(MYSQL_VIO) = {nullptr};
  int (*io_wait)(MYSQL_VIO, enum enum_vio_io_event, int) = {nullptr};
  bool (*connect)(MYSQL_VIO, struct sockaddr *, socklen_t, int) = {nullptr};
#ifdef _WIN32
#ifdef __clang__
  OVERLAPPED overlapped = {0, 0, {{0, 0}}, nullptr};
#else
  // MSVC, at least up to 2015, gives an internal error on the above.
  OVERLAPPED overlapped = {0};
#endif
  HANDLE hPipe{nullptr};
#endif
  void *ssl_arg = {nullptr};
  struct PSI_socket_locker *m_psi_read_locker = {nullptr};
  PSI_socket_locker_state m_psi_read_state;
  struct PSI_socket_locker *m_psi_write_locker = {nullptr};
  PSI_socket_locker_state m_psi_write_state;
#if defined(_WIN32)
  HANDLE handle_file_map = {nullptr};
  char *handle_map = {nullptr};
  HANDLE event_server_wrote = {nullptr};
  HANDLE event_server_read = {nullptr};
  HANDLE event_client_wrote = {nullptr};
  HANDLE event_client_read = {nullptr};
  HANDLE event_conn_closed = {nullptr};
  size_t shared_memory_remain = {0};
  char *shared_memory_pos = {nullptr};

#endif /* _WIN32 */
  bool (*is_blocking)(Vio *vio) = {nullptr};
  int (*set_blocking)(Vio *vio, bool val) = {nullptr};
  int (*set_blocking_flag)(Vio *vio, bool val) = {nullptr};
  /* Indicates whether socket or SSL based communication is blocking or not. */
  bool is_blocking_flag = {true};

 private:
  friend Vio *internal_vio_create(uint flags);
  friend void internal_vio_delete(Vio *vio);
  friend bool vio_reset(Vio *vio, enum_vio_type type, my_socket sd, void *ssl,
                        uint flags);

  explicit Vio(uint flags);
  ~Vio();

 public:
  Vio(const Vio &) = delete;
  Vio &operator=(const Vio &) = delete;
  Vio &operator=(Vio &&vio);
};

这个结构体并不大,加上所有的注释才一百多行,从这方面可以明白,它的功能肯定有限,根本没法和网络库相比。看一下官方的说明:
https://dev.mysql.com/doc/dev/mysql-server/latest/structVio.html#acaedc338e42997991e9444e4173d6df2

这个地址对此结构体的描述相当清晰,这里就不再翻译一下,狗尾续貂了。
另外在vio/vio_priv.h中还有一些相关的定义:

extern PSI_memory_key key_memory_vio;
extern PSI_memory_key key_memory_vio_read_buffer;
extern PSI_memory_key key_memory_vio_ssl_fd;

#ifdef _WIN32
size_t vio_read_pipe(Vio *vio, uchar *buf, size_t size);
size_t vio_write_pipe(Vio *vio, const uchar *buf, size_t size);
bool vio_is_connected_pipe(Vio *vio);
int vio_shutdown_pipe(Vio *vio);

size_t vio_read_shared_memory(Vio *vio, uchar *buf, size_t size);
size_t vio_write_shared_memory(Vio *vio, const uchar *buf, size_t size);
bool vio_is_connected_shared_memory(Vio *vio);
int vio_shutdown_shared_memory(Vio *vio);
void vio_delete_shared_memory(Vio *vio);
#endif /* _WIN32 */

bool vio_buff_has_data(Vio *vio);
int vio_socket_io_wait(Vio *vio, enum enum_vio_io_event event);
int vio_socket_timeout(Vio *vio, uint which, bool old_mode);

size_t vio_ssl_read(Vio *vio, uchar *buf, size_t size);
size_t vio_ssl_write(Vio *vio, const uchar *buf, size_t size);

/* When the workday is over... */
int vio_ssl_shutdown(Vio *vio);
void vio_ssl_delete(Vio *vio);
bool vio_ssl_has_data(Vio *vio);

#endif /* VIO_PRIV_INCLUDED */

从名字上基本就可以看出这些函数的定义目的,再加上函数内的注释,没有什么区别。

三、应用

先看一下怎么产生个一VIO:

Vio *vio_new(my_socket sd, enum enum_vio_type type, uint flags) {
  Vio *vio;
  MYSQL_SOCKET mysql_socket = MYSQL_INVALID_SOCKET;
  DBUG_TRACE;
  DBUG_PRINT("enter", ("sd: %d", sd));

  mysql_socket_setfd(&mysql_socket, sd);
  vio = mysql_socket_vio_new(mysql_socket, type, flags);

  return vio;
}


Vio *mysql_socket_vio_new(MYSQL_SOCKET mysql_socket, enum_vio_type type,
                          uint flags) {
  Vio *vio;
  my_socket sd = mysql_socket_getfd(mysql_socket);
  DBUG_TRACE;
  DBUG_PRINT("enter", ("sd: %d", sd));

  if ((vio = internal_vio_create(flags))) {
    if (vio_init(vio, type, sd, flags)) {
      internal_vio_delete(vio);
      return nullptr;
    }
    vio->mysql_socket = mysql_socket;
  }
  return vio;
}

至于SetBlock这些具体的属性,懂Socket的一眼就明白,不懂得就好好看看网络编程,不然说也说不清楚。说得直白一点就是设置Socket的属性是阻塞还是非阻塞的。
下来自然就是连接了:

bool vio_socket_connect(Vio *vio, struct sockaddr *addr, socklen_t len,
                        bool nonblocking, int timeout, bool *connect_done) {
  int ret, wait;
  int retry_count = 0;
  DBUG_TRACE;

  /* Only for socket-based transport types. */
  assert(vio->type == VIO_TYPE_SOCKET || vio->type == VIO_TYPE_TCPIP);

  /* If timeout is not infinite, set socket to non-blocking mode. */
  if (((timeout > -1) || nonblocking) && vio_set_blocking(vio, false))
    return true;

  /* Initiate the connection. */
  do {
    ret = mysql_socket_connect(vio->mysql_socket, addr, len);
  } while (ret < 0 && vio_should_retry(vio) &&
           (retry_count++ < vio->retry_count));

  if (connect_done) *connect_done = (ret == 0);

#ifdef _WIN32
  wait = (ret == SOCKET_ERROR) && (WSAGetLastError() == WSAEINPROGRESS ||
                                   WSAGetLastError() == WSAEWOULDBLOCK);
#else
  wait = (ret == -1) && (errno == EINPROGRESS || errno == EALREADY);
#endif

  /*
    The connection is in progress. The vio_io_wait() call can be used
    to wait up to a specified period of time for the connection to
    succeed.

    If vio_io_wait() returns 0 (after waiting however many seconds),
    the socket never became writable (host is probably unreachable.)
    Otherwise, if vio_io_wait() returns 1, then one of two conditions
    exist:

    1. An error occurred. Use getsockopt() to check for this.
    2. The connection was set up successfully: getsockopt() will
       return 0 as an error.
  */
  if (!nonblocking && wait &&
      (vio_io_wait(vio, VIO_IO_EVENT_CONNECT, timeout) == 1)) {
    int error;
    IF_WIN(int, socklen_t) optlen = sizeof(error);
    IF_WIN(char, void) *optval = (IF_WIN(char, void) *)&error;

    /*
      At this point, we know that something happened on the socket.
      But this does not means that everything is alright. The connect
      might have failed. We need to retrieve the error code from the
      socket layer. We must return success only if we are sure that
      it was really a success. Otherwise we might prevent the caller
      from trying another address to connect to.
    */
    if (connect_done) *connect_done = true;
    if (!(ret = mysql_socket_getsockopt(vio->mysql_socket, SOL_SOCKET, SO_ERROR,
                                        optval, &optlen))) {
#ifdef _WIN32
      WSASetLastError(error);
#else
      errno = error;
#endif
      ret = (error != 0);
    }
  }

  /* If necessary, restore the blocking mode, but only if connect succeeded. */
  if (!nonblocking && (timeout > -1) && (ret == 0)) {
    if (vio_set_blocking(vio, true)) return true;
  }

  if (nonblocking && wait) {
    if (connect_done) *connect_done = false;
    return false;
  } else {
    return (ret != 0);
  }
}

这是多么熟悉的味道啊,熟悉到真得不想再解释,不过还是简单说一下,不同的平台对Socket的处理略有不同,但基本上是设置协议族,相关端口转换,阻塞非阻塞的设置,如果把相关的异常去除,就会看得更加清楚。假如是小白在看这段代码,建议是使用Windows下的,然后去除异常,就非常明了整个过程了。

然后是读和写,这里只看读的:

size_t vio_read(Vio *vio, uchar *buf, size_t size) {
  ssize_t ret;
  int flags = 0;
  DBUG_TRACE;

  /* Ensure nobody uses vio_read_buff and vio_read simultaneously. */
  assert(vio->read_end == vio->read_pos);

  /* If timeout is enabled, do not block if data is unavailable. */
  if (vio->read_timeout >= 0) flags = VIO_DONTWAIT;

  while ((ret = mysql_socket_recv(vio->mysql_socket, (SOCKBUF_T *)buf, size,
                                  flags)) == -1) {
    int error = socket_errno;

    /* Error encountered that is unrelated to blocking; percolate it up. */
#if SOCKET_EAGAIN == SOCKET_EWOULDBLOCK
    if (error != SOCKET_EAGAIN)
#else
    if (error != SOCKET_EAGAIN && error != SOCKET_EWOULDBLOCK)
#endif
      break;
    /*
      Nonblocking with either EAGAIN or EWOULDBLOCK. Don't call
      io_wait. 0 bytes are available.
    */
    assert(error == SOCKET_EAGAIN || error == SOCKET_EWOULDBLOCK);
    if (!vio_is_blocking(vio)) {
      DBUG_PRINT("info", ("vio_read on nonblocking socket read no bytes"));
      return -1;
    }

    /* Wait for input data to become available. */
    if ((ret = vio_socket_io_wait(vio, VIO_IO_EVENT_READ))) break;
  }
  return ret;
}

/*
  Buffered read: if average read size is small it may
  reduce number of syscalls.
*/

size_t vio_read_buff(Vio *vio, uchar *buf, size_t size) {
  size_t rc;
#define VIO_UNBUFFERED_READ_MIN_SIZE 2048
  DBUG_TRACE;
  DBUG_PRINT("enter", ("sd: %d  buf: %p  size: %u",
                       mysql_socket_getfd(vio->mysql_socket), buf, (uint)size));

  if (vio->read_pos < vio->read_end) {
    rc = std::min<size_t>(vio->read_end - vio->read_pos, size);
    memcpy(buf, vio->read_pos, rc);
    vio->read_pos += rc;
    /*
      Do not try to read from the socket now even if rc < size:
      vio_read can return -1 due to an error or non-blocking mode, and
      the safest way to handle it is to move to a separate branch.
    */
  } else if (size < VIO_UNBUFFERED_READ_MIN_SIZE) {
    rc = vio_read(vio, (uchar *)vio->read_buffer, VIO_READ_BUFFER_SIZE);
    if (rc != 0 && rc != (size_t)-1) {
      if (rc > size) {
        vio->read_pos = vio->read_buffer + size;
        vio->read_end = vio->read_buffer + rc;
        rc = size;
      }
      memcpy(buf, vio->read_buffer, rc);
    }
  } else
    rc = vio_read(vio, buf, size);
  return rc;
#undef VIO_UNBUFFERED_READ_MIN_SIZE
}

然后就是关闭了:

int vio_shutdown(Vio *vio) {
  int r = 0;
  DBUG_TRACE;

  if (vio->inactive == false) {
    assert(vio->type == VIO_TYPE_TCPIP || vio->type == VIO_TYPE_SOCKET ||
           vio->type == VIO_TYPE_SSL);

    assert(mysql_socket_getfd(vio->mysql_socket) >= 0);
    if (mysql_socket_shutdown(vio->mysql_socket, SHUT_RDWR)) r = -1;

#ifdef USE_PPOLL_IN_VIO
    if (vio->thread_id != 0 && vio->poll_shutdown_flag.test_and_set()) {
      // Send signal to wake up from poll.
      if (pthread_kill(vio->thread_id, SIGALRM) == 0)
        vio_wait_until_woken(vio);
      else
        perror("Error in pthread_kill");
    }
#elif defined HAVE_KQUEUE
    if (vio->kq_fd != -1 && vio->kevent_wakeup_flag.test_and_set())
      vio_wait_until_woken(vio);
#endif

    if (mysql_socket_close(vio->mysql_socket)) r = -1;
#ifdef HAVE_KQUEUE
    if (vio->kq_fd == -1 || close(vio->kq_fd)) r = -1;
    vio->kq_fd = -1;
#endif
  }

  if (r) {
    DBUG_PRINT("vio_error", ("close() failed, error: %d", socket_errno));
    /* FIXME: error handling (not critical for MySQL) */
  }
  vio->inactive = true;
  vio->mysql_socket = MYSQL_INVALID_SOCKET;
  return r;
}

原始的Shutdown其实就弄个参数是关闭一方(接或者收)或者全关闭。这就是所谓的优雅的关闭Socket,可是这个Shutdown里发现还处理了线程和队列 等的相关内容,最后才Close了Socket句柄。

四、总结

如果没有基本学习一门新东西,其实成本是很高的,毕竟新东西里,全新的只是精髓的一部分,如果基础的前置条件都不过关,那么这事儿自然就无法更好的理解后面的精髓了。底下托人办事有过经验的知道,真正办事儿的正主,其实是好说话的,难在于见到正主前的各种小喽啰。
意思不太准确,只求易于理解一下。
努力啊,归来的少年!
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值