引言

做技术几年下来,要不停跟着技术的变革而学习,有时会出现“只见树木,不见森林”的情况,在项目实战中,片面的技术方案可能会因为考虑不全面而导致后期扩展困难甚至引发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,前端逐渐走向后端,后端也逐渐靠近前端,技术又开始了新的发展。全栈,下一个进阶的目标。


techfellow
524 声望5 粉丝

下一篇 »
由Nodejs来说I/O