首页 > 代码库 > mysql网络部分代码

mysql网络部分代码

今天终有点空闲(心情?)整理下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线程对象,放了很多信息,还没研究
              // ……
       }
}

 

今天就研究到这里。

 

mysql网络部分代码