2012-10-04 35 views
21

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

Processing request for 21 
Writing to 21 
Closing 21 

करीब() 0 का वापसी मूल्य 0 है, या कोई अन्य डीबग कथन मुद्रित होगा। लटकने वाले क्लाइंट के साथ इस आउटपुट के बाद, lsof एक स्थापित कनेक्शन दिखा रहा है।

सर्वर 8160 21u आईपीवी 4 32,754,237 टीसीपी स्थानीय होस्ट जड़: 9980-> स्थानीय होस्ट: 47,530 (स्थापित)

ग्राहक 17,747 12U आईपीवी 4 32,754,228 टीसीपी स्थानीय होस्ट जड़: 47530-> स्थानीय होस्ट: 9980 (स्थापित)

यह रूप में है यदि सर्वर क्लाइंट को शटडाउन अनुक्रम कभी नहीं भेजता है, और यह स्थिति तब तक लटकती है जब तक क्लाइंट की मौत नहीं हो जाती है, सर्वर की तरफ से प्रतीक्षा करें स्थिति

सेवर 8160 रूट 21u आईपीवी 4 32754237 टीसीपी लोकलहोस्ट: 9980-> लोकलहोस्ट: 47530 (CLOSE_WAIT)

यदि ग्राहक के पास टाइमआउट निर्दिष्ट है, तो यह लटकने की बजाए टाइमआउट होगा। मैं मैन्युअल रूप से

call close(21) 

सर्वर में gdb से सर्वर में चला सकता है, और क्लाइंट फिर डिस्कनेक्ट हो जाएगा। यह शायद 50,000 अनुरोधों में हो सकता है, लेकिन विस्तारित अवधि के लिए ऐसा नहीं हो सकता है।

लिनक्स संस्करण: 2.6.21.7-2.fc8xen Centos संस्करण: 5.4 (अंतिम)

सॉकेट कार्रवाई इस प्रकार हैं

सर्वर:

पूर्णांक client_socket; संरचना sockaddr_in client_addr; socklen_t client_len = sizeof (client_addr);

while(true) { 
    client_socket = accept(incoming_socket, (struct sockaddr *)&client_addr, &client_len); 
    if (client_socket == -1) 
    continue; 
    /* insert into queue here for threads to process */ 
} 

तब थ्रेड सॉकेट उठाता है और प्रतिक्रिया बनाता है।

/* get client_socket from queue */ 

/* processing request here */ 

/* now set to blocking for write; was previously set to non-blocking for reading */ 
int flags = fcntl(client_socket, F_GETFL); 
if (flags < 0) 
    abort(); 
if (fcntl(client_socket, F_SETFL, flags|O_NONBLOCK) < 0) 
    abort(); 

server_write(client_socket, response_buf, response_length); 
server_close(client_socket); 

server_write और server_close।

void server_write(int fd, char const *buf, ssize_t len) { 
    printf("Writing to %d\n", fd); 
    while(len > 0) { 
     ssize_t n = write(fd, buf, len); 
     if(n <= 0) 
     return;// I don't really care what error happened, we'll just drop the connection 
     len -= n; 
     buf += n; 
    } 
    } 

void server_close(int fd) { 
    for(uint32_t i=0; i<10; i++) { 
     int n = close(fd); 
     if(!n) {//closed successfully                                 
     return; 
     } 
     usleep(100); 
    } 
    printf("Close failed for %d\n", fd); 
    } 

ग्राहक:

क्लाइंट साइड उपयोग कर रहा है libcurl वी 7.27.0

CURL *curl = curl_easy_init(); 
CURLcode res; 
curl_easy_setopt(curl, CURLOPT_URL, url); 
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); 
curl_easy_setopt(curl, CURLOPT_WRITEDATA, write_tag); 

res = curl_easy_perform(curl); 

कुछ भी नहीं फैंसी, बस एक बुनियादी कर्ल कनेक्शन। क्लाइंट tranfer.c में लटकता है (libcurl में) क्योंकि सॉकेट को बंद होने के रूप में नहीं माना जाता है। यह सर्वर से अधिक डेटा की प्रतीक्षा कर रहा है।SO_LINGER स्थापना 1 सेकंड

struct linger l; 
l.l_onoff = 1; 
l.l_linger = 1; 
if (setsockopt(client_socket, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) == -1) 
    abort(); 

इन में जबरन बंद करने के लिए कोई फर्क नहीं बना दिया है पास

shutdown(fd, SHUT_WR);                                    
char buf[64];                                      
while(read(fd, buf, 64) > 0);                                   
/* then close */ 

से पहले

शटडाउन:

चीजें मैं अब तक की कोशिश की है। किसी भी विचार की बहुत प्रशंसा की जाएगी।

संपादित करें - यह एक कतार पुस्तकालय के अंदर एक थ्रेड-सुरक्षा समस्या होने के कारण समाप्त हो गया है जिससे सॉकेट को कई धागे से अनुपयुक्त तरीके से संभाला जा सकता है।

+0

क्या आप 100% सकारात्मक हैं, कोई अन्य थ्रेड संभवतः सॉकेट का उपयोग कर सकता है जब आप उस पर 'बंद' कहते हैं? आप अपने गैर-अवरुद्ध पढ़ने को कैसे करते हैं? –

+0

मुझे डर है कि मैंने अभी यहां लॉग इन किया है और इस मुद्दे को याद किया है। मुझे बाद में पता चला कि आसपास के कनेक्शन पास करने के लिए उपयोग की जाने वाली कतार में एक थ्रेड सुरक्षा समस्या थी। यहां कोई बग नहीं था। गलत जानकारी के लिए खेद है। – DavidMFrey

उत्तर

54

यहाँ कुछ कोड मैं कई यूनिक्स सिस्टम पर उपयोग किया है है (उदाहरण के SunOS 4, एसजीआई IRIX, HPUX 10.20, 5 CentOS, cygwin) एक सॉकेट बंद करने के लिए:

int getSO_ERROR(int fd) { 
    int err = 1; 
    socklen_t len = sizeof err; 
    if (-1 == getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&err, &len)) 
     FatalError("getSO_ERROR"); 
    if (err) 
     errno = err;    // set errno to the socket SO_ERROR 
    return err; 
} 

void closeSocket(int fd) {  // *not* the Windows closesocket() 
    if (fd >= 0) { 
     getSO_ERROR(fd); // first clear any errors, which can cause close to fail 
     if (shutdown(fd, SHUT_RDWR) < 0) // secondly, terminate the 'reliable' delivery 
     if (errno != ENOTCONN && errno != EINVAL) // SGI causes EINVAL 
      Perror("shutdown"); 
     if (close(fd) < 0) // finally call close() 
     Perror("close"); 
    } 
} 

लेकिन ऊपर करता है गारंटी नहीं है कि किसी भी buffered लिखते हैं।

अनुग्रहपूर्ण बंद: मुझे सॉकेट को बंद करने का तरीका जानने में लगभग 10 साल लगे। लेकिन एक और 10 वर्षों के लिए मैंने थोड़ी देर के लिए usleep(20000) को आलसी कहा कि 'बफर' बफर को बंद करने से पहले फ्लश किया गया था। यह स्पष्ट रूप से बहुत चालाक नहीं है, क्योंकि:

  • देरी अधिकांश समय बहुत अधिक थी।
  • देरी कुछ समय बहुत कम थी - शायद!
  • usleep() को समाप्त करने के लिए सिग्नल जैसे सिग्नल हो सकता है (लेकिन आमतौर पर मुझे इस मामले को संभालने के लिए usleep() कहा जाता है - एक हैक)।
  • कोई संकेत नहीं था कि यह काम करता है या नहीं। लेकिन यह शायद महत्वपूर्ण नहीं है यदि ए) हार्ड रीसेट पूरी तरह से ठीक है, और/या बी) आपके पास लिंक के दोनों किनारों पर नियंत्रण है।

लेकिन उचित फ्लश करना आश्चर्यजनक रूप से कठिन है। SO_LINGER का उपयोग स्पष्ट रूप से जाने का तरीका है; उदाहरण के लिए देखें:

और SIOCOUTQ लिनक्स विशिष्ट प्रतीत होता है।

नोट shutdown(fd, SHUT_WR)नहीं बंद लेखन, अपने नाम के विपरीत है, और शायद man 2 shutdown के विपरीत है।

यह कोड flushSocketBeforeClose() शून्य बाइट्स के पढ़ने तक, या टाइमर समाप्त होने तक प्रतीक्षा करता है। फ़ंक्शन haveInput() चुनिंदा (2) के लिए एक साधारण रैपर है, और एक सेकंड के 1/100 वें तक ब्लॉक करने के लिए सेट है।उपयोग की

bool haveInput(int fd, double timeout) { 
    int status; 
    fd_set fds; 
    struct timeval tv; 
    FD_ZERO(&fds); 
    FD_SET(fd, &fds); 
    tv.tv_sec = (long)timeout; // cast needed for C++ 
    tv.tv_usec = (long)((timeout - tv.tv_sec) * 1000000); // 'suseconds_t' 

    while (1) { 
     if (!(status = select(fd + 1, &fds, 0, 0, &tv))) 
     return FALSE; 
     else if (status > 0 && FD_ISSET(fd, &fds)) 
     return TRUE; 
     else if (status > 0) 
     FatalError("I am confused"); 
     else if (errno != EINTR) 
     FatalError("select"); // tbd EBADF: man page "an error has occurred" 
    } 
} 

bool flushSocketBeforeClose(int fd, double timeout) { 
    const double start = getWallTimeEpoch(); 
    char discard[99]; 
    ASSERT(SHUT_WR == 1); 
    if (shutdown(fd, 1) != -1) 
     while (getWallTimeEpoch() < start + timeout) 
     while (haveInput(fd, 0.01)) // can block for 0.01 secs 
      if (!read(fd, discard, sizeof discard)) 
       return TRUE; // success! 
    return FALSE; 
} 

उदाहरण: कुछ टिप्पणियां:

    if (!flushSocketBeforeClose(fd, 2.0)) // can block for 2s 
         printf("Warning: Cannot gracefully close socket\n"); 
        closeSocket(fd); 
    

    ऊपर में, मेरा getWallTimeEpoch()time(), और Perror() के समान है perror().

    संपादित करने के लिए एक आवरण है

  • मेरा पहला प्रवेश थोड़ा शर्मनाक है। ओपी और निमो ने बंद होने से पहले आंतरिक so_error को साफ़ करने की आवश्यकता को चुनौती दी, लेकिन अब मुझे इसके लिए कोई संदर्भ नहीं मिल रहा है। प्रश्न में सिस्टम एचपीयूएक्स 10.20 था। असफल connect() के बाद, बस close() पर कॉल करने से फ़ाइल डिस्क्रिप्टर को रिलीज़ नहीं किया गया, क्योंकि सिस्टम ने मुझे एक उत्कृष्ट त्रुटि प्रदान करने की कामना की थी। लेकिन मैं, अधिकांश लोगों की तरह, close. के वापसी मूल्य की जांच करने के लिए कभी भी परेशान नहीं था, इसलिए अंत में मैं फाइल डिस्क्रिप्टर (ulimit -n), से बाहर चला गया, जिसने अंततः मेरा ध्यान दिया।

  • (बहुत मामूली बिंदु) एक टिप्पणीकार ने हार्ड-कोडित संख्यात्मक तर्कों को shutdown() पर उदा। SHUT_WR के लिए 1. सबसे सरल जवाब यह है कि विंडोज अलग-अलग # परिभाषा/enums का उपयोग करता है उदा। SD_SEND। और कई अन्य लेखकों (जैसे बीज) कई विरासत प्रणालियों के रूप में स्थिरांक का उपयोग करते हैं।

  • इसके अलावा, मैं हमेशा अपने सभी सॉकेट्स पर FD_CLOEXEC सेट करता हूं, क्योंकि मेरे अनुप्रयोगों में मैं कभी उन्हें एक बच्चे को पास नहीं करना चाहता था और, सबसे महत्वपूर्ण बात यह है कि मैं नहीं चाहता कि एक लटका हुआ बच्चा मुझे प्रभावित करे।

नमूना CLOEXEC स्थापित करने के लिए कोड:

static void setFD_CLOEXEC(int fd) { 
     int status = fcntl(fd, F_GETFD, 0); 
     if (status >= 0) 
     status = fcntl(fd, F_SETFD, status | FD_CLOEXEC); 
     if (status < 0) 
     Perror("Error getting/setting socket FD_CLOEXEC flags"); 
    } 
+5

मेरी इच्छा है कि मैं इसे दो बार वोट दे सकता हूं। जंगली में मैंने देखा है कि यह सही ढंग से बंद सॉकेट का दूसरा नमूना है। 'Getockopt()' ing 'SO_ERROR' के लिए – grieve

+1

+1। – alk

+0

@ जोसेफ क्विंसे - क्या आपके पास "त्रुटियों का संदर्भ है ... को नज़दीक() को अनदेखा कर दिया जाएगा"? पसंदीदा रूप से POSIX spec से? – Nemo

0

यह आपके Linux वितरण में एक बग की तरह मेरे लिए लग रहा है।

GNU C library documentation का कहना है:

जब आप एक सॉकेट का उपयोग कर समाप्त कर दिया है, तो आप बस अपनी फ़ाइल वर्णनकर्ता साथ close

कुछ भी नहीं है किसी भी त्रुटि झंडे समाशोधन या करने के लिए डेटा के लिए इंतज़ार कर के बारे में बंद कर सकते हैं फ्लेश या ऐसी कोई चीज हो।

आपका कोड ठीक है; आपके ओ/एस में एक बग है।

+0

इस उत्तर की ओर झुकाव। परीक्षण करने के लिए एक और ओएस प्राप्त करने के लिए कुछ काम करेंगे। एक बार परीक्षण करने के बाद मैं इसे फिर से देखूंगा। मैं इस लिंक में @Nemo से जोड़ना चाहता हूं क्योंकि यह प्रश्न के लिए प्रासंगिक लगता है। और जिस प्रतिक्रिया से जुड़ा हुआ था उसे हटा दिया गया है। https://sites.google.com/site/michaelsafyan/software-engineering/checkforeintrwheninvokingclosethinkagain – DavidMFrey

+0

मैं इस जवाब को स्वीकार कर रहा हूं, क्योंकि मेरी थ्रेड-सुरक्षित कतार को पेंथ्रेड स्थितियों की बजाय सेमफोरों का उपयोग करने के लिए अपरिहार्य रूप से (स्वयं को स्वयं तक) हल किया गया है मुद्दा। – DavidMFrey

+3

'किसी भी त्रुटि झंडे को साफ़ करने या डेटा को फ़्लश करने या किसी भी चीज़ की प्रतीक्षा करने के बारे में कुछ भी नहीं।' तर्कसंगत रूप से, "डेटा को फ़्लश करने की प्रतीक्षा" जब आप सॉकेट का उपयोग समाप्त कर लेते हैं "। –

2

जोसेफ क्विंसे से शानदार जवाब। मेरे पास haveInput फ़ंक्शन पर टिप्पणियां हैं I आश्चर्य की बात है कि यह कितना संभव है कि चयन एक एफडी देता है जिसे आपने अपने सेट में शामिल नहीं किया था। यह एक प्रमुख ओएस बग IMHO होगा। यह एक तरह की चीज है जिसे मैं जांचूंगा कि मैंने select फ़ंक्शन के लिए यूनिट परीक्षण लिखा है, न कि सामान्य ऐप में।

if (!(status = select(fd + 1, &fds, 0, 0, &tv))) 
    return FALSE; 
else if (status > 0 && FD_ISSET(fd, &fds)) 
    return TRUE; 
else if (status > 0) 
    FatalError("I am confused"); // <--- fd unknown to function 

मेरी अन्य टिप्पणी EINTR के संचालन से संबंधित है। सिद्धांत रूप में, यदि आप select ईआईएनटीआर लौटते रहते हैं, तो आप अनंत लूप में फंस सकते हैं, क्योंकि यह त्रुटि लूप को शुरू करने देती है। बहुत कम समय समाप्ति (0.01) को देखते हुए, ऐसा होने की संभावना बहुत कम दिखाई देती है। हालांकि, मुझे लगता है कि इससे निपटने का उचित तरीका कॉलर को त्रुटियों को वापस करना होगा (flushSocketBeforeClose)।कॉलर haveInput को कॉल करना जारी रख सकता है जब तक कि इसका टाइमआउट समाप्त नहीं हुआ है, और अन्य त्रुटियों के लिए विफलता घोषित करता है।

अलावा # 1

flushSocketBeforeCloseread के मामले में जल्दी से बाहर निकलने नहीं होगा एक त्रुटि लौटने। यह समय सीमा समाप्त होने तक लूपिंग रखेगा। आप सभी त्रुटियों की उम्मीद के लिए haveInput के अंदर select पर भरोसा नहीं कर सकते हैं। read में इसकी खुद की त्रुटियां हैं (उदा: EIO)।

 while (haveInput(fd, 0.01)) 
     if (!read(fd, discard, sizeof discard)) <-- -1 does not end loop 
      return TRUE; 
संबंधित मुद्दे