内容纲要

dcplusplus 是一个开源的 P2P 客户端,界面采用 WTL 编写,使用了 boost 库,是很好的范例教材。

dcplusplus 使用的第三方库有

  • boost
  • zlib
  • minizip
  • bzip2
  • dht
  • openssl

这个客户端的主要业务逻辑在 client 和 windows 目录

  • client 存放业务逻辑
  • windows 基于 WTL 的各种 UI

使用的模式

  • 单件模式
  • MVC 模式

此客户端采用单件模式分割各个模块,采用 MVC 更新视图。

client\Singleton.h

#ifndef DCPLUSPLUS_DCPP_SINGLETON_H
#define DCPLUSPLUS_DCPP_SINGLETON_H

#include "debug.h"

namespace dcpp {

template<typename T>
class Singleton {
public:
    Singleton() { }
    virtual ~Singleton() { }

    static T* getInstance() {
        dcassert(instance);
        return instance;
    }

    static void newInstance() {
        if(instance)
            delete instance;

        instance = new T();
    }

    static void deleteInstance() {
        if(instance)
            delete instance;
        instance = NULL;
    }
protected:
    static T* instance;
private:
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);

};

template<class T> T* Singleton<T>::instance = NULL;

} // namespace dcpp

#endif // DCPLUSPLUS_DCPP_SINGLETON_H

此单件模板类并不是线程安全,实际上如果做模块划分,系统直接在主线程中初始化各个模块。

client\Speaker.h

#ifndef DCPLUSPLUS_DCPP_SPEAKER_H
#define DCPLUSPLUS_DCPP_SPEAKER_H

#include <boost/range/algorithm/find.hpp>
#include <utility>
#include <vector>

#include "Thread.h"

#include "noexcept.h"

namespace dcpp {

using std::forward;
using std::vector;
using boost::range::find;

template<typename Listener>
class Speaker {
    typedef vector<Listener*> ListenerList;

public:
    Speaker() noexcept { }
    virtual ~Speaker() { }

    /// @todo simplify when we have variadic templates

    template<typename T0>
    void fire(T0&& type) noexcept {
        Lock l(listenerCS);
        tmp = listeners;
        for(auto i = tmp.begin(); i != tmp.end(); ++i) {
            (*i)->on(forward<T0>(type));
        }
    }

    template<typename T0, typename T1>
    void fire(T0&& type, T1&& p1) noexcept {
        Lock l(listenerCS);
        tmp = listeners;
        for(auto i = tmp.begin(); i != tmp.end(); ++i) {
            (*i)->on(forward<T0>(type), forward<T1>(p1));
        }
    }

    template<typename T0, typename T1, typename T2>
    void fire(T0&& type, T1&& p1, T2&& p2) noexcept {
        Lock l(listenerCS);
        tmp = listeners;
        for(auto i = tmp.begin(); i != tmp.end(); ++i) {
            (*i)->on(forward<T0>(type), forward<T1>(p1), forward<T2>(p2));
        }
    }

    template<typename T0, typename T1, typename T2, typename T3>
    void fire(T0&& type, T1&& p1, T2&& p2, T3&& p3) noexcept {
        Lock l(listenerCS);
        tmp = listeners;
        for(auto i = tmp.begin(); i != tmp.end(); ++i) {
            (*i)->on(forward<T0>(type), forward<T1>(p1), forward<T2>(p2), forward<T3>(p3));
        }
    }

    template<typename T0, typename T1, typename T2, typename T3, typename T4>
    void fire(T0&& type, T1&& p1, T2&& p2, T3&& p3, T4&& p4) noexcept {
        Lock l(listenerCS);
        tmp = listeners;
        for(auto i = tmp.begin(); i != tmp.end(); ++i) {
            (*i)->on(forward<T0>(type), forward<T1>(p1), forward<T2>(p2), forward<T3>(p3), forward<T4>(p4));
        }
    }

    template<typename T0, typename T1, typename T2, typename T3, typename T4, typename T5>
    void fire(T0&& type, T1&& p1, T2&& p2, T3&& p3, T4&& p4, T5&& p5) noexcept {
        Lock l(listenerCS);
        tmp = listeners;
        for(auto i = tmp.begin(); i != tmp.end(); ++i) {
            (*i)->on(forward<T0>(type), forward<T1>(p1), forward<T2>(p2), forward<T3>(p3), forward<T4>(p4), forward<T5>(p5));
        }
    }

    template<typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
    void fire(T0&& type, T1&& p1, T2&& p2, T3&& p3, T4&& p4, T5&& p5, T6&& p6) noexcept {
        Lock l(listenerCS);
        tmp = listeners;
        for(auto i = tmp.begin(); i != tmp.end(); ++i) {
            (*i)->on(forward<T0>(type), forward<T1>(p1), forward<T2>(p2), forward<T3>(p3), forward<T4>(p4), forward<T5>(p5), forward<T6>(p6));
        }
    }

    template<typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7>
    void fire(T0&& type, T1&& p1, T2&& p2, T3&& p3, T4&& p4, T5&& p5, T6&& p6, T7&& p7) noexcept {
        Lock l(listenerCS);
        tmp = listeners;
        for(auto i = tmp.begin(); i != tmp.end(); ++i) {
            (*i)->on(forward<T0>(type), forward<T1>(p1), forward<T2>(p2), forward<T3>(p3), forward<T4>(p4), forward<T5>(p5), forward<T6>(p6), forward<T7>(p7));
        }
    }

    void addListener(Listener* aListener) {
        Lock l(listenerCS);
        if(find(listeners, aListener) == listeners.end())
            listeners.push_back(aListener);
    }

    void removeListener(Listener* aListener) {
        Lock l(listenerCS);
        auto it = find(listeners, aListener);
        if(it != listeners.end())
            listeners.erase(it);
    }

    void removeListeners() {
        Lock l(listenerCS);
        listeners.clear();
    }
    
protected:
    ListenerList listeners;
    ListenerList tmp;
    CriticalSection listenerCS;
};

} // namespace dcpp

#endif // !defined(SPEAKER_H)

更新视图

dcplusplus 并不直接访问业务类返回数据,更新 UI。通常这样的做法是访问业务类封装在线程里,异步返回数据,更新 UI。 但是 dcplusplus 采用业务类访问数据后直接放置在队列容器里,PostMessage(WM_SPEAKER);在 onSpeaker(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) 消息响应中访问数据更新视图。

client\TaskQueue.h

#ifndef DCPLUSPLUS_DCPP_TASK_H
#define DCPLUSPLUS_DCPP_TASK_H

#include <memory>
#include <utility>
#include <vector>

#include "Thread.h"

namespace dcpp {

using std::make_pair;
using std::pair;
using std::swap;
using std::unique_ptr;
using std::vector;

struct Task {
    virtual ~Task() { };
};

struct StringTask : public Task {
    StringTask(const string& str_) : str(str_) { }
    string str;
};

class TaskQueue {
public:
    typedef pair<uint8_t, unique_ptr<Task> > Pair;
    typedef vector<Pair> List;

    TaskQueue() {
    }

    ~TaskQueue() {
        clear();
    }

    void add(uint8_t type, std::unique_ptr<Task> && data) { Lock l(cs); tasks.push_back(make_pair(type, move(data))); }
    void get(List& list) { Lock l(cs); swap(tasks, list); }
    void clear() {
        List tmp;
        get(tmp);
    }
private:

    TaskQueue(const TaskQueue&);
    TaskQueue& operator=(const TaskQueue&);

    CriticalSection cs;
    List tasks;
};

} // namespace dcpp

#endif

建立业务类的关系

注册监听

FinishedManager::FinishedManager() { 
    QueueManager::getInstance()->addListener(this);
    UploadManager::getInstance()->addListener(this);
    SettingsManager::getInstance()->addListener(this);
}
    
FinishedManager::~FinishedManager() {
    QueueManager::getInstance()->removeListener(this);
    UploadManager::getInstance()->removeListener(this);
    SettingsManager::getInstance()->removeListener(this);

    Lock l(cs);
    for_each(downloads.begin(), downloads.end(), DeleteFunction());
    for_each(uploads.begin(), uploads.end(), DeleteFunction());
}

由于各个模块都是单件,可以直接访问并注册自己this

各个模块的初始化

client\DCPlusPlus.cpp

#include "stdinc.h"
#include "DCPlusPlus.h"

#include "ConnectionManager.h"
#include "DownloadManager.h"
#include "UploadManager.h"
#include "CryptoManager.h"
#include "ShareManager.h"
#include "SearchManager.h"
#include "QueueManager.h"
#include "ClientManager.h"
#include "HashManager.h"
#include "LogManager.h"
#include "FavoriteManager.h"
#include "SettingsManager.h"
#include "FinishedManager.h"
#include "ADLSearch.h"
#include "MappingManager.h"
#include "ConnectivityManager.h"

#include "DebugManager.h"
#include "DetectionManager.h"
#include "WebServerManager.h"
#include "ThrottleManager.h"
#include "File.h"

#include "RawManager.h"
#include "PluginManager.h"
#include "HttpManager.h"
#include "UpdateManager.h"

#include "../dht/DHT.h"
#ifdef _WIN32
# include "../windows/PluginApiWin.h"
#endif

/*
#ifdef _STLP_DEBUG
void __stl_debug_terminate() {
    int* x = 0;
    *x = 0;
}
#endif
*/

namespace dcpp {

using dht::DHT;

void startup(void (*f)(void*, const string&), void* p) {
    // "Dedicated to the near-memory of Nev. Let's start remembering people while they're still alive."
    // Nev's great contribution to dc++
    while(1) break;


#ifdef _WIN32
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif

    Util::initialize();

    ResourceManager::newInstance();
    SettingsManager::newInstance();

    TimerManager::newInstance();
    LogManager::newInstance();
    HashManager::newInstance();
    CryptoManager::newInstance();
    SearchManager::newInstance();
    ClientManager::newInstance();
    ConnectionManager::newInstance();
    DownloadManager::newInstance();
    UploadManager::newInstance();
    ThrottleManager::newInstance();
    QueueManager::newInstance();
    ShareManager::newInstance();
    FavoriteManager::newInstance();
    FinishedManager::newInstance();
    RawManager::newInstance();
    ADLSearchManager::newInstance();
    ConnectivityManager::newInstance();
    MappingManager::newInstance();
    DebugManager::newInstance();
    WebServerManager::newInstance();
    DetectionManager::newInstance();
    PluginManager::newInstance();
    PluginApiImpl::init();
#ifdef _WIN32
    PluginApiWin::init();
#endif
    HttpManager::newInstance();
    UpdateManager::newInstance();

    SettingsManager::getInstance()->load(); 

    if(!SETTING(LANGUAGE_FILE).empty()) {
        string languageFile = SETTING(LANGUAGE_FILE);
        if(!File::isAbsolute(languageFile))
            languageFile = Util::getPath(Util::PATH_LOCALE) + languageFile;
        ResourceManager::getInstance()->loadLanguage(languageFile);
    }

    FavoriteManager::getInstance()->load();

    CryptoManager::getInstance()->loadCertificates();
    DetectionManager::getInstance()->load();
    PluginManager::getInstance()->loadPlugins([&f, p](const string& str) { (*f)(p, str); });
    
    DHT::newInstance();

    if(f != NULL)
        (*f)(p, STRING(HASH_DATABASE));
    HashManager::getInstance()->startup();
    if(f != NULL)
        (*f)(p, STRING(SHARED_FILES));
    ShareManager::getInstance()->refresh(true, false, true);
    if(f != NULL)
        (*f)(p, STRING(DOWNLOAD_QUEUE));
    QueueManager::getInstance()->loadQueue();

    UpdateManager::cleanTempFiles();
}

void shutdown() {
    PluginManager::getInstance()->unloadPlugins();
    TimerManager::getInstance()->shutdown();
    HashManager::getInstance()->shutdown();
    ConnectionManager::getInstance()->shutdown();
    WebServerManager::getInstance()->shutdown();
    MappingManager::getInstance()->close();
    BufferedSocket::waitShutdown();
    
    QueueManager::getInstance()->saveQueue(true);
    SettingsManager::getInstance()->save();

    UpdateManager::deleteInstance();
    HttpManager::deleteInstance();
    PluginApiImpl::shutdown();
    PluginManager::deleteInstance();
    DetectionManager::deleteInstance();
    WebServerManager::deleteInstance();
    DebugManager::deleteInstance();
    MappingManager::deleteInstance();
    ConnectivityManager::deleteInstance();
    DHT::deleteInstance();
    ADLSearchManager::deleteInstance();
    RawManager::deleteInstance();
    FinishedManager::deleteInstance();
    ShareManager::deleteInstance();
    CryptoManager::deleteInstance();
    ThrottleManager::deleteInstance();
    DownloadManager::deleteInstance();
    UploadManager::deleteInstance();
    QueueManager::deleteInstance();
    ConnectionManager::deleteInstance();
    SearchManager::deleteInstance();
    FavoriteManager::deleteInstance();
    ClientManager::deleteInstance();
    HashManager::deleteInstance();
    LogManager::deleteInstance();
    TimerManager::deleteInstance();
    SettingsManager::deleteInstance();
    ResourceManager::deleteInstance();

#ifdef _WIN32
    ::WSACleanup();
#endif
}

} // namespace dcpp

单件类的生命周期很长,一般与应用程序生命周期一样长,适合做模块划分,MVC 模式作用在单件类上,有时间更新监听者,如果是生命周期很短的小类,那么 MVC 模式监听的作用就不大。

发表评论

电子邮件地址不会被公开。 必填项已用*标注