MySQLにおけるlocalhostと127.0.0.1

先日、会社のインターンの人にこんなことを聞かれた。

「localhostと127.0.0.1ってどう違うんですか?」

/etc/hosts に書いてあるとか、そういう月並みな説明はできるのだが、よくよく考えたらうまく説明ができない。どういう仕組で動いているのかなどをきちんと理解してるわけではないことがわかってしまったのだ。

この記事では libcgethostbyname などの UNIX の話というよりは、web開発で MySQLDATABASE_HOSTlocalhost127.0.0.1 に変えたら動いた!について少し 深掘りして考えてみようと思う。

libc/etc/nsswitch.conf に関しては、別途調べて別記事にまとめて書く。。。と思う。。。


MySQL に接続する方法は2つある。

  • unix domain socket
  • tcp

unix domain socket は同一マシン内でのプロセス間通信のことだ。(今はこれくらいの理解)

tcp でもつなぐことができるのだが、 unix domain socket のほうが遥かに高速なので基本的にはこちら使うのが一般的らしい。

参考


今回は以前あった、「LaravelからMySQLに繋がらない」という問題を再考した。

まずphp自体は名前解決どうしてるんだろうか、と思い php/php-srcの対象のコードを読んだ。

https://github.com/php/php-src/blob/master/main/network.c#L1307-L1309

PHPAPI struct hostent*  php_network_gethostbyname(char *name) {
#if !defined(HAVE_GETHOSTBYNAME_R)
    return gethostbyname(name);
#else
    if (FG(tmp_host_buf)) {
        free(FG(tmp_host_buf));
    }

    FG(tmp_host_buf) = NULL;
    FG(tmp_host_buf_len) = 0;

    memset(&FG(tmp_host_info), 0, sizeof(struct hostent));

    return gethostname_re(name, &FG(tmp_host_info), &FG(tmp_host_buf), &FG(tmp_host_buf_len));
#endif
}

gethostbyname ちゃんと使ってる....無問題。なんでだろう。。。

そしたら詳しいおっさんが、「mysql とかは localhost で指定すると unix domain socket を優先しようとする」などと教えてくれたので、mysql-server を読みに行った。

repoは mysql/mysql-server だ。

対象のコードは以下。"localhost" という文字列をgrepすると見つかった。

https://github.com/mysql/mysql-server/blob/8.0/sql-common/client.cc#L4275-L4322

#if defined(HAVE_SYS_UN_H)
  if (!net->vio &&
      (!mysql->options.protocol ||
       mysql->options.protocol == MYSQL_PROTOCOL_SOCKET) &&
      (unix_socket || mysql_unix_port) &&
      (!host || !strcmp(host, LOCAL_HOST))) {
    my_socket sock = socket(AF_UNIX, SOCK_STREAM, 0);
    DBUG_PRINT("info", ("Using socket"));
    if (sock == SOCKET_ERROR) {
      set_mysql_extended_error(mysql, CR_SOCKET_CREATE_ERROR, unknown_sqlstate,
                               ER_CLIENT(CR_SOCKET_CREATE_ERROR), socket_errno);
      goto error;
    }

    net->vio =
        vio_new(sock, VIO_TYPE_SOCKET, VIO_LOCALHOST | VIO_BUFFERED_READ);
    if (!net->vio) {
      DBUG_PRINT("error", ("Unknow protocol %d ", mysql->options.protocol));
      set_mysql_error(mysql, CR_CONN_UNKNOW_PROTOCOL, unknown_sqlstate);
      closesocket(sock);
      goto error;
    }

    host = LOCAL_HOST;
    if (!unix_socket) unix_socket = mysql_unix_port;
    host_info = (char *)ER_CLIENT(CR_LOCALHOST_CONNECTION);
    DBUG_PRINT("info", ("Using UNIX sock '%s'", unix_socket));

    memset(&UNIXaddr, 0, sizeof(UNIXaddr));
    UNIXaddr.sun_family = AF_UNIX;
    strmake(UNIXaddr.sun_path, unix_socket, sizeof(UNIXaddr.sun_path) - 1);

    if (mysql->options.extension && mysql->options.extension->retry_count)
      my_net_set_retry_count(net, mysql->options.extension->retry_count);

    if (vio_socket_connect(net->vio, (struct sockaddr *)&UNIXaddr,
                           sizeof(UNIXaddr), get_vio_connect_timeout(mysql))) {
      DBUG_PRINT("error",
                 ("Got error %d on connect to local server", socket_errno));
      set_mysql_extended_error(mysql, CR_CONNECTION_ERROR, unknown_sqlstate,
                               ER_CLIENT(CR_CONNECTION_ERROR), unix_socket,
                               socket_errno);
      vio_delete(net->vio);
      net->vio = 0;
      goto error;
    }
    mysql->options.protocol = MYSQL_PROTOCOL_SOCKET;
  }
#elif defined(_WIN32)

以下の部分が該当箇所だ。パフォーマンスのためか、LOCAL_HOST の場合は socket で繋いでるっぽい。(if文が分かりづらい....)

if (!net->vio 
    &&
    (!mysql->options.protocol || mysql->options.protocol == MYSQL_PROTOCOL_SOCKET) 
    &&
    (unix_socket || mysql_unix_port) 
    &&
    (!host || !strcmp(host, LOCAL_HOST))) { my_socket sock = socket(AF_UNIX, SOCK_STREAM, 0); /* ... */}

試しに、 /etc/hosts で以下のようにしたらどうなるだろうか。

127.0.0.1   hoge

MySQLに繋いでみる。

~ (*´ω`*) < mysql -u root --host="hoge" -P 3306
Welcome to the MySQL monitor.  Commands end with ; or \g.
[省略]

netstatの情報はこんな感じだ。

~ (*´ω`*) < netstat -an | grep 3306
tcp4       0      0  127.0.0.1.3306         127.0.0.1.50816        ESTABLISHED
tcp4       0      0  127.0.0.1.50816        127.0.0.1.3306         ESTABLISHED
tcp4       0      0  127.0.0.1.3306         *.*                    LISTEN     
tcp4       0      0  127.0.0.1.50765        127.0.0.1.3306         TIME_WAIT  

localhostで繋ぐとこんな感じ。

~ 。+゚(∩´﹏`∩)゚+。 < mysql -u root -h localhost
Welcome to the MySQL monitor.  Commands end with ; or \g.
[省略]

netstatの情報はこんな感じだ。

~ (*´ω`*) < netstat -an | grep 3306
tcp4       0      0  127.0.0.1.3306         *.*                    LISTEN     
tcp4       0      0  127.0.0.1.50816        127.0.0.1.3306         TIME_WAIT  

tcp で動いていないことが確認できた。


正しく説明するにはC言語読む必要が出てくるし、詳しいおっさんが近くにいないと厳しい。

先駆者はいたけどまぁ書いてもいいよね。

MySQLでlocalhostと127.0.0.1の違い