?#include ”ace/Reactor.h”
#include ”ace/Svc_Handler.h”
#include ”ace/Acceptor.h”
#include ”ace/Synch.h”
#include ”ace/SOCK_Acceptor.h”
#include ”ace/Thread.h”
//Add our own Reactor singleton
typedef ACE_Singleton<ACE_Reactor,ACE_Null_Mutex> Reactor;
//Create an Acceptor
typedef ACE_Acceptor<MyServiceHandler,ACE_SOCK_ACCEPTOR> Acceptor;
//Create a Connector
typedef ACE_Connector<MyServiceHandler,ACE_SOCK_CONNECTOR> Connector;
class MyServiceHandler:
public ACE_Svc_Handler<ACE_SOCK_STREAM,ACE_NULL_SYNCH>
{
public:
//Used by the two threads “globally” to determine their peer stream
static ACE_SOCK_Stream* Peer;
//Thread ID used to identify the threads
ACE_thread_t t_id;
int open(void*)
{
cout<<”Acceptor: received new connection”<<endl;
//Register with the reactor to remember this handle
Reactor::instance()
->register_handler(this,ACE_Event_Handler::READ_MASK);
//Determine the peer stream and record it globally
MyServiceHandler::Peer=&peer();
//Spawn new thread to send string every second
ACE_Thread::spawn((ACE_THR_FUNC)send_data,0,THR_NEW_LWP,&t_id);
//keep the service handler registered by returning 0 to the
//reactor
return 0;
}
static void* send_data(void*)
{
while(1)
{
cout<<”>>Hello World”<<endl;
Peer->send_n(”Hello World”,sizeof(”Hello World”));
//Go to sleep for a second before sending again
ACE_OS::sleep(1);
}
return 0;
}
int handle_input(ACE_HANDLE)
{
char* data= new char[12];
//Check if peer aborted the connection
if(Peer.recv_n(data,12)==0)
{
cout<<”P(pán)eer probably aborted connection”);
ACE_Thread::cancel(t_id); //kill sending thread ..
return -1; //de-register from the Reactor.
}
//Show what you got..
cout<<”<< %s\n”,data”<<endl;
//keep yourself registered
return 0;
}
};
//Global stream identifier used by both threads
ACE_SOCK_Stream * MyServiceHandler::Peer=0;
void main_accept()
{
ACE_INET_Addr addr(PORT_NO);
Acceptor myacceptor(addr,Reactor::instance());
while(1)
Reactor::instance()->handle_events();
return 0;
}
void main_connect()
{
ACE_INET_Addr addr(PORT_NO,HOSTNAME);
Connector myconnector;
myconnector.connect(my_svc_handler,addr);
while(1)
Reactor::instance()->handle_events();
}
int main(int argc, char* argv[])
{
// Use ACE_Get_Opt to parse and obtain arguments and then call the
// appropriate function for accept or connect.
...
}
。因?yàn)?/font>svc()是非靜態(tài)函數(shù),它可以調(diào)用任何對(duì)象實(shí)例專有的數(shù)據(jù)或成員函數(shù)。ACE對(duì)程序員隱藏了該機(jī)制的所有實(shí)現(xiàn)細(xì)節(jié)。activate()方法有著非常多的用途,它允許程序員創(chuàng)建多個(gè)線程,所有這些線程都使用svc()方法作為它們的入口。還可以設(shè)置線程優(yōu)先級(jí)、句柄、名字,等等。activate()方法的原型是:?
// = Active object activation method.
virtual int activate (long flags = THR_NEW_LWP,
int n_threads = 1,
int force_active = 0,
long priority = ACE_DEFAULT_THREAD_PRIORITY,
int grp_id = -1,
ACE_Task_Base *task = 0,
ACE_hthread_t thread_handles[] = 0,
void *stack[] = 0,
size_t stack_size[] = 0,
ACE_thread_t thread_names[] = 0);
? 第一個(gè)參數(shù)
flags描述將要?jiǎng)?chuàng)建的線程所希望具有的屬性。在線程一章里有詳細(xì)描述。可用的標(biāo)志有:?
THR_CANCEL_DISABLE, THR_CANCEL_ENABLE, THR_CANCEL_DEFERRED,
THR_CANCEL_ASYNCHRONOUS, THR_BOUND, THR_NEW_LWP, THR_DETACHED,
THR_SUSPENDED, THR_DAEMON, THR_JOINABLE, THR_SCHED_FIFO,
THR_SCHED_RR, THR_SCHED_DEFAULT
? 第二個(gè)參數(shù)
n_threads指定要?jiǎng)?chuàng)建的線程的數(shù)目。第三個(gè)參數(shù)force_active用于指定是否應(yīng)該創(chuàng)建新線程,即使activate()方法已在先前被調(diào)用過(guò)、因而任務(wù)或服務(wù)處理器已經(jīng)在運(yùn)行多個(gè)線程。如果此參數(shù)被設(shè)為false(0),且如果activate()是再次被調(diào)用,該方法就會(huì)設(shè)置失敗代碼,而不會(huì)生成更多的線程。 第四個(gè)參數(shù)用于設(shè)置運(yùn)行線程的優(yōu)先級(jí)。缺省情況下,或優(yōu)先級(jí)被設(shè)為
ACE_DEFAULT_THREAD_PRIORITY,方法會(huì)使用給定的調(diào)度策略(在flags中指定,例如,THR_SCHED_DEFAULT)的“適當(dāng)”優(yōu)先級(jí)。這個(gè)值是動(dòng)態(tài)計(jì)算的,并且是在給定策略的最低和最高優(yōu)先級(jí)之間。如果顯式地給定一個(gè)值,這個(gè)值就會(huì)被使用。注意實(shí)際的優(yōu)先級(jí)值極大地依賴于實(shí)現(xiàn),最好不要直接使用。在線程一章中,可讀到更多有關(guān)線程優(yōu)先級(jí)的內(nèi)容。 還可以傳入將要?jiǎng)?chuàng)建的線程的線程句柄、線程名和棧空間,以在線程創(chuàng)建過(guò)程中使用。如果它們被設(shè)置為
NULL,它們就不會(huì)被使用。但是如果要使用activate創(chuàng)建多個(gè)線程,就必須傳入線程的名字或句柄,才能有效地對(duì)它們進(jìn)行使用。 下面的例子可以幫助你進(jìn)一步理解
activate方法的使用:?
?例
7-6?#include ”ace/Reactor.h”
#include ”ace/Svc_Handler.h”
#include ”ace/Acceptor.h”
#include ”ace/Synch.h”
#include ”ace/SOCK_Acceptor.h”
class MyServiceHandler; //forward declaration
typedef ACE_Singleton<ACE_Reactor,ACE_Null_Mutex> Reactor;
typedef ACE_Acceptor<MyServiceHandler,ACE_SOCK_ACCEPTOR> Acceptor;
class MyServiceHandler:
public ACE_Svc_Handler<ACE_SOCK_STREAM,ACE_MT_SYNCH>
{
// The two thread names are kept here
ACE_thread_t thread_names[2];
public:
int open(void*)
{
ACE_DEBUG((LM_DEBUG, ”Acceptor: received new connection \n”));
//Register with the reactor to remember this handler..
Reactor::instance()
->register_handler(this,ACE_Event_Handler::READ_MASK);
ACE_DEBUG((LM_DEBUG,”Acceptor: ThreadID:(%t) open\n”));
//Create two new threads to create and send messages to the
//remote machine.
activate(THR_NEW_LWP,
2, //2 new threads
0, //force active false, if already created don’t try again.
ACE_DEFAULT_THREAD_PRIORITY,//Use default thread priority
-1,
this,//Which ACE_Task object to create? In this case this one.
0,// don’t care about thread handles used
0,// don’t care about where stacks are created
0,//don’t care about stack sizes
thread_names); // keep identifiers in thread_names
//keep the service handler registered with the acceptor.
return 0;
}
void send_message1(void)
{
//Send message type 1
ACE_DEBUG((LM_DEBUG,”(%t)Sending message::>>”));
//Send the data to the remote peer
ACE_DEBUG((LM_DEBUG,”Sent message1”));
peer().send_n(”Message1”,LENGTH_MSG_1);
} //end send_message1
int send_message2(void)
{
//Send message type 1
ACE_DEBUG((LM_DEBUG,”(%t)Sending message::>>”));
//Send the data to the remote peer
ACE_DEBUG((LM_DEBUG,”Sent Message2”));
peer().send_n(”Message2”,LENGTH_MSG_2);
}//end send_message_2
int svc(void)
{
ACE_DEBUG( (LM_DEBUG,”(%t) Svc thread \n”));
if(ACE_Thread::self()== thread_names[0])
while(1) send_message1(); //send message 1s forever
else
while(1) send_message2(); //send message 2s forever
return 0; // keep the com, piler happy.
}
int handle_input(ACE_HANDLE)
{
ACE_DEBUG((LM_DEBUG,”(%t) handle_input ::”));
char* data= new char[13];
//Check if peer aborted the connection
if(peer().recv_n(data,12)==0)
{
printf(”P(pán)eer probably aborted connection”);
return -1; //de-register from the Reactor.
}
//Show what you got..
ACE_OS::printf(”<< %s\n”,data);
//keep yourself registered
return 0;
}
};
int main(int argc, char* argv[])
{
ACE_INET_Addr addr(10101);
ACE_DEBUG((LM_DEBUG,”Thread: (%t) main”));
//Prepare to accept connections
Acceptor myacceptor(addr,Reactor::instance());
// wait for something to happen.
while(1)
Reactor::instance()->handle_events();
return 0;
}
?在此例中,服務(wù)處理器在它的
open()方法中被登記到反應(yīng)堆上,隨后程序調(diào)用activate()來(lái)創(chuàng)建2個(gè)線程。線程的名字被記錄下來(lái),以便在它們調(diào)用svc()例程時(shí),我們可以將它們區(qū)別開(kāi)。每個(gè)線程發(fā)送一條不同類型的消息給遠(yuǎn)地對(duì)端。注意在此例中,線程的創(chuàng)建是完全透明的。此外,因?yàn)槿肟谑瞧胀ǖ姆庆o態(tài)成員函數(shù),它不需要進(jìn)行“丑陋的”改動(dòng)來(lái)記住數(shù)據(jù)成員,比如說(shuō)對(duì)端流。無(wú)論何時(shí)只要我們需要,我們就可以簡(jiǎn)單地調(diào)用成員函數(shù)peer()來(lái)獲取底層的流。7.3.1.5使用服務(wù)處理器中的消息隊(duì)列機(jī)制
的底層方法,因?yàn)樵?/font>ACE_Svc_Handler類中已對(duì)它們的大多數(shù)進(jìn)行了包裝。ACE_Message_Queue是用于使ACE_Message_Block進(jìn)隊(duì)或出隊(duì)的隊(duì)列。每個(gè)ACE_Message_Block都含有指向“引用計(jì)數(shù)”(reference-counted)的ACE_Data_Block的指針,ACE_Data_Block依次又指向存儲(chǔ)在塊中的實(shí)際數(shù)據(jù)(見(jiàn)“消息隊(duì)列”一章)。這使得ACE_Message_Block可以很容易地進(jìn)行數(shù)據(jù)共享。 ACE_Message_Block
的主要作用是進(jìn)行高效數(shù)據(jù)操作,而不帶來(lái)許多拷貝開(kāi)銷。每個(gè)消息塊都有一個(gè)讀指針和寫(xiě)指針。無(wú)論何時(shí)我們從塊中讀取時(shí),讀指針會(huì)在數(shù)據(jù)塊中向前增長(zhǎng)。類似地,當(dāng)我們向塊中寫(xiě)的時(shí)候,寫(xiě)指針也會(huì)向前移動(dòng),這很像在流類型系統(tǒng)中的情況。可以通過(guò)ACE_Message_Block的構(gòu)造器向它傳遞分配器,以用于分配內(nèi)存(有關(guān)Allocator的更多信息,參見(jiàn)“內(nèi)存管理”一章)。例如,可以使用ACE_Cached_Allocation_Strategy,它預(yù)先分配內(nèi)存并從內(nèi)存池中返回指針,而不是在需要的時(shí)候才從堆中分配內(nèi)存。這樣的功能在需要可預(yù)測(cè)的性能時(shí)十分有用,比如在實(shí)時(shí)系統(tǒng)中。 下面的例子演示怎樣使用消息隊(duì)列的一些功能:
例
7-7#include ”ace/Reactor.h”
#include ”ace/Svc_Handler.h”
#include ”ace/Acceptor.h”
#include ”ace/Synch.h”
#include ”ace/SOCK_Acceptor.h”
#include ”ace/Thread.h”
#define NETWORK_SPEED 3
class MyServiceHandler; //forward declaration
typedef ACE_Singleton<ACE_Reactor,ACE_Null_Mutex> Reactor;
typedef ACE_Acceptor<MyServiceHandler,ACE_SOCK_ACCEPTOR> Acceptor;
class MyServiceHandler:
public ACE_Svc_Handler<ACE_SOCK_STREAM,ACE_MT_SYNCH>{
// The message sender and creator threads are handled here.
ACE_thread_t thread_names[2];
public:
int open(void*)
{
ACE_DEBUG((LM_DEBUG, ”Acceptor: received new connection \n”));
//Register with the reactor to remember this handler..
Reactor::instance()
->register_handler(this,ACE_Event_Handler::READ_MASK);
ACE_DEBUG((LM_DEBUG,”Acceptor: ThreadID:(%t) open\n”));
//Create two new threads to create and send messages to the
//remote machine.
activate(THR_NEW_LWP,
2, //2 new threads
0,
ACE_DEFAULT_THREAD_PRIORITY,
-1,
this,
0,
0,
0,
thread_names); // identifiers in thread_handles
//keep the service handler registered with the acceptor.
return 0;
}
void send_message(void)
{
//Dequeue the message and send it off
ACE_DEBUG((LM_DEBUG,”(%t)Sending message::>>”));
//dequeue the message from the message queue
ACE_Message_Block *mb;
ACE_ASSERT(this->getq(mb)!=-1);
int length=mb->length();
char *data =mb->rd_ptr();
//Send the data to the remote peer
ACE_DEBUG((LM_DEBUG,”%s \n”,data,length));
peer().send_n(data,length);
//Simulate very SLOW network.
ACE_OS::sleep(NETWORK_SPEED);
//release the message block
mb->release();
} //end send_message
int construct_message(void)
{
// A very fast message creation algorithm
// would lead to the need for queuing messages..
// here. These messages are created and then sent
// using the SLOW send_message() routine which is
// running in a different thread so that the message
//construction thread isn’t blocked.
ACE_DEBUG((LM_DEBUG,”(%t)Constructing message::>> ”));
// Create a new message to send
ACE_Message_Block *mb;
char *data=”Hello Connector”;
ACE_NEW_RETURN (mb,ACE_Message_Block (16,//Message 16 bytes long
ACE_Message_Block::MB_DATA,//Set header to data
0,//No continuations.
data//The data we want to send
), 0);
mb->wr_ptr(16); //Set the write pointer.
// Enqueue the message into the message queue
// we COULD have done a timed wait for enqueuing in case
// someone else holds the lock to the queue so it doesn’t block
//forever..
ACE_ASSERT(this->putq(mb)!=-1);
ACE_DEBUG((LM_DEBUG,”Enqueued msg successfully\n”));
}
int svc(void)
{
ACE_DEBUG( (LM_DEBUG,”(%t) Svc thread \n”));
//call the message creator thread
if(ACE_Thread::self()== thread_names[0])
while(1) construct_message(); //create messages forever
else
while(1) send_message(); //send messages forever
return 0; // keep the compiler happy.
}
int handle_input(ACE_HANDLE)
{
ACE_DEBUG((LM_DEBUG,”(%t) handle_input ::”));
char* data= new char[13];
//Check if peer aborted the connection
if(peer().recv_n(data,12)==0)
{
printf(”P(pán)eer probably aborted connection”);
return -1; //de-register from the Reactor.
}
//Show what you got..
ACE_OS::printf(”<< %s\n”,data);
//keep yourself registered
return 0;
}
};
int main(int argc, char* argv[])
{
ACE_INET_Addr addr(10101);
ACE_DEBUG((LM_DEBUG,”Thread: (%t) main”));
//Prepare to accept connections
Acceptor myacceptor(addr,Reactor::instance());
// wait for something to happen.
while(1)
Reactor::instance()->handle_events();
return 0;
}
這個(gè)例子演示怎樣使用
putq()和getq()方法來(lái)在隊(duì)列中放入或取出消息塊。它還演示怎樣創(chuàng)建消息塊,隨后設(shè)置它的寫(xiě)指針,并根據(jù)它的讀指針進(jìn)行讀取。注意消息塊中的實(shí)際數(shù)據(jù)的起始位置由消息塊的讀指針指示。消息塊的length()成員函數(shù)返回在消息塊中存儲(chǔ)的底層數(shù)據(jù)的長(zhǎng)度,其中不包括ACE_Message_Block中用于管理目的的部分。另外,我們也顯示了怎樣使用release()方法來(lái)釋放消息塊(mb)。 要了解更多關(guān)于如何使用消息塊、數(shù)據(jù)塊或是消息隊(duì)列的信息,請(qǐng)閱讀此教程中有關(guān)“消息隊(duì)列”、
ASX框架和其他相關(guān)的部分。
7.4
接受器和連接器模式工作原理 接受器和連接器工廠(也就是
ACE_Connector和ACE_Acceptor)有著非常類似的運(yùn)行結(jié)構(gòu)。它們的工作可大致劃分為三個(gè)階段:
端點(diǎn)或連接初始化階段
服務(wù)初始化階段
服務(wù)處理階段
7.4.1 端點(diǎn)或連接初始化階段 在使用接受器的情況下,應(yīng)用級(jí)程序員可以調(diào)用
ACE_Acceptor工廠的open()方法,或是它的缺省構(gòu)造器(它實(shí)際上會(huì)調(diào)用open()方法),來(lái)開(kāi)始被動(dòng)偵聽(tīng)連接。當(dāng)接受器工廠的open()方法被調(diào)用時(shí),如果反應(yīng)堆單體還沒(méi)有被實(shí)例化,open()方法就首先對(duì)其進(jìn)行實(shí)例化。隨后它調(diào)用底層具體接受器的open()方法。于是具體接受器會(huì)完成必要的初始化來(lái)偵聽(tīng)連接。例如,在使用ACE_SOCK_Acceptor的情況中,它打開(kāi)socket,將其綁定到用戶想要在其上偵聽(tīng)新連接的端口和地址上。在綁定端口后,它將會(huì)發(fā)出偵聽(tīng)調(diào)用。open方法隨后將接受器工廠登記到反應(yīng)堆。因而在接收到任何到來(lái)的連接請(qǐng)求時(shí),反應(yīng)堆會(huì)自動(dòng)回調(diào)接受器工廠的handle_input()方法。注意正是因?yàn)檫@一原因,接受器工廠才從ACE_Event_Handler類層次派生;這樣它才可以響應(yīng)ACCEPT事件,并被反應(yīng)堆自動(dòng)回調(diào)。 在使用連接器的情況中,應(yīng)用程序員調(diào)用連接器工廠的
connect()方法或connect_n()方法來(lái)發(fā)起到對(duì)端的連接。除了其他一些選項(xiàng),這兩個(gè)方法的參數(shù)包括我們想要連接到的遠(yuǎn)地地址,以及我們是想要同步還是異步地完成連接。我們可以同步或異步地發(fā)起NUMBER_CONN個(gè)連接:
//Synchronous
OurConnector.connect_n(NUMBER_CONN,ArrayofMySvcHandlers,Remote_Addr,0,
ACE_Synch_Options::synch);
//Asynchronous
OurConnector.connect_n(NUMBER_CONN,ArrayofMySvcHandlers,Remote_Addr,0,
ACE_Synch_Options::asynch);
如果連接請(qǐng)求是異步的,
ACE_Connector會(huì)在反應(yīng)堆上登記自己,等待連接被建立(ACE_Connector也派生自ACE_Event_Handler類層次)。一旦連接被建立,反應(yīng)堆將隨即自動(dòng)回調(diào)連接器。但如果連接請(qǐng)求是同步的,connect()調(diào)用將會(huì)阻塞,直到連接被建立、或是超時(shí)到期為止。超時(shí)值可通過(guò)改變特定的ACE_Synch_Options來(lái)指定。詳情請(qǐng)參見(jiàn)參考手冊(cè)。
7.4.2 接受器的服務(wù)初始化階段 在有連接請(qǐng)求在指定的地址和端口上到來(lái)時(shí),反應(yīng)堆自動(dòng)回調(diào)
ACE_Acceptor工廠的handle_input()方法。 該方法是一個(gè)“模板方法”(
Template Method)。模板方法用于定義一個(gè)算法的若干步驟的順序,并允許改變特定步驟的執(zhí)行。這種變動(dòng)是通過(guò)允許子類定義這些方法的實(shí)現(xiàn)來(lái)完成的。(有關(guān)模板方法的更多信息見(jiàn)“設(shè)計(jì)模式”參考指南)。 在我們的這個(gè)案例中,模板方法將算法定義如下:
:創(chuàng)建服務(wù)處理器。 accept_svc_handler():將連接接受進(jìn)前一步驟創(chuàng)建的服務(wù)處理器。 activate_svc_handler():?jiǎn)?dòng)這個(gè)新服務(wù)處理器。
這些方法都可以被重新編寫(xiě),從而靈活地決定這些操作怎樣來(lái)實(shí)際執(zhí)行。
這樣,
handle_input()將首先調(diào)用make_svc_handler()方法,創(chuàng)建適當(dāng)類型的服務(wù)處理器(如我們?cè)谏厦娴睦又兴吹降哪菢樱?wù)處理器的類型由應(yīng)用程序員在ACE_Acceptor模板被實(shí)例化時(shí)傳入)。在缺省情況下,make_svc_handler()方法只是實(shí)例化恰當(dāng)?shù)姆?wù)處理器。但是,make_svc_handler()是一個(gè)“橋接”(bridge)方法,可被重載以提供更多復(fù)雜功能。(橋接是一種設(shè)計(jì)模式,它使類層次的接口與實(shí)現(xiàn)去耦合。參閱“設(shè)計(jì)模式”參考文獻(xiàn))。例如,服務(wù)處理器可創(chuàng)建為進(jìn)程級(jí)或線程級(jí)的單體,或者從庫(kù)中動(dòng)態(tài)鏈接,從磁盤(pán)中加載,甚或通過(guò)更復(fù)雜的方式創(chuàng)建,如從數(shù)據(jù)庫(kù)中查找并獲取服務(wù)處理器,并將它裝入內(nèi)存。 在服務(wù)處理器被創(chuàng)建后,
handle_input()方法調(diào)用accept_svc_handler()。該方法將連接“接受進(jìn)”服務(wù)處理器;缺省方式是調(diào)用底層具體接受器的accept()方法。在ACE_SOCK_Acceptor被用作具體接受器的情況下,它調(diào)用BSD accept()例程來(lái)建立連接(“接受”連接)。在連接建立后,連接句柄在服務(wù)處理器中被自動(dòng)設(shè)置(接受“進(jìn)”服務(wù)處理器);這個(gè)服務(wù)處理器是先前通過(guò)調(diào)用make_svc_handler()創(chuàng)建的。該方法也可被重載,以提供更復(fù)雜的功能。例如,不是實(shí)際創(chuàng)建新連接,而是“回收利用”舊連接。在我們演示各種不同的接受和連接策略時(shí),將更為詳盡地討論這一點(diǎn)。
7.4.3 連接器的服務(wù)初始化階段 應(yīng)用發(fā)出的
connect()方法與接受器工廠中的handle_input()相類似,也就是,它是一個(gè)“模板方法”。 在我們的這個(gè)案例中,模板方法
connect()定義下面一些可被重定義的步驟:
:創(chuàng)建服務(wù)處理器。 connect_svc_handler():將連接接受進(jìn)前一步驟創(chuàng)建的服務(wù)處理器。 activate_svc_handler():?jiǎn)?dòng)這個(gè)新服務(wù)處理器。
每一方法都可以被重新編寫(xiě),從而靈活地決定這些操作怎樣來(lái)實(shí)際執(zhí)行。
這樣,在應(yīng)用發(fā)出
connect()調(diào)用后,連接器工廠通過(guò)調(diào)用make_svc_handler()來(lái)實(shí)例化恰當(dāng)?shù)姆?wù)處理器,一如在接受器的案例中所做的那樣。其缺省行為只是實(shí)例化適當(dāng)?shù)念悾⑶乙部梢酝ㄟ^(guò)與接受器完全相同的方式重載。進(jìn)行這樣的重載的原因可以與上面提到的原因非常類似。 在服務(wù)處理器被創(chuàng)建后,
connect()調(diào)用確定連接是要成為異步的還是同步的。如果是異步的,在繼續(xù)下一步驟之前,它將自己登記到反應(yīng)堆,隨后調(diào)用connect_svc_handler()方法。該方法的缺省行為是調(diào)用底層具體連接器的connect()方法。在使用ACE_SOCK_Connector的情況下,這意味著將適當(dāng)?shù)倪x項(xiàng)設(shè)置為阻塞或非阻塞式I/O,然后發(fā)出BSD connect()調(diào)用。如果連接被指定為同步的,connect()調(diào)用將會(huì)阻塞、直到連接完全建立。在這種情況下,在連接建立后,它將在服務(wù)處理器中設(shè)置句柄,以與它現(xiàn)在連接到的對(duì)端通信(該句柄即是通過(guò)在服務(wù)處理器中調(diào)用peer()方法獲得的在流中存儲(chǔ)的句柄,見(jiàn)上面的例子)。在服務(wù)處理器中設(shè)置句柄后,連接器模式將進(jìn)行到最后階段:服務(wù)處理。 如果連接被指定為異步的,在向底層的具體連接器發(fā)出非阻塞式
connect()調(diào)用后,對(duì)connect_svc_handler()的調(diào)用將立即返回。在使用ACE_SOCK_Connector的情況中,這意味著發(fā)出非阻塞式BSD connect()調(diào)用。在連接稍后被實(shí)際建立時(shí),反應(yīng)堆將回調(diào)ACE_Connector工廠的handle_output()方法,該方法在通過(guò)make_svc_handler()方法創(chuàng)建的服務(wù)處理器中設(shè)置新句柄。然后工廠將進(jìn)行到下一階段:服務(wù)處理。 與
accept_svc_handler()情況一樣,connect_svc_handler()是一個(gè)“橋接”方法,可進(jìn)行重載以提供變化的功能。
7.4.4 服務(wù)處理 一旦服務(wù)處理器被創(chuàng)建、連接被建立,以及句柄在服務(wù)處理器中被設(shè)置,
ACE_Acceptor的handle_input()方法(或者在使用ACE_Connector的情況下,是handle_output()或connect_svc_handler())將調(diào)用activate_svc_handler()方法。該方法將隨即啟用服務(wù)處理器。其缺省行為是調(diào)用作為服務(wù)處理器的入口的open()方法。如我們?cè)谏厦娴睦又兴吹降模诜?wù)處理器開(kāi)始執(zhí)行時(shí),open()方法是第一個(gè)被調(diào)用的方法。是在open()方法中,我們調(diào)用activate()方法來(lái)創(chuàng)建多個(gè)線程控制;并在反應(yīng)堆上登記服務(wù)處理器,這樣當(dāng)新的數(shù)據(jù)在連接上到達(dá)時(shí),它會(huì)被自動(dòng)回調(diào)。該方法也是一個(gè)“橋接”方法,可被重載以提供更為復(fù)雜的功能。特別地,這個(gè)重載的方法可以提供更為復(fù)雜的并發(fā)策略,比如,在另一不同的進(jìn)程中運(yùn)行服務(wù)處理器。
7.5
調(diào)諧接受器和連接器策略 如上面所提到的,因?yàn)槭褂昧丝梢灾剌d的橋接方法,很容易對(duì)接受器和連接器進(jìn)行調(diào)諧。橋接方法允許調(diào)諧:
服務(wù)處理器的創(chuàng)建策略:通過(guò)重載接受器或連接器的make_svc_handler()方法來(lái)實(shí)現(xiàn)。例如,這可以意味著復(fù)用已有的服務(wù)處理器,或使用某種復(fù)雜的方法來(lái)獲取服務(wù)處理器,如上面所討論的那樣。 連接策略:連接創(chuàng)建策略可通過(guò)重載connect_svc_handler()或accept_svc_handler()方法來(lái)改變。 服務(wù)處理器的并發(fā)策略:服務(wù)處理器的并發(fā)策略可通過(guò)重載activate_svc_handler()方法來(lái)改變。例如,服務(wù)處理器可以在另外的進(jìn)程中創(chuàng)建。
如上所示,調(diào)諧是通過(guò)重載
ACE_Acceptor或ACE_Connector類的橋接方法來(lái)完成的。ACE的設(shè)計(jì)使得程序員很容易完成這樣的重載和調(diào)諧。
7.5.1 ACE_Strategy_Connector和ACE_Strategy_Acceptor類 為了方便上面所提到的對(duì)接受器和連接器模式的調(diào)諧方法,
ACE提供了兩種特殊的“可調(diào)諧”接受器和連接器工廠,那就是ACE_Strategy_Acceptor和ACE_Strategy_Connector。它們和ACE_Acceptor與ACE_Connector非常類似,同時(shí)還使用了“策略”模式。 策略模式被用于使算法行為與類的接口去耦合。其基本概念是允許一個(gè)類(稱為
Context Class,上下文類)的底層算法獨(dú)立于使用該類的客戶進(jìn)行變動(dòng)。這是通過(guò)具體策略類的幫助來(lái)完成的。具體策略類封裝執(zhí)行操作的算法或方法。這些具體策略類隨后被上下文類用于執(zhí)行各種操作(上下文類將“工作”委托給具體策略類)。因?yàn)樯舷挛念惒恢苯訄?zhí)行任何操作,當(dāng)需要改變功能時(shí),無(wú)需對(duì)它進(jìn)行修改。對(duì)上下文類所做的唯一修改是使用另一個(gè)具體策略類來(lái)執(zhí)行改變了的操作。(要閱讀有關(guān)策略模式的更多信息,參見(jiàn)“設(shè)計(jì)模式”的附錄)。 在
ACE中,ACE_Strategy_Connector和ACE_Strategy_Acceptor使用若干具體策略類來(lái)改變算法,以創(chuàng)建服務(wù)處理器,建立連接,以及為服務(wù)處理器設(shè)置并發(fā)方法。如你可能已經(jīng)猜到的一樣,ACE_Strategy_Connector和ACE_Strategy_Acceptor利用了上面提到的橋接方法所提供的可調(diào)諧性。
7.5.1.1
使用策略接受器和連接器 在
ACE中已有若干具體的策略類可用于“調(diào)諧”策略接受器和連接器。當(dāng)類被實(shí)例化時(shí),它們作為參數(shù)被傳入策略接受器或連接器。表7-2顯示了可用于調(diào)諧策略接受器和連接器類的一些類。需要修改 | 具體策略類 | 描述 |
創(chuàng)建策略 ( 重定義make_svc_handler()) | ACE_NOOP_Creation_Strategy | 這個(gè)具體策略并不實(shí)例化服務(wù)處理器,而只是一個(gè)空操作。 |
ACE_Singleton_Strategy | 保證服務(wù)處理器被創(chuàng)建為單體。也就是,所有連接將有效地使用同一個(gè)服務(wù)處理例程。 |
ACE_DLL_Strategy | 通過(guò)從動(dòng)態(tài)鏈接庫(kù)中動(dòng)態(tài)鏈接服務(wù)處理器來(lái)對(duì)它進(jìn)行創(chuàng)建。 |
連接策略 ( 重定義connect_svc_handler()) | ACE_Cached_Connect_Strategy | 檢查是否有已經(jīng)連接到特定的遠(yuǎn)地地址的服務(wù)處理器沒(méi)有在被使用。如果有這樣一個(gè)服務(wù)處理器,就對(duì)它進(jìn)行復(fù)用。 |
并發(fā)策略 ( 重定義activate_svc_handler()) | ACE_NOOP_Concurrency_Strategy | 一個(gè)“無(wú)為”( do-nothing)的并發(fā)策略。它甚至不調(diào)用服務(wù)處理器的open()方法。 |
ACE_Process_Strategy | 在另外的進(jìn)程中創(chuàng)建服務(wù)處理器,并調(diào)用它的 open()方法。 |
ACE_Reactive_Strategy | 先在反應(yīng)堆上登記服務(wù)處理器,然后調(diào)用它的 open()方法。 |
ACE_Thread_Strategy | 先調(diào)用服務(wù)處理器的 open()方法,然后調(diào)用它的activate()方法,以讓另外的線程來(lái)啟動(dòng)服務(wù)處理器的svc()方法。 |
表
7-2 用于調(diào)諧策略接受器和連接器類的類
下面的例子演示策略接受器和連接器類的使用。
例
7-8#include ”ace/Reactor.h”
#include ”ace/Svc_Handler.h”
#include ”ace/Acceptor.h”
#include ”ace/Synch.h”
#include ”ace/SOCK_Acceptor.h”
#define PORT_NUM 10101
#define DATA_SIZE 12
//forward declaration
class My_Svc_Handler;
//instantiate a strategy acceptor
typedef ACE_Strategy_Acceptor<My_Svc_Handler,ACE_SOCK_ACCEPTOR> MyAcceptor;
//instantiate a concurrency strategy
typedef ACE_Process_Strategy<My_Svc_Handler> Concurrency_Strategy;
// Define the Service Handler
class My_Svc_Handler:
public ACE_Svc_Handler <ACE_SOCK_STREAM,ACE_NULL_SYNCH>
{
private:
char* data;
public:
My_Svc_Handler()
{
data= new char[DATA_SIZE];
}
My_Svc_Handler(ACE_Thread_Manager* tm)
{
data= new char[DATA_SIZE];
}
int open(void*)
{
cout<<”Connection established”<<endl;
//Register with the reactor
ACE_Event_Handler::READ_MASK);
return 0;
}
int handle_input(ACE_HANDLE)
{
peer().recv_n(data,DATA_SIZE);
ACE_OS::printf(”<< %s\n”,data);
// keep yourself registered with the reactor
return 0;
}
};
int main(int argc, char* argv[])
{
ACE_INET_Addr addr(PORT_NUM);
//Concurrency Strategy
Concurrency_Strategy my_con_strat;
//Instantiate the acceptor
MyAcceptor acceptor(addr, //address to accept on
ACE_Reactor::instance(), //the reactor to use
0,
// don’t care about creation strategy0, // don’t care about connection estb. strategy
&my_con_strat
); // use our new process concurrency strategy
while(1) /* Start the reactor’s event loop */
ACE_Reactor::instance()->handle_events();
}
這個(gè)例子基于上面的例
7-2。唯一的不同是它使用了ACE_Strategy_Acceptor,而不是使用ACE_Acceptor;并且它還使用ACE_Process_Strategy作為服務(wù)處理器的并發(fā)策略。這種并發(fā)策略保證一旦連接建立后,服務(wù)處理器在單獨(dú)的進(jìn)程中被實(shí)例化。如果在特定服務(wù)上的負(fù)載變得過(guò)于繁重,使用ACE_Process_Strategy可能是一個(gè)好主意。但是,在大多數(shù)情況下,使用ACE_Process_Strategy會(huì)過(guò)于昂貴,而ACE_Thread_Strategy可能是更好的選擇。
7.5.1.2
使用ACE_Cached_Connect_Strategy進(jìn)行連接緩存
在許多應(yīng)用中,客戶會(huì)連接到服務(wù)器,然后重新連接到同一服務(wù)器若干次;每次都要建立連接,執(zhí)行某些工作,然后掛斷連接(比如像在
Web客戶中所做的那樣)。不用說(shuō),這樣做是非常低效而昂貴的,因?yàn)檫B接建立和掛斷是非常昂貴的操作。在這樣的情況下,連接者可以采用一種更好的策略:“記住”老連接并保持它,直到確定客戶不會(huì)再重新建立連接為止。ACE_Cached_Connect_Strategy就提供了這樣一種緩存策略。這個(gè)策略對(duì)象被ACE_Strategy_Connector用于提供基于緩存的連接建立。如果一個(gè)連接已經(jīng)存在,ACE_Strategy_Connector將會(huì)復(fù)用它,而不是創(chuàng)建新的連接。 當(dāng)客戶試圖重新建立連接到先前已經(jīng)連接的服務(wù)器時(shí),
ACE_Cached_Connect_Strategy確保對(duì)老的連接和服務(wù)處理器進(jìn)行復(fù)用,而不是創(chuàng)建新的連接和服務(wù)處理器。因而,實(shí)際上,ACE_Cached_Connect_Strategy不僅管理連接建立策略,它還管理服務(wù)處理器創(chuàng)建策略。因?yàn)樵诖死校脩?b>不想創(chuàng)建新的服務(wù)處理器,我們將ACE_Null_Creation_Strategy傳遞給ACE_Strategy_Connector。如果連接先前沒(méi)有建立過(guò),ACE_Cached_Connect_Strategy將自動(dòng)使用內(nèi)部的創(chuàng)建策略來(lái)實(shí)例化適當(dāng)?shù)姆?wù)處理器,它是在這個(gè)模板類被實(shí)例化時(shí)傳入的。這個(gè)策略可被設(shè)置為用戶想要使用的任何一種策略。除此而外,也可以將ACE_Cached_Connect_Strategy自己在其構(gòu)造器中使用的創(chuàng)建、并發(fā)和recycling策略傳給它。下面的例子演示這些概念:
例
7-9#include ”ace/Reactor.h”
#include ”ace/Svc_Handler.h”
#include ”ace/Connector.h”
#include ”ace/Synch.h”
#include ”ace/SOCK_Connector.h”
#include ”ace/INET_Addr.h”
#define PORT_NUM 10101
#define DATA_SIZE 16
//forward declaration
class My_Svc_Handler;
//Function prototype
static void make_connections(void *arg);
// Template specializations for the hashing function for the
// hash_map which is used by the cache. The cache is used internally by the
// Cached Connection Strategy . Here we use ACE_Hash_Addr
// as our external identifier. This utility class has already
// overloaded the == operator and the hash() method. (The
// hashing function). The hash() method delegates the work to
// hash_i() and we use the IP address and port to get a
// a unique integer hash value.
size_t ACE_Hash_Addr<ACE_INET_Addr>::hash_i (const ACE_INET_Addr &addr) const
{
return addr.get_ip_address () + addr.get_port_number ();
}
//instantiate a strategy acceptor
typedef ACE_Strategy_Connector<My_Svc_Handler,ACE_SOCK_CONNECTOR>
STRATEGY_CONNECTOR;
//Instantiate the Creation Strategy
typedef ACE_NOOP_Creation_Strategy<My_Svc_Handler>
NULL_CREATION_STRATEGY;
//Instantiate the Concurrency Strategy
typedef ACE_NOOP_Concurrency_Strategy<My_Svc_Handler>
NULL_CONCURRENCY_STRATEGY;
//Instantiate the Connection Strategy
typedef ACE_Cached_Connect_Strategy<My_Svc_Handler,
ACE_SOCK_CONNECTOR,
ACE_SYNCH_RW_MUTEX>
CACHED_CONNECT_STRATEGY;
class My_Svc_Handler:
public ACE_Svc_Handler <ACE_SOCK_STREAM,ACE_MT_SYNCH>
{
private:
char* data;
public:
My_Svc_Handler()
{
data= new char[DATA_SIZE];
}
My_Svc_Handler(ACE_Thread_Manager* tm)
{
data= new char[DATA_SIZE];
}
//Called before the service handler is recycled..
int
recycle (void *a=0)/P>
{
ACE_DEBUG ((LM_DEBUG,
”(%P|%t) recycling Svc_Handler %d with handle %d\n”,
this, this->peer ().get_handle ()));
return 0;
}
int open(void*)
{
ACE_DEBUG((LM_DEBUG,”(%t)Connection established \n”));
//Register the service handler with the reactor
ACE_Reactor::instance()
->register_handler(this,ACE_Event_Handler::READ_MASK);
activate(THR_NEW_LWP|THR_DETACHED);
return 0;
}
int handle_input(ACE_HANDLE)
{
ACE_DEBUG((LM_DEBUG,”Got input in thread: (%t) \n”));
peer().recv_n(data,DATA_SIZE);
ACE_DEBUG((LM_DEBUG,”<< %s\n”,data));
//keep yourself registered with the reactor
return 0;
}
int svc(void)
{
//send a few messages and then mark connection as idle so that it can
// be recycled
later.ACE_DEBUG((LM_DEBUG,”Started the service routine \n”));
for(int i=0;i<3;i++)
{
ACE_DEBUG((LM_DEBUG,”(%t)>>Hello World\n”));
ACE_OS::fflush(stdout);
peer().send_n(”Hello World”,sizeof(”Hello World”));
}
//Mark the service handler as being idle now and let the
//other threads reuse this connection
this->idle(1);
//Wait for the thread to die
this->thr_mgr()->wait();
return 0;
}
};
ACE_INET_Addr *addr;
int main(int argc, char* argv[])
{
addr= new ACE_INET_Addr(PORT_NUM,argv[1]);
//Creation Strategy
NULL_CREATION_STRATEGY creation_strategy;
//Concurrency Strategy
NULL_CONCURRENCY_STRATEGY concurrency_strategy;
//Connection Strategy
CACHED_CONNECT_STRATEGY caching_connect_strategy;
//instantiate the connector
STRATEGY_CONNECTOR connector(
ACE_Reactor::instance(), //the reactor to use
&creation_strategy,
&caching_connect_strategy,
&concurrency_strategy);
//Use the thread manager to spawn a single thread
//to connect multiple times passing it the address
//of the strategy connector
if(ACE_Thread_Manager::instance()->spawn(
(ACE_THR_FUNC) make_connections,
(void *) &connector,
THR_NEW_LWP) == -1)
ACE_ERROR ((LM_ERROR, ”(%P|%t) %p\n%a”, ”client thread spawn failed”));
while(1) /* Start the reactor’s event loop */
ACE_Reactor::instance()->handle_events();
}
//Connection establishment function, tries to establish connections
//to the same server again and re-uses the connections from the cache
void make_connections(void *arg)
{
ACE_DEBUG((LM_DEBUG,”(%t)Prepared to connect \n”));
STRATEGY_CONNECTOR *connector= (STRATEGY_CONNECTOR*) arg;
for (int i = 0; i < 10; i++)
{
My_Svc_Handler *svc_handler = 0;
// Perform a blocking connect to the server using the Strategy
// Connector with a connection caching strategy. Since we are
// connecting to the same <server_addr> these calls will return the
// same dynamically allocated <Svc_Handler> for each <connect> call.
if (connector->connect (svc_handler, *addr) == -1)
{
ACE_ERROR ((LM_ERROR, ”(%P|%t) %p\n”, ”connection failed\n”));
return;
}
// Rest for a few seconds so that the connection has been freed up
ACE_OS::sleep (5);
}}
在上面的例子中,緩存式連接策略被用于緩存連接。要使用這一策略,需要一點(diǎn)額外的工作:定義
ACE_Cached_Connect_Strategy在內(nèi)部使用的哈希映射管理器的hash()方法。這個(gè)hash()方法用于對(duì)服務(wù)處理器和ACE_Cached_Connect_Strategy內(nèi)部使用的連接進(jìn)行哈希運(yùn)算,放入緩存映射中。它簡(jiǎn)單地使用IP地址和端口號(hào)的總和作為哈希函數(shù),這也許并不是很好的哈希函數(shù)。 這個(gè)例子比至今為止我們所列舉的例子都要復(fù)雜一點(diǎn),所以有理由多進(jìn)行一點(diǎn)討論。
我們?yōu)?/p>
ACE_Strategy_Acceptor使用空操作并發(fā)和創(chuàng)建策略。使用空操作創(chuàng)建策略是必須的。如上面所解釋的,如果沒(méi)有使用ACE_NOOP_Creation_Strategy,ACE_Cached_Connection_Strategy將會(huì)產(chǎn)生斷言失敗。但是,在使用ACE_Cached_Connect_Strategy時(shí),任何并發(fā)策略都可以和策略接受器一起使用。如上面所提到的,ACE_Cached_Connect_Strategys所用的底層創(chuàng)建策略可以由用戶來(lái)設(shè)置。還可以設(shè)置recycling策略。這是在實(shí)例化caching_connect_strategy時(shí),通過(guò)將所需的創(chuàng)建和recycling策略的對(duì)象傳給它的構(gòu)造器來(lái)完成的。在這里我們沒(méi)有這樣做,而是使用了缺省的創(chuàng)建和recycling策略。 在適當(dāng)?shù)卦O(shè)置連接器后,我們使用
Thread_Manager來(lái)派生新線程,且將make_connections()方法作為線程的入口。該方法使用我們的新的策略連接器來(lái)連接到遠(yuǎn)地站點(diǎn)。在連接建立后,該線程休眠5秒鐘,然后使用我們的緩存式連接器來(lái)重新創(chuàng)建同樣的連接。于是該線程應(yīng)該在連接器緩存中找到這個(gè)連接并復(fù)用它。 和平常一樣,一旦連接建立后,反應(yīng)堆回調(diào)我們的服務(wù)處理器(
My_Svc_Handler)。隨后My_Svc_Handler的open()方法通過(guò)調(diào)用My_Svc_Handler的activate()方法來(lái)使它成為主動(dòng)對(duì)象。svc()方法隨后發(fā)送三條消息給遠(yuǎn)地主機(jī),并通過(guò)調(diào)用服務(wù)處理器的idle()方法將該連接標(biāo)記為空閑。注意this->thr_mrg_wait()要求線程管理器等待所有在線程管理器中的線程終止。如果你不要求線程管理器等待其它線程,根據(jù)在ACE中設(shè)定的語(yǔ)義,一旦ACE_Task(在此例中是ACE_Task類型的ACE_Svc_Handler)中的線程終止,ACE_Task對(duì)象(在此例中是ACE_My_Svc_Handler)就會(huì)被自動(dòng)刪除。如果發(fā)生了這種情況,在Cache_Connect_Strategy查找先前緩存的連接時(shí),它就不會(huì)如我們期望的那樣找到My_Svc_Handler,因?yàn)樗呀?jīng)被刪除掉了。 在
My_Svc_Handler中還重載了ACE_Svc_Handler中的recycle()方法。當(dāng)有舊連接被ACE_Cache_Connect_Strategy找到時(shí),這個(gè)方法就會(huì)被自動(dòng)回調(diào),這樣服務(wù)處理器就可以在此方法中完成回收利用所特有的操作。在我們的例子中,我們只是打印出在緩存中找到的處理器的this指針的地址。在程序運(yùn)行時(shí),每次連接建立后所使用的句柄的地址是相同的,從而說(shuō)明緩存工作正常。
7.6
通過(guò)接受器和連接器模式使用簡(jiǎn)單事件處理器 有時(shí), ,使用重量級(jí)的
ACE_Svc_Handler作為接受器和連接器的處理器不僅沒(méi)有必要,而且會(huì)導(dǎo)致代碼臃腫。在這樣情況下,用戶可以使用較輕的ACE_Event_Handler方法來(lái)作為反應(yīng)堆在連接一旦建立時(shí)所回調(diào)的類。要采用這種方法,程序員需要重載get_handle()方法,并包含將要用于事件處理器的具體底層流。下面的例子有助于演示這些變動(dòng)。這里我們還編寫(xiě)了新的peer()方法,它返回底層流的引用(reference),就像在ACE_Svc_Handler類中所做的那樣。
例
7-10#include ”ace/Reactor.h”
#include ”ace/Svc_Handler.h”
#include ”ace/Acceptor.h”
#include ”ace/Synch.h”
#include ”ace/SOCK_Acceptor.h”
#define PORT_NUM 10101
#define DATA_SIZE 12
//forward declaration
class My_Event_Handler;
//Create the Acceptor class
typedef ACE_Acceptor<My_Event_Handler,ACE_SOCK_ACCEPTOR>
MyAcceptor;
//Create an event handler similar to as seen in example 2. We have to
//overload the get_handle() method and write the peer() method. We also
//provide the data member peer_ as the underlying stream which is
//used.
class My_Event_Handler: public ACE_Event_Handler
{
private:
char* data;
//Add a new attribute for the underlying stream which will be used by
//the Event Handler
ACE_SOCK_Stream peer_;
public:
My_Event_Handler()
{
data= new char[DATA_SIZE];
}
int open(void*)
{
cout<<”Connection established”<<endl;
//Register the event handler with the reactor
ACE_Reactor::instance()->register_handler(this,
ACE_Event_Handler::READ_MASK);
return 0;
}
int handle_input(ACE_HANDLE)
{
// After using the peer() method of our ACE_Event_Handler to obtain a
//reference to the underlying stream of the service handler class we
//call recv_n() on it to read the data which has been received. This
//data is stored in the data array and then printed out
peer().recv_n(data,DATA_SIZE);
ACE_OS::printf(”<< %s\n”,data);
// keep yourself registered with the reactor
return 0;
}
// new method which returns the handle to the reactor when it
//asks for it.
ACE_HANDLE get_handle(void) const
{
return this->peer_.get_handle();
}
//new method which returns a reference to the peer stream
ACE_SOCK_Stream &peer(void) const
{
return (ACE_SOCK_Stream &) this->peer_;
}
};
int main(int argc, char* argv[])
{
ACE_INET_Addr addr(PORT_NUM);
//create the acceptor
MyAcceptor acceptor(addr, //address to accept on
ACE_Reactor::instance()); //the reactor to use
while(1) /* Start the reactor’s event loop */
ACE_Reactor::instance()->handle_events();
}
文轉(zhuǎn)載至ACE開(kāi)發(fā)者/FONT>
posted on 2007-02-27 21:40
walkspeed 閱讀(7256)
評(píng)論(1) 編輯 收藏 引用 所屬分類:
ACE Farmeworks