2012-08-06 14 views
9

पृष्ठभूमि: मैं एक मल्टीप्लेयर गेम विकसित करने में मदद करता हूं, जो ज्यादातर सी ++ में लिखा जाता है, जो मानक क्लाइंट-सर्वर आर्किटेक्चर का उपयोग करता है। सर्वर को स्वयं संकलित किया जा सकता है, और क्लाइंट को सर्वर से संकलित किया जाता है ताकि आप गेम होस्ट कर सकें।क्लाइंट-सर्वर गेम में कोड रखना

समस्या

खेल एक ही वर्गों में दोनों क्लाइंट और सर्वर कोड को जोड़ती है, और यह बहुत बोझिल हो रहा है।

// Server + client 
Point Ship::calcPosition() 
{ 
    // Do position calculations; actual (server) and predictive (client) 
} 

// Server only 
void Ship::explode() 
{ 
    // Communicate to the client that this ship has died 
} 

// Client only 
#ifndef SERVER_ONLY 
void Ship::renderExplosion() 
{ 
    // Renders explosion graphics and sound effects 
} 
#endif 

और हैडर:

class Ship 
{ 
    // Server + client 
    Point calcPosition(); 

    // Server only 
    void explode(); 

    // Client only 
    #ifndef SERVER_ONLY 
    void renderExplosion(); 
    #endif 
} 

आप देख सकते हैं, इसके बाद सर्वर संकलन

उदाहरण के लिए, निम्नलिखित कुछ का एक छोटा सा नमूना आप एक सामान्य वर्ग में देख सकते है केवल, प्रीप्रोसेसर परिभाषाओं का उपयोग ग्राफिक्स और ध्वनि कोड (जो बदसूरत लगता है) को बाहर करने के लिए किया जाता है।

प्रश्न:

एक क्लाइंट-सर्वर का आयोजन किया और साफ वास्तुकला में कोड रखने के लिए सर्वोत्तम प्रथाओं में से कुछ क्या हैं?

धन्यवाद!

संपादित करें: है कि अच्छा संगठन का उपयोग खुला स्रोत परियोजनाओं के उदाहरण भी स्वागत :)

उत्तर

2

एक ग्राहक ठूंठ वर्ग ग्राहक एपीआई है कि परिभाषित करें।

सर्वर को लागू करने वाले सर्वर वर्ग को परिभाषित करें।

एक सर्वर स्टब को परिभाषित करें जो सर्वर कॉल पर आने वाले संदेश को मानचित्र करता है।

स्टब क्लास में सर्वर पर प्रॉक्सी कमांड को छोड़कर कोई कार्यान्वयन नहीं है जो आप कभी भी प्रोटोकॉल का उपयोग कर रहे हैं।

अब आप अपने डिज़ाइन को बदले बिना प्रोटोकॉल बदल सकते हैं।

या

MACE-RPC की तरह एक पुस्तकालय का उपयोग करें स्वचालित रूप से सर्वर एपीआई से क्लाइंट और सर्वर स्टब्स उत्पन्न करने के लिए।

+1

क्या आपको अपने डिजाइन के उदाहरण के बारे में पता चल जाएगा? – faffy

3

मैं Strategy design pattern का उपयोग करने पर विचार करता हूं, जिससे आपके पास क्लाइंट और सर्वर दोनों के लिए सामान्य रूप से एक शिप क्लास होगा, फिर शिपस्पेसिफिकिक्स जैसी कुछ अन्य श्रेणी पदानुक्रम बनाएं जो जहाज की विशेषता होगी। शिपस्पेसिफिकिक्स या तो सर्वर या क्लाइंट कंक्रीट व्युत्पन्न कक्षा के साथ बनाया जाएगा और जहाज में इंजेक्शन दिया जाएगा।

यह कुछ इस तरह दिखाई दे सकता है:

class ShipSpecifics 
{ 
    // create appropriate methods here, possibly virtual or pure virtual 
    // they must be common to both client and server 
}; 

class Ship 
{ 
public: 
    Ship() : specifics_(NULL) {} 

    Point calcPosition(); 
    // put more common methods/attributes here 

    ShipSpecifics *getSpecifics() { return specifics_; } 
    void setSpecifics(ShipSpecifics *s) { specifics_ = s; } 

private: 
    ShipSpecifics *specifics_; 
}; 

class ShipSpecificsClient : public ShipSpecifics 
{ 
    void renderExplosion(); 
    // more client stuff here 
}; 

class ShipSpecificsServer : public ShipSpecifics 
{ 
    void explode(); 
    // more server stuff here 
}; 

कक्षाएं जहाज और ShipSpecifics कोड बेस दोनों क्लाइंट और सर्वर के लिए आम में होगा, और ShipSpecificsServer और ShipSpecificsClient वर्गों स्पष्ट रूप से सर्वर और ग्राहक में होगा कोड आधार क्रमशः।

उपयोग की तरह कुछ हो सकता है निम्नलिखित:

// client usage 
int main(int argc, argv) 
{ 
    Ship *theShip = new Ship(); 
    ShipSpecificsClient *clientSpecifics = new ShipSpecificsClient(); 

    theShip->setSpecifics(clientSpecifics); 

    // everything else... 
} 

// server usage 
int main(int argc, argv) 
{ 
    Ship *theShip = new Ship(); 
    ShipSpecificsServer *serverSpecifics = new ShipSpecificsServer(); 

    theShip->setSpecifics(serverSpecifics); 

    // everything else... 
} 
1

क्यों नहीं एक सरल तरीका अपनाने? एक ही शीर्षलेख प्रदान करें जो जहाज वर्ग क्या करेगा, टिप्पणियों के साथ, लेकिन ifdefs नहीं। फिर अपने प्रश्न में किए गए ifdef के अंदर क्लाइंट कार्यान्वयन प्रदान करें, लेकिन क्लाइंट को संकलित नहीं किया जा रहा है (खाली) कार्यान्वयन का एक वैकल्पिक सेट प्रदान करें।

यह मुझे मारता है कि यदि आप स्पष्ट हैं आपकी टिप्पणियों और कोड संरचना में, इस दृष्टिकोण को प्रस्तावित अधिक "परिष्कृत" समाधानों की तुलना में पढ़ने और समझना बहुत आसान होगा।

इस दृष्टिकोण का अतिरिक्त लाभ है कि यदि साझा कोड, यहां calcPosition() को क्लाइंट बनाम सर्वर पर थोड़ा अलग निष्पादन पथ लेने की आवश्यकता है, और क्लाइंट कोड को अन्यथा क्लाइंट-फ़ंक्शन कॉल करने की आवश्यकता है (नीचे उदाहरण देखें), आपको बिल्ड जटिलताओं का सामना नहीं करना पड़ेगा।

हैडर:

class Ship 
{ 
    // Server + client 
    Point calcPosition(); 

    // Server only 
    void explode(); 
    Point calcServerActualPosition(); 

    // Client only 
    void renderExplosion(); 
    Point calcClientPredicitedPosition(); 
} 

बॉडी:

// Server + client 
Point Ship::calcPosition() 
{ 
    // Do position calculations; actual (server) and predictive  (client) 
    return isClient ? calcClientPredicitedPosition() : 
        calcServerActualPosition(); 
} 

// Server only 
void Ship::explode() 
{ 
    // Communicate to the client that this ship has died 
} 

Point Ship::calcServerActualPosition() 
{ 
    // Returns ship's official position 
} 


// Client only 
#ifndef SERVER_ONLY 

void Ship::renderExplosion() 
{ 
    // Renders explosion graphics and sound effects 
} 

Point Ship::calcClientPredicitedPosition() 
{ 
    // Returns client's predicted position 
} 

#else 

// Empty stubs for functions not used on server 
void Ship::renderExplosion()    { } 
Point Ship::calcClientPredicitedPosition() { return Point(); } 

#endif 

इस कोड को काफी पठनीय लगता है (एक तरफ संज्ञानात्मक मतभेद ग्राहक द्वारा शुरू की केवल/#ifndef SERVER_ONLY बिट, अलग-अलग नामों के साथ सुधारी जा सकने वाली से), विशेष रूप से यदि पैटर्न पूरे आवेदन में दोहराया जाता है।

मुझे लगता है कि केवल एक ही दोष यह है कि आपको अपने क्लाइंट-फ़ंक्शन फ़ंक्शन हस्ताक्षर दो बार दोहराना होगा, लेकिन यदि आप गड़बड़ करते हैं, तो यह संकलक त्रुटि देखने के बाद ठीक होने के लिए यह स्पष्ट और छोटा होगा।

+1

ठीक है, यह अब तक का सबसे आसान दृष्टिकोण है, लेकिन यह पहले से मौजूद कोड से कम बोझिल है। – faffy

संबंधित मुद्दे