mysqld源碼閱讀(啓動:網絡模塊初始化)

環境信息

mysql5.7.27
OS版本: linux版本

閱讀分析

網絡模塊的初始化
mysqld_main --> network_init()

代碼目錄信息:

  if (init_ssl())
    unireg_abort(MYSQLD_ABORT_EXIT);
  if (network_init())
    unireg_abort(MYSQLD_ABORT_EXIT);

分析network_init()

network_init主要做了以下幾件事情:
set_ports() 進行相應的初始化端口
創建監聽對象Mysqld_socket_listener,並對其進行相應的初始化操作。然後對listener進行相應的偵聽動作

static bool network_init(void)
{
  if (opt_bootstrap)
    return false;

  set_ports(); //端口設置
  //others to do 
 Mysqld_socket_listener *mysqld_socket_listener=
      new (std::nothrow) Mysqld_socket_listener(bind_addr_str,
                                                mysqld_port, back_log,
                                                mysqld_port_timeout,
                                                unix_sock_name);
}//監聽對象
// others to do 
mysqld_socket_acceptor=
      new (std::nothrow) Connection_acceptor<Mysqld_socket_listener>(mysqld_socket_listener);//
//others to do 
    if (mysqld_socket_acceptor->init_connection_acceptor())//實例化並監聽
      return true; // mysqld_socket_acceptor would be freed in unireg_abort.

set_ports端口設置分析:

static void set_ports()
{
  char  *env;
  if (!mysqld_port && !opt_disable_networking)
  {         // Get port if not from commandline
    mysqld_port= MYSQL_PORT;

    /*
      if builder specifically requested a default port, use that
      (even if it coincides with our factory default).
      only if they didn't do we check /etc/services (and, failing
      on that, fall back to the factory default of 3306).
      either default can be overridden by the environment variable
      MYSQL_TCP_PORT, which in turn can be overridden with command
      line options.
    */

#if MYSQL_PORT_DEFAULT == 0 //這個參數是在make編譯生成之後,在include/mysql_version.h生成,其值爲 #define MYSQL_PORT_DEFAULT          @MYSQL_TCP_PORT_DEFAULT@
	//MYSQL_TCP_PORT_DEFAULT這個值是在cmake/mysql_version.cmake的文件中配置的
	/*
	[root@pcserver mysql-5.7.27]# grep -rn "MYSQL_TCP_PORT_DEFAULT" *
	cmake/mysql_version.cmake:88:SET(MYSQL_TCP_PORT_DEFAULT "3306")
	cmake/mysql_version.cmake:91:  SET(MYSQL_TCP_PORT ${MYSQL_TCP_PORT_DEFAULT})
	cmake/mysql_version.cmake:92:  SET(MYSQL_TCP_PORT_DEFAULT "0")
	cmake/mysql_version.cmake:93:ELSEIF(MYSQL_TCP_PORT EQUAL MYSQL_TCP_PORT_DEFAULT)
	cmake/mysql_version.cmake:94:  SET(MYSQL_TCP_PORT_DEFAULT "0")
	include/mysql_version.h.in:18:#define MYSQL_PORT_DEFAULT          @MYSQL_TCP_PORT_DEFAULT@
	scripts/mysql_config.pl.in:161:if ( '@MYSQL_TCP_PORT_DEFAULT@' == 0 ) {
	scripts/mysql_config.sh:106:if [ @MYSQL_TCP_PORT_DEFAULT@ -eq 0 ]; then

	*/
    struct  servent *serv_ptr;
    if ((serv_ptr= getservbyname("mysql", "tcp")))
      mysqld_port= ntohs((u_short) serv_ptr->s_port); /* purecov: inspected */
#endif
    if ((env = getenv("MYSQL_TCP_PORT"))) 
      mysqld_port= (uint) atoi(env);    /* purecov: inspected 通過getenv獲取啓動配置命令行的端口*/
  }
  if (!mysqld_unix_port)
  {
#ifdef _WIN32
    mysqld_unix_port= (char*) MYSQL_NAMEDPIPE;
#else
    mysqld_unix_port= (char*) MYSQL_UNIX_ADDR;
#endif
    if ((env = getenv("MYSQL_UNIX_PORT")))
      mysqld_unix_port= env;      /* purecov: inspected */
  }
}

關於創建監聽的操作分析:
創建 Mysqld_socket_listener 類型對象

Mysqld_socket_listener *mysqld_socket_listener=
      new (std::nothrow) Mysqld_socket_listener(bind_addr_str,
                                                mysqld_port, back_log,
                                                mysqld_port_timeout,
                                                unix_sock_name);

通過Connection_acceptor模板進行Mysqld_socket_listener的初始化,通過循環監控不同類型的client connection event的,也就是通過循環監聽客戶端來的連接

創建mysql_socket_acceptor對象:

mysqld_socket_acceptor=
      new (std::nothrow) Connection_acceptor<Mysqld_socket_listener>(mysqld_socket_listener);

初始化listener:

    if (mysqld_socket_acceptor->init_connection_acceptor())
      return true; // mysqld_socket_acceptor would be freed in unireg_abort.

實現:

template <typename Listener> class Connection_acceptor
{
//some operations
  bool init_connection_acceptor()
  {
    return m_listener->setup_listener();
  }
//some operations
  }

調用了setup_listener()的方法實現公僕幹嘛

而此時我們查看Mysqld_socket_listener中的setup_listener()具體的實現:
通過以下關鍵變量來記錄listener的主要信息
m_tcp_port:記錄tcp socket連接
m_unix_sockname: 使用的是std::string類型記錄如果使用socket文件,則文件名(路徑+文件)
通過使用m_socket_map來區分所使用的連接是tcp_socket還是unix socket的連接方式

通過兩個if判斷,分別判斷是否tcp還是socket文件連接,並記錄於m_socket_map中
然後通過迭代器循環實現監聽事件的記錄,代碼通過pool_info_t實現監聽相應的事件

for (socket_map_iterator_t sock_map_iter=  m_socket_map.begin();
       sock_map_iter != m_socket_map.end(); ++sock_map_iter)  //創建迭代器,並通過循環的方式監聽事件
  {
    MYSQL_SOCKET listen_socket= sock_map_iter->first;
    mysql_socket_set_thread_owner(listen_socket);
#ifdef HAVE_POLL
    m_poll_info.m_fds[count].fd= mysql_socket_getfd(listen_socket); //輪詢記錄監聽事件
    m_poll_info.m_fds[count].events= POLLIN;
    m_poll_info.m_pfs_fds[count]= listen_socket;
    count++;
#else  // HAVE_POLL
    FD_SET(mysql_socket_getfd(listen_socket), &m_select_info.m_client_fds);
    if ((uint) mysql_socket_getfd(listen_socket) > m_select_info.m_max_used_connection)
      m_select_info.m_max_used_connection= mysql_socket_getfd(listen_socket);
      //如果記錄HAVE_POLL事件,則直接使用m_select_info記錄socket_listener的相應信息
#endif // HAVE_POLL
  }

然後我們回到mysqld_main.cc中的mysqld_main函數,繼續往後閱讀,找到如下部分

#if defined(_WIN32)
  setup_conn_event_handler_threads();
#else
  mysql_mutex_lock(&LOCK_socket_listener_active);
  // Make it possible for the signal handler to kill the listener.
  socket_listener_active= true;
  mysql_mutex_unlock(&LOCK_socket_listener_active);

  if (opt_daemonize)
    mysqld::runtime::signal_parent(pipe_write_fd,1);

  mysqld_socket_acceptor->connection_event_loop();
#endif /* _WIN32 */

通過nysqld_socket_acceptor變量引用connect_event_loop()方法,循環監聽客戶端來的連接。通過模板調用如下 :

/**
    Connection acceptor loop to accept connections from clients.
  */
  void connection_event_loop()
  {
    Connection_handler_manager *mgr= Connection_handler_manager::get_instance();
    while (!abort_loop)
    {
      Channel_info *channel_info= m_listener->listen_for_connection_event();
      if (channel_info != NULL)
        mgr->process_new_connection(channel_info);
    }
  }

通過查看Mysqld_socket_listener類中listen_for_connection_event()來得到Channel_info信息,然後通過process_new_connection創建新的連接來。再進一步查看Connection_handler_manager的process_new_connection的方法,發現有m_connection_handler->add_connection(channel_info)在connection_handler中增加一個connection,並記錄數量增加

void
Connection_handler_manager::process_new_connection(Channel_info* channel_info)
{
  if (abort_loop || !check_and_incr_conn_count())
  {
    channel_info->send_error_and_close_channel(ER_CON_COUNT_ERROR, 0, true);
    delete channel_info;
    return;
  }

  if (m_connection_handler->add_connection(channel_info)) //增加一個connection
  {
    inc_aborted_connects(); //記錄數量增加
    delete channel_info;
  }
}

對於m_connection_handler的add_connection增加connection,看具體的實現:


bool Per_thread_connection_handler::add_connection(Channel_info* channel_info)
{
  int error= 0;
  my_thread_handle id;

  DBUG_ENTER("Per_thread_connection_handler::add_connection");

  // Simulate thread creation for test case before we check thread cache
  DBUG_EXECUTE_IF("fail_thread_create", error= 1; goto handle_error;);

  if (!check_idle_thread_and_enqueue_connection(channel_info))
    DBUG_RETURN(false);

  /*
    There are no idle threads avaliable to take up the new
    connection. Create a new thread to handle the connection
  */
  channel_info->set_prior_thr_create_utime();
  error= mysql_thread_create(key_thread_one_connection, &id,
                             &connection_attrib,
                             handle_connection,
                             (void*) channel_info);//給定信息,創建thread進程
                             
#ifndef DBUG_OFF
handle_error:
#endif // !DBUG_OFF

  if (error)
  {
    connection_errors_internal++;
    if (!create_thd_err_log_throttle.log())
      sql_print_error("Can't create thread to handle new connection(errno= %d)",
                      error);
    channel_info->send_error_and_close_channel(ER_CANT_CREATE_THREAD,
                                               error, true);
    Connection_handler_manager::dec_connection_count();
    DBUG_RETURN(true);
  }

  Global_THD_manager::get_instance()->inc_thread_created();
  DBUG_PRINT("info",("Thread created"));
  DBUG_RETURN(false);
}


創建mysql_thread_create創建thread,並接收一個方法參數 handle_connection,這個是一個函數

extern "C" void *handle_connection(void *arg)
{
//sometings ...
 THD *thd= init_new_thd(channel_info); //創建線程對象
 //somethings...
mysql_thread_set_psi_id(thd->thread_id()); //初始化線程信息等
    mysql_thread_set_psi_THD(thd);
    mysql_socket_set_thread_owner(
      thd->get_protocol_classic()->get_vio()->mysql_socket);

    thd_manager->add_thd(thd);//向線程管理器增加一個線程信息

    if (thd_prepare_connection(thd))
      handler_manager->inc_aborted_connects();
    else
    {
      while (thd_connection_alive(thd))//循環檢測線程狀態
      {
        if (do_command(thd)) // 執行相關參數命令
          break;
      }
      end_connection(thd);
    }
    close_connection(thd, 0, false, false);

    thd->get_stmt_da()->reset_diagnostics_area();
    thd->release_resources();

//somethings...
}

總結 :
大體瞭解了此部分的代碼,後續繼續學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章