हमने Google Play के लिए स्टिकर कैसे बनाए हैं

लंबे समय से मुझे अपने ज्ञान को समुदाय के साथ साझा करने का विचार था। पहले मैं एस्ट्रोफिजिक्स या जीटीआर पर कुछ लिखना चाहता था, लेकिन मैंने फिर भी तय किया कि उस विषय क्षेत्र के बारे में लिखना अधिक सही होगा, जिसे मैं पेशेवर रूप से देखता हूं। इसलिए, मैं बनाने की प्रक्रिया और एंड्रॉइड के लिए गेम एप्लिकेशन के कार्यान्वयन की सूक्ष्मताओं (डिजाइन से प्रकाशन तक और ऐप खरीदारी में) की विस्तार से व्याख्या करने की कोशिश करूंगा।
परिचय
मैं पहली कक्षा के बाद से प्रोग्रामिंग में लगा हुआ हूं, मैंने सेंट पीटर्सबर्ग स्टेट पॉलिटेक्निक यूनिवर्सिटी के एप्लाइड गणित से स्नातक किया है। हाल ही में (लगभग एक साल पहले) मैंने मोबाइल प्लेटफॉर्म के लिए विकास की खोज की। यह दिलचस्प हो गया कि यह क्या है और इसके साथ क्या खाया जाता है। मैं वर्तमान में दोस्तों / सहयोगियों की एक टीम में कई परियोजनाओं का विकास कर रहा हूं, लेकिन मैं अपने पहले अनुभव के बारे में लिखना चाहूंगा। ऐसा अनुभव एक गेम एप्लिकेशन लिख रहा था - "
स्टिकर " (हू एम आई?)।
ये किस प्रकार के स्टिकर हैं?जो लोग नहीं जानते हैं उनके लिए - मैं समझाता हूँ। स्टिकर - यह एक ऐसा टेबल गेम है जिसमें प्रत्येक खिलाड़ी को अपने माथे पर कुछ प्रसिद्ध चरित्र के साथ कागज का एक टुकड़ा मिलता है (पात्रों को एक दूसरे को खेलने से आविष्कार किया जाता है)। प्रत्येक प्रतिभागी का लक्ष्य उस चरित्र का अनुमान लगाना है जो उसने अनुमान लगाया है।
गेमप्ले एक अनुक्रमिक हां / कोई सवाल का काम नहीं है और अन्य खिलाड़ियों से उनके उत्तर प्राप्त कर रहा है।
विकल्प कई कारणों से स्टिकर पर गिर गया।
सबसे पहले, हमें बाजार में कोई एनालॉग नहीं मिला (यह वर्णित टेबल गेम के नियमों के कार्यान्वयन को संदर्भित करता है)।
दूसरे, मैं बहुत समय लेने वाली नहीं कुछ लिखना चाहती थी।
तीसरा, खेल हमारे हलकों में काफी लोकप्रिय है और हमने सोचा कि शायद किसी को इसे खेलने के लिए और वस्तुतः दिलचस्पी होगी।
विकास की प्रक्रिया
समस्या का बयान
यह कार्य काफी असमान था। क्लाइंट-सर्वर एप्लिकेशन को लागू करना आवश्यक है जो अपने उपयोगकर्ताओं को निम्नलिखित सुविधाओं का उपयोग करने की अनुमति देता है:
- अपना खाता बनाएँ
- अपने खाते का उपयोग करके प्रमाणीकरण
- प्लेयर रैंकिंग देखें
- गेम रूम बनाना
- खेल के कमरे में प्रवेश
- गेमप्ले की भागीदारी
गेमप्ले एक चरण परिवर्तन है:
यूआई डिजाइन
सौभाग्य से, मेरी पत्नी एक डिजाइनर है और मुझे व्यावहारिक रूप से पैलेट, तत्वों की व्यवस्था और अन्य डिजाइनर चीजों को चुनने में भाग नहीं लेना है। खेल को खिलाड़ी को प्रदान करने वाली क्षमताओं के विश्लेषण के आधार पर, यह निर्णय लिया गया कि कितने खेल राज्य (गतिविधियाँ) होंगे और उनमें से प्रत्येक में क्या नियंत्रण होना चाहिए:
- मुख्य मेनू
- प्रमाणीकरण
- पंजीकरण
- खिलाड़ी रेटिंग तक पहुंच
- प्लेयर रेटिंग
- कक्ष सूची
- कमरे में प्रवेश
- अपना खुद का कमरा बनाएँ
- मुख्य मेनू पर जाएं
- वर्तमान कमरा
- गेम स्टेट नंबर 1: इनपुट प्रश्न
- एक प्रश्न दर्ज करना
- सवालों के इतिहास में संक्रमण
- उत्तर इनपुट पर जाएं
- मुख्य मेनू पर जाएं
- खेल राज्य नंबर 2: प्रश्नों के इतिहास को देखें
- प्रश्न इनपुट पर जाएं
- उत्तर इनपुट पर जाएं
- मुख्य मेनू पर जाएं
- गेम स्थिति नंबर 3: इनपुट प्रतिक्रिया
- प्रतिक्रिया इनपुट
- प्रश्न इनपुट पर जाएं
- सवालों के इतिहास में संक्रमण
- मुख्य मेनू पर जाएं
- खेल राज्य संख्या 4: मतदान
- वॉयस इनपुट
- मुख्य मेनू पर जाएं
- जीत
- हार
- विकिपीडिया पर जाएं (चरित्र पृष्ठ पर)
- मुख्य मेनू पर जाएं
DB डिजाइन
जैसे ही यह हमारे लिए स्पष्ट हो गया कि खेल राज्य और वस्तुएं किस खेल में मौजूद हैं, हम डेटाबेस के संदर्भ में उन्हें औपचारिक रूप देने के लिए आगे बढ़े।
तो, हमें निम्न तालिकाओं की आवश्यकता है:
- उपयोगकर्ता। एक तालिका जो सभी उपयोगकर्ताओं के बारे में जानकारी संग्रहीत करती है
- खेल। एक तालिका जो सभी कमरों के बारे में जानकारी संग्रहीत करती है
- वर्ण। एक तालिका जो सभी वर्णों के बारे में जानकारी संग्रहीत करती है
- प्रश्न। एक तालिका जो उपयोगकर्ता द्वारा बनाए गए वर्णों के बारे में प्रश्नों को संग्रहीत करती है
- जवाब। वह तालिका जहाँ उपयोगकर्ता प्रतिक्रियाएँ संग्रहीत की जाती हैं
खेल के शुरुआती संस्करण में केवल ये टेबल थे, लेकिन खेल विकसित हुआ और नए जोड़े गए। मैं बाकी तालिकाओं का वर्णन नहीं करूंगा, अन्यथा कथन अत्यधिक लंबा होगा। डेटाबेस आरेख में सभी तालिकाओं को दिखाया गया है, लेकिन उनकी उपस्थिति आगे की चर्चा में बाधा नहीं बनेगी।
तालिकाओं के बीच संबंध कई बार बदल गए (फुर्तीले, बोलने के लिए), लेकिन अंत में निम्नलिखित बने रहे:
- प्रत्येक उपयोगकर्ता एक चरित्र के साथ जुड़ा हो सकता है।
- प्रत्येक उपयोगकर्ता केवल एक कमरे में हो सकता है।
- प्रत्येक प्रश्न केवल एक उपयोगकर्ता द्वारा पूछा जा सकता है।
- प्रत्येक प्रश्न केवल एक वर्ण से संबंधित हो सकता है।
- प्रत्येक प्रश्न केवल एक खेल के भीतर ही पूछा जा सकता है।
- प्रत्येक उत्तर केवल एक उपयोगकर्ता द्वारा दिया जा सकता है।
- प्रत्येक उत्तर केवल एक प्रश्न का दिया जा सकता है।
और डेटा का सामान्यीकरण कहां है?डीबीएमएस पर लोड को कम करने के लिए केवल डुप्लिकेट संचार की आवश्यकता होती है और वे खेल के पहले संस्करण से दूर दिखाई देते हैं। तालिकाओं की संख्या में वृद्धि के साथ, कुछ डेटा नमूनों के लिए एकत्रीकरण की संख्या में वृद्धि हुई है।
आवेदन स्तर
अंत में, हम सॉफ्टवेयर कार्यान्वयन के लिए तैयार हो गए। तो, मैं सबसे सामान्य शब्दों के साथ शुरू करूंगा। पूरी परियोजना में 4 मॉड्यूल शामिल हैं:
- Venta। एक पुस्तकालय जो उपयोगी उपयोगिताओं को संकलित करता है
- प्रोटोकॉल। इंटरैक्शन प्रोटोकॉल के विवरण के साथ लाइब्रेरी
- सर्वर। एप्लिकेशन सर्वर
- क्लाइंट। आवेदन के ग्राहक पक्ष
चूंकि मैं पहिया को फिर से मजबूत करना चाहता हूं और तीसरे पक्ष के पुस्तकालयों (हां, हां, कई पैदल प्रोग्रामरों की एक क्लासिक समस्या) की परेशानियों की तरह नहीं है, मैंने खुद कुछ चीजें लिखने का फैसला किया। मैं इस पुस्तकालय को लंबे समय से लिख रहा हूं और इसमें मेरे लिए (डेटाबेस, क्लाइंट-सर्वर इंटरैक्शन, एक्टर्स, गणित, एन्क्रिप्शन ...) के साथ कई उपयोगी उपयोगिताओं का समावेश है।
इस लेख में, मैं इस पुस्तकालय के नेटवर्क भाग के बारे में बात करना चाहता हूं। मैंने क्लाइंट और सर्वर के बीच वस्तुओं को क्रमबद्ध / deserializing द्वारा बातचीत को लागू करने का निर्णय लिया, जिसके बीच अनुरोध और उत्तर हैं।
संदेश वस्तु प्राथमिक सूचना इकाई (स्थान के स्तर पर, निश्चित रूप से) हस्तांतरित की जा रही है:
"Message.java"package com.gesoftware.venta.network.model; import com.gesoftware.venta.utility.CompressionUtility; import java.nio.charset.Charset; import java.io.Serializable; import java.util.Arrays; public final class Message implements Serializable { private final long m_Timestamp; private final byte[] m_Data; public Message(final byte data[]) { m_Timestamp = System.currentTimeMillis(); m_Data = data; } public Message(final String data) { this(data.getBytes()); } public Message(final Object object) { this(CompressionUtility.compress(object)); } public final byte[] getData() { return m_Data; } public final int getSize() { return (m_Data != null)?m_Data.length:0; } @Override public final String toString() { return (m_Data != null)?new String(m_Data, Charset.forName("UTF-8")):null; } private boolean messagesHasSameSizes(final Message message) { return m_Data != null && m_Data.length == message.m_Data.length; } private boolean messagesAreEqual(final Message message) { if (!messagesHasSameSizes(message)) return false; for (int i = 0; i < message.m_Data.length; i++) if (m_Data[i] != message.m_Data[i]) return false; return true; } public final Object getObject() { return CompressionUtility.decompress(m_Data); } public final long getTimestamp() { return m_Timestamp; } @Override public final boolean equals(Object obj) { return obj instanceof Message && messagesAreEqual((Message) obj); } @Override public final int hashCode() { return Arrays.hashCode(m_Data); } }
मैं इस ऑब्जेक्ट के विवरण पर ध्यान नहीं दूंगा, कोड काफी टिप्पणी की गई है।
नेटवर्क के साथ काम का सरलीकरण दो वर्गों के उपयोग के कारण होता है:
- सर्वर (सर्वर साइड)
- कनेक्शन (ग्राहक पक्ष)
प्रकार
सर्वर का एक ऑब्जेक्ट बनाते समय, आपको उस पोर्ट को निर्दिष्ट करना होगा जिस पर वह आने वाले कनेक्शन की प्रतीक्षा करेगा और
IServerHandler को कार्यान्वित
करेगा"IServerHandler.java" package com.gesoftware.venta.network.handlers; import com.gesoftware.venta.network.model.Message; import com.gesoftware.venta.network.model.ServerResponse; import java.net.InetAddress; public interface IServerHandler { public abstract boolean onConnect(final String clientID, final InetAddress clientAddress); public abstract ServerResponse onReceive(final String clientID, final Message message); public abstract void onDisconnect(final String clientID); }
क्लाइंट, बदले में, प्रकार
कनेक्शन का ऑब्जेक्ट बनाते समय,
IClientHandler इंटरफ़ेस का कार्यान्वयन प्रदान करना चाहिए।
"IClientHandler.java" package com.gesoftware.venta.network.handlers; import com.gesoftware.venta.network.model.Message; import com.gesoftware.venta.network.model.ServerResponse; import java.net.InetAddress; public interface IServerHandler { public abstract boolean onConnect(final String clientID, final InetAddress clientAddress); public abstract ServerResponse onReceive(final String clientID, final Message message); public abstract void onDisconnect(final String clientID); }
अब सर्वर की आंतरिक संरचना के बारे में थोड़ा। जैसे ही अगला क्लाइंट सर्वर से जुड़ता है, उसके लिए एक अद्वितीय हैश की गणना की जाती है और दो स्ट्रीम बनाई जाती हैं: प्राप्त स्ट्रीम और सेंड स्ट्रीम। प्राप्त स्ट्रीम अवरुद्ध है और क्लाइंट से एक संदेश का इंतजार कर रहा है। जैसे ही क्लाइंट से संदेश प्राप्त हुआ है, यह पुस्तकालय उपयोगकर्ता द्वारा पंजीकृत हैंडलर को प्रेषित किया जाता है। प्रसंस्करण के परिणामस्वरूप, पाँच में से एक घटना हो सकती है:
- ग्राहक को डिस्कनेक्ट करना (मान लें कि डिस्कनेक्ट अनुरोध आ गया है)
- एक ग्राहक को एक प्रतिक्रिया भेजना
- किसी अन्य क्लाइंट के लिए प्रतिक्रिया भेजना
- सभी जुड़े ग्राहकों के लिए एक प्रतिक्रिया भेजना
- ग्राहकों के एक समूह को एक प्रतिक्रिया भेजना
यदि अब कनेक्टेड क्लाइंट में से किसी एक को संदेश भेजना आवश्यक है, तो उसे इस ग्राहक की कतार भेजने वाले संदेश में रखा जाता है, और भेजने के लिए जिम्मेदार सूत्र को सूचित किया जाता है कि नए संदेश कतार में दिखाई दिए हैं।
स्पष्ट रूप से, डेटा प्रवाह नीचे चित्र द्वारा प्रदर्शित किया जा सकता है।
पुस्तकालय के नेटवर्क मॉड्यूल में डेटा प्रवाह क्लाइंट X सर्वर (लाल तीर) के लिए एक अनुरोध भेजता है। अनुरोध क्लाइंट के अनुरूप रिसीवर स्ट्रीम में प्राप्त होता है। यह तुरंत संदेश हैंडलर (पीला तीर) को कॉल करता है। प्रसंस्करण के परिणामस्वरूप, एक निश्चित प्रतिक्रिया बनती है, जिसे क्लाइंट एक्स (हरे तीर) की भेजने वाली कतार में रखा जाता है। प्रेषक कतार (काला तीर) में संदेशों के लिए भेजने की धारा की जाँच करता है और ग्राहक (नीला तीर) के लिए एक प्रतिक्रिया भेजता है।
उदाहरण (बहु-उपयोगकर्ता इको सर्वर) package com.gesoftware.venta.network; import com.gesoftware.venta.logging.LoggingUtility; import com.gesoftware.venta.network.handlers.IClientHandler; import com.gesoftware.venta.network.handlers.IServerHandler; import com.gesoftware.venta.network.model.Message; import com.gesoftware.venta.network.model.ServerResponse; import java.net.InetAddress; import java.util.TimerTask; public final class NetworkTest { private final static int c_Port = 5502; private static void startServer() { final Server server = new Server(c_Port, new IServerHandler() { @Override public boolean onConnect(final String clientID, final InetAddress clientAddress) { LoggingUtility.info("Client connected: " + clientID); return true; } @Override public ServerResponse onReceive(final String clientID, final Message message) { LoggingUtility.info("Client send message: " + message.toString()); return new ServerResponse(message); } @Override public void onDisconnect(final String clientID) { LoggingUtility.info("Client disconnected: " + clientID); } }); (new Thread(server)).start(); } private static class Task extends TimerTask { private final Connection m_Connection; public Task(final Connection connection) { m_Connection = connection; } @Override public void run() { m_Connection.send(new Message("Hello, current time is: " + System.currentTimeMillis())); } } private static void startClient() { final Connection connection = new Connection("localhost", c_Port, new IClientHandler() { @Override public void onReceive(final Message message) { LoggingUtility.info("Server answer: " + message.toString()); } @Override public void onConnectionLost(final String message) { LoggingUtility.info("Connection lost: " + message); } }); connection.connect(); (new java.util.Timer("Client")).schedule(new Task(connection), 0, 1000); } public static void main(final String args[]) { LoggingUtility.setLoggingLevel(LoggingUtility.LoggingLevel.LEVEL_DEBUG); startServer(); startClient(); } }
बहुत छोटा है, है ना?
खेल सर्वर
गेम सर्वर की वास्तुकला बहुस्तरीय है। तुरंत मैं उसे स्कीम दूंगा, और फिर एक विवरण।
तो, कनेक्शन पूल का उपयोग डेटाबेस के साथ बातचीत करने के लिए किया जाता है (मैं BoneCP लाइब्रेरी का उपयोग करता हूं)। तैयार बयानों के साथ काम करने के लिए, मैंने कनेक्शन को अपनी कक्षा (वेंटा लाइब्रेरी) में लपेटा।
DBConnection.java package com.gesoftware.venta.db; import com.gesoftware.venta.logging.LoggingUtility; import com.jolbox.bonecp.BoneCPConfig; import com.jolbox.bonecp.BoneCP; import java.io.InputStream; import java.util.AbstractList; import java.util.LinkedList; import java.util.HashMap; import java.util.Map; import java.sql.*; public final class DBConnection { private BoneCP m_Pool; public final class DBStatement { private final PreparedStatement m_Statement; private final Connection m_Connection; private DBStatement(final Connection connection, final PreparedStatement statement) { m_Connection = connection; m_Statement = statement; } public final boolean setInteger(final int index, final int value) { try { m_Statement.setInt(index, value); return true; } catch (final SQLException e) { LoggingUtility.debug("Can't set integer value: " + value + " because of " + e.getMessage()); } return false; } public final boolean setLong(final int index, final long value) { try { m_Statement.setLong(index, value); return true; } catch (final SQLException e) { LoggingUtility.debug("Can't set long value: " + value + " because of " + e.getMessage()); } return false; } public final boolean setString(final int index, final String value) { try { m_Statement.setString(index, value); } catch (final SQLException e) { LoggingUtility.debug("Can't set string value: " + value + " because of " + e.getMessage()); } return false; } public final boolean setEnum(final int index, final Enum value) { return setString(index, value.name()); } public final boolean setBinaryStream(final int index, final InputStream stream, final long length) { try { m_Statement.setBinaryStream(index, stream); return true; } catch (final SQLException e) { LoggingUtility.debug("Can't set stream value: " + stream + " because of " + e.getMessage()); } return false; } } public DBConnection(final String host, final int port, final String name, final String user, final String pass) { final BoneCPConfig config = new BoneCPConfig(); config.setJdbcUrl("jdbc:mysql://" + host + ":" + port + "/" + name); config.setUsername(user); config.setPassword(pass); config.setMaxConnectionsPerPartition(5); config.setMinConnectionsPerPartition(5); config.setPartitionCount(1); try { m_Pool = new BoneCP(config); } catch (final SQLException e) { LoggingUtility.error("Can't initialize connections pool: " + e.getMessage()); m_Pool = null; } } @Override protected final void finalize() throws Throwable { super.finalize(); if (m_Pool != null) m_Pool.shutdown(); } public final DBStatement createStatement(final String query) { try { LoggingUtility.debug("Total: " + m_Pool.getTotalCreatedConnections() + "; Free: " + m_Pool.getTotalFree() + "; Leased: " + m_Pool.getTotalLeased()); final Connection connection = m_Pool.getConnection(); return new DBStatement(connection, connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)); } catch (final SQLException e) { LoggingUtility.error("Can't create prepared statement using query: " + e.getMessage()); } catch (final Exception e) { LoggingUtility.error("Connection wasn't established: " + e.getMessage()); } return null; } private void closeStatement(final DBStatement query) { if (query == null) return; try { if (query.m_Statement != null) query.m_Statement.close(); if (query.m_Connection != null) query.m_Connection.close(); } catch (final SQLException ignored) {} } public final long insert(final DBStatement query) { try { query.m_Statement.execute(); final ResultSet resultSet = query.m_Statement.getGeneratedKeys(); if (resultSet.next()) return resultSet.getInt(1); } catch (final SQLException e) { LoggingUtility.error("Can't execute insert query: " + query.toString()); } finally { closeStatement(query); } return 0; } public final boolean update(final DBStatement query) { try { query.m_Statement.execute(); return true; } catch (final SQLException e) { LoggingUtility.error("Can't execute update query: " + query.m_Statement.toString()); } finally { closeStatement(query); } return false; } public final boolean exists(final DBStatement query) { final AbstractList<Map<String, Object>> results = select(query); return results != null && results.size() != 0; } public final AbstractList<Map<String, Object>> select(final DBStatement query) { try { final AbstractList<Map<String, Object>> results = new LinkedList<Map<String, Object>>(); query.m_Statement.execute(); final ResultSetMetaData metaData = query.m_Statement.getMetaData(); final ResultSet resultSet = query.m_Statement.getResultSet(); while (resultSet.next()) { final Map<String, Object> row = new HashMap<String, Object>(); for (int columnID = 1; columnID <= metaData.getColumnCount(); columnID++) row.put(metaData.getColumnName(columnID), resultSet.getObject(columnID)); results.add(row); } return results; } catch (final SQLException e) { LoggingUtility.error("Can't execute select query: " + query.toString()); } finally { closeStatement(query); } return null; } }
आपको DBController.java वर्ग पर भी ध्यान देना चाहिए:
DBController.java package com.gesoftware.venta.db; import com.gesoftware.venta.logging.LoggingUtility; import java.util.*; public abstract class DBController<T> { protected final DBConnection m_Connection; protected DBController(final DBConnection connection) { m_Connection = connection; LoggingUtility.core(getClass().getCanonicalName() + " controller initialized"); } protected final Collection<T> getCollection(final DBConnection.DBStatement selectStatement) { if (selectStatement == null) return new LinkedList<T>(); final AbstractList<Map<String, Object>> objectsCollection = m_Connection.select(selectStatement); if ((objectsCollection == null)||(objectsCollection.size() == 0)) return new LinkedList<T>(); final Collection<T> parsedObjectsCollection = new ArrayList<T>(objectsCollection.size()); for (final Map<String, Object> object : objectsCollection) parsedObjectsCollection.add(parse(object)); return parsedObjectsCollection; } protected final T getObject(final DBConnection.DBStatement selectStatement) { if (selectStatement == null) return null; final AbstractList<Map<String, Object>> objectsCollection = m_Connection.select(selectStatement); if ((objectsCollection == null)||(objectsCollection.size() != 1)) return null; return parse(objectsCollection.get(0)); } protected abstract T parse(final Map<String, Object> objectMap); }
DBController क्लास को किसी विशेष टेबल की वस्तुओं के साथ काम करने के लिए डिज़ाइन किया गया है। सर्वर अनुप्रयोग में, डेटाबेस तालिकाओं में से प्रत्येक के लिए नियंत्रक बनाए जाते हैं। नियंत्रक स्तर पर, डेटाबेस में डेटा डालने, पुनर्प्राप्त करने, अद्यतन करने के तरीके लागू किए जाते हैं।
कुछ ऑपरेशनों में एक साथ कई तालिकाओं में डेटा बदलने की आवश्यकता होती है। इसके लिए, एक प्रबंधक स्तर बनाया गया है। प्रत्येक प्रबंधक की पहुंच सभी नियंत्रकों तक होती है। प्रबंधक स्तर पर, उच्च-स्तर के संचालन को लागू किया जाता है, उदाहरण के लिए, "कमरा ए में उपयोगकर्ता एक्स रखें"। अमूर्तता के एक नए स्तर पर जाने के अलावा, प्रबंधक एक डेटा कैशिंग तंत्र को लागू करते हैं। उदाहरण के लिए, डेटाबेस में जाने की आवश्यकता नहीं है जब भी कोई प्रमाणित करने की कोशिश करता है या अपनी रेटिंग जानना चाहता है। उपयोगकर्ता या उपयोगकर्ता रेटिंग के लिए जिम्मेदार प्रबंधक इस डेटा को संग्रहीत करते हैं। इस प्रकार, डेटाबेस पर समग्र भार कम हो जाता है।
अमूर्तता का अगला स्तर हैंडलर है। निम्न वर्ग IserverHandler इंटरफ़ेस के कार्यान्वयन के रूप में उपयोग किया जाता है:
StickersHandler.java package com.gesoftware.stickers.server.handlers; import com.gesoftware.stickers.model.common.Definitions; public final class StickersHandler implements IServerHandler { private final Map<Class, StickersQueryHandler> m_Handlers = new SynchronizedMap<Class, StickersQueryHandler>(); private final StickersManager m_Context; private final JobsManager m_JobsManager; public StickersHandler(final DBConnection connection) { m_Context = new StickersManager(connection); m_JobsManager = new JobsManager(Definitions.c_TasksThreadSleepTime); registerQueriesHandlers(); registerJobs(); } private void registerJobs() { m_JobsManager.addTask(new TaskGameUpdateStatus(m_Context)); m_JobsManager.addTask(new TaskGameUpdatePhase(m_Context)); } private void registerQueriesHandlers() { m_Handlers.put(QueryAuthorization.class, new QueryAuthorizationHandler(m_Context)); m_Handlers.put(QueryRegistration.class, new QueryRegistrationHandler(m_Context)); m_Handlers.put(QueryRating.class, new QueryRatingHandler(m_Context)); m_Handlers.put(QueryLogout.class, new QueryLogoutHandler(m_Context)); m_Handlers.put(QueryRoomRefreshList.class, new QueryRoomRefreshListHandler(m_Context)); m_Handlers.put(QueryRoomCreate.class, new QueryRoomCreateHandler(m_Context)); m_Handlers.put(QueryRoomSelect.class, new QueryRoomSelectHandler(m_Context)); m_Handlers.put(QueryRoomLeave.class, new QueryRoomLeaveHandler(m_Context)); m_Handlers.put(QueryGameLeave.class, new QueryGameLeaveHandler(m_Context)); m_Handlers.put(QueryGameIsStarted.class, new QueryGameIsStartedHandler(m_Context)); m_Handlers.put(QueryGameWhichPhase.class, new QueryGameWhichPhaseHandler(m_Context)); m_Handlers.put(QueryGameAsk.class, new QueryGameAskHandler(m_Context)); m_Handlers.put(QueryGameAnswer.class, new QueryGameAnswerHandler(m_Context)); m_Handlers.put(QueryGameVote.class, new QueryGameVoteHandler(m_Context)); m_Handlers.put(QueryUserHasInvites.class, new QueryUserHasInvitesHandler(m_Context)); m_Handlers.put(QueryUserAvailable.class, new QueryUserAvailableHandler(m_Context)); m_Handlers.put(QueryUserInvite.class, new QueryUserInviteHandler(m_Context)); } @SuppressWarnings("unchecked") private synchronized Serializable userQuery(final String clientID, final Object query) { final StickersQueryHandler handler = getHandler(query.getClass()); if (handler == null) { LoggingUtility.error("Handler is not registered for " + query.getClass()); return new ResponseCommonMessage("Internal server error: can't process: " + query.getClass()); } return handler.processQuery(m_Context.getClientsManager().getClient(clientID), query); } private StickersQueryHandler getHandler(final Class c) { return m_Handlers.get(c); } private ServerResponse answer(final Serializable object) { return new ServerResponse(new Message(object)); } @Override public boolean onConnect(final String clientID, final InetAddress clientAddress) { LoggingUtility.info("User <" + clientID + "> connected from " + clientAddress.getHostAddress()); m_Context.getClientsManager().clientConnected(clientID); return true; } @Override public final ServerResponse onReceive(final String clientID, final Message message) { final Object object = message.getObject(); if (object == null) { LoggingUtility.error("Unknown object accepted"); return answer(new ResponseCommonMessage("Internal server error: empty object")); } return new ServerResponse(new Message(userQuery(clientID, object))); } @Override public void onDisconnect(final String clientID) { m_Context.getClientsManager().clientDisconnected(clientID); LoggingUtility.info("User <" + clientID + "> disconnected"); } public void stop() { m_JobsManager.stop(); } }
इस वर्ग में संबंधित हैंडलर ऑब्जेक्ट्स में अनुरोध ऑब्जेक्ट्स की मैपिंग होती है। यह दृष्टिकोण (हालांकि यह निष्पादन के समय में सबसे तेज़ नहीं है), मेरी राय में, कोड को अच्छी तरह से व्यवस्थित करने की अनुमति देता है। प्रत्येक हैंडलर अनुरोध से संबंधित केवल एक विशिष्ट कार्य को हल करता है। उदाहरण के लिए, उपयोगकर्ता पंजीकरण।
उपयोगकर्ता पंजीकरण प्रोसेसर package com.gesoftware.stickers.server.handlers.registration; import com.gesoftware.stickers.model.enums.UserStatus; import com.gesoftware.stickers.model.objects.User; import com.gesoftware.stickers.model.queries.registration.QueryRegistration; import com.gesoftware.stickers.model.responses.registration.ResponseRegistrationInvalidEMail; import com.gesoftware.stickers.model.responses.registration.ResponseRegistrationFailed; import com.gesoftware.stickers.model.responses.registration.ResponseRegistrationSuccessfully; import com.gesoftware.stickers.model.responses.registration.ResponseUserAlreadyRegistered; import com.gesoftware.stickers.server.handlers.StickersQueryHandler; import com.gesoftware.stickers.server.managers.StickersManager; import com.gesoftware.venta.logging.LoggingUtility; import com.gesoftware.venta.utility.ValidationUtility; import java.io.Serializable; public final class QueryRegistrationHandler extends StickersQueryHandler<QueryRegistration> { public QueryRegistrationHandler(final StickersManager context) { super(context); } @Override public final Serializable process(final User user, final QueryRegistration query) { if (!ValidationUtility.isEMailValid(query.m_EMail)) return new ResponseRegistrationInvalidEMail(); if (m_Context.getUsersManager().isUserRegistered(query.m_EMail)) return new ResponseUserAlreadyRegistered(); if (!m_Context.getUsersManager().registerUser(query.m_EMail, query.m_PasswordHash, query.m_Name)) return new ResponseRegistrationFailed(); LoggingUtility.info("User <" + user.m_ClientID + "> registered as " + query.m_EMail); return new ResponseRegistrationSuccessfully(); } @Override public final UserStatus getStatus() { return UserStatus.NotLogged; } }
कोड पढ़ना बहुत आसान है, है ना?
ग्राहक का आवेदन
क्लाइंट एप्लिकेशन हैंडलर के साथ ठीक उसी तर्क को लागू करता है, लेकिन केवल सर्वर प्रतिक्रियाएं। यह IClientHandler इंटरफ़ेस से विरासत में मिली कक्षा में लागू किया गया है।
विभिन्न गतिविधियों की संख्या खेल राज्यों की संख्या के समान है। सर्वर के साथ बातचीत का सिद्धांत काफी सरल है:
- उपयोगकर्ता कुछ कार्रवाई करता है (उदाहरण के लिए, "गेम दर्ज करें" बटन पर क्लिक करता है)
- क्लाइंट एप्लिकेशन उपयोगकर्ता के लिए एक प्रगति संवाद प्रदर्शित करता है।
- क्लाइंट एप्लिकेशन सर्वर पर उपयोगकर्ता क्रेडेंशियल्स भेजता है
- सर्वर अनुरोध को संसाधित करता है और प्रतिक्रिया भेजता है
- उत्तर के अनुरूप हैंडलर प्रगति संवाद को छुपाता है
- प्रतिक्रिया संसाधित होती है और परिणाम क्लाइंट को आउटपुट होते हैं
इस प्रकार, क्लाइंट और सर्वर दोनों पर व्यावसायिक तर्क बड़ी संख्या में छोटे संरचित वर्गों में विभाजित है।
एक और बात मैं इन-ऐप खरीदारी के बारे में बात करना चाहूंगा। जैसा कि यहां कई लेखों में देखा गया है, इन-ऐप खरीदारी एक आवेदन को मुद्रीकृत करने के लिए एक बहुत ही सुविधाजनक समाधान है। मैंने सलाह ली और विज्ञापन को विज्ञापन में जोड़ा और $ 1 के लिए इसे निष्क्रिय करने की क्षमता का फैसला किया।
जब मैं बिलिंग से निपटना शुरू कर रहा था, तो मैंने यह सोचने में बहुत समय लगा दिया कि यह Google पर कैसे काम करता है। लंबे समय से मैं यह समझने की कोशिश कर रहा था कि किसी सर्वर पर भुगतान कैसे मान्य किया जाए, क्योंकि ऐसा लगता है कि Google द्वारा भुगतान (कहना, भुगतान नंबर) के बारे में कुछ जानकारी जारी करने के बाद, इसे गेम सर्वर पर स्थानांतरित कर दिया जाए, और इससे Google API से संपर्क करके, जाँच करें चाहे भुगतान हो। जैसा कि यह निकला, ऐसी योजना केवल सदस्यता के लिए काम करती है। साधारण खरीद के लिए, सब कुछ बहुत सरल है। एप्लिकेशन में खरीदारी करते समय, Google JSON को खरीद और उसकी स्थिति (चेक) और इस चेक के इलेक्ट्रॉनिक हस्ताक्षर के बारे में जानकारी देता है। इस प्रकार, सब कुछ प्रश्न पर निर्भर करता है "क्या आप Google पर भरोसा करते हैं?"। :) वास्तव में, इस तरह की जोड़ी प्राप्त करने के बाद, इसे गेम सर्वर पर भेजा जाता है, जिसमें केवल दो चीजों की जांच करनी होगी:
- क्या आपने पहले ही सर्वर को इस तरह का अनुरोध भेजा है (यह Google द्वारा अनियंत्रित संचालन के लिए है, खेल मुद्रा खरीदना कहते हैं)
- क्या इलेक्ट्रॉनिक हस्ताक्षर द्वारा चेक को सही तरीके से हस्ताक्षरित किया गया है (आखिरकार, सामान्य Google कुंजी सर्वर सहित सभी के लिए जानी जाती है):
इस नोट पर, मैं अपनी पहली और अराजक कहानी समाप्त करना चाहूंगा।
मैंने अपने लेख को कई बार पढ़ा, मैं समझता हूं कि यह एक तकनीकी पाठ का आदर्श नहीं है, और शायद यह समझना मुश्किल है, लेकिन भविष्य में (यदि ऐसा होता है), मैं स्थिति को ठीक करने का प्रयास करूंगा।संदर्भ
तृतीय-पक्ष पुस्तकालय
निष्कर्ष
अगर किसी को अंत तक पढ़ने का धैर्य था, तो मैं आभार व्यक्त करता हूं, क्योंकि मैं एक पेशेवर लेखक होने का दिखावा नहीं करता । मैं आपको बहुत डांटने के लिए कहता हूं, क्योंकि यहां प्रकाशित करने का यह मेरा पहला अनुभव है। प्रकाशन के कारणों में से एक "हेब्राफ़ेक्ट" है जिसे मुझे सर्वर के लोड परीक्षण का संचालन करने की आवश्यकता है, साथ ही एक खेल दर्शकों की भर्ती करने की भी आवश्यकता है, इसलिए मैं प्रकाशन के उद्देश्य के स्वार्थी घटक के लिए माफी माँगता हूँ। मैं त्रुटियों / अशुद्धियों के संकेत के लिए आभारी रहूंगा। आपका ध्यान के लिए धन्यवाद!
अंत में, एक छोटा सा सर्वेक्षण (फिलहाल मैं इसे जोड़ नहीं सकता): क्या यह भविष्य में प्रकाशित होने लायक है? यदि ऐसा है, तो प्रकाशन किस विषय पर होगा:- गणित: रेखीय बीजगणित
- गणित: विश्लेषण
- गणित: संख्यात्मक और अनुकूलन के तरीके
- गणित: असतत गणित और एल्गोरिथम सिद्धांत
- गणित: कम्प्यूटेशनल ज्यामिति
- प्रोग्रामिंग: कंप्यूटर ग्राफिक्स की मूल बातें ( उदाहरण के लिए, यह परियोजना )
- प्रोग्रामिंग: शेडर प्रोग्रामिंग
- प्रोग्रामिंग: खेल विकास
- भौतिकी: सापेक्षता का सिद्धांत
- भौतिकी: खगोल भौतिकी
कहाँ क्या?