| 订阅 | 在线投稿
分享
 
 
 

[翻译学习]事件通知: C++中的消息传递

来源:互联网网民  宽屏版  评论
2006-01-09 07:43:03

事件通知: C++中的消息传递

PlugwareSolutionsLtd

Notifiers make anonymous communication between objects in a system possible. Because they are anonymous, objects communicating have no knowledge of one another and therefore are independent of the object they are communicating with. They are also easy to understand, providing a seamless migration as new developers are introduced to a project. Other languages (notably SmallTalk) have this feature built in; C++ gives users the freedom to create their own.

事件通知使得系统中各对象间可以实现匿名通信。因为是匿名的,通信的每个对象都不知道其他对象的相关信息,因此通信的过程依赖于其在与谁进行通信。很容易理解,工程需要能够让开发者很容易进行移植。其他语言(尤其是SmallTalk)内建了这种特性,C++提供给了用户更大的自由来自己创建这项功能。

Design [url=mhtml:file://C:Documents%20and%20SettingsAdministratorMy%20DocumentsCodeGuru%20Generic%20Notifiers%20Message%20Passing%20in%20C++.mht!http://www.codeguru.com/RealMedia/ads/Creatives/default/empty.gif]

设计

Those interested in sending messages to subscribers do so through the notifier class methods; those interested in receiving messages implement the subscriber interface and register with the appropriate notifier. The notifier is responsible for message routing; for each type of message, a different notifier exists. Notifier makes it possible to uncouple senders and receivers (subscribers) of messages.

那些需要发送消息给关注者的对象通过事件通知类来实现这项操作,而那些需要接收消息的对象则需要完成接收消息的接口,并向适当的通知类注册。事件通知类负责消息的路由,对于每种不同类型的消息存在有一种不同的通知。事件通知类对于每一种消息能够区分谁是发送者,谁是接收者。

[翻译学习]事件通知: C++中的消息传递

图 1. 事件通知协作图

A Thread-Safe Notifier

一个线程安全的事件通知类

The notifier must fulfill three requirements:

simple

extensible

thread-safe

一个事件通知类必须满足三个要素:

简单

可扩展

线程安全

Simplicity is a vague term. I choose to define it in terms of complexity—the less complex a piece of code is, the easier it is to maintain, explain, optimize and test. The less complex a piece of code is, the simpler it is. Convenient?

简单是一个比较含混的概念,我的意思是在复杂度方面――代码的复杂度越低,其维护、说明、优化及测试就越容易。代码的复杂度越低,他就越简单。

Extensibility is a difficult metric to gauge. Although there are many definitions, the common theme amongst them is the ability to add and incorporate new functionality, or technical advances, into an existing code base. My ideal notion of extensibility is code that allows me to maintain it over time without major work to the original code. Templates make this possible by parameterizing key types and allowing one to work with those outside of the implementation.

可扩展性是一个比较难衡量的标准。虽然对于可扩展性存在有非常多的定义,而这些定义的共同点就是能够添加及合成新的函数,或者基于已有的代码能够进行技术上的升级。我对可扩展性的看法是在我后来对代码进行维护的时候不需要做非常大量的工作。模板技术因为可以使用参数化的类型使得其扩展性可以非常的好。

Thread safety relates to the fact that clients using a notifier in a multithreaded environment should not have to be concerned with thread synchronization issues—subscribers will need to, but the notifier should not.

线程安全也就是要使客户程序在多线程环境中使用此通知机制的时候不需要关心线程同步的问题――事件关注者需要,而事件通知者不需要。

The notifier has the following responsibilities:

registering subscribers

unregistering subscribers

notifying subscribers

事件通知者有如下的职责:

注册事件关注者

注销事件关注者

通知事件关注者

To do so, a subscriber map is maintained. The subscriber serves as the key, a flag indicating whether or not the subscriber is registered serves as the data. A flag is used so that unregistration does not affect (remove an entry from) the subscription map. This is important if subscribers choose to unsubscribe during synchronous notification.

为了实现这些功能,我们需要维护一个事件关注者映射(map)。以关注者对象作为这个映射的键值(key),一个指示是否有关注者已注册的标志值作为数据(data)。因为使用了这个标志,所以在注销事件关注者时不需要从关注者映射中移除此数据。这在对事件关注者进行同步通知的情况下是很重要的。

Upon registration, the subscriber is inserted into the subscriber map; if it already exists, a check is made to determine whether or not it has been unregistered. If the subscriber has been flagged for unregistration but has not been removed from the subscriber map, its state is reset to 'registered.'

在注册事件关注者的时候,这个新的关注者将被插入到关注者映射中。如果此关注者已存在于映射中,则检查此关注者是否已被标记为未注册。如果此关注者已被标记为未注册,而又未从映射中删除,则此关注者的状态将被重新置为’已注册’。

Notification involves obtaining a lock to the critical section, iterating through the map, and notifying the subscribers. Any subscriber flagged as unregistered will not be notified. Once subscribers have been notified, all subscribers flagged as unregistered from the subscriber map are removed.

事件通知首先会获取一个临界区锁,然后遍历关注者映射,对所有的关注者发送事件通知消息。所有被标记为未注册的关注者都不会被通知。一旦对关注者进行了一次消息通知,映射中所有被标记为未注册的关注者都会被删除掉。

Unregistration involves flagging the subscriber for unregistration. The next time notification occurs, the subscriber will be removed from the map.

注销使得该关注者被标记为未注册。当下一次进行消息通知的时候,该关注者会被删除掉。

Asynchronous Notification

异步通知

Asynchronous notification is straightforward using the thread pool created in Windows Thread Pooling. A work unit is defined as one that, while processing, notifies subscribers of the event.

异步通知直接使用Windows Thread Pooling中创建的线程池来实现。异步通知能够在处理的同时通知关注者有事件发生。

void async_notifier<T_state>::process() {

notifier<T_state>::instance().notify(m_state);

}

That is all there is to it!

这就是异步通知所做的所有事情。

Demonstration

示例

Our need for notifiers arose when we created a service that starts a socket listening. While waiting for incoming connections, the calling thread sleeps as it awaits notification:

我们对事件通知机制的需求来源于当我们创建一个用来监听连接套接字的服务时。被调用线程在等待连接到来的时候一直处于睡眠状态,直到接收到了一个事件通知。

// wait for accept

WSANETWORKEVENTS c_events = {0};

wait_for_event(e_event_accept, c_events, INFINITE);

// accept

SOCKADDR_IN sa_client = {0};

int n_sa = sizeof(sa_client);

smart_socket_handle sp_conn(accept((SOCKADDR*)&c_sa_client, n_sa));

If the service is to shut down cleanly, we need a way to signal the waiting socket to shut down. Moreover, there were several objects that required notification of the service shutdown. Notifiers turned out to be the best solution because we could quickly bind them to the shutdown event.

如果我们要完全地关闭这个服务,则我们需要一种方法来通知这个监听套接字让其也关闭。而且,所有那些关注此事件的对象也要被关闭。事件通知成为了最好的解决方法,因为我们可以简单地将其与关闭事件相绑定。

namespace events

{

enum service

{

service_start,

service_pause,

service_resume,

service_stop

};

typedef notifier<service> service_notifier;

events::service is the notification data; publishers simply need to inform our subscriber:

event::service是通知的数据,发布者需要通知给关注者的事件。

template<class T_connection>

struct listener :

socket,

core::work_unit,

subscriber<events::service>

of the event:

事件如下:

void on_shutdown()

{

// notify our listener(s) to stop

events::service_notifier::instance()->

notify(events::service_stop);

The handler in listener takes care of the dirty work:

监听句柄对这些消息要进行处理:

void on_notify(const state& event)

{

switch (event)

{

case events::service_start:

case events::service_resume:

start_listening();

break;

case events::service_stop:

case events::service_pause:

stop_listening();

break;

default: throw std::invalid_argument("listener::on_notify");

}

}

Using the Code

使用这些代码

To create a subscriber, derive your class from subscriber. You must implement the on_notify method.

要创建一个事件关注者,你需要从subscriber派生一个自己的类,并且必须实现on_notify方法。

struct work : core::work_unit, subscriber<events>

{

work();

void process();

void on_notify(const events& e);

bool m_work;

double m_data;

};

To receive notification of events, call subscriber::subscribe.

当要接收通知事件的时候,调用subscriber::subscribe.

void work::process()

{

// We subscribe in process because we are created during

// notification. If we were to subscribe during work::work

// we would be added to the notifier map and spawning would

// never cease to end!

subscribe();

To discontinue notification of events, call subscriber::unsubscribe.

当要停止事件通知时,调用subscriber::unsubscribe.

void work::on_notify(const events& e)

{

switch (e)

{

case die:

unsubscribe();

std::cout << "dying: " << this << std::endl;

m_work = false;

break;

To synchronously notify subscribers, call notifier::notify.

当要对事件的关注者进行同步通知时,调用notifier::notify.

void input::process()

{

std::cout << "(S)pawn/(D)ie: ";

switch (::tolower(::getch()))

{

// die and leave

case 'd':

notifier<events>::instance().notify(die);

To asynchronously notify subscribers, create and queue an instance of async_notifier.

当要对事件进行的接收者进行异步通知时,创建一个async_notifier的实例。

// and then kill it asynchronously

global::thread_pool::instance().queue_request(

new async_notifier<events>(die));

About the Demo Project

关于示例工程

The demo project models the scenario above. Workers that perform work until notified of shutdown are created. Upon notification, the workers die and the system gracefully shuts down. More workers may be spawned to simulate a heavy load on the code.

这个示例工程模拟了上面的假设。首先创建一个工作任务,该任务直到接收到一个关闭事件,收到事件通知时,该任务结束,系统完全地关闭。可能还需要创建更多的任务来模块高负载下代码运行的情况。

The thread pool is initialized.

Work is queued and is killed asynchronously using the notifier.

A spawn work unit and an input work unit are queued.

The main thread waits on an event signal when the input work unit dies.

线程池初始化

任务进入队列,使用异步消息发送机制

一个派生任务单元和一个输入任务单元被加入队列

主线程等待输入任务单元结束时产生的事件信号

There are some important considerations to keep in mind when going through the tested. Our main thread waits on an event that is signaled when input is dying. Depending on the amount of work pending, exiting the application immediately could be disastrous.

在我们进入到示例工作之前还有几件重要的事件要说明一下。我们的主线程一直等待输入任务单元结束时产生的事件信号。根据未完成的工作数量来看,程序立即结束的结果可能是损失惨重的。

To understand, consider the following scenario: The user has instructed input to die; input synchronously notifies all subscribers to die and signals our main thread to die.

需要理解的是,我们假设的情况是这样的:用户指示输入任务单元结束,然后输入任务单元同步通知所有的事件关注者结束,然后发送结束信号使得主线程结束。

For the sake of argument, assume there are 100 work units queued and pending. Threads in the thread pool will not die until all work is extinguished and they have received the shutdown flag.

假设有100个工作单元在队列中并且还未执行,线程池中的线程将将会一直等到所有的工作都结束,并且他们也收到了关闭标志后才会结束。

Meanwhile, main is unwinding and destroys the process heap. Our work units are finally released as the last bit of work trickles out. Without a heap, what happens?

同时,主线程也会销毁进程的堆信息。当然,我们的工作单元已做完最后的一点工作,也已完全释放了资源。如果没有了堆信息,将会发生什么情况呢?

To keep things simple, I pause the main thread during which the machinery gently shuts down. More elaborate synchronization mechanisms may be employed. However, during shutdown I am not encumbered by performance requirements and prefer the simple implementation. Your mileage may vary.

为了让事情更简单一点,在程序运行的过程中我使用了一些sleep调用使得程序能够慢慢地结束,这也使得同步机制能够表现地更详细。然后,在关闭的过程中,我没有满足性能上的需求,也没能使得其更简单地执行。可能你需要等待比较长的时间。

Conclusions

总结

Notifiers are a great way of coupling parts of a system that are interested in state changes but don't want to be bound to one another. Templates provide a natural mechanism for reuse of the notification subsystem with different state data. Synchronous and asynchronous notification provides the flexibility required for all but the most esoteric situations.

事件通知是一种很好的方法,可用来将对系统中各部分状态变化感兴趣但是又不想限制在一起的各部分分开。模块提供了一种自然的解决方法,可使得事件通知子系统能用于不同的状态数据。而同步和异步通知则为不同的解决方法提供了非常大的弹性。

Whether you choose to use a freely available implementation or to roll your own, notifiers make building complex solutions in any language more manageable.

不管你是使用一种已存在的免费的实现方法还是自己来构建,通知机制都可在多种不同的语言中构造综合的解决方案。

Happy Coding!

享受编程的快乐!

 
特别声明:以上内容(如有图片或视频亦包括在内)为网络用户发布,本站仅提供信息存储服务。
 
 
事件通知: C++中的消息传递 PlugwareSolutionsLtd Notifiers make anonymous communication between objects in a system possible. Because they are anonymous, objects communicating have no knowledge of one another and therefore are independent of the object they are communicating with. They are also easy to understand, providing a seamless migration as new developers are introduced to a project. Other languages (notably SmallTalk) have this feature built in; C++ gives users the freedom to create their own. 事件通知使得系统中各对象间可以实现匿名通信。因为是匿名的,通信的每个对象都不知道其他对象的相关信息,因此通信的过程依赖于其在与谁进行通信。很容易理解,工程需要能够让开发者很容易进行移植。其他语言(尤其是SmallTalk)内建了这种特性,C++提供给了用户更大的自由来自己创建这项功能。 Design[url=http://www.codeguru.com/RealMedia/ads/click_lx.cgi/ew/ewsoftware/www.codeguru.com/Cpp/Cpp/cpp_mfc/templates/article/c7077/i/1783112423/336x280/default/empty.gif/33643930636665653430656365383930] [url=mhtml:file://C:\Documents%20and%20Settings\Administrator\My%20Documents\CodeGuru%20Generic%20Notifiers%20Message%20Passing%20in%20C++.mht!http://www.codeguru.com/RealMedia/ads/Creatives/default/empty.gif][/url] 设计 Those interested in sending messages to subscribers do so through the notifier class methods; those interested in receiving messages implement the subscriber interface and register with the appropriate notifier. The notifier is responsible for message routing; for each type of message, a different notifier exists. Notifier makes it possible to uncouple senders and receivers (subscribers) of messages. 那些需要发送消息给关注者的对象通过事件通知类来实现这项操作,而那些需要接收消息的对象则需要完成接收消息的接口,并向适当的通知类注册。事件通知类负责消息的路由,对于每种不同类型的消息存在有一种不同的通知。事件通知类对于每一种消息能够区分谁是发送者,谁是接收者。 [img]http://dev.csdn.net/C:\Documents[/img] 图 1. 事件通知协作图 A Thread-Safe Notifier 一个线程安全的事件通知类   The notifier must fulfill three requirements: simple extensible thread-safe 一个事件通知类必须满足三个要素: 简单 可扩展 线程安全 Simplicity is a vague term. I choose to define it in terms of complexity—the less complex a piece of code is, the easier it is to maintain, explain, optimize and test. The less complex a piece of code is, the simpler it is. Convenient? 简单是一个比较含混的概念,我的意思是在复杂度方面――代码的复杂度越低,其维护、说明、优化及测试就越容易。代码的复杂度越低,他就越简单。 Extensibility is a difficult metric to gauge. Although there are many definitions, the common theme amongst them is the ability to add and incorporate new functionality, or technical advances, into an existing code base. My ideal notion of extensibility is code that allows me to maintain it over time without major work to the original code. Templates make this possible by parameterizing key types and allowing one to work with those outside of the implementation. 可扩展性是一个比较难衡量的标准。虽然对于可扩展性存在有非常多的定义,而这些定义的共同点就是能够添加及合成新的函数,或者基于已有的代码能够进行技术上的升级。我对可扩展性的看法是在我后来对代码进行维护的时候不需要做非常大量的工作。模板技术因为可以使用参数化的类型使得其扩展性可以非常的好。 Thread safety relates to the fact that clients using a notifier in a multithreaded environment should not have to be concerned with thread synchronization issues—subscribers will need to, but the notifier should not. 线程安全也就是要使客户程序在多线程环境中使用此通知机制的时候不需要关心线程同步的问题――事件关注者需要,而事件通知者不需要。   The notifier has the following responsibilities: registering subscribers unregistering subscribers notifying subscribers 事件通知者有如下的职责: 注册事件关注者 注销事件关注者 通知事件关注者 To do so, a subscriber map is maintained. The subscriber serves as the key, a flag indicating whether or not the subscriber is registered serves as the data. A flag is used so that unregistration does not affect (remove an entry from) the subscription map. This is important if subscribers choose to unsubscribe during synchronous notification. 为了实现这些功能,我们需要维护一个事件关注者映射(map)。以关注者对象作为这个映射的键值(key),一个指示是否有关注者已注册的标志值作为数据(data)。因为使用了这个标志,所以在注销事件关注者时不需要从关注者映射中移除此数据。这在对事件关注者进行同步通知的情况下是很重要的。 Upon registration, the subscriber is inserted into the subscriber map; if it already exists, a check is made to determine whether or not it has been unregistered. If the subscriber has been flagged for unregistration but has not been removed from the subscriber map, its state is reset to 'registered.' 在注册事件关注者的时候,这个新的关注者将被插入到关注者映射中。如果此关注者已存在于映射中,则检查此关注者是否已被标记为未注册。如果此关注者已被标记为未注册,而又未从映射中删除,则此关注者的状态将被重新置为’已注册’。 Notification involves obtaining a lock to the critical section, iterating through the map, and notifying the subscribers. Any subscriber flagged as unregistered will not be notified. Once subscribers have been notified, all subscribers flagged as unregistered from the subscriber map are removed. 事件通知首先会获取一个临界区锁,然后遍历关注者映射,对所有的关注者发送事件通知消息。所有被标记为未注册的关注者都不会被通知。一旦对关注者进行了一次消息通知,映射中所有被标记为未注册的关注者都会被删除掉。 Unregistration involves flagging the subscriber for unregistration. The next time notification occurs, the subscriber will be removed from the map. 注销使得该关注者被标记为未注册。当下一次进行消息通知的时候,该关注者会被删除掉。 Asynchronous Notification 异步通知 Asynchronous notification is straightforward using the thread pool created in Windows Thread Pooling. A work unit is defined as one that, while processing, notifies subscribers of the event. 异步通知直接使用Windows Thread Pooling中创建的线程池来实现。异步通知能够在处理的同时通知关注者有事件发生。 void async_notifier<T_state>::process() { notifier<T_state>::instance().notify(m_state); } That is all there is to it! 这就是异步通知所做的所有事情。 Demonstration 示例 Our need for notifiers arose when we created a service that starts a socket listening. While waiting for incoming connections, the calling thread sleeps as it awaits notification: 我们对事件通知机制的需求来源于当我们创建一个用来监听连接套接字的服务时。被调用线程在等待连接到来的时候一直处于睡眠状态,直到接收到了一个事件通知。 // wait for accept WSANETWORKEVENTS c_events = {0}; wait_for_event(e_event_accept, c_events, INFINITE); // accept SOCKADDR_IN sa_client = {0}; int n_sa = sizeof(sa_client); smart_socket_handle sp_conn(accept((SOCKADDR*)&c_sa_client, n_sa)); If the service is to shut down cleanly, we need a way to signal the waiting socket to shut down. Moreover, there were several objects that required notification of the service shutdown. Notifiers turned out to be the best solution because we could quickly bind them to the shutdown event. 如果我们要完全地关闭这个服务,则我们需要一种方法来通知这个监听套接字让其也关闭。而且,所有那些关注此事件的对象也要被关闭。事件通知成为了最好的解决方法,因为我们可以简单地将其与关闭事件相绑定。 namespace events { enum service { service_start, service_pause, service_resume, service_stop }; typedef notifier<service> service_notifier; events::service is the notification data; publishers simply need to inform our subscriber: event::service是通知的数据,发布者需要通知给关注者的事件。 template<class T_connection> struct listener : socket, core::work_unit, subscriber<events::service> of the event: 事件如下: void on_shutdown() { // notify our listener(s) to stop events::service_notifier::instance()-> notify(events::service_stop); The handler in listener takes care of the dirty work: 监听句柄对这些消息要进行处理: void on_notify(const state& event) { switch (event) { case events::service_start: case events::service_resume: start_listening(); break; case events::service_stop: case events::service_pause: stop_listening(); break; default: throw std::invalid_argument("listener::on_notify"); } } Using the Code 使用这些代码 To create a subscriber, derive your class from subscriber. You must implement the on_notify method. 要创建一个事件关注者,你需要从subscriber派生一个自己的类,并且必须实现on_notify方法。 struct work : core::work_unit, subscriber<events> { work(); void process(); void on_notify(const events& e); bool m_work; double m_data; }; To receive notification of events, call subscriber::subscribe. 当要接收通知事件的时候,调用subscriber::subscribe. void work::process() { // We subscribe in process because we are created during // notification. If we were to subscribe during work::work // we would be added to the notifier map and spawning would // never cease to end! subscribe(); To discontinue notification of events, call subscriber::unsubscribe. 当要停止事件通知时,调用subscriber::unsubscribe. void work::on_notify(const events& e) { switch (e) { case die: unsubscribe(); std::cout << "dying: " << this << std::endl; m_work = false; break; To synchronously notify subscribers, call notifier::notify. 当要对事件的关注者进行同步通知时,调用notifier::notify. void input::process() { std::cout << "(S)pawn/(D)ie: "; switch (::tolower(::getch())) { // die and leave case 'd': notifier<events>::instance().notify(die); To asynchronously notify subscribers, create and queue an instance of async_notifier. 当要对事件进行的接收者进行异步通知时,创建一个async_notifier的实例。 // and then kill it asynchronously global::thread_pool::instance().queue_request( new async_notifier<events>(die)); About the Demo Project 关于示例工程 The demo project models the scenario above. Workers that perform work until notified of shutdown are created. Upon notification, the workers die and the system gracefully shuts down. More workers may be spawned to simulate a heavy load on the code. 这个示例工程模拟了上面的假设。首先创建一个工作任务,该任务直到接收到一个关闭事件,收到事件通知时,该任务结束,系统完全地关闭。可能还需要创建更多的任务来模块高负载下代码运行的情况。 The thread pool is initialized. Work is queued and is killed asynchronously using the notifier. A spawn work unit and an input work unit are queued. The main thread waits on an event signal when the input work unit dies. 线程池初始化 任务进入队列,使用异步消息发送机制 一个派生任务单元和一个输入任务单元被加入队列 主线程等待输入任务单元结束时产生的事件信号 There are some important considerations to keep in mind when going through the tested. Our main thread waits on an event that is signaled when input is dying. Depending on the amount of work pending, exiting the application immediately could be disastrous. 在我们进入到示例工作之前还有几件重要的事件要说明一下。我们的主线程一直等待输入任务单元结束时产生的事件信号。根据未完成的工作数量来看,程序立即结束的结果可能是损失惨重的。 To understand, consider the following scenario: The user has instructed input to die; input synchronously notifies all subscribers to die and signals our main thread to die. 需要理解的是,我们假设的情况是这样的:用户指示输入任务单元结束,然后输入任务单元同步通知所有的事件关注者结束,然后发送结束信号使得主线程结束。 For the sake of argument, assume there are 100 work units queued and pending. Threads in the thread pool will not die until all work is extinguished and they have received the shutdown flag. 假设有100个工作单元在队列中并且还未执行,线程池中的线程将将会一直等到所有的工作都结束,并且他们也收到了关闭标志后才会结束。 Meanwhile, main is unwinding and destroys the process heap. Our work units are finally released as the last bit of work trickles out. Without a heap, what happens? 同时,主线程也会销毁进程的堆信息。当然,我们的工作单元已做完最后的一点工作,也已完全释放了资源。如果没有了堆信息,将会发生什么情况呢? To keep things simple, I pause the main thread during which the machinery gently shuts down. More elaborate synchronization mechanisms may be employed. However, during shutdown I am not encumbered by performance requirements and prefer the simple implementation. Your mileage may vary. 为了让事情更简单一点,在程序运行的过程中我使用了一些sleep调用使得程序能够慢慢地结束,这也使得同步机制能够表现地更详细。然后,在关闭的过程中,我没有满足性能上的需求,也没能使得其更简单地执行。可能你需要等待比较长的时间。 Conclusions 总结 Notifiers are a great way of coupling parts of a system that are interested in state changes but don't want to be bound to one another. Templates provide a natural mechanism for reuse of the notification subsystem with different state data. Synchronous and asynchronous notification provides the flexibility required for all but the most esoteric situations. 事件通知是一种很好的方法,可用来将对系统中各部分状态变化感兴趣但是又不想限制在一起的各部分分开。模块提供了一种自然的解决方法,可使得事件通知子系统能用于不同的状态数据。而同步和异步通知则为不同的解决方法提供了非常大的弹性。 Whether you choose to use a freely available implementation or to roll your own, notifiers make building complex solutions in any language more manageable. 不管你是使用一种已存在的免费的实现方法还是自己来构建,通知机制都可在多种不同的语言中构造综合的解决方案。 Happy Coding! 享受编程的快乐!
󰈣󰈤
 
 
 
>>返回首页<<
 
 热帖排行
 
 
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
©2005- 王朝网络 版权所有