網游服務器端設計思考:心跳設計

網絡游戲服務器的主要作用是模擬整個游戲世界,客戶端用過網絡連接把一些信息數據發給服務器,在操作合法的情況下,更新服務器上該客戶端對應的player實體、所在場景等,并把這些操作及其影響廣播出去。讓別的客戶端能顯示這些操作。

??? 在這個模擬過程中,需要解決的一個重要問題是:多長時間處理(更新)一次該服務器上的待處理事件,體現在實際開發中,這就是一個服務器端的心跳設計問題(tick)。

??? 在網絡游戲服務器端的心跳設計里主要面臨以下幾個問題:

  1. 心跳函數的處理間隔往往是固定的,因為需要模擬現實世界中時間的性質,不能讓游戲世界表現得忽快忽慢。但處理間隔固定不代表一定要和真實時間一致,有可能有快放慢放的需求。
  2. 固定間隔的心跳,間隔多少長?50ms,100ms,500ms?
  3. 由于服務器每次心跳處理的事件數量和復雜度不一樣,每次處理所需的時間也會不同,服務器繁忙時和閑置時相差很遠,應該使用什么策略來應對?
  4. 編碼實現時應該怎么設計?是和游戲主循環在同一個線程里,還是把心跳寫到一個單獨的timer線程里,或者干脆做成一臺心跳服務器(心跳指令定期通過TCP發出,或者通過同步卡),邏輯服務器都由心跳服務器控制tick的頻率。
  5. 心跳必須和邏輯程序寫在一個進程空間里嗎?有沒有以獨立運行的心跳服務?

??? 為了解決以上問題,本文將對心跳進行分類,從不同角度進行討論。

?

一、按照策略分類

??? 就心跳間隔策略而言,現在的網游服務器端主要分為兩種。分別是固定tick時間和固定sleep時間,可以通過下圖進行具體的說明:

image?

圖 1-1

??? 如上圖1-1中,畫出兩種間隔策略的示意圖,漸變顏色的橫條代表時間,Tick1、Tick2代表程序兩次不同的更新操作,Run1、Run2代表在心跳函數里處理更新操作所需的時間,Sleep1、Sleep2代表讓出CPU時間片的時間。

? (1)固定Tick時間:顧名思義就是指程序每次心跳的時間都是等長的、固定的。如圖中的“圖A”,Tick1和Tick2的時間是相等的,如果實際執行的比上次執行時間長(Run2 > Run1),則Sleep2 < Sleep1,同時滿足等式:Tick1 = Tick2 = Run1 + Sleep1 = Run2 + Sleep2

? (2)固定Sleep時間:每次心跳,更新操作執行完成后,sleep固定的時間。如圖中的“圖B”,Sleep1 = Sleep2,Run1和Run2不一定相等,同時滿足等式:Tick1 = Run1 + Sleep1,Tick2 = Run2 + Sleep2

??? 下面結合具體的代碼對比說明這兩種策略

?

1.1 固定Tick時間

??? 使用固定tick時間的心跳策略的一大好處就是,在負荷不高的情況下,由于相鄰兩次tick的時間一定,所以開始執行Run1到開始執行Run2的時間間隔一定。tick時間固定帶來的另一個好處就是容易實現邏輯服務器運行時快放慢放功能(見??),當然固定tick時間同樣帶來一些問題,如下圖:

image

圖 1-2

??? 如圖1-2,在負荷不高的情況下,心跳函數可以按照上圖中“圖A”的時間線正常的運行,如果在服務器運行的過程中遇到一些突發事件(開新服、做活動、大世界內大范圍的幫戰),會導致服務器CPU負荷變高,從而使得一次tick無法處理完當前所有事件,出現“圖B”中的情況Run1 > Tick1,這時Sleep1不管取什么值都不能滿足等式Tick1 = Run1 + Sleep1,

??? 這樣一來就帶來第一個問題:高負荷情況下如何保證CPU能充分利用的情況下,tick1和tick2兩次心跳互相不干擾?伴隨而來的另一個問題是tick時間設為多長才能滿足低負荷時固定間隔的要求,同時不能經常出現“圖B”的情況?

??? 下面結合實例講解固定tick時間的心跳如何編寫,以及如何處理以上兩個問題。

?

Mangos-Zero

??? mangos-zero項目中的邏輯服務進程mangosd的心跳函數采用如圖1-2中的“圖C”的方法,當更新的處理時間Run1大于固定大小的tick時間時,下一個tick到來時不sleep直接執行Run2,實現代碼如下:

?

   1: /// Heartbeat for the World
   2: void WorldRunnable::run()
   3: {
   4:     ///- Init new SQL thread for the world database
   5:     WorldDatabase.ThreadStart();     // let thread do safe mySQL requests (one connection call enough)
   6:     sWorld.InitResultQueue();
   7:? 
   8:     uint32 realCurrTime = 0;
   9:     uint32 realPrevTime = WorldTimer::tick();
  10:? 
  11:     uint32 prevSleepTime = 0;        // used for balanced full tick time length near WORLD_SLEEP_CONST
  12:? 
  13:     ///- While we have not World::m_stopEvent, update the world
  14:     while (!World::IsStopped())
  15:     {
  16:         ++World::m_worldLoopCounter;
  17:         realCurrTime = WorldTimer::getMSTime();  //----------------(1)
  18:? 
  19:         uint32 diff = WorldTimer::tick();        //--------------(2)
  20:? 
  21:         sWorld.Update( diff );                   //--------------(3)
  22:         realPrevTime = realCurrTime;
  23:? 
  24:         // diff (D0) include time of previous sleep (d0) + tick time (t0)
  25:         // we want that next d1 + t1 == WORLD_SLEEP_CONST
  26:         // we can't know next t1 and then can use (t0 + d1) == WORLD_SLEEP_CONST requirement
  27:         // d1 = WORLD_SLEEP_CONST - t0 = WORLD_SLEEP_CONST - (D0 - d0) = WORLD_SLEEP_CONST + d0 - D0
  28:         if (diff <= WORLD_SLEEP_CONST+prevSleepTime)    //----------------(4)
  29:         {
  30:             prevSleepTime = WORLD_SLEEP_CONST+prevSleepTime-diff;
  31:             ACE_Based::Thread::Sleep(prevSleepTime);
  32:         }
  33:         else
  34:             prevSleepTime = 0;
  35:? 
  36:         #ifdef WIN32
  37:             if (m_ServiceStatus == 0) World::StopNow(SHUTDOWN_EXIT_CODE);
  38:             while (m_ServiceStatus == 2) Sleep(1000);
  39:         #endif
  40:     }
  41:? 
  42:     sWorld.KickAll();                // save and kick all players
  43:     sWorld.UpdateSessions( 1 );      // real players unload required UpdateSessions call
  44:? 
  45:     // unload battleground templates before different singletons destroyed
  46:     sBattleGroundMgr.DeleteAllBattleGrounds();
  47:? 
  48:     sWorldSocketMgr->StopNetwork();
  49:? 
  50:     sMapMgr.UnloadAll();             // unload all grids (including locked in memory)
  51:? 
  52:     ///- End the database thread
  53:     WorldDatabase.ThreadEnd();       // free mySQL thread resources
  54: }

?

??? 以上代碼是游戲世界的主循環,看while循環里的代碼,主要干下面幾件事:

(1)從WorldTimer::getMSTime()得到一個uint32的值realCurrTime,realCurrTime是循環的(到增加到0xFFFFFFFF后,在增加就變成0),表示當前時間,單位是毫秒,是一個相對前一次tick的時間。

(2)使用WorldTimer::tick();計算上次tick到這次tick的時間差diff,該值理論上等于realCurrTime – realPrevTime

(3)sWorld.Update( diff );就是tick里的處理函數,游戲邏輯在這里得到更新處理。

(4)這里就是圖1-2中的“圖C”所描述的,如果運行時間大于固定的tick時間,則不sleep繼續占用CPU來處理更新,直到能在一個tick處理所有操作為止,這個時候才會sleep讓出CPU時間片。

(5)WORLD_SLEEP_CONST就是固定的tick的時間長度,在這里是50ms

??? 總結:現在可以回答本節前面的兩個問題:在高負荷情況下mangos采用圖1-2中“圖C”的方式提高服務器的響應速度,每個tick時間長度為50ms,也就是每秒鐘更新20次,能滿足更新的需求。

?

timer_thread

??? 出于模塊化的考慮,固定tick時間策略還有一種實現方式:使用單獨的線程做timer,周期性產生心跳信號或者心跳task。工作原理如下圖:

?

image?

圖 1-3

??? 圖1-3也是比較常見的設計方案,服務器進程采用多線程方式,主循環線程、timer線程及其他非工作線程向任務隊列(task queue)中添加task,而工作線程不斷的從任務隊列中取出任務執行相應的處理。這里提到的timer thread就是用來產生心跳任務的,timer thread會每隔50ms產生一個heartbeat task放入任務隊列中。一般來說隊列中的heartbeat task的數量會遠遠大于其他task,所以這種策略也可以稱為固定tick時間的心跳策略。在服務器高負荷運行的情況下,近似于mangos所采用的圖1-2中“圖C”的方式進行處理。多個worker thread的情況下,還需要對heartbeat task加鎖。

?

1.2 固定Sleep時間

??? 固定Sleep也是一種比較常見的心跳函數間隔處理策略,如下圖,每次心跳處理函數執行完畢后sleep固定長度時間。

?

image

??? 圖 1-4

??? 如圖1-4,Sleep1 = Sleep2,Run1和Run2不一定相等,同時滿足等式:Tick1 = Run1 + Sleep1,Tick2 = Run2 + Sleep2。下面結合實例進行說明:

?

天龍

??? 根據網上流出的天龍源代碼,GameServer工程的主循環至線程的心跳函數的調用過程如下:

?

   1: BOOL Server::Loop( )
   2: {
   3:     ........
   4:? 
   5:     ret = g_pThreadManager->Start( ) ; //--------(1)
   6:? 
   7:     ........
   8:? 
   9:      return TRUE ;
  10: }
  11:                     |
  12:                     |
  13:                    \|/
  14: BOOL ThreadManager::Start( )
  15: {
  16:     ........
  17:? 
  18:     BOOL ret ;
  19:     m_pServerThread->start() ;               //--------(2)
  20:     MySleep( 500 ) ;
  21:     ret = m_pThreadPool->Start( ) ;
  22:     
  23:     ........
  24: }
  25:                     |
  26:                     |
  27:                    \|/
  28: VOID Thread::start () 
  29: { 
  30:     ........
  31:? 
  32: #if defined(__LINUX__)
  33:     pthread_create( &m_TID, NULL , MyThreadProcess , this );
  34: #elif defined(__WINDOWS__)
  35:     m_hThread = ::CreateThread( NULL, 0, MyThreadProcess , this, 0, &m_TID ) ;
  36: #endif
  37:? 
  38:     ........
  39: }
  40:                     |
  41:                     |
  42:                    \|/
  43: VOID ServerThread::run( )
  44: {
  45:     ........
  46:? 
  47:     _MY_TRY
  48:     {
  49:         g_pServerManager->m_ThreadID = getTID() ;
  50:? 
  51:         while( IsActive() )
  52:         {
  53:             if( g_pServerManager )
  54:             {
  55:                 BOOL ret = g_pServerManager->Tick( ) ;   //--------(3)
  56:                 Assert( ret ) ;
  57:             }
  58:? 
  59:             ........
  60:         }
  61:     }
  62:     _MY_CATCH
  63:     {
  64:         ........
  65:     }
  66:? 
  67:     ........
  68: }

?

??? 如上,主循環啟動一個“用來處理服務器之間數據通訊的線程”m_pServerThread,以及一個線程池m_pThreadPool。首先看m_pServerThread的run ()函數,調用g_pServerManager->tick ()函數,代碼如下:

?

   1: BOOL ServerManager::Tick( )
   2: {
   3:     ........
   4:? 
   5:     BOOL ret ;
   6:? 
   7:     _MY_TRY
   8:     {
   9:         ret = Select( ) ;       //--------(1)
  10:         Assert( ret ) ;
  11:     }
  12:     _MY_CATCH
  13:     {
  14:         SaveCodeLog( ) ;
  15:     }
  16:? 
  17:     ........
  18: }
  19:                     |
  20:                     |
  21:                    \|/
  22: BOOL ServerManager::Select( )
  23: {
  24: __ENTER_FUNCTION
  25:? 
  26:     MySleep(50) ;             //--------(2)
  27:     if( m_MaxFD==INVALID_SOCKET && m_MinFD==INVALID_SOCKET )
  28:     {
  29:         return TRUE ;
  30:     }
  31:? 
  32:     m_Timeout[SELECT_USE].tv_sec  = m_Timeout[SELECT_BAK].tv_sec;
  33:     m_Timeout[SELECT_USE].tv_usec = m_Timeout[SELECT_BAK].tv_usec;
  34:? 
  35:     m_ReadFDs[SELECT_USE]   = m_ReadFDs[SELECT_BAK];
  36:     m_WriteFDs[SELECT_USE]  = m_WriteFDs[SELECT_BAK];
  37:     m_ExceptFDs[SELECT_USE] = m_ExceptFDs[SELECT_BAK];
  38:? 
  39:     _MY_TRY 
  40:     {
  41:         INT iRet = SocketAPI::select_ex(    (INT)m_MaxFD+1 , 
  42:                                             &m_ReadFDs[SELECT_USE] , 
  43:                                             &m_WriteFDs[SELECT_USE] , 
  44:                                             &m_ExceptFDs[SELECT_USE] , 
  45:                                             &m_Timeout[SELECT_USE] ) ; //--------(3)
  46:         if( iRet==SOCKET_ERROR )
  47:         {
  48:             Assert(FALSE) ;
  49:         }
  50:     } 
  51:     _MY_CATCH
  52:     {
  53:         Log::SaveLog( SERVER_LOGFILE, "ERROR: ServerManager::Select( )..." ) ;
  54:     }
  55:? 
  56:     return TRUE ;
  57:? 
  58: __LEAVE_FUNCTION
  59:? 
  60:     return FALSE ;
  61:? 
  62: }

?

??? 如上,在Tick ()函數里首先調用ServerManager::Select (),在Select函數中(2)調用MySleep(50)讓出CPU時間片50ms,然后給select_ex設置100us的超時,可以認為每次執行完處理后,會固定sleep 50ms。

??? 再來看看Threadpool里線程的tick函數

?

   1: BOOL Scene::Tick( )
   2: {
   3:     ........    
   4:? 
   5:     //網絡處理
   6:     _MY_TRY
   7:     {
   8:         ret = m_pScenePlayerManager->Select( ) ; //--------(1)
   9:         Assert( ret ) ;
  10:     }
  11:     _MY_CATCH
  12:     {
  13:         SaveCodeLog( ) ;
  14:     }
  15:? 
  16:     ........
  17: }
  18:                     |
  19:                     |
  20:                    \|/
  21: BOOL ScenePlayerManager::Select( )
  22: {
  23:     {
  24:         MySleep( 50 ) ;          //--------(2)
  25:     }
  26:? 
  27:     if( m_MaxFD==INVALID_SOCKET && m_MinFD==INVALID_SOCKET )
  28:         return TRUE ;
  29:? 
  30:     m_Timeout[SELECT_USE].tv_sec  = m_Timeout[SELECT_BAK].tv_sec;
  31:     m_Timeout[SELECT_USE].tv_usec = m_Timeout[SELECT_BAK].tv_usec;
  32:? 
  33:     m_ReadFDs[SELECT_USE]   = m_ReadFDs[SELECT_BAK];
  34:     m_WriteFDs[SELECT_USE]  = m_WriteFDs[SELECT_BAK];
  35:     m_ExceptFDs[SELECT_USE] = m_ExceptFDs[SELECT_BAK];
  36:? 
  37:     _MY_TRY 
  38:     {
  39:         INT ret = SocketAPI::select_ex(    (INT)m_MaxFD+1 , 
  40:                                         &m_ReadFDs[SELECT_USE] , 
  41:                                         &m_WriteFDs[SELECT_USE] , 
  42:                                         &m_ExceptFDs[SELECT_USE] , 
  43:                                         &m_Timeout[SELECT_USE] ) ; //--------(3)
  44:         if( ret == SOCKET_ERROR )
  45:         {
  46:             Assert(FALSE) ;
  47:         }
  48:     } 
  49:     _MY_CATCH
  50:     {
  51:         SaveCodeLog( ) ;
  52:     }
  53:? 
  54:     ........   
  55: }

???

??? 根據以上代碼中的(1)、(2)、(3)可以看到場景的tick函數中采用同樣的方法,每次調用時先調用MySleep(50)讓出CPU時間片50ms,然后再執行相應的處理代碼。

??? 總結

(a)固定Sleep時間在高負荷情況下,由于每次都會強制讓出CPU時間片,會不會導致響應不及時?

(b)在每次運行執行處理函數的時間比較穩當的情況下,這種策略還是能提供比較穩定的tick間隔。

(c)這樣的設計目前為止本人還沒有看出有什么好處?請各位博友指教。

?

?

二、按照物理位置分類

??? 按照物理位置分類,分為兩種,一種是運行在同一物理主機上,另一種是運行在不同的物理主機上。該分類是受到云風一篇blog的啟發(見《心跳服務器》)。

(1)心跳服務和邏輯程序運行在同一物理主機上:據我所知,大部分的網游服務器程序都采用這種方式,而且心跳服務往往和邏輯程序寫在同一個進程里。心跳服務和邏輯程序不在一個進程里,但運行在同一物理主機上的情況本人沒有見過,也不敢妄加臆斷。

(2)心跳服務運行在一臺獨立的物理主機上:(a)按照云風的說法,這樣做最大的好處就是方便實現服務器的快放慢放功能,從而很容易重現bug。博文中指出使用使用10Hz的心跳間隔,以局域網內0.1~0.2ms的ping值,不知道能不能容忍?而且使用TCP連接,多兩次網絡IO,不知道效率如何?(b)另一種獨立的心跳服務器是本人讀研時參與過的大屏幕顯示同步時遇到的,當時一塊大屏幕有24臺主機,每臺主機負責繪制圖像中的一塊區域。使用一臺單獨的心跳服務器,用同步卡向著24臺主機的串口周期性的發信號,這24臺主機收到從串口來的心跳信號后,同時開始繪制這一幀。不過據說精確度有待提高………

?

?

三、總結

??? 綜上所述,最常見的心跳服務是這樣設計的:使用固定tick時間策略,與邏輯處理程序寫在同一個進程里,tick時間50ms,100ms。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/444960.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/444960.shtml
英文地址,請注明出處:http://en.pswp.cn/news/444960.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

算法(25)-括號

各種括號1.LeetCode-22 括號生成--各種括號排列組合2.LeetCode-20 有效括號(是否)--堆棧3.LeetCode-32 最長有效括號(長度)--dp4.LeetCode-301刪除無效括號 --多種刪除方式1.LeetCode-22 括號生成–各種括號排列組合 數字 n 代表生成括號的對數&#xff0c;請你設計一個函數&a…

(二十)深入淺出TCPIP之epoll的一些思考

Epoll基本介紹 在linux的網絡編程中,很長的時間都在使用select來做事件觸發。在linux新的內核中,有了一種替換它的機制,就是epoll。相比于 select,epoll最大的好處在于它不會隨著監聽fd數目的增長而降低效率。因為在內核中的select實現中,它是采用輪詢來處理的,輪詢的fd…

leetcode542 01矩陣

給定一個由 0 和 1 組成的矩陣&#xff0c;找出每個元素到最近的 0 的距離。 兩個相鄰元素間的距離為 1 。 示例 1: 輸入: 0 0 0 0 1 0 0 0 0 輸出: 0 0 0 0 1 0 0 0 0 示例 2: 輸入: 0 0 0 0 1 0 1 1 1 輸出: 0 0 0 0 1 0 1 2 1 注意: 給定矩陣的元素個數不超過 10000。…

RPC、RMI與MOM與組播 通信原理 .

遠程過程調用&#xff08;RPC&#xff09;&#xff1a; 即對遠程站點機上的過程進行調用。當站點機A上的一個進程調用另一個站點機上的過程時&#xff0c;A上的調用進程掛起&#xff0c;B上的被調用過程執行&#xff0c;并將結果返回給調用進程&#xff0c;使調用進程繼續執行【…

網關服務器 .

之前想著要把什么什么給寫一下&#xff0c;每次都太懶了&#xff0c;都是想起了才來寫一下。今天只討論游戲服務器的網關服務器。 1.轉發 轉發客戶端和服務器間的消息&#xff0c;網關將場景、會話、數據、名字、平臺等服務器的數據轉發給客戶端&#xff0c;接收客戶端的數據&a…

算法(26)-最長系列

最長系列1.LeetCode-32 最長有效括號--子串2.LeetCode-300 最長上升子序列--長度3.LeetCode-32 最長回文子串--是什么5.LeetCode-512 最長回文子序列--長度6.LeetCode-1143 最長公共子序列--長度6.LeetCode-128 最長連續序列--長度7.LeetCode-14 最長公共前綴-字符串8.劍指offe…

一個簡單的游戲服務器框架 .

最近一段時間不是很忙&#xff0c;就寫了一個自己的游戲服務器框架雛形&#xff0c;很多地方還不夠完善&#xff0c;但是基本上也算是能夠跑起來了。我先從上層結構說起&#xff0c;一直到實現細節吧&#xff0c;想起什么就寫什么。 第一部分 服務器邏輯 服務器這邊簡單的分為三…

游戲登陸流程 .

當公司有很多游戲的時候&#xff0c;那么公司往往會有一個統一的賬號管理平臺&#xff0c;就就像盛大通行證、網易通行證&#xff0c;戰網平臺&#xff0c;這些平臺統一管理游戲的賬號數據。 打個比方&#xff0c;現在我們玩星辰變&#xff0c;那么玩家登陸游戲的時候…

leetcode97 交錯字符串

給定三個字符串 s1, s2, s3, 驗證 s3 是否是由 s1 和 s2 交錯組成的。 示例 1: 輸入: s1 "aabcc", s2 "dbbca", s3 "aadbbcbcac" 輸出: true 示例 2: 輸入: s1 "aabcc", s2 "dbbca", s3 "aadbbbaccc" 輸…

算法(27)-最大系列

最大系列1.LeetCode-239 滑動窗口的最大值2.LeetCode-53 連續子數組的最大和3.LeetCode-152 乘積最大的子數組。4.劍指 Offer 14- I. 剪繩子為k個整數段&#xff0c;使各個段成績最大1.dp數學推導1.LeetCode-239 滑動窗口的最大值 窗口由左往右最大值數組Left&#xff0c;和由…

mysql數據庫表的導入導出

MySQL寫入數據通常用insert語句&#xff0c;如 復制代碼 代碼如下: insert into person values(張三&#xff0c;20)&#xff0c;&#xff08;李四&#xff0c;21&#xff09;&#xff0c;&#xff08;王五&#xff0c;70&#xff09;…; 但有時為了更快速地插入大批量數據或…

leetcode 33 搜索旋轉排序數組 到處是細節的好題

這個題想了想就會做&#xff0c;只是細節真的能卡死人&#xff0c;找了好久的bug。甚至我懷疑我現在的代碼可能還有錯&#xff0c;只是沒例子測出來。 假設按照升序排序的數組在預先未知的某個點上進行了旋轉。 ( 例如&#xff0c;數組 [0,1,2,4,5,6,7] 可能變為 [4,5,6,7,0,1…

多線程中局部靜態變量初始化的陷阱

C當中常常需要一個全局唯一的對象實例&#xff0c;這時候&#xff0c;我們就會想到單件模式。如何實現這一模式&#xff1f;全局變量當然是一個簡單可行的方法&#xff0c;然而&#xff0c;這太丑陋。嗯&#xff0c;其實&#xff0c;丑陋倒也罷了&#xff0c;最嚴重的是它將引誘…

MachineLearning(8)-PCA,LDA基礎+sklearn 簡單實踐

PCA,LDA基礎sklearn 簡單實踐1.PCAsklearn.decomposition.PCA1.PCA理論基礎2.sklearn.decomposition.PCA簡單實踐2.LDAsklearn.discriminant_analysis.LinearDiscriminantAnalysis2.1 LDA理論基礎2.2 sklearn LDA簡單實踐1.PCAsklearn.decomposition.PCA 1.PCA理論基礎 PCA:&…

引用變量和引用數組

前兩天沒事干,重拾C++的一些書籍,翻到引用這,無意寫了些DD: 其實引用和指針有很多相似的地方,又有不同的(太多了,不過說到效率上,比如函數傳參數,我們可以用引用,指針,哪種好呢,引用不必為站再分配空間了,而指針還學要分配4字節的空間給指針變量) 我們知道如何…

leetcode198 打家劫舍

你是一個專業的小偷&#xff0c;計劃偷竊沿街的房屋。每間房內都藏有一定的現金&#xff0c;影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統&#xff0c;如果兩間相鄰的房屋在同一晚上被小偷闖入&#xff0c;系統會自動報警。 給定一個代表每個房屋存放金額的…

linux下的RPC

一、概述 在傳統的編程概念中&#xff0c;過程是由程序員在本地編譯完成&#xff0c;并只能局限在本地運行的一段代碼&#xff0c;也即其主程序和過程之間的運行關系是本地調用關系。因此這種結構在網絡日益發展的今天已無法適應實際需求。總而言之&#xff0c;傳統過程調用模式…

算法(28)--矩陣搜索系列

矩陣搜索1.leetcode-200. 島嶼數量2.leetcode-695. 島嶼的最大面積3.leetcode-463. 島嶼的周長4.劍指 Offer 12. 矩陣中的路徑5.leetcode-329. 矩陣中的最長遞增路徑6.leetcode-1091. 二進制矩陣中的最短路徑1.leetcode-200. 島嶼數量 給你一個由 ‘1’&#xff08;陸地&#…

leetcode213 打家劫舍II

你是一個專業的小偷&#xff0c;計劃偷竊沿街的房屋&#xff0c;每間房內都藏有一定的現金。這個地方所有的房屋都圍成一圈&#xff0c;這意味著第一個房屋和最后一個房屋是緊挨著的。同時&#xff0c;相鄰的房屋裝有相互連通的防盜系統&#xff0c;如果兩間相鄰的房屋在同一晚…

linux下安裝boost

以下是在ubuntu 7.10 (內核 2.6.22-14)下安裝的例子&#xff1a; 一、下載最新的 boost 庫&#xff0c;下載地址&#xff1a; http://www.boost.org/users/download/ 二、在適當的位置解壓 boost 庫&#xff0c;推薦把 boost 庫解壓到 /usr/local/ 下&#xff1a; $ cd dowlo…