2013-01-07 11 views
10

का उपयोग करके एकाधिक सॉकेट में डेटा भेजें, मैं splice() का उपयोग करके एकाधिक सॉकेट्स को लिखने के लिए टीई() के साथ "मास्टर" पाइप डुप्लिकेट कर रहा हूं। स्वाभाविक रूप से इन पाइप अलग-अलग दरों पर खाली हो जाएंगे, इस पर निर्भर करता है कि मैं गंतव्य सॉकेट में कितना विभाजन कर सकता हूं। तो जब मैं अगला "मास्टर" पाइप में डेटा जोड़ने के लिए जाता हूं और फिर टीई() इसे फिर से करता हूं, तो मुझे ऐसी स्थिति हो सकती है जहां मैं पाइप पर 64 केबी लिख सकता हूं लेकिन केवल "दास" पाइप में से एक को 4 केबी टी कर सकता हूं। मैं अनुमान लगा रहा हूं कि अगर मैं सॉकेट में "मास्टर" पाइप को विभाजित करता हूं, तो मैं उस दास पाइप को शेष 60 केबी (टी) करने में सक्षम नहीं होगा। क्या यह सच है? मुझे लगता है कि मैं एक tee_offset (0 से शुरू) का ट्रैक रख सकता हूं जिसे मैंने "unteed" डेटा की शुरुआत में सेट किया है और उसके बाद इसे अलग नहीं किया है। तो इस मामले में मैं tee_offset को 4096 पर सेट कर दूंगा और इससे अधिक तब तक विभाजन नहीं करूँगा जब तक कि मैं इसे अन्य पाइपों तक नहीं कर पाता। क्या मैं यहां सही दिशा में चल रहा हूं? मेरे लिए कोई सुझाव/चेतावनियां?पाइप, टीई() और स्प्लिस()

उत्तर

20

यदि मैं सही ढंग से समझता हूं, तो आपके पास डेटा का कुछ वास्तविक समय स्रोत है जिसे आप एकाधिक सॉकेट में मल्टीप्लेक्स करना चाहते हैं। आपके पास अपने डेटा का उत्पादन करने वाले किसी भी "स्रोत" पाइप को मिला है, और आपके पास प्रत्येक सॉकेट के लिए "गंतव्य" पाइप है जिस पर आप डेटा भेजना चाहते हैं। आप क्या कर रहे हैं tee() का उपयोग स्रोत पाइप से प्रत्येक गंतव्य पाइप में डेटा कॉपी करने के लिए और splice() गंतव्य पाइप से सॉकेट में कॉपी करने के लिए कर रहा है।

मौलिक मुद्दा जो आप यहां हिट करने जा रहे हैं वह यह है कि यदि सॉकेट में से कोई भी नहीं रह सकता है - यदि आप डेटा भेज रहे हैं तो आप इसे भेज सकते हैं, तो आपको कोई समस्या होगी। यह आपके पाइप के उपयोग से संबंधित नहीं है, यह सिर्फ एक मौलिक मुद्दा है। तो, आप इस मामले में सामना करने के लिए एक रणनीति चुनना चाहेंगे - मैं सुझाव देता हूं कि अगर आप इसे सामान्य होने की उम्मीद नहीं करते हैं, तो भी ये चीजें अक्सर बाद में आपको काटने के लिए आती हैं। आपके मूल विकल्प या तो अपमानजनक सॉकेट को बंद करना है, या डेटा आउटपुट बफर को साफ़ करने तक डेटा छोड़ना है - उदाहरण के लिए बाद की पसंद ऑडियो/वीडियो स्ट्रीमिंग के लिए अधिक उपयुक्त हो सकती है।

समस्या जो पाइप के उपयोग से संबंधित है, हालांकि, लिनक्स पर एक पाइप के बफर का आकार कुछ हद तक लचीला है। लिनक्स 2.6.11 के बाद यह 64K तक डिफ़ॉल्ट है (tee() कॉल 2.6.17 में जोड़ा गया था) - pipe manpage देखें। 2.6.35 के बाद से यह मान F_SETPIPE_SZ विकल्प fcntl() (fcntl manpage पर) /proc/sys/fs/pipe-size-max द्वारा निर्दिष्ट सीमा तक बदल दिया जा सकता है, लेकिन उपयोगकर्ता-स्थान में गतिशील रूप से आवंटित योजना की तुलना में बफरिंग अभी भी अधिक अजीब है हो। इसका मतलब है कि धीमी सॉकेट से निपटने की आपकी क्षमता कुछ हद तक सीमित होगी - चाहे यह स्वीकार्य है उस दर पर निर्भर करता है जिस पर आप प्राप्त करने की उम्मीद करते हैं और डेटा भेजने में सक्षम होते हैं।

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

आप इसे कैसे संभालेंगे आपके डेटा के महत्व पर निर्भर करता है।यदि आपको वास्तव में tee() और splice() की गति की आवश्यकता है, और आपको विश्वास है कि धीमी सॉकेट एक बेहद दुर्लभ घटना होगी, तो आप ऐसा कुछ कर सकते हैं (मैंने माना है कि आप गैर-अवरुद्ध आईओ और एक थ्रेड का उपयोग कर रहे हैं , लेकिन इसी तरह यह भी एक से अधिक थ्रेड के साथ काम करेगा) कुछ:

  1. सभी पाइप गैर अवरुद्ध कर रहे हैं सुनिश्चित करें (fcntl(d, F_SETFL, O_NONBLOCK) का उपयोग प्रत्येक फ़ाइल वर्णनकर्ता गैर अवरुद्ध करने के लिए)।
  2. प्रत्येक गंतव्य पाइप के लिए शून्य पर read_counter चर प्रारंभ करें।
  3. स्रोत पाइप में कुछ होने तक प्रतीक्षा करने के लिए epoll() जैसे कुछ का उपयोग करें।
  4. सभी गंतव्य पाइपों पर लूप जहां read_counter शून्य है, प्रत्येक को डेटा स्थानांतरित करने के लिए tee() पर कॉल करना। सुनिश्चित करें कि आप झंडे में SPLICE_F_NONBLOCK पास करते हैं।
  5. tee() द्वारा स्थानांतरित राशि द्वारा प्रत्येक गंतव्य पाइप के लिए read_counter वृद्धि। सबसे कम परिणामी मूल्य का ट्रैक रखें।
  6. read_counter का निम्नतम परिणाम प्राप्त करें - यदि यह शून्य है, तो स्रोत पाइप से उस मात्रा को डेटा छोड़ दें (उदाहरण के लिए /dev/null पर खोले गए गंतव्य के साथ splice() कॉल का उपयोग करके)। डेटा छोड़ने के बाद, पाइप पर read_counter से निकाली गई राशि घटाएं (चूंकि यह सबसे कम मूल्य था, इसलिए इसका परिणाम नकारात्मक हो सकता है)।
  7. चरण से दोहराएं।

नोट: एक बात है कि मुझे अतीत में फिसल गया है SPLICE_F_NONBLOCK प्रभावित करता है कि क्या पाइपों पर tee() और splice() संचालन गैर अवरुद्ध कर रहे हैं, और O_NONBLOCK आप fnctl() के साथ सेट है कि क्या अन्य कॉल के साथ बातचीत को प्रभावित करता है (उदाहरण के लिए read() और write()) गैर-अवरुद्ध हैं। यदि आप सबकुछ गैर-अवरुद्ध होना चाहते हैं, तो दोनों सेट करें। यह भी याद रखें कि आपके सॉकेट को गैर-अवरुद्ध करना या splice() कॉल डेटा को स्थानांतरित करने के लिए कॉल कर सकते हैं (जब तक कि आप थ्रेड किए गए दृष्टिकोण का उपयोग नहीं कर रहे हों)।

जैसा कि आप देख सकते हैं, इस रणनीति में एक बड़ी समस्या है - जैसे ही एक सॉकेट ब्लॉक हो जाता है, सबकुछ बंद हो जाता है - उस सॉकेट के लिए गंतव्य पाइप भर जाएगी, और फिर स्रोत पाइप स्थिर हो जाएगा। इसलिए, यदि आप चरण tee()EAGAIN चरण में EAGAIN लौटाते हैं तो आप या तो उस सॉकेट को बंद करना चाहते हैं, या कम से कम "डिस्कनेक्ट" करना चाहते हैं (यानी इसे अपने लूप से बाहर ले जाएं) जैसे कि आप नहीं लिखते इसके आउटपुट बफर खाली होने तक इसके लिए और कुछ भी। जो आप चुनते हैं उस पर निर्भर करता है कि क्या आपकी डेटा स्ट्रीम स्किप की गई बिट्स से वसूली कर सकती है या नहीं।

आप अधिक शान से फिर नेटवर्क विलंबता से निपटने के लिए आप और अधिक बफरिंग करने की जरूरत करने जा रहे हैं, और यह या तो उपयोगकर्ता के अंतरिक्ष बफ़र्स शामिल करने के लिए या शायद (जो बल्कि tee() और splice() के फायदे को नकारता) जा रहा है चाहते हैं डिस्क-आधारित बफर। डिस्क-आधारित बफरिंग निश्चित रूप से उपयोगकर्ता-स्पेस बफरिंग से काफी धीमी हो जाएगी, और इसलिए उचित नहीं है कि संभवतः आप बहुत तेज गति चाहते हैं क्योंकि आपने पहले स्थान पर tee() और splice() चुना है, लेकिन मैं इसे पूर्णता के लिए उल्लेख करता हूं।

एक बात करता है, तो आप अंत किसी भी बिंदु पर उपयोगकर्ता के अंतरिक्ष से डेटा डालने कि ध्यान देने योग्य है vmsplice() कॉल जो writev() कॉल करने के लिए एक पाइप में उपयोगकर्ता के अंतरिक्ष से प्रदर्शन कर सकते हैं, "उत्पादन इकट्ठा" एक समान तरीके से है।यह उपयोगी हो सकता है यदि आप पर्याप्त बफरिंग कर रहे हैं कि आपने अपने डेटा को कई अलग आवंटित बफर के बीच विभाजित किया है (उदाहरण के लिए यदि आप पूल आवंटन दृष्टिकोण का उपयोग कर रहे हैं)।

अंत में, आप tee() और splice() का उपयोग करने की "तेज़" योजना के बीच स्वैपिंग सॉकेट की कल्पना कर सकते हैं और यदि वे जारी रखने में विफल रहते हैं, तो उन्हें धीमे उपयोगकर्ता-स्थान बफरिंग पर ले जा सकते हैं। यह आपके कार्यान्वयन को जटिल बनाने जा रहा है, लेकिन यदि आप बड़ी संख्या में कनेक्शन प्रबंधित कर रहे हैं और उनमें से केवल एक छोटा सा अनुपात धीमा है तो आप अभी भी कुछ हद तक शामिल उपयोगकर्ता-स्थान पर प्रतिलिपि बनाने की मात्रा को कम कर रहे हैं। हालांकि, यह क्षणिक नेटवर्क मुद्दों से निपटने के लिए कभी भी एक अल्पकालिक उपाय होगा - जैसा कि मैंने मूल रूप से कहा था, यदि आपके सॉकेट आपके स्रोत से धीमे हैं तो आपको मौलिक समस्या मिली है। अंततः आप कुछ बफरिंग सीमा को हिट करेंगे और डेटा या क्लोज़ कनेक्शन को छोड़ने की आवश्यकता होगी।

कुल मिलाकर, मैं ध्यान से विचार किया जाएगा तुम क्यों tee() और splice() की गति की जरूरत है और क्या, आपके उपयोग-मामले के लिए, बस स्मृति में या डिस्क पर उपयोगकर्ता के अंतरिक्ष बफरिंग अधिक उचित होगा। यदि आप आश्वस्त हैं कि गति हमेशा उच्च रहेगी, और सीमित बफरिंग स्वीकार्य है तो ऊपर उल्लिखित दृष्टिकोण को काम करना चाहिए।

इसके अलावा, मुझे एक बात का जिक्र करना चाहिए कि यह आपके कोड को बेहद लिनक्स-विशिष्ट बना देगा - मुझे पता नहीं है कि इन यूनिक्सों को अन्य यूनिक्स रूपों में समर्थन दिया जा रहा है। sendfile() कॉल splice() से अधिक प्रतिबंधित है, लेकिन अधिक पोर्टेबल हो सकता है। यदि आप वास्तव में चीजों को पोर्टेबल बनाना चाहते हैं, तो उपयोगकर्ता-स्थान बफरिंग तक चिपके रहें।

मुझे बताएं कि क्या कुछ भी है जो मैंने कवर किया है जिसे आप अधिक विस्तार चाहते हैं।

+3

मेरी इच्छा है कि मैं आपका जवाब +10 कर सकता हूं। हां आपने मेरी समस्या का अच्छी तरह से वर्णन किया है, और यदि कोई सॉकेट नहीं रख सकता है तो आप समस्या के बारे में सही हैं। प्रत्येक सॉकेट को एक ही गति पर, समय के साथ, जब तक कोई प्राप्तकर्ता विफल नहीं हो जाता है। इस मामले में केवल एक समझदार चीज इसे प्रतिकृति सेट से छोड़ देती है। लेकिन जिस चीज को आपने याद किया है वह यह है कि जबकि पाइप डिफ़ॉल्ट रूप से 64 केबी होते हैं, तो आप उन्हें 1 एमबी तक फेंक सकते हैं (एक सीमा स्वयं जिसे संशोधित/proc/sys/fs/pipe-max-size द्वारा संशोधित किया जा सकता है।) मेरे पास है पर्याप्त स्मृति है कि मैं प्रत्येक पाइप के लिए 64 एमबी आवंटित कर सकता था। तुम क्या सोचते हो? – Eloff

+0

मुझे कभी भी 'F_SETPIPE_SZ' के बारे में पता नहीं था, धन्यवाद! मैंने अपना जवाब संपादित कर लिया है। याद रखें कि 2.6.35 अभी भी * थोड़ा * नया है (उदा। उबंटू 10.04 एलटीएस 2.6.32 AFAIK है)। जब तक आप लिनक्स-विशिष्टता को ध्यान में रखते हैं, तब तक दृष्टिकोण ठीक लगता है। मैं कोड के दायरे को सीमित करने की कोशिश करता हूं जो कि लिनक्स-विशिष्ट है, बस मामले में। याद रखने के लिए केवल एक और बात यह है कि यह समाधान का केवल एक पहलू है - यदि प्रदर्शन महत्वपूर्ण है तो मैं यह देखने के लिए गैर-अवरुद्ध आईओ बनाम धागे बनाम प्रक्रियाओं के साथ खेलना चाहता हूं जो आपके लिए सबसे अच्छा काम करता है। पाइप के बारे में अच्छी चीजों में से एक यह है कि यदि आपको इसकी आवश्यकता हो तो वे 'कांटा()' में अच्छी तरह से काम करते हैं। – Cartroo

+0

एक बात जिसे मैं उल्लेख करना भूल गया - एक मल्टीप्रोसेस दृष्टिकोण आज के मल्टीकोर सिस्टम पर प्रदर्शन को बेहतर बनाने का एक स्पष्ट तरीका प्रतीत हो सकता है, लेकिन ध्यान रखें कि बहुत मेमोरी शेयरिंग होने जा रही है, इसलिए यह सीधा नहीं है। उदाहरण के लिए, [NUMA] (http://en.wikipedia.org/wiki/Non-Uniform_Memory_Access) आर्किटेक्चर (उदा। एएमडी ऑप्टरन्स) पर, एकाधिक कोर अक्सर एक ही मेमोरी तक पहुंचने से प्रदर्शन हिट बना सकते हैं। यहां तक ​​कि [एसएमपी] (http://en.wikipedia.org/wiki/Symmetric_multiprocessing) सिस्टम पर यह स्पष्ट नहीं है कि क्या आपकी बाधा मेमोरी बस है तो मल्टीप्रोसेस आपको कुछ खरीद लेगा या नहीं। – Cartroo

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