क्या एक सुन सॉकेट को साझा करने के लिए कई प्रक्रियाओं का एक तरीका है?


90

सॉकेट प्रोग्रामिंग में, आप एक सुनने वाला सॉकेट बनाते हैं और फिर कनेक्ट होने वाले प्रत्येक क्लाइंट के लिए, आपको एक सामान्य स्ट्रीम सॉकेट मिलता है जिसका उपयोग आप क्लाइंट के अनुरोध को संभालने के लिए कर सकते हैं। ओएस पर्दे के पीछे आने वाले कनेक्शनों की कतार का प्रबंधन करता है।

दो प्रक्रियाएँ एक ही समय में एक ही पोर्ट से नहीं जुड़ सकती हैं - वैसे भी, डिफ़ॉल्ट रूप से।

मैं सोच रहा था कि क्या कोई रास्ता है (किसी भी प्रसिद्ध ओएस, विशेष रूप से विंडोज पर) एक प्रक्रिया के कई उदाहरणों को लॉन्च करने के लिए, जैसे कि वे सभी सॉकेट से बांधते हैं, और इसलिए वे प्रभावी रूप से कतार साझा करते हैं। प्रत्येक प्रक्रिया उदाहरण तब एकल पिरोया जा सकता है; नया कनेक्शन स्वीकार करते समय यह अवरुद्ध हो जाएगा। जब कोई ग्राहक जुड़ा होता है, तो निष्क्रिय प्रक्रिया में से एक उस ग्राहक को स्वीकार कर लेता है।

यह प्रत्येक प्रक्रिया को एक बहुत ही सरल, एकल-थ्रेडेड कार्यान्वयन करने की अनुमति देगा, जब तक कि स्पष्ट साझा की गई मेमोरी के माध्यम से कुछ भी साझा न किया जाए, और उपयोगकर्ता अधिक उदाहरणों को शुरू करके प्रसंस्करण बैंडविड्थ को समायोजित करने में सक्षम होगा।

क्या ऐसी सुविधा मौजूद है?

संपादित करें: उन लोगों के लिए जो "थ्रेड्स का उपयोग क्यों नहीं करते?" जाहिर है धागे एक विकल्प हैं। लेकिन एक ही प्रक्रिया में कई थ्रेड्स के साथ, सभी ऑब्जेक्ट्स साझा करने योग्य होते हैं और यह सुनिश्चित करने के लिए बहुत सावधानी बरतनी होती है कि ऑब्जेक्ट्स या तो साझा नहीं किए जाते हैं, या केवल एक समय में एक थ्रेड के लिए दिखाई देते हैं, या बिल्कुल अपरिवर्तनीय हैं, और सबसे लोकप्रिय भाषाएं हैं और रनटम्स में इस जटिलता के प्रबंधन के लिए अंतर्निहित समर्थन का अभाव है।

एक समान कार्यकर्ता प्रक्रियाओं को शुरू करने से, आपको एक समवर्ती प्रणाली मिलेगी जिसमें डिफ़ॉल्ट कोई साझाकरण नहीं है, जिससे एक सही और मापनीय कार्यान्वयन का निर्माण करना बहुत आसान हो जाता है।


2
मैं सहमत हूं, एक सही और मजबूत कार्यान्वयन बनाने के लिए कई प्रक्रियाएं आसान बना सकती हैं। स्केलेबल, मुझे यकीन नहीं है, यह आपकी समस्या डोमेन पर निर्भर करता है।
मार्क

जवाबों:


92

आप लिनक्स और यहां तक ​​कि विंडोज में दो (या अधिक) प्रक्रियाओं के बीच एक सॉकेट साझा कर सकते हैं।

Linux (या POSIX प्रकार OS) के अंतर्गत, fork()अभिभावक बच्चे के लिए माता-पिता के सभी फ़ाइल विवरणों की प्रतियां रखने का कारण होगा। जो भी इसे बंद नहीं करता है वह साझा करना जारी रखेगा, और उदाहरण के लिए (टीसीपी सुनने वाले सॉकेट के साथ) का उपयोग accept()ग्राहकों के लिए नए सॉकेट के लिए किया जा सकता है। यह है कि ज्यादातर मामलों में अपाचे सहित कई सर्वर काम करते हैं।

विंडोज पर एक ही बात मूल रूप से सच है, सिवाय इसके कि कोई fork()सिस्टम कॉल नहीं है, इसलिए मूल प्रक्रिया का उपयोग करने की आवश्यकता होगीCreateProcess चाइल्ड प्रोसेस (जो निश्चित रूप से एक ही निष्पादन योग्य का उपयोग कर सकते हैं) बनाने के या कुछ और करने की आवश्यकता होगी, और इसे एक इनहेरिटेबल हैंडल पास करने की आवश्यकता है।

सुनने वाले सॉकेट को एक अंतर्निहित संभाल बनाना पूरी तरह से तुच्छ गतिविधि नहीं है, लेकिन बहुत मुश्किल भी नहीं है। DuplicateHandle()डुप्लिकेट हैंडल (हालांकि मूल प्रक्रिया में अभी भी) बनाने के लिए उपयोग करने की आवश्यकता है, जिस पर अंतर्निहित ध्वज सेट होगा। फिर आप STARTUPINFOCreateProcess में चाइल्ड प्रोसेस को स्ट्रक्चर में उस हैंडल को एक STDIN, OUTया ERRहैंडल के रूप में दे सकते हैं (यह मानते हुए कि आप इसे किसी और चीज के लिए इस्तेमाल नहीं करना चाहते हैं)।

संपादित करें:

एमडीएसएन लाइब्रेरी को पढ़ना, ऐसा प्रतीत होता है कि WSADuplicateSocketऐसा करने का अधिक मजबूत या सही तंत्र है; यह अभी भी अनौपचारिक है क्योंकि माता-पिता / बच्चे की प्रक्रियाओं को काम करने की आवश्यकता है जो कुछ आईपीसी तंत्र द्वारा दोहराए जाने की आवश्यकता है (हालांकि यह फाइलसिस्टम में फ़ाइल के रूप में सरल हो सकता है)

स्पष्टीकरण:

ओपी के मूल प्रश्न के उत्तर में, नहीं, कई प्रक्रियाएं नहीं हो सकती हैं bind(); सिर्फ मूल जनक प्रक्रिया कहेंगे bind(), listen()आदि, बच्चे प्रक्रियाओं बस द्वारा अनुरोध को संसाधित हैं accept(), send(), recv()आदि


3
एकाधिक प्रक्रिया सॉकेट ऑप्शननाम निर्दिष्ट करके बाँध सकती हैं। RuseAddress सॉकेट विकल्प।
सिपविज़

पर बात क्या है? वैसे भी प्रक्रियाएं थ्रेड्स की तुलना में अधिक भारी होती हैं।
एंटोन टायखी

7
प्रक्रियाएं थ्रेड्स की तुलना में अधिक भारी हैं, लेकिन जैसा कि वे केवल स्पष्ट रूप से साझा की गई चीजों को साझा करते हैं, कम सिंक्रनाइज़ेशन की आवश्यकता होती है जो प्रोग्रामिंग को आसान बनाता है और कुछ मामलों में और भी अधिक कुशल हो सकता है।
मार्क

11
इसके अलावा, यदि कोई बच्चा किसी तरह से दुर्घटनाग्रस्त हो जाता है या टूट जाता है, तो इससे माता-पिता के प्रभावित होने की संभावना कम होती है।
मार्कर

3
यह भी नोट करने के लिए अच्छा है कि, लिनक्स में, आप कांटे का उपयोग किए बिना अन्य कार्यक्रमों के लिए सॉकेट्स को "पास" कर सकते हैं (और उनके माता-पिता / बच्चे के संबंध नहीं हैं, यूनिक्स सॉकेट्स का उपयोग कर सकते हैं)।
राही

34

अधिकांश अन्य ने तकनीकी कारण बताए हैं कि यह क्यों काम करता है। यहाँ कुछ अजगर कोड है जिसे आप अपने लिए प्रदर्शित करने के लिए चला सकते हैं:

import socket
import os

def main():
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serversocket.bind(("127.0.0.1", 8888))
    serversocket.listen(0)

    # Child Process
    if os.fork() == 0:
        accept_conn("child", serversocket)

    accept_conn("parent", serversocket)

def accept_conn(message, s):
    while True:
        c, addr = s.accept()
        print 'Got connection from in %s' % message
        c.send('Thank you for your connecting to %s\n' % message)
        c.close()

if __name__ == "__main__":
    main()

ध्यान दें कि वास्तव में आईडी की दो प्रक्रियाएं हैं:

$ lsof -i :8888
COMMAND   PID    USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  26972 avaitla    3u  IPv4 0xc26aa26de5a8fc6f      0t0  TCP localhost:ddi-tcp-1 (LISTEN)
Python  26973 avaitla    3u  IPv4 0xc26aa26de5a8fc6f      0t0  TCP localhost:ddi-tcp-1 (LISTEN)

टेलनेट और प्रोग्राम चलाने के परिणाम इस प्रकार हैं:

$ telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Thank you for your connecting to parent
Connection closed by foreign host.
$ telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Thank you for your connecting to child
Connection closed by foreign host.
$ telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Thank you for your connecting to parent
Connection closed by foreign host.

$ python prefork.py 
Got connection from in parent
Got connection from in child
Got connection from in parent

2
तो एक कनेक्शन के लिए, या तो माता-पिता या बच्चे को मिलता है। लेकिन जो कनेक्शन प्राप्त करता है वह अनिश्चितकालीन है, है ना?
Hot.PxL

1
हां, मुझे लगता है कि यह इस बात पर निर्भर करता है कि ओएस द्वारा किस प्रक्रिया को चलाया जाना है।
अनिल वैतला

14

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

देख:

AF_UNIX सॉकेट बनाने के लिए:

उदाहरण के लिए कोड:


13

ऐसा लगता है कि इस सवाल का जवाब पहले ही पूरी तरह से MarkR और zackthehack द्वारा दिया जा चुका है, लेकिन मैं जोड़ना चाहूंगा कि Nginx सुनने वाले सॉकेट इनहेरिटेंस मॉडल का एक उदाहरण है।

यहाँ एक अच्छा वर्णन है:

         Implementation of HTTP Auth Server Round-Robin and
                Memory Caching for NGINX Email Proxy

                            June 6, 2007
             Md. Mansoor Peerbhoy <mansoor@zimbra.com>

...

एक NGINX कार्यकर्ता प्रक्रिया का प्रवाह

मुख्य एनजीआईएनएक्स प्रक्रिया के बाद कॉन्फ़िगरेशन फ़ाइल और वर्कर्स की कॉन्फ़िगर की गई संख्या में फ़र्क की प्रक्रिया को पढ़ता है, प्रत्येक कार्यकर्ता प्रक्रिया एक लूप में प्रवेश करती है जहां यह अपने संबंधित सॉकेट्स के किसी भी इवेंट की प्रतीक्षा करता है।

प्रत्येक कार्यकर्ता प्रक्रिया सिर्फ सुनने वाले सॉकेट से शुरू होती है, क्योंकि अभी तक कोई कनेक्शन उपलब्ध नहीं हैं। इसलिए, प्रत्येक कार्यकर्ता प्रक्रिया के लिए सेट किया गया ईवेंट डिस्क्रिप्टर केवल सुनने वाले सॉकेट्स से शुरू होता है।

(नोट) NGINX को कई ईवेंट पोलिंग तंत्रों में से किसी एक का उपयोग करने के लिए कॉन्फ़िगर किया जा सकता है: aio / devpoll / epoll / eventpoll / kqueue / poll / rtsig / select

जब कोई कनेक्शन सुनने वाले सॉकेट्स (POP3 / IMAP / SMTP) में से किसी एक पर आता है, तो प्रत्येक कार्यकर्ता प्रक्रिया उसके ईवेंट पोल से निकलती है, क्योंकि प्रत्येक NGINX कार्यकर्ता प्रक्रिया सुनने वाले सॉकेट को विरासत में मिलती है। फिर, प्रत्येक NGINX कार्यकर्ता प्रक्रिया एक वैश्विक म्यूटेक्स का अधिग्रहण करने का प्रयास करेगी। कार्यकर्ता प्रक्रियाओं में से एक लॉक का अधिग्रहण करेगा, जबकि अन्य अपने संबंधित ईवेंट पोलिंग लूप में वापस जाएंगे।

इस बीच, वैश्विक म्यूटेक्स का अधिग्रहण करने वाली कार्यकर्ता प्रक्रिया ट्रिगर घटनाओं की जांच करेगी, और ट्रिगर किए गए प्रत्येक इवेंट के लिए आवश्यक कार्य कतार अनुरोध बनाएगी। एक ईवेंट एक एकल सॉकेट डिस्क्रिप्टर से मेल खाता है जो डिस्क्रिप्टर के सेट से आता है जिसे कार्यकर्ता घटनाओं से देख रहा था।

यदि ट्रिगर की गई घटना एक नए आने वाले कनेक्शन से मेल खाती है, तो NGINX सुनने वाले सॉकेट से कनेक्शन को स्वीकार करता है। फिर, यह फ़ाइल डिस्क्रिप्टर के साथ एक संदर्भ डेटा संरचना को जोड़ता है। यह संदर्भ कनेक्शन के बारे में जानकारी रखता है (चाहे POP3 / IMAP / SMTP, उपयोगकर्ता अभी भी प्रमाणित है, आदि)। फिर, इस नवनिर्मित सॉकेट को उस कार्यकर्ता प्रक्रिया के लिए सेट किए गए इवेंट डिस्क्रिप्टर में जोड़ा जाता है।

कार्यकर्ता अब म्यूटेक्स (जिसका अर्थ है कि कोई भी घटना जो अन्य श्रमिकों पर पहुंच सकती है, भविष्यवाणी कर सकता है) को त्याग देता है, और प्रत्येक अनुरोध को संसाधित करना शुरू कर देता है जो पहले कतारबद्ध था। प्रत्येक अनुरोध उस घटना से मेल खाती है जिसे संकेत दिया गया था। प्रत्येक सॉकेट डिस्क्रिप्टर से जो संकेत दिया गया था, कार्यकर्ता प्रक्रिया संबंधित विवरण डेटा संरचना को पुनः प्राप्त करता है जो पहले उस डिस्क्रिप्टर से जुड़ा था, और फिर संबंधित कॉल बैक फ़ंक्शन को कॉल करता है जो उस कनेक्शन की स्थिति के आधार पर कार्रवाई करते हैं। उदाहरण के लिए, एक नए स्थापित IMAP कनेक्शन के मामले में, NGINX जो पहली चीज़ करेगा, वह है
जुड़े हुए सॉकेट पर मानक IMAP स्वागत संदेश (* OK IMAP4 तैयार)।

द्वारा और प्रत्येक कार्यकर्ता प्रक्रिया प्रत्येक उत्कृष्ट घटना के लिए कार्य कतार प्रविष्टि को संसाधित करती है, और अपने ईवेंट पोलिंग लूप में वापस आती है। एक बार जब किसी ग्राहक के साथ कोई संबंध स्थापित किया जाता है, तो आमतौर पर घटनाएं अधिक तेजी से होती हैं, क्योंकि जब भी कनेक्टेड सॉकेट पढ़ने के लिए तैयार होता है, तो रीड इवेंट को ट्रिगर किया जाता है, और संबंधित कार्रवाई की जानी चाहिए।


11

यह सुनिश्चित नहीं है कि यह मूल प्रश्न के लिए कितना प्रासंगिक है, लेकिन लिनक्स कर्नेल 3.9 में टीसीपी / यूडीपी सुविधा जोड़ने वाला एक पैच है: SO_REUSEPORT सॉकेट विकल्प के लिए टीसीपी और यूडीपी समर्थन; नया सॉकेट विकल्प एक ही होस्ट पर एक ही पोर्ट पर बाइंड करने के लिए कई सॉकेट्स की अनुमति देता है, और मल्टीकोर सिस्टम नेटवर्क के अनुप्रयोगों के प्रदर्शन को सुधारने का इरादा है मल्टीकोर सिस्टम के शीर्ष पर चल रहा है। संदर्भ लिंक में वर्णित अधिक जानकारी लिनक्स कर्नेल 3.9 में LWN SOWN में उपलब्ध हो सकती है :

SO_REUSEPORT विकल्प गैर-मानक है, लेकिन कई अन्य UNIX प्रणालियों के समान रूप में उपलब्ध है (विशेष रूप से, BSD, जहां यह विचार उत्पन्न हुआ था)। यह कांटा पैटर्न का उपयोग किए बिना, मल्टीकोर सिस्टम पर चल रहे नेटवर्क अनुप्रयोगों में से अधिकतम प्रदर्शन को निचोड़ने के लिए एक उपयोगी विकल्प प्रदान करता है।


LWN लेख से यह लगभग ऐसा लगता है जैसे SO_REUSEPORTएक थ्रेड पूल बनाता है, जहां प्रत्येक सॉकेट एक अलग थ्रेड पर होता है, लेकिन समूह में केवल एक सॉकेट ही कार्य करता है accept। क्या आप समूह में सभी सॉकेट की पुष्टि कर सकते हैं, प्रत्येक को डेटा की एक प्रति मिल सकती है?
jww

4

लिनक्स 3.9 से शुरू होकर, आप सॉकेट पर SO_REUSEPORT सेट कर सकते हैं और फिर उस सॉकेट में कई गैर-संबंधित प्रक्रियाएं साझा कर सकते हैं। यह पूर्ववर्ती योजना की तुलना में सरल है, कोई और अधिक सिग्नल की परेशानी, बच्चे की प्रक्रियाओं के लिए एफडी रिसाव, आदि।

लिनक्स 3.9 ने सॉकेट सर्वर लिखने का नया तरीका पेश किया

SO_REUSEPORT सॉकेट विकल्प


3

एक ही काम है जिसका एकमात्र काम आने वाले कनेक्शनों के लिए सुनना है। जब एक कनेक्शन प्राप्त होता है, तो यह कनेक्शन को स्वीकार करता है - यह एक अलग सॉकेट डिस्क्रिप्टर बनाता है। स्वीकृत सॉकेट आपके उपलब्ध कार्यकर्ता कार्यों में से एक को पारित किया जाता है, और मुख्य कार्य सुनने के लिए वापस चला जाता है।

s = socket();
bind(s);
listen(s);
while (1) {
  s2 = accept(s);
  send_to_worker(s2);
}

श्रमिक को सॉकेट कैसे दिया जाता है? ध्यान रखें कि विचार यह है कि एक कार्यकर्ता एक अलग प्रक्रिया है।
डैनियल इयरविकर

कांटा () शायद, या ऊपर दिए गए अन्य विचारों में से एक। या हो सकता है कि आप डेटा प्रोसेसिंग से सॉकेट I / O को पूरी तरह से अलग कर दें; एक आईपीसी तंत्र के माध्यम से कार्यकर्ता प्रक्रियाओं को पेलोड भेजें। OpenSSH और अन्य OpenBSD उपकरण इस पद्धति का उपयोग करते हैं (थ्रेड्स के बिना)।
हुगहुगाह 15

3

विंडोज (लिनक्स और लिनक्स) के तहत एक प्रक्रिया के लिए एक सॉकेट खोलना संभव है और फिर उस सॉकेट को किसी अन्य प्रक्रिया में पास करना है, ताकि दूसरी प्रक्रिया भी उस सॉकेट का उपयोग कर सके (और बदले में इसे पास करे, क्या ऐसा करना चाहिए) ।

महत्वपूर्ण फ़ंक्शन कॉल WSADuplicateSocket () है।

यह एक मौजूदा सॉकेट के बारे में जानकारी के साथ एक संरचना को आबाद करता है। यह संरचना तब, आपकी पसंद के IPC तंत्र के माध्यम से, एक अन्य मौजूदा प्रक्रिया को पारित कर दी जाती है (ध्यान दें कि मैं मौजूदा कहता हूं - जब आप WSADuplicateSocket () कहते हैं, तो आपको लक्ष्य प्रक्रिया का संकेत देना चाहिए जो उत्सर्जित सूचना प्राप्त करेगी)।

प्राप्त करने की प्रक्रिया तब सूचना की इस संरचना में गुजरने वाले WSASocket () को कॉल कर सकती है, और अंतर्निहित सॉकेट के लिए एक हैंडल प्राप्त कर सकती है।

दोनों प्रक्रियाएं अब एक ही अंतर्निहित सॉकेट के लिए एक हैंडल रखती हैं।


2

ऐसा लगता है कि आप जो चाहते हैं वह एक प्रक्रिया है जो नए ग्राहकों के लिए सुन रही है और कनेक्शन मिलने के बाद कनेक्शन को बंद कर दें। यह करने के लिए कि थ्रेड्स पार करना आसान है और .Net में आपके पास बिगिनैप्ट इत्यादि विधियां भी हैं, जो आपके लिए बहुत सी पाइपलाइनों की देखभाल करती हैं। प्रक्रिया की सीमाओं के बीच कनेक्शन को बंद करने के लिए जटिल होगा और कोई प्रदर्शन लाभ नहीं होगा।

वैकल्पिक रूप से आप एक ही सॉकेट पर कई प्रक्रियाओं को बाध्य कर सकते हैं और सुन सकते हैं।

TcpListener tcpServer = new TcpListener(IPAddress.Loopback, 10090);
tcpServer.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
tcpServer.Start();

while (true)
{
    TcpClient client = tcpServer.AcceptTcpClient();
    Console.WriteLine("TCP client accepted from " + client.Client.RemoteEndPoint + ".");
}

यदि आप उपरोक्त कोड को क्रियान्वित करने वाली प्रत्येक प्रक्रिया में दो प्रक्रियाएँ करते हैं, तो यह काम करेगा और पहली प्रक्रिया को सभी कनेक्शन मिलते हैं। यदि पहली प्रक्रिया को दूसरे को मार दिया जाता है तो कनेक्शन मिल जाता है। सॉकेट साझा करने के साथ, मुझे यकीन नहीं है कि विंडोज कैसे तय करता है कि किस प्रक्रिया को नए कनेक्शन मिलते हैं, हालांकि त्वरित परीक्षण सबसे पुरानी प्रक्रिया को पहले प्राप्त करने के लिए इंगित करता है। जैसे कि क्या यह साझा करता है यदि पहली प्रक्रिया व्यस्त है या ऐसा कुछ भी है जो मुझे नहीं पता है।


2

यदि आप HTTP का उपयोग कर रहे हैं, तो एक और दृष्टिकोण (जो कई जटिल विवरणों से बचा जाता है) विंडोज में । HTTP.SYS का उपयोग करना है । यह कई प्रक्रियाओं को एक ही पोर्ट पर विभिन्न URL को सुनने की अनुमति देता है। Server 2003/2008 / Vista / 7 पर यह है कि IIS कैसे काम करता है, इसलिए आप इसके साथ पोर्ट साझा कर सकते हैं। (XP SP2 HTTP.SYS पर समर्थित है, लेकिन IIS5.1 इसका उपयोग नहीं करता है।)

अन्य उच्च स्तरीय एपीआई (WCF सहित) HTTP.SYS का उपयोग करते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.