SIGPIPEs को कैसे रोकें (या उन्हें ठीक से संभालें)


259

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

जवाबों:


253

आप आम तौर पर SIGPIPEअपने कोड में सीधे त्रुटि को अनदेखा करना चाहते हैं । इसका कारण यह है कि सी में सिग्नल हैंडलर पर कई प्रतिबंध हैं कि वे क्या कर सकते हैं।

ऐसा करने का सबसे पोर्टेबल तरीका SIGPIPEहैंडलर को सेट करना है SIG_IGN। यह किसी भी सॉकेट या पाइप को एक SIGPIPEसंकेत पैदा करने से रोक देगा ।

SIGPIPEसंकेत को अनदेखा करने के लिए, निम्न कोड का उपयोग करें:

signal(SIGPIPE, SIG_IGN);

यदि आप send()कॉल का उपयोग कर रहे हैं , तो विकल्प का उपयोग करने के लिए एक और विकल्प है MSG_NOSIGNAL, जो SIGPIPEप्रति कॉल के आधार पर व्यवहार को बंद कर देगा । ध्यान दें कि सभी ऑपरेटिंग सिस्टम MSG_NOSIGNALध्वज का समर्थन नहीं करते हैं ।

अंत में, आप SO_SIGNOPIPEसॉकेट फ़्लैग पर विचार करना चाह सकते हैं जिसे setsockopt()कुछ ऑपरेटिंग सिस्टम पर सेट किया जा सकता है । यह SIGPIPEकेवल उन सॉकेट्स पर लिखने से होने से रोकेगा जिन पर यह सेट है।


75
इसे स्पष्ट करने के लिए: संकेत (SIGPIPE, SIG_IGN);
३h बजे।।

5
यह आवश्यक हो सकता है अगर आपको 141 ​​(128 + 13: SIGPIPE) का एग्जिट रिटर्न कोड मिल रहा हो। SIG_IGN सिग्नल हैंडलर की अनदेखी है।
२०:५० पर वेल्क्रो vel

6
सॉकेट्स के लिए, MSG_NOSIGNAL के साथ सेंड () का उपयोग करना वास्तव में आसान है, जैसा @ @ पलाश ने कहा।
पावेल वेसलोव

3
यदि मेरा कार्यक्रम टूटी हुई पाइप (मेरे मामले में एक सॉकेट) को लिखता है तो वास्तव में क्या होता है? SIG_IGN कार्यक्रम को SIG_PIPE को अनदेखा करता है, लेकिन फिर क्या इसका परिणाम कुछ अवांछित होता है?
सूडो

2
उल्लेख के लायक है, क्योंकि मैंने इसे एक बार पाया था, फिर इसे बाद में भूल गया और भ्रमित हो गया, फिर दूसरी बार इसकी खोज की। डीबगर्स (मुझे पता है कि gdb करता है) आपके सिग्नल हैंडलिंग को ओवरराइड करता है, इसलिए अनदेखा सिग्नलों को अनदेखा नहीं किया जाता है! SIGPIPE सुनिश्चित करने के लिए अब डिबगर के बाहर अपने कोड का परीक्षण करें। stackoverflow.com/questions/6821469/…
जेट्स्की एस-टाइप

155

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

अधिकांश BSD- आधारित (MacOS, FreeBSD ...) सिस्टम पर, (यह मानते हुए कि आप C / C ++ का उपयोग कर रहे हैं), आप इसके साथ कर सकते हैं:

int set = 1;
setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));

इसके प्रभाव में, SIGPIPE सिग्नल उत्पन्न होने के बजाय, EPIPE वापस कर दिया जाएगा।


45
यह अच्छा लगता है, लेकिन SO_NOSIGPIPE लिनक्स में मौजूद नहीं है - इसलिए यह व्यापक रूप से पोर्टेबल समाधान नहीं है।
नोबार

1
हाय सब, क्या आप मुझे बता सकते हैं कि मुझे यह कोड कहां लगाना है? मुझे समझ में नहीं आता
R. Dewi

9
लिनक्स में, मैं भेजने के
Aadishri

मैक ओएस एक्स
किरूगन

116

मैं पार्टी के लिए सुपर लेट हूं, लेकिन SO_NOSIGPIPEपोर्टेबल नहीं हूं , और आपके सिस्टम पर काम नहीं कर सकता (यह बीएसडी की बात लगती है)।

एक अच्छा विकल्प यदि आप कहते हैं, बिना लिनक्स सिस्टम आपके भेजे (2) कॉल पर ध्वज SO_NOSIGPIPEको सेट करने के लिए होगा MSG_NOSIGNAL

write(...)द्वारा प्रतिस्थापित उदाहरण send(...,MSG_NOSIGNAL)( nobar की टिप्पणी देखें)

char buf[888];
//write( sockfd, buf, sizeof(buf) );
send(    sockfd, buf, sizeof(buf), MSG_NOSIGNAL );

5
दूसरे शब्दों में, लिखने के लिए प्रतिस्थापन के रूप में भेजें (..., MSG_NOSIGNAL) का उपयोग करें () और आपको SIGPIPE नहीं मिलेगा। यह सॉकेट्स (समर्थित प्लेटफार्मों पर) के लिए अच्छी तरह से काम करना चाहिए, लेकिन भेजना () सॉकेट्स (पाइप नहीं) के साथ उपयोग करने के लिए सीमित लगता है, इसलिए यह SIGPIPE समस्या का सामान्य समाधान नहीं है।
नोबार

@ Ray2k धरती पर कौन अभी भी लिनक्स <2.2 के लिए एप्लिकेशन विकसित कर रहा है? यह वास्तव में एक अर्ध-गंभीर प्रश्न है; कम से कम 2.6 के साथ सबसे distros जहाज।
पार्थियन ने

1
@ पार्थियन शॉट: आपको अपने जवाब पर पुनर्विचार करना चाहिए। पुराने एम्बेडेड सिस्टम का रखरखाव पुराने लिनक्स संस्करणों की देखभाल करने का एक वैध कारण है।
kirsche40

1
@ kirsche40 मैं ओपी नहीं हूं- मैं सिर्फ उत्सुक था।
पार्थियन शॉट

@ यह मेरे लिए पूरी तरह से काम किया है। मैं QTcpSocketसॉकेट्स के उपयोग को रैप करने के लिए एक क्यूटी ऑब्जेक्ट का उपयोग कर रहा हूं , मुझे writeओएस send( socketDescriptorविधि का उपयोग करके ) कॉल विधि को बदलना पड़ा । किसी को QTcpSocketकक्षा में इस विकल्प को सेट करने के लिए एक क्लीनर विकल्प पता है ?
एमिलियो गोंजालेज मोंटाना

29

इस पोस्ट में मैंने सोलारिस केस के संभावित समाधान का वर्णन किया जब न तो SO_NOSIGPIPE और न ही MSG_NOSIGNAL उपलब्ध है।

इसके बजाय, हमें अस्थायी रूप से लाइब्रेरी कोड को निष्पादित करने वाले वर्तमान थ्रेड में SIGPIPE को दबाना होगा। यहां यह करने का तरीका बताया गया है: SIGPIPE को दबाने के लिए हम पहले जांच लें कि क्या यह लंबित है या नहीं। यदि ऐसा होता है, तो इसका मतलब है कि यह इस धागे में अवरुद्ध है, और हमें कुछ नहीं करना है। यदि लाइब्रेरी अतिरिक्त SIGPIPE उत्पन्न करती है, तो इसे लंबित एक के साथ विलय कर दिया जाएगा, और यह एक सेशन नहीं है। यदि SIGPIPE लंबित नहीं है, तो हम इसे इस थ्रेड में ब्लॉक करते हैं, और यह भी जांचें कि क्या यह पहले से ही ब्लॉक था। फिर हम अपने लिखे पर अमल करने के लिए स्वतंत्र हैं। जब हम SIGPIPE को उसकी मूल स्थिति में पुनर्स्थापित करना चाहते हैं, तो हम निम्न कार्य करते हैं: यदि SIGPIPE मूल रूप से लंबित था, तो हमारे पास कुछ भी नहीं था। अन्यथा हम जांच करते हैं कि क्या यह लंबित है। यदि यह करता है (जिसका अर्थ है कि क्रियाओं ने एक या अधिक SIGPIPEs उत्पन्न किए हैं), तो हम इस धागे में इसकी प्रतीक्षा करते हैं, इस प्रकार इसकी लंबित स्थिति को साफ़ करना (ऐसा करने के लिए हम शून्य समय-सीमा के साथ sigtimedwait का उपयोग करते हैं); यह एक ऐसे परिदृश्य में अवरुद्ध होने से बचने के लिए है जहाँ दुर्भावनापूर्ण उपयोगकर्ता ने पूरी प्रक्रिया में मैन्युअल रूप से SIGPIPE को भेजा: इस मामले में हम लंबित देखेंगे, लेकिन अन्य सूत्र इससे पहले कि हम इसके लिए इंतजार करने के लिए एक बदलाव था) इसे संभाल लें। लंबित स्थिति साफ़ करने के बाद हम इस धागे में SIGPIPE को अनब्लॉक करते हैं, लेकिन केवल अगर यह मूल रूप से अवरुद्ध नहीं किया गया था।

उदाहरण कोड https://github.com/kroki/XProb/blob/1447f3d93b6dbf273919af15e59f35cc3558acc23/src/libxprobes.cnLL156


22

स्थानीय रूप से संभालें

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

मेरे पास मेरे एक ऐप में एक संचार परत है जो मेरे ऐप को बाहरी एक्सेसरी के साथ संवाद करने की अनुमति देता है। जब एक लेखन त्रुटि होती है, तो मैं संचार परत में फेंक देता हूं और अपवाद करता हूं और इसे वहां संभालने के लिए इसे पकड़ने की कोशिश करने के लिए बुलबुला देता हूं।

कोड:

एक संकेत संकेत को अनदेखा करने के लिए कोड ताकि आप इसे स्थानीय स्तर पर संभाल सकें:

// We expect write failures to occur but we want to handle them where 
// the error occurs rather than in a SIGPIPE handler.
signal(SIGPIPE, SIG_IGN);

यह कोड SIGPIPE सिग्नल को बढ़ने से रोकेगा, लेकिन सॉकेट का उपयोग करने की कोशिश करते समय आपको पढ़ने / लिखने में त्रुटि मिलेगी, इसलिए आपको इसके लिए जांच करनी होगी।


क्या यह कॉल सॉकेट कनेक्ट करने के बाद या उससे पहले किया जाना चाहिए? यह जगह के लिए सबसे अच्छा कहां है। साभार
अहमद

@Ahmed मैं व्यक्तिगत रूप में रख applicationDidFinishLaunching: । मुख्य बात यह है कि यह सॉकेट कनेक्शन के साथ बातचीत करने से पहले होना चाहिए।
सैम

लेकिन आपको सॉकेट का उपयोग करने की कोशिश करते समय एक पढ़ने / लिखने की त्रुटि मिलेगी, इसलिए आपको इसके लिए जांच करनी होगी। - मैं पूछ सकता हूं कि यह कैसे संभव है? संकेत (SIGPIPE, SIG_IGN) मेरे लिए काम करता है, लेकिन डिबग मोड के तहत यह एक त्रुटि देता है क्या इस त्रुटि को भी अनदेखा करना संभव है?
जोंगबनाग

@ Dreyfus15 मुझे विश्वास है कि मैं इसे स्थानीय रूप से संभालने के लिए कोशिश / कैच ब्लॉक में सॉकेट का उपयोग करके सिर्फ कॉल लपेटता हूं। जब से मैंने यह देखा है, तब से थोड़ी देर हो गई है।
सैम

15

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


6

या मैं सिर्फ एक हैंडलर के साथ SIGPIPE को पकड़ूं और इसे अनदेखा करूं?

मेरा मानना ​​है कि यह सही है। आप यह जानना चाहते हैं कि दूसरे छोर ने अपने डिस्क्रिप्टर को कब बंद किया है और यही SIGPIPE आपको बताता है।

सैम


3

लिनक्स मैनुअल ने कहा:

EPIPE स्थानीय अंत एक कनेक्शन उन्मुख सॉकेट पर बंद कर दिया गया है। जब तक MSG_NOSIGNAL सेट नहीं हो जाता है, इस मामले में यह प्रक्रिया एक SIGPIPE भी प्राप्त करेगी।

लेकिन Ubuntu 12.04 के लिए यह सही नहीं है। मैंने उस मामले के लिए एक परीक्षण लिखा और मुझे हमेशा EPIPE SOTPIPE प्राप्त हुआ। अगर मैं उसी टूटे सॉकेट में दूसरी बार लिखने की कोशिश करूं तो SIGPIPE उत्पन्न होता है। इसलिए आपको SIGPIPE को नजरअंदाज करने की जरूरत नहीं है अगर यह संकेत होता है तो इसका मतलब है कि आपके प्रोग्राम में लॉजिक एरर है।


1
मैं सबसे पहले निश्चित रूप से लिनक्स में सॉकेट पर EPIPE देखे बिना SIGPIPE प्राप्त करता हूं। यह एक कर्नेल बग की तरह लगता है।
दावमैक

3

यहां दुर्घटना को रोकने के लिए सबसे अच्छा अभ्यास क्या है?

हर किसी के अनुसार या तो सिगाइप्स को अक्षम करें, या त्रुटि को पकड़ें और अनदेखा करें।

क्या यह जांचने का कोई तरीका है कि क्या लाइन का दूसरा हिस्सा अभी भी पढ़ रहा है?

हां, चयन () का उपयोग करें।

चयन () यहां काम नहीं करता है क्योंकि यह हमेशा कहता है कि सॉकेट लिखने योग्य है।

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

जब दूर का छोर अपने फ़ाइल हैंडल को बंद कर देता है, तो चयन आपको बताएगा कि पढ़ने के लिए डेटा तैयार है। जब आप जाते हैं और पढ़ते हैं, तो आपको 0 बाइट्स मिलेंगे, जो कि ओएस आपको बताता है कि फ़ाइल हैंडल बंद कर दिया गया है।

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

ध्यान दें कि आप पास से एक सिगिपिप प्राप्त कर सकते हैं (), साथ ही साथ जब आप लिखते हैं।

किसी भी बफ़र किए गए डेटा को बंद कर देता है। यदि दूसरा छोर पहले से ही बंद हो गया है, तो बंद विफल हो जाएगा, और आपको एक सिगिपिप प्राप्त होगा।

यदि आप बफ़र्ड TCPIP का उपयोग कर रहे हैं, तो एक सफल लेखन का अर्थ है कि आपके डेटा को भेजने के लिए कतारबद्ध किया गया है, इसका मतलब यह नहीं है कि इसे भेजा गया है। जब तक आप सफलतापूर्वक कॉल बंद नहीं करते, तब तक आप नहीं जानते कि आपका डेटा भेजा जा चुका है।

सिगिप्प आपको बताता है कि कुछ गलत हो गया है, यह आपको नहीं बताता है कि आपको इसके बारे में क्या करना चाहिए या क्या करना चाहिए।


सिगिप्प आपको बताता है कि कुछ गलत हो गया है, यह आपको नहीं बताता है कि आपको इसके बारे में क्या करना चाहिए या क्या करना चाहिए। बिल्कुल सही। वस्तुतः SIGPIPE का एकमात्र उद्देश्य पाइपेड कमांड लाइन उपयोगिताओं को फाड़ना है जब अगले चरण में अब इनपुट की आवश्यकता नहीं होती है। यदि आपका प्रोग्राम नेटवर्किंग कर रहा है, तो आपको आमतौर पर SIGPIPE प्रोग्राम को ब्लॉक या अनदेखा करना चाहिए।
डिप्रेस्डैनियल

यकीनन। SIGPIPE "XXX खोजें | हेड" जैसी स्थितियों के लिए अभिप्रेत है। एक बार जब सिर अपनी 10 लाइनों से मेल खाता है, तो खोज के साथ कोई बिंदु नहीं है। इसलिए सिर बाहर निकलता है, और अगली बार जब वह उससे बात करने की कोशिश करता है, तो वह सिगिप्प हो जाता है और जानता है कि वह भी बाहर निकल सकता है।
बेन अवेलिंग

वास्तव में, SIGPIPE का एकमात्र उद्देश्य कार्यक्रमों को फूहड़ बनाने की अनुमति देना है और लिखने की त्रुटियों की जांच नहीं करना है!
विलियम पर्ससेल

2

एक आधुनिक POSIX प्रणाली (यानी लिनक्स) के तहत, आप sigprocmask()फ़ंक्शन का उपयोग कर सकते हैं ।

#include <signal.h>

void block_signal(int signal_to_block /* i.e. SIGPIPE */ )
{
    sigset_t set;
    sigset_t old_state;

    // get the current state
    //
    sigprocmask(SIG_BLOCK, NULL, &old_state);

    // add signal_to_block to that existing state
    //
    set = old_state;
    sigaddset(&set, signal_to_block);

    // block that signal also
    //
    sigprocmask(SIG_BLOCK, &set, NULL);

    // ... deal with old_state if required ...
}

यदि आप बाद में पिछली स्थिति को पुनर्स्थापित करना चाहते हैं, तो सुनिश्चित करें कि old_stateकहीं सुरक्षित बचाना है । यदि आप उस फ़ंक्शन को कई बार कॉल करते हैं, तो आपको या तो एक स्टैक का उपयोग करने की आवश्यकता है या केवल पहले या आखिरी को बचाने के लिए old_state... या शायद एक फ़ंक्शन है जो एक विशिष्ट अवरुद्ध संकेत को हटा देता है।

अधिक जानकारी के लिए मैन पेज पढ़ें ।


इस तरह अवरुद्ध संकेतों के सेट को पढ़ना-संशोधित-लिखना आवश्यक नहीं है। sigprocmaskअवरुद्ध संकेतों के सेट में जोड़ता है, इसलिए आप एक कॉल के साथ यह सब कर सकते हैं।
लुच्स

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

1
लेकिन आप करते हैं: sigprocmask()पुरानी कॉल को पढ़ने के लिए पहली कॉल , sigaddsetइसे संशोधित करने के लिए कॉल , दूसरी कॉल sigprocmask()इसे लिखने के लिए । आप पहले कॉल को हटा सकते हैं, इसके साथ आरंभ कर सकते हैं sigemptyset(&set)और दूसरी कॉल को बदल सकते हैं sigprocmask(SIG_BLOCK, &set, &old_state)
लुक्स

आह! हा हा! आप सही हे। मैं करता हूँ। खैर ... मेरे सॉफ़्टवेयर में, मुझे नहीं पता कि मैंने कौन से सिग्नल पहले से ब्लॉक किए हैं या ब्लॉक नहीं किए हैं। तो मैं इसे इस तरह से करता हूं। हालाँकि, मैं इस बात से सहमत हूँ कि यदि आपके पास केवल एक "सेट सिग्नल इस तरह" है, तो एक सरल स्पष्ट + सेट पर्याप्त है।
एलेक्सिस विल्के 5
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.