引言
做技術幾年下來,要不停跟著技術的變革而學習,有時會出現“只見樹木,不見森林”的情況,在專案實戰中,片面的技術方案可能會因為考慮不全面而導致後期擴充套件困難甚至引發bug。本文件試圖以一個問題的解決方案為主線,描繪出目前常用技術的變遷及使用。
問題提出
剛學程式設計的時候,試圖寫一個下載程式:給定一個URL網址,下載並儲存為檔案。
基本的C語言知識,加上網上找的資料,就可以完成這個功能。
std::string DownloadFile(const std::string& url)
{
// Download code:use while
...
}
bool SaveFile(const std::string& fileName, const std::string& content)
{
// Save file code:check success
...
}
int main(int argc, char* argv[])
{
std::string url = argv[1];
std::string content = DownloadFile(url);
SaveFile(content);
return 0;
}
這個是我上學時寫的程式,現在看起來有很多問題(都有什麼問題?),不過基本的功能算是實現了。如果能把裡面的string全換成char*來實現,說明C語言考試能過。
這個程式體現了結構化程式程式設計的特點:順序,迴圈,分支以及函式。
問題進化:多執行緒
但是實際工作中不可能如此簡單,比如能不能同時下載多個檔案,或者將一個檔案分片下載?(Flashget,迅雷)
這就引入了多執行緒:
void DownloadThread(void* param)
{
if (param)
{
std::string url = (const char*)param;
SaveFile(DownloadFile(url));
DestroySemaphore();
}
}
int main(int argc, char* argv[])
{
std::string urllist = argv[1];
std::vector ulist = ParseUrlList(urllist);
for (auto it = ulist.begin(); it != ulist.end(); it++)
{
int pThread = CreateThread(DownloadThread, it->str());
int pSem = CreateSemaphore();
InitSemaphore(pSem);
// save thread context and init sem
...
}
// 執行緒同步
WaitAllSemaphore();
return 0;
}
到這裡還遠沒有結束,比如如何控制併發的執行緒數量,如果讓多個下載執行緒寫入同一個檔案(執行緒互斥?),甚至是多程式的配合等。
這個例子中,問題演變為如何讓CPU同時做更多的工作?這其實是技術演變的一個主線,如何讓高速的CPU和低速的IO(磁碟,網路等)配合的更高效。
問題進化:UserInterface
自從Windows系統出來後,客戶端程式設計再也不像前面那樣簡單直接了。
總要給使用者一個東西,讓他們點吧,而我們的程式不可能自己去處理所有螢幕的點選事件,來判斷使用者到底點了哪個pixcel,它又屬於哪一個button。這些通過和作業系統配合,應用程式能很好的完成。
我們想給下載工具寫一個介面,比如做一個PC版的,讓它能在電腦上跑起來,就像迅雷一樣。
LRESULT CALLBACK WndProc( //WndProc名稱可自由定義
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
switch (uMsg)
{
case WM_CREATE:
OnCreate();
break;
case WM_CLOSE:
OnClose();
break;
case WM_DOWNLOAD: // 可以自定義訊息
OnDownload(wParam, lParam);
break;
case WM_STOP_DOWNLOAD:
OnStopDownload();
break;
case WM_DOWNLOAD_PROGRESS:
OnDownloadProgress();
break;
// 此處還有各種訊息
...
case WM_QUIT:
PostQuitMessage(0); // 通知該執行緒的GetMessage,可以退出了;
break;
default:
DefWndProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
int main(int argc, char* argv[])
{
WNDCLASS wndClass = {};
wndClass.style = WS_WINDOW;
wndClass.hIcon = HICON_NONE;
... // 大約1x個引數
wndClass.lpfnWndProc = WndProc;
RegisterClass(wndClass);
HWND hWnd = CreateWindow(wndClass, ...);
ShowWindow(hWnd, SW_SHOW);
MSG msg = {};
while (GetMessage(&msg)) // 這裡面有一個WaitSemaphore
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
上例中引入了一個重要的概念Callback,意思就是你等著,我來調你。
同一個應用,不僅僅是我們的程式來完成功能,和需要和系統配合。連線系統和我們程式的,在這裡就是Callback和MSG。還有隱含的訊息佇列。
這個訊息驅動模型被Windows發明出來後,一直用到今天。
當然,Windows程式這樣的寫法太土了,WndProc裡面的switch誇張的分支能有上千個分支,(Windows的資源管理程式碼中,分支就上千個)。
於是乎,各種Framework就跳出來解救廣大程式設計師了,什麼MFC,ATL、WTL之類。
比如ATL
CApp theApp;
int Run()
{
CMessageLoop loop;
theApp.AddMessageLoop(loop);
CMainWindow wnd;
wnd.Create();
wnd.ShowWindow();
loop.Run();
theApp.RemoveMessageLoop();
}
int main(int argc, char* argv[])
{
theApp.Init();
int nRet = Run();
theApp.term();
return 0;
}
在CMainWindow的實現裡面,可能是這樣的:
class CMainWindow: public CWindow
{
// message map
void OnCreate();
void OnClose();
void OnHandler();
//....
}
其它的系統,也隱藏了視窗建立等細節,在系統層面,就封裝好了,方便程式設計師使用。
比如Android:
public class MyWindow extends Activity {
private Handler mMainHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case XXX:
onXXX();
break;
default:
break;
}
}
}
protected void onCreate(Bundle savedInstanceState) {
//
}
protected void onDestroy() {
//
}
}
Android中的Handler,其實就是一個訊息處理機制(類比WndProc)。我們需要理解訊息,訊息佇列及訊息處理。
在IOS中,訊息佇列別隱藏起來,取而代之的是Delegate模式:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([NetChatApp class]));
}
}
UIApplicationMain中,就維護了訊息佇列(Run Loop),檢測應用的生命週期,並通過Delegate分發處理。
指令碼語言興起
隨著網際網路的發展,Web程式語言興起,帶動了指令碼語言的快速發展;如今,指令碼語言也可以和好的實現後端邏輯,Nodejs,前端逐漸走向後端,後端也逐漸靠近前端,技術又開始了新的發展。全棧,下一個進階的目標。