Contents
疑问
我们看到用 mysql_secure_installation 安装配置的 mysql-server (mariadb-server),默认的 host 有 localhost, 127.0.0.1 和 ::1 .
那么问题来了,默认情况下,对于 IPv4, localhost 跟 127.0.0.1 应该是等同的。为什么在 mysql 这个数据库的认证记录中,会同时出现 localhost 和 127.0.0.1 两个不同的记录呢?
+-----------+------+-------------------------------------------+
| host | user | password |
+-----------+------+-------------------------------------------+
| localhost | root | *84BB5DF4823DA319BBF86C99624479A198E6EEE9 |
| 127.0.0.1 | root | *84BB5DF4823DA319BBF86C99624479A198E6EEE9 |
| ::1 | root | *84BB5DF4823DA319BBF86C99624479A198E6EEE9 |
+-----------+------+-------------------------------------------+
mysql 建立本地通信的两种方式: localhost, 127.0.0.1
查阅相关手册,用 localhost 或者用 127.0.0.1 作为 host 的登录方式是不一样的。
如果没有指定或者指定了 localhost, mysql 将会以 UNIX domain socket 的方式与 mysqld 进行通信,这是一种无须经过网络栈的 IPC 通信方式。
如果要强制 mysql 使用网络栈与本机 mysqld 进行通信,则需要指定 host 为 127.0.0.1 .
由于使用 UNIX domain socket 不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程,所以它的性能通常会优于网络 socket 的通信方式。
man mysql_table
MYSQL PARAMETERS
hosts The hosts that Postfix will try to connect to and query from. Specify unix: for UNIX domain sock‐
ets, inet: for TCP connections (default). Example:
hosts = host1.some.domain host2.some.domain:port
hosts = unix:/file/nameThe hosts are tried in random order, with all connections over UNIX domain sockets being tried
before those over TCP. The connections are automatically closed after being idle for about 1
minute, and are re-opened as necessary. Postfix versions 2.0 and earlier do not randomize the host
order.NOTE: if you specify localhost as a hostname (even if you prefix it with inet:), MySQL will con‐
nect to the default UNIX domain socket. In order to instruct MySQL to connect to localhost over
TCP you have to specify
hosts = 127.0.0.1
https://dev.mysql.com/doc/refman/5.5/en/connecting.html
On Unix, MySQL programs treat the host name localhost specially, in a way that is likely different from what you expect compared to other network-based programs. For connections to localhost, MySQL programs attempt to connect to the local server by using a Unix socket file. This occurs even if a --port or -P option is given to specify a port number. To ensure that the client makes a TCP/IP connection to the local server, use --host or -h to specify a host name value of 127.0.0.1, or the IP address or name of the local server. You can also specify the connection protocol explicitly, even for localhost, by using the --protocol=TCP option. For example:
shell> mysql --host=127.0.0.1
shell> mysql --protocol=TCP
我们可以用 strace 跟踪服务端和客户端的 socket 建立过程。
实验环境:
Red Hat Enterprise Linux Server release 7.1 (Maipo)
mariadb-5.5.41-2.el7_0.x86_64
(可参考 https://feichashao.com/mariadb/ 进行配置)
localhost (AF_UNIX)
1. 启动 mysqld 服务后,查看 mysqld 对应的 pid:
tcp LISTEN 0 50 *:3306 *:* users:(("mysqld",22013,13))
pid=22013.
2. 在一个终端窗口中,用 strace 追踪该进程(server):
3. 在另一个终端窗口,用 strace 追踪 mysql 命令(client):
4. 建立连接后, Ctrl-C 关闭两个终端的 strace.
5. 从 server 端的输出(/tmp/myserver_localhost.out) 可以看到 AF_UNIX 的建立过程:
(有省略)
22013 20:03:14.244716 fcntl(15, F_SETFL, O_RDWR) = 0 <0.000008>
22013 20:03:14.244744 getsockname(34, {sa_family=AF_LOCAL, sun_path="/var/lib/mysql/mysql.sock"}, [28]) = 0 <0.000023>
22769 20:03:16.626385 shutdown(34, SHUT_RDWR) = 0 <0.000018>
22769 20:03:16.626441 close(34) = 0 <0.000028>
6. 从 client 端的输出(/tmp/myclient_localhost.out) 可以看到相应的 AF_UNIX 连接:
(有省略)
22767 20:03:14.244198 connect(3, {sa_family=AF_LOCAL, sun_path="/var/lib/mysql/mysql.sock"}, 110) = 0 <0.000933>
22767 20:03:14.245284 read(3, "R\0\0\0\n5.5.41-MariaDB\0\20\0\0\0kXV*ld0a\0\377\367\10\2\0\17\240\25\0\0\0\0\0\0\0\0\0\0y_LA@]616Y?m\0mysql_native_password\0", 16384) = 86 <0.000354>
22767 20:03:16.623663 close(3) = 0 <0.000028>
7. 另外我们可以看到,socket 文件是在 my.cnf 里定义的:
socket=/var/lib/mysql/mysql.sock
Note: AF_UNIX also known as AF_LOCAL.
127.0.0.1 (AF_INET)
同理,使用 127.0.0.1 作为 host, 我们能看到不同的输出。
1. 启动 mysqld 服务后,查看 mysqld 对应的 pid:
tcp LISTEN 0 50 *:3306 *:* users:(("mysqld",22013,13))
pid=22013.
2. strace server:
3. strace client:
4. 从 Server 端可以看到:
22013 20:03:52.755513 fcntl(13, F_SETFL, O_RDWR) = 0 <0.000009>
22013 20:03:52.755541 getsockname(34, {sa_family=AF_INET, sin_port=htons(3306), sin_addr=inet_addr("127.0.0.1")}, [16]) = 0 <0.000009>
22790 20:03:54.093376 shutdown(34, SHUT_RDWR) = 0 <0.000074>
22790 20:03:54.093493 close(34) = 0 <0.000028>
5. 从 Client 端可以看到:
22788 20:03:52.755203 fcntl(3, F_SETFL, O_RDONLY) = 0 <0.000006>
22788 20:03:52.755229 fcntl(3, F_GETFL) = 0x2 (flags O_RDWR) <0.000006>
22788 20:03:52.755253 connect(3, {sa_family=AF_INET, sin_port=htons(3306), sin_addr=inet_addr("127.0.0.1")}, 16) = 0 <0.000677>
22788 20:03:54.090230 close(3) = 0 <0.000044>
实例代码
我们可以在 Linux 下简单实现 Unix domain socket 通信。
在同一目录下,分别建立 server.c 和 client.c 文件。
server 在当前目录下建立 server_socket 文件,绑定并监听该 socket.
client 则连接到 server_socket 文件,与 server 进行通信。
// server.c #include#include #include #include #include #include int main() { /* delete the socket file */ unlink("server_socket"); /* create a socket */ int server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un server_addr; server_addr.sun_family = AF_UNIX; strcpy(server_addr.sun_path, "server_socket"); /* bind with the local file */ bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); /* listen */ listen(server_sockfd, 5); char ch; int client_sockfd; struct sockaddr_un client_addr; socklen_t len = sizeof(client_addr); while(1) { printf("server waiting:\n"); /* accept a connection */ client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len); /* exchange data */ read(client_sockfd, &ch, 1); printf("get char from client: %c\n", ch); ++ch; write(client_sockfd, &ch, 1); /* close the socket */ close(client_sockfd); } return 0; }
// client.c #include#include #include #include #include #include int main() { /* create a socket */ int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un address; address.sun_family = AF_UNIX; strcpy(address.sun_path, "server_socket"); /* connect to the server */ int result = connect(sockfd, (struct sockaddr *)&address, sizeof(address)); if(result == -1) { perror("connect failed: "); exit(1); } /* exchange data */ char ch = 'A'; write(sockfd, &ch, 1); read(sockfd,&ch, 1); printf("get char from server: %c\n", ch); /* close the socket */ close(sockfd); return 0; }
先运行 server, 后运行 client,可见效果:
server waiting:
get char from client: A
[root@host1 unix_socket]# ./client
get char from server: B
在 mariadb 的源码中,我们也能看到类似部分:
// mariadb-5.5.44/sql-common/client.c /* snip */ #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); /* snip */
参考资料
1. unix(7) http://man7.org/linux/man-pages/man7/unix.7.html
2. 本地socket unix domain socket http://blog.csdn.net/bingqingsuimeng/article/details/8470029