2009-11-10 11 views
27

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

class Test(SocketServer.BaseRequestHandler): 

def handle(self): 

    print "From:", self.client_address 

    while True:  

    data = self.request.recv(1024) 
    if not data: break 

    if data[4] == "\x20":    
     self.request.sendall("hello") 
    if data[4] == "\x21": 
     self.request.sendall("bye") 
    else: 
     print "unknow packet" 
    self.request.close() 
    print "Disconnected", self.client_address 

launch = SocketServer.ThreadingTCPServer(('', int(sys.argv[1])),Test) 

launch.allow_reuse_address= True; 

launch.serve_forever() 

ग्राहक एक ही स्रोत पोर्ट पर गुणकों अनुरोध भेजता है, लेकिन सर्वर अटक जाती है, किसी भी मदद बहुत सराहना की हैं, तो धन्यवाद!

उत्तर

31

नेटवर्क हमेशा अप्रत्याशित है। टीसीपी इस यादृच्छिक व्यवहार को आपके लिए दूर कर देता है। टीसीपी एक अद्भुत चीज करता है: यह गारंटी देता है कि बाइट एक ही क्रम में पहुंचेंगे। परंतु! यह गारंटी देता है कि वे उसी तरह कटा हुआ पहुंचेंगे। आप बस नहीं मान सकते हैं कि कनेक्शन के एक छोर से प्रत्येक प्रेषण() वास्तव में बाइट्स की एक ही संख्या के साथ दूर अंत में एक recv() का परिणाम देगा।

जब आप socket.recv(x) कहते हैं, तो आप कह रहे हैं कि 'सॉकेट से एक्स बाइट्स पढ़ने तक वापस न आएं'। इसे "अवरुद्ध I/O" कहा जाता है: जब तक आपका अनुरोध भर नहीं जाता है तब तक आप ब्लॉक (प्रतीक्षा) करेंगे। यदि आपके प्रोटोकॉल में प्रत्येक संदेश बिल्कुल 1024 बाइट था, तो socket.recv(1024) पर कॉल करना बहुत अच्छा काम करेगा। लेकिन ऐसा लगता है कि यह सच नहीं है। यदि आपके संदेश बाइट्स की निश्चित संख्या हैं, तो बस उस नंबर को socket.recv() पर पास करें और आप कर चुके हैं।

लेकिन यदि आपके संदेश अलग-अलग लंबाई के हो सकते हैं तो क्या होगा? पहली चीज़ जो आपको करने की ज़रूरत है: एक स्पष्ट संख्या के साथ socket.recv() पर कॉल करना बंद करें। इस बदलना:

data = self.request.recv(1024) 
इस के लिए

:

data = self.request.recv() 

मतलब है recv() हमेशा वापस आ जाएगी जब भी यह नया डेटा हो जाता है।

लेकिन अब आपको एक नई समस्या है: प्रेषक ने आपको एक पूरा संदेश कब भेजा है, तो आप कैसे जानते हैं? जवाब है: आप नहीं करते हैं। आपको संदेश की लंबाई को अपने प्रोटोकॉल का एक स्पष्ट हिस्सा बनाना होगा। यहां सबसे अच्छा तरीका है: प्रत्येक संदेश को लम्बाई के साथ उपसर्ग करें, या तो एक निश्चित आकार पूर्णांक के रूप में (socket.ntohs() या socket.ntohl() का उपयोग करके नेटवर्क बाइट ऑर्डर में परिवर्तित करें!) या कुछ डिलीमीटर (जैसे '123:') के बाद एक स्ट्रिंग के रूप में। यह दूसरा दृष्टिकोण अक्सर कम कुशल होता है, लेकिन पाइथन में यह आसान है।

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

length = None 
buffer = "" 
while True: 
    data += self.request.recv() 
    if not data: 
    break 
    buffer += data 
    while True: 
    if length is None: 
     if ':' not in buffer: 
     break 
     # remove the length bytes from the front of buffer 
     # leave any remaining bytes in the buffer! 
     length_str, ignored, buffer = buffer.partition(':') 
     length = int(length_str) 

    if len(buffer) < length: 
     break 
    # split off the full message from the remaining bytes 
    # leave any remaining bytes in the buffer! 
    message = buffer[:length] 
    buffer = buffer[length:] 
    length = None 
    # PROCESS MESSAGE HERE 
+22

हंस एल नीचे टिप्पणी में सही है कि python request.recv() में एक अनिवार्य पैरा अगर bufize के रूप में वैध कॉल नहीं है। आदर्श रूप से यह उत्तर हटाया जाना चाहिए या संपादित किया जाना चाहिए। http://docs.python.org/library/socket.html – prashantsunkari

+0

"इसे" अवरुद्ध I/O "कहा जाता है:" मुझे इस शब्द को ब्रेमेन डाउन में पसंद है .... – repzero

2

यह टीसीपी की प्रकृति है: प्रोटोकॉल पैकेट भरता है (निचला परत आईपी पैकेट होता है) और उन्हें भेजता है। आप एमटीयू (अधिकतम स्थानांतरण इकाई) पर कुछ हद तक नियंत्रण कर सकते हैं।

दूसरे शब्दों में: आपको एक प्रोटोकॉल तैयार करना होगा जो टीसीपी के शीर्ष पर सवारी करे जहां आपका "पेलोड डिलीनेशन" परिभाषित किया गया हो। "पेलोड डिलीनेशन" से मेरा मतलब है कि जिस तरह से आप प्रोटोकॉल का समर्थन करते हैं, उस संदेश की इकाई निकालने का तरीका। यह "हर NULL समाप्त तारों" के रूप में सरल हो सकता है।

113

जवाब लैरी हेस्टिंग्स द्वारा सॉकेट के बारे में कुछ महान सामान्य सलाह है, लेकिन वहाँ गलतियों की एक जोड़ी के रूप में यह कैसे recv(bufsize) विधि अजगर सॉकेट मॉड्यूल में काम करता है से संबंधित है कर रहे हैं।

तो, स्पष्ट करने के लिए है, क्योंकि यह मदद के लिए इस के लिए देख दूसरों को भ्रमित हो सकता है:

  1. recv(bufsize) विधि के लिए bufsize परम वैकल्पिक नहीं है। यदि आप recv() (परम के बिना) कॉल करते हैं तो आपको एक त्रुटि मिलेगी।
  2. recv(bufsize) में बफरलेन अधिकतम आकार है। कम उपलब्ध होने पर आरईवी कम बाइट्स को खुशी से वापस कर देगा।

विवरण के लिए the documentation देखें।

अब, यदि आप किसी ग्राहक से डेटा प्राप्त कर रहे हैं और जानना चाहते हैं कि आपको सभी डेटा कब प्राप्त हुआ है, तो शायद आपको इसे अपने प्रोटोकॉल में जोड़ना होगा - जैसा लैरी सुझाव देता है। संदेश के अंत का निर्धारण करने के लिए रणनीतियों के लिए this recipe देखें।

जैसा कि नुस्खा बताता है, कुछ प्रोटोकॉल के लिए, क्लाइंट डेटा भेजने पर बस डिस्कनेक्ट हो जाएगा। उन मामलों में, आपके while True लूप को ठीक काम करना चाहिए। यदि ग्राहक डिस्कनेक्ट नहीं करता है, तो आपको अपनी सामग्री की लंबाई को सिग्नल करने, अपने संदेशों को सीमित करने, या टाइमआउट लागू करने के लिए कुछ तरीका पता लगाना होगा।

यदि आप अपना सटीक ग्राहक कोड और अपने परीक्षण प्रोटोकॉल का वर्णन पोस्ट कर सकते हैं तो मुझे और मदद करने की कोशिश करने में खुशी होगी।

+0

मुझे मिली सबसे अच्छी विधि है संदेश/फ़ाइल/डेटा में बाइट्स की संख्या का पता लगाने के लिए, संदेश के पहले संदेश/फ़ाइल/डेटा की लंबाई, हेडर के रूप में, 'del'' जैसे डिलीमीटर के साथ भेजें। 'recv' जब तक आप ':' का पता लगाकर संदेश की लंबाई प्राप्त न करें, फिर' rev' 'जो आपको हेडर पर स्पष्ट रूप से आवश्यक है। यदि यह एक फ़ाइल है तो फ़ाइल को 'रिकर्व' खंडों में एक लूप बनाएं, जबकि सुनिश्चित करें कि 'rev' आकार को अंतिम बाइट तक 2 तक विभाजित करना सुनिश्चित करें (यदि 'कुल बाइट% 2! = 0')। मैं बड़ी फाइलों (जीबी लायक) को स्थानांतरित करने के लिए उस विधि का उपयोग करता हूं और यह प्रगति सलाखों के लिए अच्छी तरह से उधार देता है। – DuckPuncher

14

आप वैकल्पिक रूप से recv(x_bytes, socket.MSG_WAITALL) का उपयोग कर सकते हैं, जो केवल यूनिक्स पर काम करता है, और वास्तव में x_bytes वापस आ जाएगा।

0

मुझे पता है कि यह पुराना है, लेकिन मुझे उम्मीद है कि यह किसी की मदद करेगा।

नियमित अजगर सॉकेट मैंने पाया का उपयोग करना है कि आप भेजने के लिए और

# tcp_echo_server.py 
import socket 

ADDRESS = '' 
PORT = 54321 

connections = [] 
host = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
host.setblocking(0) 
host.bind((ADDRESS, PORT)) 
host.listen(10) # 10 is how many clients it accepts 

def close_socket(connection): 
    try: 
     connection.shutdown(socket.SHUT_RDWR) 
    except: 
     pass 
    try: 
     connection.close() 
    except: 
     pass 

def read(): 
    for i in reversed(range(len(connections))): 
     try: 
      data, sender = connections[i][0].recvfrom(1500) 
      return data 
     except (BlockingIOError, socket.timeout, OSError): 
      pass 
     except (ConnectionResetError, ConnectionAbortedError): 
      close_socket(connections[i][0]) 
      connections.pop(i) 
    return b'' # return empty if no data found 

def write(data): 
    for i in reversed(range(len(connections))): 
     try: 
      connections[i][0].sendto(data, connections[i][1]) 
     except (BlockingIOError, socket.timeout, OSError): 
      pass 
     except (ConnectionResetError, ConnectionAbortedError): 
      close_socket(connections[i][0]) 
      connections.pop(i) 

# Run the main loop 
while True: 
    try: 
     con, addr = host.accept() 
     connections.append((con, addr)) 
    except BlockingIOError: 
     pass 

    data = read() 
    if data != b'': 
     print(data) 
     write(b'ECHO: ' + data) 
     if data == b"exit": 
      break 

# Close the sockets 
for i in reversed(range(len(connections))): 
    close_socket(connections[i][0]) 
    connections.pop(i) 
close_socket(host) 

ग्राहक समान है

# tcp_client.py 
import socket 

ADDRESS = "localhost" 
PORT = 54321 

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.connect((ADDRESS, PORT)) 
s.setblocking(0) 

def close_socket(connection): 
    try: 
     connection.shutdown(socket.SHUT_RDWR) 
    except: 
     pass 
    try: 
     connection.close() 
    except: 
     pass 

def read(): 
    """Read data and return the read bytes.""" 
    try: 
     data, sender = s.recvfrom(1500) 
     return data 
    except (BlockingIOError, socket.timeout, AttributeError, OSError): 
     return b'' 
    except (ConnectionResetError, ConnectionAbortedError, AttributeError): 
     close_socket(s) 
     return b'' 

def write(data): 
    try: 
     s.sendto(data, (ADDRESS, PORT)) 
    except (ConnectionResetError, ConnectionAbortedError): 
     close_socket(s) 

while True: 
    msg = input("Enter a message: ") 
    write(msg.encode('utf-8')) 

    data = read() 
    if data != b"": 
     print("Message Received:", data) 

    if msg == "exit": 
     break 

close_socket(s) 
1

नोट sendto और recvfrom का उपयोग कर पैकेट में जानकारी प्राप्त कर सकते हैं कि सही कारण क्यों अपने कोड जमे हुए है क्योंकि आपने बहुत अधिक अनुरोध.recv() बफर आकार सेट किया है। यहाँ से समझाया गया है What means buffer size in socket.recv(buffer_size)

इस कोड को जब तक यह एक खाली टीसीपी संदेश प्राप्त करेंगे काम करेंगे (अगर आप इस खाली संदेश मुद्रित करना चाहते हैं, यह b'' दिखाने चाहते हैं):

while True:  
    data = self.request.recv(1024) 
    if not data: break 

और ध्यान दें, कि खाली टीसीपी संदेश भेजने के लिए कोई रास्ता नहीं है। socket.send(b'') बस काम नहीं करेगा।

क्यों? चूंकि खाली संदेश केवल तभी भेजा जाता है जब आप socket.close() टाइप करते हैं, इसलिए आपकी स्क्रिप्ट तब तक लूप होगी जब तक आप अपना कनेक्शन बंद नहीं करेंगे। हंस एल यहां बताया गया है कि कुछ good methods to end message हैं।

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