標籤:address 設定 擷取 一段 oid oop 建立 style bre
今天終有點空閑(心情?)整理下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網路部分代碼