今天终有点空闲(心情?)整理下mysql的网络部分代码了。整体网络部分的代码是非常简单的,mysql几乎没有做过多的封装,很直接的调用系统函数,一目了然。
总体看来,mysql主要使用了一个连接一个线程的模型,对客户端连接的socket使用非阻塞模式。linux版本,甚至包括OSX都用的是poll函数监听客户端请求。中间细节的处理没有太多复杂的操作,可见mysql这一块没有很特别的优化。
先说说Mysql启动网络几个关键函数。
Mysql启动时,首先进入的是mysql_main这个函数,这个函数相当于main函数。在这个main中,主要做了一些列初始化工作,包括初始化环境变量,shutdown线程,最后进入无限循环处理客户端连接请求,当然,无线循环可以被关闭操作或者异常打断,从而退出mysqld。Main首先调用network_init()进行初始化网络socket,在最后,调用handle_connection_sockets()进入循环处理客户端请求连接。简单写出来的伪代码就是如下:
int sql_main() { network_init(); // 初始化socket handle_connection_sockets();// 循环处理连接请求 return 0; }
以下是network_init()的伪代码,忽略了一些平台,错误处理的代码,只写出主要逻辑:
void network_init() { // 地址信息,ai是一个链表,而a则是bind的地址信息 struct addrinfo *ai, *a; // set_port是确定mysql的监听端口,默认下会取编译配置的端口, // 若编译配置都没有设置,则用3306 // 环境变量MYSQL_TCP_PORT会覆盖以上配置 set_port(); // 调用getaddrinfo通过主机名获得地址信息,注意这里的hint.ai_flags设置为AI_PASSIVE表示只获取能bind的地址信息。默认下host name是”0.0.0.0” hint.ai_flags = AI_PASSIVE; getaddrinfo(host name, hint, &ai); // 调用create_socket根据地址信息创建出ip_sock,这个ip_sock就是用了accept连接请求的。ip_sock是全局变量。 ip_sock = create_socket(ai, a); // 设置地址可复用,这样mysqld可以从停止中快速重新bind,而不用等待time wait状态 setsockopt(ip_sock, reuseaddr, 1); // 关闭只使用ipv6 setsockopt(ip_sock, ipv6only, 0); // 以下mysql使用了一个for循环多次尝试bind,这也是为了保证bind的成功率吧,每次尝试若失败,则sleep一段时间。 // sleep x seconds for every try // x: 1, 3, 7, 13, 22, 35, 52, 74, ... for(retry = 1;;retry++) { ret = bind(ip_sock, a); // bind成功出去继续 if (0 == ret) { break; } // 失败,若不是因为地址仍然在使用中的错误,就出去继续,因此mysqld就会启动失败了 if (SOCKET_EADDRINUSE != errno){ break; } // 尝试失败过多,mysqld启动失败 // 这个可以在mysql启动时用参数--port-open-timeout=#设置启动最多等待时间 if(wait_time > max_wait_time){ break; } wait_time += retry * retry / 3 + 1; sleep(wait_time); } listen(ip_sock, back_log); // 以下,读出mysql.sock这个文件,把本机sock参数加载进来,这样mysql其实就有两个socket来出来客户端连接请求,一个是上面的,一个是下面这个主要给本机使用的,其代码更简单 #ifdef UNIX_SOCK unix_sock = socket(); strmov(UNIXaddr.sun_path, mysqld_unix_port); // load the local address setsockopt(unix_sock, reuseaddr, 1); bind(unix_sock); listen(unix_sock, back_log); #endif // #ifdef UNIX_SOCK }
以上代码中,set_port()和create_socket()是很简单的,仅仅是简单的调用getenv来获得环境变量从而设置port,而create_socket()则是调用socket来生成socket套接字,所以就不列出了。
接下来就会进入handle函数了,handle函数主要是处理连接请求,主要实现办法是poll监听请求的到来,然后把accept出new_sock,把new_sock交给一个新建的线程,之后的任务就是这个子线程的事了:
void handle_connection_sockets(){ int socket_count = 0; // 是的就两个,一个ip_sock,一个unix_sock,前者用来accept远程客户端,后者用来accept本机 pollfd fds[2]; fds[socket_count].fd = ip_sock; fds[socket_count].events= POLLIN; int ip_flags = fcntl(ip_sock, F_GETFL, 0); socket_count++; fds[socket_count].fd = unix_sock; fds[socket_count].events= POLLIN; int socket_flags=fcntl(unix_sock, F_GETFL, 0); socket_count++; int sock = 0, flags = 0, new_sock = 0; // 这就是mysqld主线程的主循环 while(!abort_loop){ // poll监听 int ret_val = poll(fds, socket_count, -1); for (int i = 0; i < socket_count; ++i){ if (fds[i].revents & POLLIN){ sock = fds[i].fd; flags = fcntl(sock, F_GETFL, 0); break; } } // 非阻塞的socket fcntl(sock, F_SETFL, flags | O_NONBLOCK); // accept,这里accept10次,直到成功为止,10次都不成功,则这个连接请求失败 // MAX_ACCEPT_RETRY = 10 for (int retry = 0; retry == MAX_ACCEPT_RETRY; ++retry) { new_sock = accept(sock); if (no error) { break; } sleep(1); } if (new_sock == INVALID_SOCKET){ continue; } // 获得对端的地址信息 sockaddr_storage dummy; if (getsockname(new_sock, &dummy) < 0){ shotdown(new_sock, SHUT_RDWR); close(new_sock); continue; } // 以下创建THD这个对象,THD是mysql线程对象,放了很多信息,还没研究 // …… } }
今天就研究到这里。
时间: 2024-11-12 11:50:10