एक स्केलेबल टीसीपी / आईपी आधारित सर्वर कैसे लिखें


148

मैं एक नया विंडोज सर्विस एप्लिकेशन लिखने के डिज़ाइन चरण में हूं जो लंबे समय तक चलने वाले कनेक्शनों के लिए टीसीपी / आईपी कनेक्शन स्वीकार करता है (यानी यह HTTP की तरह नहीं है जहां कई छोटे कनेक्शन हैं, बल्कि एक क्लाइंट कनेक्ट होता है और घंटों या दिनों के लिए जुड़ा रहता है) यहां तक ​​कि सप्ताह)।

मैं नेटवर्क आर्किटेक्चर को डिजाइन करने के सर्वोत्तम तरीकों के लिए विचारों की तलाश कर रहा हूं। मुझे सेवा के लिए कम से कम एक धागा शुरू करने की आवश्यकता है। मैं Asynch API (BeginRecieve, आदि ..) का उपयोग करने पर विचार कर रहा हूं क्योंकि मुझे नहीं पता कि मैं किसी भी समय (संभवत: सैकड़ों) कितने ग्राहकों से जुड़ा होगा। मैं निश्चित रूप से प्रत्येक कनेक्शन के लिए एक धागा शुरू नहीं करना चाहता हूं।

डेटा मुख्य रूप से मेरे सर्वर से ग्राहकों के लिए प्रवाहित होगा, लेकिन इस अवसर पर ग्राहकों से भेजे गए कुछ कमांड होंगे। यह मुख्य रूप से एक मॉनिटरिंग ऐपेटिटॉन है जिसमें मेरा सर्वर ग्राहकों को समय-समय पर स्टेटस डेटा भेजता है।

यह संभव के रूप में स्केलेबल बनाने के लिए सबसे अच्छा तरीका है पर कोई सुझाव? बेसिक वर्कफ़्लो? धन्यवाद।

संपादित करें: स्पष्ट होने के लिए, मैं .net आधारित समाधानों की तलाश में हूं (यदि संभव हो तो C #, लेकिन कोई भी .net भाषा काम करेगी।

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

संपादित करें: मैं सभी को धन्यवाद देना चाहता हूं जिन्होंने अच्छे उत्तर दिए। दुर्भाग्य से, मैं केवल एक ही स्वीकार कर सकता था, और मैंने अधिक प्रसिद्ध शुरुआत / समाप्ति विधि को स्वीकार करने का विकल्प चुना। एसाक का समाधान बेहतर हो सकता है, लेकिन यह अभी भी काफी नया है कि मुझे नहीं पता कि यह कैसे काम करेगा।

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


1
क्या आप पूरी तरह से आश्वस्त हैं कि इसे लंबे समय तक चलने वाला कनेक्शन होना चाहिए? यह सीमित जानकारी से बताने के लिए प्रदान की कठिन है, लेकिन मैं केवल करना होगा कि अगर बिल्कुल जरूरी ..
Markt

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

1
यह एक वैध कारण नहीं है। Http समर्थन लंबे समय तक चलने वाले कनेक्शनों को ठीक करता है। आप बस एक कनेक्शन खोलते हैं और एक repsonse (रुकी हुई पोल) की प्रतीक्षा करते हैं। यह कई AJAX शैली ऐप आदि के लिए ठीक काम करता है। आपको कैसे लगता है कि जीमेल काम करता है :-)
TFD

2
जीमेल ईमेल के लिए समय-समय पर काम करता है, यह लंबे समय तक चलने वाला कनेक्शन नहीं रखता है। यह ईमेल के लिए ठीक है, जहां वास्तविक समय की प्रतिक्रिया की आवश्यकता नहीं है।
एरिक फनकेनबसच

2
मतदान, या खींच, अच्छी तरह से तराजू लेकिन जल्दी विलंबता विकसित करता है। पुश करने का पैमाना नहीं है, लेकिन विलंबता को कम करने या समाप्त करने में मदद करता है।
andrewbadera

जवाबों:


92

मैंने अतीत में ऐसा ही कुछ लिखा है। मेरे शोध के वर्षों से पता चला है कि एसिंक्रोनस सॉकेट्स का उपयोग करके अपने स्वयं के सॉकेट कार्यान्वयन को लिखना सबसे अच्छा दांव था। इसका मतलब यह था कि ग्राहक वास्तव में कुछ भी नहीं कर रहे थे जो वास्तव में अपेक्षाकृत कम संसाधनों की आवश्यकता थी। जो कुछ भी घटित होता है उसे .net थ्रेड पूल द्वारा नियंत्रित किया जाता है।

मैंने इसे एक वर्ग के रूप में लिखा था जो सर्वरों के लिए सभी कनेक्शनों का प्रबंधन करता है।

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

private List<xConnection> _sockets;

इसके अलावा आपको इनकमिंग कनेक्शन के लिए वास्तव में सुनने वाले सॉकेट की आवश्यकता है।

private System.Net.Sockets.Socket _serverSocket;

प्रारंभ विधि वास्तव में सर्वर सॉकेट शुरू करती है और किसी भी इनकमिंग कनेक्शन के लिए सुनना शुरू करती है।

public bool Start()
{
  System.Net.IPHostEntry localhost = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName());
  System.Net.IPEndPoint serverEndPoint;
  try
  {
     serverEndPoint = new System.Net.IPEndPoint(localhost.AddressList[0], _port);
  }
  catch (System.ArgumentOutOfRangeException e)
  {
    throw new ArgumentOutOfRangeException("Port number entered would seem to be invalid, should be between 1024 and 65000", e);
  }
  try
  {
    _serverSocket = new System.Net.Sockets.Socket(serverEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
   }
   catch (System.Net.Sockets.SocketException e)
   {
      throw new ApplicationException("Could not create socket, check to make sure not duplicating port", e);
    }
    try
    {
      _serverSocket.Bind(serverEndPoint);
      _serverSocket.Listen(_backlog);
    }
    catch (Exception e)
    {
       throw new ApplicationException("Error occured while binding socket, check inner exception", e);
    }
    try
    {
       //warning, only call this once, this is a bug in .net 2.0 that breaks if 
       // you're running multiple asynch accepts, this bug may be fixed, but
       // it was a major pain in the ass previously, so make sure there is only one
       //BeginAccept running
       _serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
    }
    catch (Exception e)
    {
       throw new ApplicationException("Error occured starting listeners, check inner exception", e);
    }
    return true;
 }

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

_ServerSocket.BeginAccept (नया AsyncCallback (acceptCallback)), _serverSocket ऊपर) अनिवार्य रूप से हमारे सर्वर सॉकेट को जब भी कोई उपयोगकर्ता जोड़ता है, तो AcceptCallback विधि को कॉल करने के लिए सेट करता है। यह विधि .Net थ्रेडपूल से चलती है, जो यदि आपके पास कई ब्लॉकिंग ऑपरेशन हैं, तो स्वचालित रूप से अतिरिक्त वर्कर थ्रेड बनाने का काम करती है। यह सर्वर पर किसी भी लोड को बेहतर तरीके से संभालना चाहिए।

    private void acceptCallback(IAsyncResult result)
    {
       xConnection conn = new xConnection();
       try
       {
         //Finish accepting the connection
         System.Net.Sockets.Socket s = (System.Net.Sockets.Socket)result.AsyncState;
         conn = new xConnection();
         conn.socket = s.EndAccept(result);
         conn.buffer = new byte[_bufferSize];
         lock (_sockets)
         {
           _sockets.Add(conn);
         }
         //Queue recieving of data from the connection
         conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), conn);
         //Queue the accept of the next incomming connection
         _serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
       }
       catch (SocketException e)
       {
         if (conn.socket != null)
         {
           conn.socket.Close();
           lock (_sockets)
           {
             _sockets.Remove(conn);
           }
         }
         //Queue the next accept, think this should be here, stop attacks based on killing the waiting listeners
         _serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
       }
       catch (Exception e)
       {
         if (conn.socket != null)
         {
           conn.socket.Close();
           lock (_sockets)
           {
             _sockets.Remove(conn);
           }
         }
         //Queue the next accept, think this should be here, stop attacks based on killing the waiting listeners
         _serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
       }
     }

उपरोक्त कोड अनिवार्य रूप से बस आने वाले कनेक्शन को स्वीकार करने के लिए समाप्त हो गया है, कतारें BeginReceiveजो एक कॉलबैक है जो क्लाइंट को डेटा भेजने पर चलेगी, और फिर उस कतार को आगे बढ़ाएगा acceptCallbackजो अगले ग्राहक कनेक्शन को स्वीकार करेगी जो अंदर आता है।

BeginReceiveविधि कॉल क्या सॉकेट क्या जब यह ग्राहक से डेटा प्राप्त करने के लिए बताता है। इसके लिए BeginReceive, आपको इसे एक बाइट सरणी देने की आवश्यकता है, जो क्लाइंट के डेटा भेजने पर डेटा को कॉपी करेगा। ReceiveCallbackविधि कहा जाता हो जाएगा, जो कि हम प्राप्त जानकारी का क्या।

private void ReceiveCallback(IAsyncResult result)
{
  //get our connection from the callback
  xConnection conn = (xConnection)result.AsyncState;
  //catch any errors, we'd better not have any
  try
  {
    //Grab our buffer and count the number of bytes receives
    int bytesRead = conn.socket.EndReceive(result);
    //make sure we've read something, if we haven't it supposadly means that the client disconnected
    if (bytesRead > 0)
    {
      //put whatever you want to do when you receive data here

      //Queue the next receive
      conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), conn);
     }
     else
     {
       //Callback run but no data, close the connection
       //supposadly means a disconnect
       //and we still have to close the socket, even though we throw the event later
       conn.socket.Close();
       lock (_sockets)
       {
         _sockets.Remove(conn);
       }
     }
   }
   catch (SocketException e)
   {
     //Something went terribly wrong
     //which shouldn't have happened
     if (conn.socket != null)
     {
       conn.socket.Close();
       lock (_sockets)
       {
         _sockets.Remove(conn);
       }
     }
   }
 }

EDIT: इस पैटर्न में मैं यह बताना भूल गया कि कोड के इस क्षेत्र में:

//put whatever you want to do when you receive data here

//Queue the next receive
conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), conn);

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

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

public bool Send(byte[] message, xConnection conn)
{
  if (conn != null && conn.socket.Connected)
  {
    lock (conn.socket)
    {
    //we use a blocking mode send, no async on the outgoing
    //since this is primarily a multithreaded application, shouldn't cause problems to send in blocking mode
       conn.socket.Send(bytes, bytes.Length, SocketFlags.None);
     }
   }
   else
     return false;
   return true;
 }

उपरोक्त भेजने की विधि वास्तव में एक तुल्यकालिक Sendकॉल का उपयोग करती है , मेरे लिए जो संदेश के आकार और मेरे आवेदन के बहुआयामी प्रकृति के कारण ठीक थी। यदि आप प्रत्येक ग्राहक को भेजना चाहते हैं, तो आपको बस _sockets सूची के माध्यम से लूप करना होगा।

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

public class xConnection : xBase
{
  public byte[] buffer;
  public System.Net.Sockets.Socket socket;
}

यहाँ संदर्भ के लिए usingमैं भी शामिल हूँ क्योंकि मैं हमेशा परेशान हो जाता हूँ जब वे शामिल नहीं हैं।

using System.Net.Sockets;

मुझे आशा है कि यह उपयोगी है, यह सबसे साफ कोड नहीं हो सकता है, लेकिन यह काम करता है। कोड के लिए कुछ बारीकियां भी हैं जिन्हें आपको बदलने के बारे में थका होना चाहिए। एक के लिए, BeginAcceptकिसी भी एक समय में केवल एक को बुलाया जाता है। इस के आसपास एक बहुत कष्टप्रद .net बग हुआ करता था, जो वर्षों पहले था इसलिए मुझे विवरण याद नहीं है।

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

इसके अलावा, मैंने अपने कोड का एक बहुत हैक कर लिया, लेकिन जो हो रहा है उसका सार छोड़ दिया। यह आप डिजाइन के लिए एक अच्छी शुरुआत होनी चाहिए। यदि आपके पास इसके आसपास कोई और प्रश्न है तो एक टिप्पणी छोड़ दें।


1
यह एक अच्छा जवाब है केविन .. लगता है कि आप इनाम पाने के लिए ट्रैक पर हैं। :)
एरिक फुन्केनबस

6
मुझे नहीं पता कि यह सबसे अधिक मतदान का जवाब क्यों है। शुरुआत * अंत * सी # में नेटवर्किंग करने का सबसे तेज़ तरीका नहीं है, और न ही सबसे अधिक स्केलेबल। यह सिंक्रोनस की तुलना में तेज है, लेकिन विंडोज में हुड के नीचे चलने वाले बहुत सारे ऑपरेशन हैं जो वास्तव में इस नेटवर्क पथ को धीमा कर देते हैं।
esac

6
ध्यान रखें कि पिछली टिप्पणी में esac ने क्या लिखा था। शुरुआत-अंत पैटर्न शायद आपके लिए एक बिंदु तक काम करेगा, बिल्ली मेरा कोड वर्तमान में शुरुआत-अंत का उपयोग कर रहा है, लेकिन .net 3.5 में इसकी सीमाओं में सुधार है। मैं इनाम के बारे में परवाह नहीं करता, लेकिन आप इस जवाब को लागू करने पर भी मेरे उत्तर में लिंक को पढ़ने की सलाह देंगे। "संस्करण 3.5 में सॉकेट प्रदर्शन संवर्द्धन"
jvanderh

1
मैं बस उनके अंदर फेंकना चाहता था क्योंकि मैं पर्याप्त स्पष्ट नहीं हो सकता था, यह .net 2.0 युग कोड है जहां मैं यह कहता हूं कि यह एक बहुत व्यवहार्य पैटर्न था। हालाँकि, esac का उत्तर कुछ अधिक आधुनिक प्रतीत होता है यदि लक्ष्यीकरण .net 3.5, मेरे पास एकमात्र नाइटपिक है जो घटनाओं को फेंक रहा है :) लेकिन इसे आसानी से बदला जा सकता है। इसके अलावा, मैंने इस कोड के साथ थ्रूपुट परीक्षण किया और दोहरे कोर ओपेरॉन 2Ghz पर 100Mbps ईथरनेट को अधिकतम करने में सक्षम था, और इस कोड के शीर्ष पर एक एन्क्रिप्शन परत जोड़ा।
केविन निस्बेट

1
@KevinNisbet मुझे पता है कि यह बहुत देर हो चुकी है, लेकिन किसी के लिए भी इस जवाब का उपयोग करके अपने स्वयं के सर्वरों को डिजाइन करना - भेजना भी अतुल्यकालिक होना चाहिए, क्योंकि अन्यथा आप गतिरोध की संभावना के लिए खुद को खोलते हैं। यदि दोनों पक्ष अपने संबंधित बफ़र्स को भरने वाले डेटा लिखते हैं, तो Sendदोनों तरफ से तरीके अनिश्चित रूप से अवरुद्ध हो जाएंगे, क्योंकि इनपुट डेटा को पढ़ने में कोई बाधा नहीं है।
लुआं

83

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

इन मुद्दों को हल करने के लिए, उन्होंने तरीकों का * Async सेट पेश किया: MSDN से http://msdn.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.aspx

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

इन संवर्द्धन की मुख्य विशेषता उच्च-मात्रा वाले अतुल्यकालिक सॉकेट I / O के दौरान वस्तुओं के बार-बार आवंटन और सिंक्रनाइज़ेशन से बचना है। सिस्टम.नेट.सॉकेट्स द्वारा वर्तमान में लागू किया गया आरंभ / समाप्ति डिज़ाइन पैटर्न :: :: सॉकेट क्लास को सिस्टम की आवश्यकता होती है .. ::। IAsyncResult ऑब्जेक्ट को प्रत्येक एसिंक्रोनस सॉकेट ऑपरेशन के लिए आवंटित किया जाता है।

कवर के तहत, * Async API IO पूरा करने वाले पोर्ट का उपयोग करता है जो नेटवर्किंग संचालन करने का सबसे तेज़ तरीका है, http://msdn.microsoft.com/en-us/magazine/cc302334.aspx देखें

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

 public class Telnet
{
    private readonly Pool<SocketAsyncEventArgs> m_EventArgsPool;
    private Socket m_ListenSocket;

    /// <summary>
    /// This event fires when a connection has been established.
    /// </summary>
    public event EventHandler<SocketAsyncEventArgs> Connected;

    /// <summary>
    /// This event fires when a connection has been shutdown.
    /// </summary>
    public event EventHandler<SocketAsyncEventArgs> Disconnected;

    /// <summary>
    /// This event fires when data is received on the socket.
    /// </summary>
    public event EventHandler<SocketAsyncEventArgs> DataReceived;

    /// <summary>
    /// This event fires when data is finished sending on the socket.
    /// </summary>
    public event EventHandler<SocketAsyncEventArgs> DataSent;

    /// <summary>
    /// This event fires when a line has been received.
    /// </summary>
    public event EventHandler<LineReceivedEventArgs> LineReceived;

    /// <summary>
    /// Specifies the port to listen on.
    /// </summary>
    [DefaultValue(23)]
    public int ListenPort { get; set; }

    /// <summary>
    /// Constructor for Telnet class.
    /// </summary>
    public Telnet()
    {           
        m_EventArgsPool = new Pool<SocketAsyncEventArgs>();
        ListenPort = 23;
    }

    /// <summary>
    /// Starts the telnet server listening and accepting data.
    /// </summary>
    public void Start()
    {
        IPEndPoint endpoint = new IPEndPoint(0, ListenPort);
        m_ListenSocket = new Socket(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

        m_ListenSocket.Bind(endpoint);
        m_ListenSocket.Listen(100);

        //
        // Post Accept
        //
        StartAccept(null);
    }

    /// <summary>
    /// Not Yet Implemented. Should shutdown all connections gracefully.
    /// </summary>
    public void Stop()
    {
        //throw (new NotImplementedException());
    }

    //
    // ACCEPT
    //

    /// <summary>
    /// Posts a requests for Accepting a connection. If it is being called from the completion of
    /// an AcceptAsync call, then the AcceptSocket is cleared since it will create a new one for
    /// the new user.
    /// </summary>
    /// <param name="e">null if posted from startup, otherwise a <b>SocketAsyncEventArgs</b> for reuse.</param>
    private void StartAccept(SocketAsyncEventArgs e)
    {
        if (e == null)
        {
            e = m_EventArgsPool.Pop();
            e.Completed += Accept_Completed;
        }
        else
        {
            e.AcceptSocket = null;
        }

        if (m_ListenSocket.AcceptAsync(e) == false)
        {
            Accept_Completed(this, e);
        }
    }

    /// <summary>
    /// Completion callback routine for the AcceptAsync post. This will verify that the Accept occured
    /// and then setup a Receive chain to begin receiving data.
    /// </summary>
    /// <param name="sender">object which posted the AcceptAsync</param>
    /// <param name="e">Information about the Accept call.</param>
    private void Accept_Completed(object sender, SocketAsyncEventArgs e)
    {
        //
        // Socket Options
        //
        e.AcceptSocket.NoDelay = true;

        //
        // Create and setup a new connection object for this user
        //
        Connection connection = new Connection(this, e.AcceptSocket);

        //
        // Tell the client that we will be echo'ing data sent
        //
        DisableEcho(connection);

        //
        // Post the first receive
        //
        SocketAsyncEventArgs args = m_EventArgsPool.Pop();
        args.UserToken = connection;

        //
        // Connect Event
        //
        if (Connected != null)
        {
            Connected(this, args);
        }

        args.Completed += Receive_Completed;
        PostReceive(args);

        //
        // Post another accept
        //
        StartAccept(e);
    }

    //
    // RECEIVE
    //    

    /// <summary>
    /// Post an asynchronous receive on the socket.
    /// </summary>
    /// <param name="e">Used to store information about the Receive call.</param>
    private void PostReceive(SocketAsyncEventArgs e)
    {
        Connection connection = e.UserToken as Connection;

        if (connection != null)
        {
            connection.ReceiveBuffer.EnsureCapacity(64);
            e.SetBuffer(connection.ReceiveBuffer.DataBuffer, connection.ReceiveBuffer.Count, connection.ReceiveBuffer.Remaining);

            if (connection.Socket.ReceiveAsync(e) == false)
            {
                Receive_Completed(this, e);
            }              
        }
    }

    /// <summary>
    /// Receive completion callback. Should verify the connection, and then notify any event listeners
    /// that data has been received. For now it is always expected that the data will be handled by the
    /// listeners and thus the buffer is cleared after every call.
    /// </summary>
    /// <param name="sender">object which posted the ReceiveAsync</param>
    /// <param name="e">Information about the Receive call.</param>
    private void Receive_Completed(object sender, SocketAsyncEventArgs e)
    {
        Connection connection = e.UserToken as Connection;

        if (e.BytesTransferred == 0 || e.SocketError != SocketError.Success || connection == null)
        {
            Disconnect(e);
            return;
        }

        connection.ReceiveBuffer.UpdateCount(e.BytesTransferred);

        OnDataReceived(e);

        HandleCommand(e);
        Echo(e);

        OnLineReceived(connection);

        PostReceive(e);
    }

    /// <summary>
    /// Handles Event of Data being Received.
    /// </summary>
    /// <param name="e">Information about the received data.</param>
    protected void OnDataReceived(SocketAsyncEventArgs e)
    {
        if (DataReceived != null)
        {                
            DataReceived(this, e);
        }
    }

    /// <summary>
    /// Handles Event of a Line being Received.
    /// </summary>
    /// <param name="connection">User connection.</param>
    protected void OnLineReceived(Connection connection)
    {
        if (LineReceived != null)
        {
            int index = 0;
            int start = 0;

            while ((index = connection.ReceiveBuffer.IndexOf('\n', index)) != -1)
            {
                string s = connection.ReceiveBuffer.GetString(start, index - start - 1);
                s = s.Backspace();

                LineReceivedEventArgs args = new LineReceivedEventArgs(connection, s);
                Delegate[] delegates = LineReceived.GetInvocationList();

                foreach (Delegate d in delegates)
                {
                    d.DynamicInvoke(new object[] { this, args });

                    if (args.Handled == true)
                    {
                        break;
                    }
                }

                if (args.Handled == false)
                {
                    connection.CommandBuffer.Enqueue(s);
                }

                start = index;
                index++;
            }

            if (start > 0)
            {
                connection.ReceiveBuffer.Reset(0, start + 1);
            }
        }
    }

    //
    // SEND
    //

    /// <summary>
    /// Overloaded. Sends a string over the telnet socket.
    /// </summary>
    /// <param name="connection">Connection to send data on.</param>
    /// <param name="s">Data to send.</param>
    /// <returns>true if the data was sent successfully.</returns>
    public bool Send(Connection connection, string s)
    {
        if (String.IsNullOrEmpty(s) == false)
        {
            return Send(connection, Encoding.Default.GetBytes(s));
        }

        return false;
    }

    /// <summary>
    /// Overloaded. Sends an array of data to the client.
    /// </summary>
    /// <param name="connection">Connection to send data on.</param>
    /// <param name="data">Data to send.</param>
    /// <returns>true if the data was sent successfully.</returns>
    public bool Send(Connection connection, byte[] data)
    {
        return Send(connection, data, 0, data.Length);
    }

    public bool Send(Connection connection, char c)
    {
        return Send(connection, new byte[] { (byte)c }, 0, 1);
    }

    /// <summary>
    /// Sends an array of data to the client.
    /// </summary>
    /// <param name="connection">Connection to send data on.</param>
    /// <param name="data">Data to send.</param>
    /// <param name="offset">Starting offset of date in the buffer.</param>
    /// <param name="length">Amount of data in bytes to send.</param>
    /// <returns></returns>
    public bool Send(Connection connection, byte[] data, int offset, int length)
    {
        bool status = true;

        if (connection.Socket == null || connection.Socket.Connected == false)
        {
            return false;
        }

        SocketAsyncEventArgs args = m_EventArgsPool.Pop();
        args.UserToken = connection;
        args.Completed += Send_Completed;
        args.SetBuffer(data, offset, length);

        try
        {
            if (connection.Socket.SendAsync(args) == false)
            {
                Send_Completed(this, args);
            }
        }
        catch (ObjectDisposedException)
        {                
            //
            // return the SocketAsyncEventArgs back to the pool and return as the
            // socket has been shutdown and disposed of
            //
            m_EventArgsPool.Push(args);
            status = false;
        }

        return status;
    }

    /// <summary>
    /// Sends a command telling the client that the server WILL echo data.
    /// </summary>
    /// <param name="connection">Connection to disable echo on.</param>
    public void DisableEcho(Connection connection)
    {
        byte[] b = new byte[] { 255, 251, 1 };
        Send(connection, b);
    }

    /// <summary>
    /// Completion callback for SendAsync.
    /// </summary>
    /// <param name="sender">object which initiated the SendAsync</param>
    /// <param name="e">Information about the SendAsync call.</param>
    private void Send_Completed(object sender, SocketAsyncEventArgs e)
    {
        e.Completed -= Send_Completed;              
        m_EventArgsPool.Push(e);
    }        

    /// <summary>
    /// Handles a Telnet command.
    /// </summary>
    /// <param name="e">Information about the data received.</param>
    private void HandleCommand(SocketAsyncEventArgs e)
    {
        Connection c = e.UserToken as Connection;

        if (c == null || e.BytesTransferred < 3)
        {
            return;
        }

        for (int i = 0; i < e.BytesTransferred; i += 3)
        {
            if (e.BytesTransferred - i < 3)
            {
                break;
            }

            if (e.Buffer[i] == (int)TelnetCommand.IAC)
            {
                TelnetCommand command = (TelnetCommand)e.Buffer[i + 1];
                TelnetOption option = (TelnetOption)e.Buffer[i + 2];

                switch (command)
                {
                    case TelnetCommand.DO:
                        if (option == TelnetOption.Echo)
                        {
                            // ECHO
                        }
                        break;
                    case TelnetCommand.WILL:
                        if (option == TelnetOption.Echo)
                        {
                            // ECHO
                        }
                        break;
                }

                c.ReceiveBuffer.Remove(i, 3);
            }
        }          
    }

    /// <summary>
    /// Echoes data back to the client.
    /// </summary>
    /// <param name="e">Information about the received data to be echoed.</param>
    private void Echo(SocketAsyncEventArgs e)
    {
        Connection connection = e.UserToken as Connection;

        if (connection == null)
        {
            return;
        }

        //
        // backspacing would cause the cursor to proceed beyond the beginning of the input line
        // so prevent this
        //
        string bs = connection.ReceiveBuffer.ToString();

        if (bs.CountAfterBackspace() < 0)
        {
            return;
        }

        //
        // find the starting offset (first non-backspace character)
        //
        int i = 0;

        for (i = 0; i < connection.ReceiveBuffer.Count; i++)
        {
            if (connection.ReceiveBuffer[i] != '\b')
            {
                break;
            }
        }

        string s = Encoding.Default.GetString(e.Buffer, Math.Max(e.Offset, i), e.BytesTransferred);

        if (connection.Secure)
        {
            s = s.ReplaceNot("\r\n\b".ToCharArray(), '*');
        }

        s = s.Replace("\b", "\b \b");

        Send(connection, s);
    }

    //
    // DISCONNECT
    //

    /// <summary>
    /// Disconnects a socket.
    /// </summary>
    /// <remarks>
    /// It is expected that this disconnect is always posted by a failed receive call. Calling the public
    /// version of this method will cause the next posted receive to fail and this will cleanup properly.
    /// It is not advised to call this method directly.
    /// </remarks>
    /// <param name="e">Information about the socket to be disconnected.</param>
    private void Disconnect(SocketAsyncEventArgs e)
    {
        Connection connection = e.UserToken as Connection;

        if (connection == null)
        {
            throw (new ArgumentNullException("e.UserToken"));
        }

        try
        {
            connection.Socket.Shutdown(SocketShutdown.Both);
        }
        catch
        {
        }

        connection.Socket.Close();

        if (Disconnected != null)
        {
            Disconnected(this, e);
        }

        e.Completed -= Receive_Completed;
        m_EventArgsPool.Push(e);
    }

    /// <summary>
    /// Marks a specific connection for graceful shutdown. The next receive or send to be posted
    /// will fail and close the connection.
    /// </summary>
    /// <param name="connection"></param>
    public void Disconnect(Connection connection)
    {
        try
        {
            connection.Socket.Shutdown(SocketShutdown.Both);
        }
        catch (Exception)
        {
        }            
    }

    /// <summary>
    /// Telnet command codes.
    /// </summary>
    internal enum TelnetCommand
    {
        SE = 240,
        NOP = 241,
        DM = 242,
        BRK = 243,
        IP = 244,
        AO = 245,
        AYT = 246,
        EC = 247,
        EL = 248,
        GA = 249,
        SB = 250,
        WILL = 251,
        WONT = 252,
        DO = 253,
        DONT = 254,
        IAC = 255
    }

    /// <summary>
    /// Telnet command options.
    /// </summary>
    internal enum TelnetOption
    {
        Echo = 1,
        SuppressGoAhead = 3,
        Status = 5,
        TimingMark = 6,
        TerminalType = 24,
        WindowSize = 31,
        TerminalSpeed = 32,
        RemoteFlowControl = 33,
        LineMode = 34,
        EnvironmentVariables = 36
    }
}

यह बहुत सीधे आगे है, और एक सरल उदाहरण है। धन्यवाद। मैं प्रत्येक विधि के समर्थक और विपक्ष का मूल्यांकन करने जा रहा हूं।
एरिक फनकेनबसच

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

1
मेरे मामले में, मेरे टेलनेट सर्वर के लिए, 100%, हाँ वे क्रम में हैं। कुंजी AcceptAsync, ReceiveAsync, आदि को कॉल करने से पहले उचित कॉलबैक विधि सेट कर रही है। मेरे मामले में मैं SendAsync को एक अलग थ्रेड पर करता हूं, इसलिए यदि यह स्वीकार / भेजें / प्राप्त / भेजें / प्राप्त / प्राप्त करने के लिए संशोधित पैटर्न है, तो इसे संशोधित करने की आवश्यकता होगी।
esac

1
बिंदु # 2 भी कुछ ऐसा है जिसे आपको ध्यान में रखना होगा। मैं SocketAsyncEventArgs संदर्भ में अपना 'कनेक्शन' ऑब्जेक्ट संग्रहीत कर रहा हूं। इसका मतलब यह है कि मेरे पास केवल प्रति कनेक्शन बफर प्राप्त होता है। मैं इस SocketAsyncEventArgs के साथ एक अन्य प्राप्त पोस्ट नहीं कर रहा हूं जब तक DataReceived पूरा नहीं हो जाता है, इसलिए इस पर कोई और डेटा पूरा होने तक पढ़ा नहीं जा सकता है। मुझे पता है कि इस डेटा पर कोई लंबा ऑपरेशन नहीं किया जाना चाहिए। मैं वास्तव में लॉकफ़्री कतार पर प्राप्त सभी डेटा के पूरे बफर को स्थानांतरित करता हूं और फिर इसे एक अलग थ्रेड पर संसाधित करता हूं। यह नेटवर्क हिस्से पर कम विलंबता सुनिश्चित करता है।
को'09

1
एक साइड नोट पर, मैंने इस कोड के लिए यूनिट परीक्षण और लोड परीक्षण लिखे, और जैसा कि मैंने 1 उपयोगकर्ता से 250 उपयोगकर्ताओं (एक एकल दोहरे कोर सिस्टम, 4 जीबी रैम) पर 100 बाइट्स के लिए प्रतिक्रिया समय बढ़ाया (1) पैकेट) और 10000 बाइट्स (3 पैकेट) पूरे उपयोगकर्ता लोड वक्र में समान रहे।
esac

46

Coversant के क्रिस मुलिंस द्वारा लिखित .NET का उपयोग करके स्केलेबल टीसीपी / आईपी की वास्तव में अच्छी चर्चा हुआ करती थी, दुर्भाग्य से ऐसा प्रतीत होता है कि उसका ब्लॉग अपने पूर्व स्थान से गायब हो गया है, इसलिए मैं स्मृति से उसकी सलाह लेने की कोशिश करूंगा (कुछ उपयोगी टिप्पणियाँ उनके इस धागे में दिखाई देते हैं: C ++ बनाम C #: एक अत्यधिक स्केलेबल IOCP सर्वर का विकास )

सबसे पहले और सबसे महत्वपूर्ण बात यह है कि स्केलेबिलिटी प्रदान करने के लिए कक्षा में उपयोग करने वाले Begin/Endऔर कक्षा में दोनों Asyncतरीके SocketIO कम्प्लीशन पोर्ट्स (IOCP) का उपयोग करते हैं। यह बहुत बड़ा अंतर बनाता है (जब सही ढंग से उपयोग किया जाता है, तो नीचे देखें) स्केलेबिलिटी की तुलना में आप अपने समाधान को लागू करने के लिए वास्तव में किन दो तरीकों को अपनाते हैं।

क्रिस मुलिंस के पोस्ट का उपयोग करने पर आधारित था Begin/End, जो कि मेरे पास व्यक्तिगत रूप से अनुभव है। ध्यान दें कि क्रिस ने इस पर आधारित एक समाधान डाला जो कि 32GB की मशीन पर 10,000GB तक समवर्ती ग्राहक कनेक्शन को 2GB मेमोरी के साथ स्केल करता है, और पर्याप्त मेमोरी के साथ 64-बिट प्लेटफॉर्म पर 100,000 में अच्छी तरह से। इस तकनीक के साथ मेरे अपने अनुभव से (इस तरह के भार के पास कहीं नहीं) मेरे पास इन सांकेतिक आंकड़ों पर संदेह करने का कोई कारण नहीं है।

IOCP बनाम थ्रेड-प्रति-कनेक्शन या 'सेलेक्ट' प्राइमेटिव्स

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

संदर्भ स्विच वह है जो निश्चित रूप से 'थ्रेड-प्रति-कनेक्शन' तंत्र को मार देगा, हालांकि यह एक व्यवहार्य समाधान है यदि आप केवल कुछ दर्जन कनेक्शनों के साथ काम कर रहे हैं। यह तंत्र हालांकि कल्पना के 'फैलाव' के खिंचाव से नहीं है।

IOCP का उपयोग करते समय महत्वपूर्ण विचार

स्मृति

सबसे पहले और सबसे महत्वपूर्ण यह समझना महत्वपूर्ण है कि IOCP आसानी से .NET के तहत मेमोरी मुद्दों में परिणाम कर सकता है यदि आपका कार्यान्वयन बहुत भोला है। हर IOCP BeginReceiveकॉल बफर के "पिनिंग" में परिणाम देगा जिसे आप पढ़ रहे हैं। क्यों यह एक समस्या है की एक अच्छी व्याख्या के लिए, देखें: यूं जिन की वेबलॉग: आउटऑफमोरी एक्ससेप्शन और पिनिंग

सौभाग्य से इस समस्या से बचा जा सकता है, लेकिन इसके लिए थोड़े से व्यापार की आवश्यकता है। सुझाए गए समाधान byte[]में कम से कम 90KB या-तो (स्टार्टर 2 के रूप में, आवश्यक आकार बाद के संस्करणों में बड़ा हो सकता है) के स्टार्ट-अप (या पास में) में एक बड़ा बफर आवंटित करना है । ऐसा करने का कारण यह है कि बड़े मेमोरी आवंटन स्वचालित रूप से एक गैर-कॉम्पैक्टिंग मेमोरी सेगमेंट (बड़े ऑब्जेक्ट हीप) में समाप्त हो जाते हैं जो प्रभावी रूप से स्वचालित रूप से पिन किए जाते हैं। स्टार्ट-अप में एक बड़े बफ़र को आवंटित करके आप यह सुनिश्चित करते हैं कि अमिट मेमोरी का यह ब्लॉक अपेक्षाकृत 'कम पते' पर है जहाँ यह रास्ते में नहीं मिलेगा और विखंडन का कारण बनेगा।

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

सबसे सरल उपाय यह होगा कि हर कनेक्शन को इस बफ़र में एक अद्वितीय ऑफ़सेट पर एक बाइट दिया जाए। तब आप BeginReceiveपढ़ने के लिए एकल बाइट के लिए कॉल कर सकते हैं, और आपके द्वारा प्राप्त कॉलबैक के परिणामस्वरूप शेष रीडिंग का प्रदर्शन कर सकते हैं।

प्रसंस्करण

जब आप अपने द्वारा Beginकिए गए कॉल से कॉलबैक प्राप्त करते हैं , तो यह महसूस करना बहुत महत्वपूर्ण है कि कॉलबैक में कोड निम्न-स्तरीय IOCP थ्रेड पर निष्पादित होगा। यह पूरी तरह से आवश्यक है कि आप इस कॉलबैक में लंबे ऑपरेशन से बचें। जटिल प्रसंस्करण के लिए इन थ्रेड्स का उपयोग आपकी थरथराहट को उतनी ही प्रभावी ढंग से मार देगा जितना कि 'थ्रेड-प्रति-कनेक्शन' का उपयोग करके।

सुझाए गए समाधान का उपयोग केवल कॉलबैक का उपयोग करके आने वाले डेटा को संसाधित करने के लिए किसी कार्य आइटम को कतारबद्ध करना है, जिसे किसी अन्य थ्रेड पर निष्पादित किया जाएगा। कॉलबैक के अंदर किसी भी संभावित ब्लॉकिंग ऑपरेशन से बचें ताकि IOCP थ्रेड अपने पूल में जल्द से जल्द लौट सके। .NET 4.0 में मैं सुझाव दूंगा कि सबसे आसान समाधान एक स्पॉन है Task, जो क्लाइंट सॉकेट का संदर्भ देता है और पहले बाइट की एक प्रति जो पहले से ही BeginReceiveकॉल द्वारा पढ़ी गई थी । यह कार्य तब सॉकेट के सभी डेटा को पढ़ने के लिए ज़िम्मेदार होता है जो आपके द्वारा संसाधित किए जा रहे अनुरोध का प्रतिनिधित्व करते हैं, उसे निष्पादित करते हैं, और फिर BeginReceiveIOCP के लिए सॉकेट को एक बार फिर से पंक्तिबद्ध करने के लिए एक नया कॉल करते हैं। Pre .NET 4.0, आप थ्रेडपूल का उपयोग कर सकते हैं, या अपना स्वयं का थ्रेडेड कार्य-कतार कार्यान्वयन बना सकते हैं।

सारांश

मूल रूप से, मैं इस समाधान के लिए केविन के सैंपल कोड का उपयोग करने का सुझाव दूंगा, जिसमें निम्नलिखित चेतावनी दी गई हैं:

  • सुनिश्चित करें कि आप जिस बफर को पास करते हैं BeginReceiveवह पहले से 'पिनड' है
  • सुनिश्चित करें कि आपके द्वारा पास किया गया कॉलबैक BeginReceiveआने वाले डेटा की वास्तविक प्रसंस्करण को संभालने के लिए एक कार्य को कतार से ज्यादा कुछ नहीं करता है

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


1
मेमोरी के एक छोटे से ब्लॉक को पिन करने के लिए, GCHandle ऑब्जेक्ट Alloc पद्धति का उपयोग बफर को पिन करने के लिए किया जा सकता है। एक बार यह हो जाने के बाद, मार्शल ऑब्जेक्ट के UnsafeAddrOfPinnedArrayElement का उपयोग बफर को पॉइंटर प्राप्त करने के लिए किया जा सकता है। उदाहरण के लिए: GCHandle gchTheCards = GCHandle.Alloc (TheData, GCHandleType.Pinned); IntPtr pAddr = Marshal.UnsafeAddrOfPinnedArrayElement (दत्ता, 0); (sbyte *) pTheData = (sbyte *) pAddr.ToPointer ();
बॉब ब्रायन

@BobBryan जब तक मैं एक सूक्ष्म बिंदु याद नहीं करता, जिसे आप बनाने की कोशिश कर रहे हैं, वह दृष्टिकोण वास्तव में उस समस्या के साथ मदद नहीं करता है जो मेरे समाधान को बड़े ब्लॉकों को आवंटित करके संबोधित करने की कोशिश कर रहा है, अर्थात् छोटे पिन वाले ब्लॉकों के बार-बार आवंटन में निहित नाटकीय स्मृति विखंडन की संभावना। स्मृति का।
jerjvl

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

@ बब्बर पिनिंग के बाद से बफर को स्वचालित रूप से तब होता है जब आप बिगिनरिव कहते हैं, पिनिंग वास्तव में यहाँ मुख्य बिंदु नहीं है; दक्षता थी;) ... और यह एक चिंताजनक सर्वर लिखने की कोशिश करते समय विशेष रूप से एक चिंता का विषय है, इसलिए बफर स्पेस के लिए उपयोग करने के लिए बड़े ब्लॉकों को आवंटित करने की आवश्यकता है।
जेरज्वल

@jerryjvl वास्तव में एक पुराना प्रश्न सामने लाने के लिए क्षमा करें, हालाँकि मैंने हाल ही में BeginXXX / EndXXX asynch विधियों के साथ इस सटीक समस्या का पता लगाया है। यह एक बेहतरीन पोस्ट है, लेकिन इसे खोजने के लिए काफी खुदाई की गई। मुझे आपके सुझाए गए समाधान पसंद हैं, लेकिन इसके एक हिस्से को नहीं समझते हैं: "फिर आप एक बाइट को पढ़ने के लिए एक शुरुआती कॉल कर सकते हैं, और आपके द्वारा प्राप्त कॉलबैक के परिणामस्वरूप शेष रीडिंग का प्रदर्शन कर सकते हैं।" आपके द्वारा प्राप्त कॉलबैक के परिणामस्वरूप बाकी तैयारियों को पूरा करने का क्या मतलब है?
मौसिमो

22

ऊपर दिए गए कोड नमूनों के माध्यम से आपको पहले ही उत्तर का अधिकांश भाग मिल गया। अतुल्यकालिक IO ऑपरेशन का उपयोग करना यहां जाने का बिल्कुल तरीका है। Async IO वह तरीका है जो Win32 को आंतरिक रूप से स्केल करने के लिए डिज़ाइन किया गया है। आपके द्वारा प्राप्त किए जा सकने वाले सर्वोत्तम प्रदर्शन को पूर्णता पोर्ट्स का उपयोग करके प्राप्त किया जा सकता है, अपनी सॉकेट्स को पूरा करने वाले पोर्टों से बांध कर और एक पोर्ट को पूरा करने के लिए प्रतीक्षा कर रहा है। सामान्य ज्ञान को पूरा करने के लिए प्रति सीपीयू (कोर) 2-4 धागे हैं। मैं Windows प्रदर्शन टीम से रिक वाइक द्वारा इन तीन लेखों पर जाने की अत्यधिक अनुशंसा करता हूं:

  1. प्रदर्शन के लिए डिजाइनिंग अनुप्रयोग - भाग 1
  2. प्रदर्शन के लिए डिजाइनिंग अनुप्रयोग - भाग 2
  3. प्रदर्शन के लिए डिजाइनिंग अनुप्रयोग - भाग 3

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

दूसरी बात जो आपको करने की ज़रूरत है वह यह सुनिश्चित करें कि आप बेहतर .NET अनुप्रयोग प्रदर्शन और स्केलेबिलिटी बुक पर जाएं, जो ऑनलाइन उपलब्ध है। आप अध्याय 5 में थ्रेड्स, एसिंक्रोनस कॉल और लॉक्स के उपयोग के बारे में उचित और वैध सलाह पाएंगे। लेकिन असली रत्न अध्याय 17 में हैं जहां आपको अपने थ्रेड पूल को ट्यून करने पर व्यावहारिक मार्गदर्शन के रूप में ऐसे उपहार मिलेंगे। मेरे ऐप्स में कुछ गंभीर समस्याएं थीं, जब तक कि मैंने इस अध्याय में सिफारिशों के अनुसार maxIothreads / maxWorkerThreads समायोजित नहीं किया।

आप कहते हैं कि आप एक शुद्ध टीसीपी सर्वर करना चाहते हैं, इसलिए मेरा अगला बिंदु सहज है। हालाँकि , यदि आप अपने आप को ढूंढते हैं और WebRequest वर्ग और उसके डेरिवेटिव का उपयोग करते हैं, तो चेतावनी दी जाती है कि उस दरवाजे पर एक ड्रैगन की रखवाली होती है: ServicePointManager । यह एक कॉन्फ़िगरेशन क्लास है जिसका जीवन में एक उद्देश्य है: अपने प्रदर्शन को बर्बाद करना। सुनिश्चित करें कि आप अपने सर्वर को कृत्रिम रूप से लगाए गए ServicePoint.ConnectionLimit से मुक्त करते हैं या आपका एप्लिकेशन कभी भी स्केल नहीं होगा (मैं आपको स्वयं को खोजने देता हूं कि डिफ़ॉल्ट मान क्या है ...)। आप http अनुरोधों में Expect100Continue शीर्ष लेख भेजने की डिफ़ॉल्ट नीति पर भी पुनर्विचार कर सकते हैं।

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

जब आप BeginRead / BeginWrite API के साथ खेल रहे हों और गंभीर काम शुरू करें, तब आपको पता चलेगा कि आपको अपने ट्रैफ़िक पर सुरक्षा की आवश्यकता है, यानी। NTLM / Kerberos प्रमाणीकरण और ट्रैफ़िक एन्क्रिप्शन, या कम से कम ट्रैफ़िक छेड़छाड़ सुरक्षा। आपके ऐसा करने का तरीका यह है कि आप अंतर्निहित System.Net.Security.NegotiateStream (या SslStream का उपयोग करते हैं, यदि आपको क्रॉस डिस्पैच डोमेन पर जाने की आवश्यकता है)। इसका मतलब यह है कि सीधे सॉकेट एसिंक्रोनस ऑपरेशन पर निर्भर होने के बजाय आप ऑस्ट्रेंटेडस्ट्रीम एसिंक्रोनस ऑपरेशंस पर भरोसा करेंगे। जैसे ही आप एक सॉकेट प्राप्त करते हैं (या तो क्लाइंट से कनेक्ट या सर्वर पर स्वीकार से) आप सॉकेट पर एक स्ट्रीम बनाते हैं और इसे प्रमाणीकरण के लिए सबमिट करें, या तो BeginAuthenticateAsClient या BeginAuthenticateAsServer को कॉल करके। प्रमाणीकरण पूर्ण होने के बाद (मूल InitiateSecurityContext / AcceptSecurityContext पागलपन से कम से कम आपकी सुरक्षित ...) आप अपने प्रमाणीकरण स्ट्रीम की RemoteIdentity गुण की जाँच करके और जो भी ACL सत्यापन आपके उत्पाद का समर्थन करना चाहिए कर सकते हैं। उसके बाद आप BeginWrite का उपयोग करके संदेश भेजेंगे और आप उन्हें BeginRead के साथ प्राप्त करेंगे। यह वह समस्या है जिसके बारे में मैं पहले बात कर रहा था कि आप कई प्राप्त बफ़र्स पोस्ट नहीं कर पाएंगे, क्योंकि AuthenticateStream क्लासेस इसका समर्थन नहीं करती हैं। BeginRead ऑपरेशन आंतरिक रूप से सभी IO का प्रबंधन करता है जब तक कि आपको एक संपूर्ण फ़्रेम प्राप्त नहीं हुआ है, अन्यथा यह संदेश प्रमाणीकरण (डिक्रिप्ट फ़्रेम और फ़्रेम पर हस्ताक्षर को मान्य) को संभाल नहीं सका। हालांकि मेरे अनुभव में ऑथेंटिकेटेड स्ट्रीम्स द्वारा किया गया काम काफी अच्छा है और इसे लेकर कोई समस्या नहीं होनी चाहिए। अर्थात। आपको केवल 4-5% सीपीयू के साथ जीबी नेटवर्क को संतृप्त करने में सक्षम होना चाहिए। ऑथेंटिकेटेड स्ट्रीम्स क्लासेस आप पर प्रोटोकॉल स्पेसिफिक फ्रेम साइज लिमिट्स (16k फॉर एसएसएल, 12k फॉर केर्बोरस) भी लगाएंगी।

यह आपको सही रास्ते पर शुरू करना चाहिए। मैं यहाँ कोड पोस्ट नहीं करने जा रहा हूँ, MSDN पर एक अच्छा उदाहरण है । मैंने इस तरह की कई परियोजनाएं की हैं और मैं लगभग 1000 उपयोगकर्ताओं को समस्याओं के बिना जोड़ने में सक्षम था। ऊपर आपको अधिक सॉकेट हैंडल के लिए कर्नेल की अनुमति देने के लिए रजिस्ट्री कुंजियों को संशोधित करना होगा। और सुनिश्चित करें कि आप एक सर्वर OS पर तैनात हैं , जो कि W2K3 XP या Vista (यानी क्लाइंट ओएस) नहीं है, यह एक बड़ा अंतर बनाता है।

BTW सुनिश्चित करें कि यदि आपके पास सर्वर पर डेटाबेस ऑपरेशन हैं या IO फाइल करें तो आप उनके लिए एसिंक्स फ्लेवर का भी उपयोग करते हैं, या आप थ्रेड पूल को कुछ ही समय में निकाल देंगे। SQL सर्वर कनेक्शन के लिए सुनिश्चित करें कि आप कनेक्शन स्ट्रिंग में 'Asyncronous Processing = true' जोड़ते हैं।


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

11

मुझे अपने कुछ समाधानों में इस तरह का सर्वर मिला है। यहाँ .net में करने के विभिन्न तरीकों की बहुत विस्तार से व्याख्या की गई है: .NET में हाई-परफॉर्मेंस सॉकेट्स के साथ वायर के करीब पहुँचें।

हाल ही में हम अपने कोड को बेहतर बनाने के तरीकों की तलाश कर रहे हैं और इस पर गौर करेंगे: " संस्करण 3.5 में सॉकेट प्रदर्शन संवर्द्धन " जो विशेष रूप से "उन अनुप्रयोगों के उपयोग के लिए शामिल किया गया था, जो अतुल्यकालिक नेटवर्क I / O का उपयोग उच्चतम प्रदर्शन प्राप्त करने के लिए करते हैं"।

"इन एन्हांसमेंट्स की मुख्य विशेषता उच्च आयतन वाले अतुल्यकालिक सॉकेट I / O के दौरान वस्तुओं के बार-बार आवंटन और सिंक्रनाइज़ेशन से बचना है। वर्तमान में सॉकेट क्लास द्वारा एसिंक्रोनस सॉकेट I / O के लिए लागू किया गया आरंभ / अंत डिज़ाइन पैटर्न के लिए एक सिस्टम की आवश्यकता है। IAsyncResult ऑब्जेक्ट को प्रत्येक एसिंक्रोनस सॉकेट ऑपरेशन के लिए आवंटित किया जाना चाहिए। "

यदि आप लिंक का अनुसरण करते हैं तो आप पढ़ते रह सकते हैं। मैं व्यक्तिगत रूप से कल उनके नमूना कोड का परीक्षण करूंगा जो मुझे मिला है उसके खिलाफ बेंचमार्क करना।

संपादित करें: यहां आप नए 3.5 सॉकेट का उपयोग करके क्लाइंट और सर्वर दोनों के लिए कार्य कोड पा सकते हैं। इसलिए आप इसे कुछ मिनटों के भीतर परीक्षण कर सकते हैं और कोड को प्राप्त कर सकते हैं। यह एक सरल तरीका है लेकिन बहुत बड़ा कार्यान्वयन शुरू करने का आधार है। इसके अलावा MSDN पत्रिका में लगभग दो साल पहले का यह लेख एक दिलचस्प पढ़ा गया था।



9

क्या आपने केवल WCF नेट टीसीपी बाइंडिंग और एक प्रकाशन / सदस्यता पैटर्न का उपयोग करने पर विचार किया है? WCF आपको प्लंबिंग के बजाय अपने डोमेन पर [ज्यादातर] ध्यान केंद्रित करने की अनुमति देगा।

WCF के बहुत सारे नमूने हैं और यहां तक ​​कि IDesign के डाउनलोड अनुभाग पर एक प्रकाशन / सदस्यता रूपरेखा उपलब्ध है जो उपयोगी हो सकती है: http://www.idesign.net


8

मैं एक बात के बारे में सोच रहा हूँ:

मैं निश्चित रूप से प्रत्येक कनेक्शन के लिए एक धागा शुरू नहीं करना चाहता हूं।

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

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

यहां कोड है - यह देखने के लिए कि यह कैसे चल रहा है, http://mdpopescu.blogspot.com/2009/05/multi-threaded-server.html पर जाएं और चित्र पर क्लिक करें।

सर्वर वर्ग:

  public class Server
  {
    private static readonly TcpListener listener = new TcpListener(IPAddress.Any, 9999);

    public Server()
    {
      listener.Start();
      Console.WriteLine("Started.");

      while (true)
      {
        Console.WriteLine("Waiting for connection...");

        var client = listener.AcceptTcpClient();
        Console.WriteLine("Connected!");

        // each connection has its own thread
        new Thread(ServeData).Start(client);
      }
    }

    private static void ServeData(object clientSocket)
    {
      Console.WriteLine("Started thread " + Thread.CurrentThread.ManagedThreadId);

      var rnd = new Random();
      try
      {
        var client = (TcpClient) clientSocket;
        var stream = client.GetStream();
        while (true)
        {
          if (rnd.NextDouble() < 0.1)
          {
            var msg = Encoding.ASCII.GetBytes("Status update from thread " + Thread.CurrentThread.ManagedThreadId);
            stream.Write(msg, 0, msg.Length);

            Console.WriteLine("Status update from thread " + Thread.CurrentThread.ManagedThreadId);
          }

          // wait until the next update - I made the wait time so small 'cause I was bored :)
          Thread.Sleep(new TimeSpan(0, 0, rnd.Next(1, 5)));
        }
      }
      catch (SocketException e)
      {
        Console.WriteLine("Socket exception in thread {0}: {1}", Thread.CurrentThread.ManagedThreadId, e);
      }
    }
  }

सर्वर मुख्य कार्यक्रम:

namespace ManyThreadsServer
{
  internal class Program
  {
    private static void Main(string[] args)
    {
      new Server();
    }
  }
}

ग्राहक वर्ग:

  public class Client
  {
    public Client()
    {
      var client = new TcpClient();
      client.Connect(IPAddress.Loopback, 9999);

      var msg = new byte[1024];

      var stream = client.GetStream();
      try
      {
        while (true)
        {
          int i;
          while ((i = stream.Read(msg, 0, msg.Length)) != 0)
          {
            var data = Encoding.ASCII.GetString(msg, 0, i);
            Console.WriteLine("Received: {0}", data);
          }
        }
      }
      catch (SocketException e)
      {
        Console.WriteLine("Socket exception in thread {0}: {1}", Thread.CurrentThread.ManagedThreadId, e);
      }
    }
  }

ग्राहक मुख्य कार्यक्रम:

using System;
using System.Threading;

namespace ManyThreadsClient
{
  internal class Program
  {
    private static void Main(string[] args)
    {
      // first argument is the number of threads
      for (var i = 0; i < Int32.Parse(args[0]); i++)
        new Thread(RunClient).Start();
    }

    private static void RunClient()
    {
      new Client();
    }
  }
}

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

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

1
हालांकि मैं मार्सेल की टिप्पणी से सहमत हूं कि थ्रेड्स में थ्रेड्स के दृश्य के बारे में थ्रेडपूल से नहीं आते हैं, बाकी का कथन सही नहीं है। मेमोरी के बारे में नहीं है कि एक मशीन में कितना स्थापित किया गया है, विंडोज़ पर सभी एप्लिकेशन वर्चुअल एड्रेस स्पेस में चलते हैं और 32 बिट सिस्टम पर है जो आपको आपके ऐप के लिए डेटा पर 2GB देता है (इससे कोई फर्क नहीं पड़ता कि बॉक्स पर कितना रैम स्थापित है)। उन्हें अभी भी रनटाइम द्वारा प्रबंधित किया जाना चाहिए। Async IO करने से प्रतीक्षा करने के लिए थ्रेड का उपयोग नहीं होता है (यह IOCP का उपयोग करता है जो ओवरलैप्ड IO की अनुमति देता है) और एक बेहतर समाधान है और MUCH को बेहतर बनाएगा।
ब्रायन ओनील

7
जब बहुत सारे थ्रेड चल रहे हैं तो यह मेमोरी नहीं है जो कि समस्या है लेकिन सीपीयू। थ्रेड्स के बीच संदर्भ स्विच अपेक्षाकृत महंगा ऑपरेशन है और अधिक सक्रिय थ्रेड आपके पास अधिक संदर्भ स्विच हैं जो होने वाले हैं। कुछ साल पहले मैंने अपने पीसी पर C # कंसोल ऐप के साथ और लगभग एक टेस्ट चलाया। 500 थ्रेड्स मेरा सीपीयू 100% था, थ्रेड्स कुछ भी महत्वपूर्ण नहीं कर रहे थे। नेटवर्क कॉम्स के लिए थ्रेड्स की संख्या को कम रखना बेहतर है।
सिपविज़

1
मैं या तो एक टास्क समाधान के साथ जाऊंगा या async / प्रतीक्षा का उपयोग करूंगा। टास्क सॉल्यूशन सरल लगता है जबकि एसिंक्स / वेट अधिक स्केलेबल होने की संभावना है (वे विशेष रूप से आईओ-बाउंड स्थितियों के लिए थे)।
मार्सेल पॉपेस्क्यू

5

.NET का एकीकृत Async IO ( BeginRead, आदि) का उपयोग करना एक अच्छा विचार है यदि आप सभी विवरण सही प्राप्त कर सकते हैं। जब आप अपने सॉकेट / फ़ाइल को ठीक से सेट करते हैं, तो यह ओएस के अंतर्निहित IOCP कार्यान्वयन का उपयोग करेगा, आपके संचालन को बिना किसी थ्रेड (या सबसे खराब स्थिति में, एक थ्रेड का उपयोग करके पूर्ण करने की अनुमति देगा, जो मुझे लगता है कि कर्नेल के IO थ्रेड थ्रेड का उपयोग करता है) .NET का थ्रेड पूल, जो थ्रेड कंजेक्शन को कम करने में मदद करता है।)

मुख्य गोटा यह सुनिश्चित करने के लिए है कि आप अपनी सॉकेट / फाइलें गैर-अवरोधक मोड में खोलें। अधिकांश डिफ़ॉल्ट सुविधा फ़ंक्शंस (जैसे File.OpenRead) ऐसा नहीं करते हैं, इसलिए आपको अपना स्वयं का लिखना होगा।

अन्य मुख्य चिंताओं में से एक त्रुटि हैंडलिंग है - एसिंक्रोनस I / O कोड लिखते समय त्रुटियों को ठीक से संभालना, तुल्यकालिक कोड में करने की तुलना में बहुत कठिन है। दौड़ की स्थिति और गतिरोध को समाप्त करना भी बहुत आसान है, भले ही आप सीधे थ्रेड का उपयोग नहीं कर रहे हों, इसलिए आपको इसके बारे में पता होना चाहिए।

यदि संभव हो, तो आपको स्केलेबल एसिंक्रोनस आईओ करने की प्रक्रिया को आसान बनाने के लिए एक सुविधा पुस्तकालय का उपयोग करना चाहिए।

इस तरह की प्रोग्रामिंग को करने में कठिनाई को कम करने के लिए डिज़ाइन किया गया .NET लाइब्रेरी का एक उदाहरण Microsoft की कंज़ेक्टिबिलिटी कोऑर्डिनेशन रनटाइम है। यह बहुत अच्छा लग रहा है, लेकिन जैसा कि मैंने इसका उपयोग नहीं किया है, मैं इस पर टिप्पणी नहीं कर सकता कि यह कितना अच्छा होगा।

मेरी व्यक्तिगत परियोजनाओं के लिए जो एसिंक्रोनस नेटवर्क या डिस्क I / O करने की आवश्यकता है, मैं .NET संगामिति / IO टूल के एक सेट का उपयोग करता हूं जिसे मैंने पिछले एक साल में बनाया है, जिसे Squared.Task कहा जाता है । यह imvu.task और मुड़ जैसे पुस्तकालयों से प्रेरित है , और मैंने रिपॉजिटरी में कुछ काम करने वाले उदाहरणों को शामिल किया है जो नेटवर्क I / O करते हैं। मैंने इसे कुछ अनुप्रयोगों में भी उपयोग किया है जो मैंने लिखा है - सबसे बड़ा सार्वजनिक रूप से जारी किया गया एक एनडेक्सर (जो इसे थ्रेडलेस डिस्क I / O के लिए उपयोग करता है)। पुस्तकालय imvu.task के साथ मेरे अनुभव के आधार पर लिखा गया था और इसमें काफी व्यापक इकाई परीक्षणों का एक सेट है, इसलिए मैं आपको इसे आज़माने के लिए प्रोत्साहित करता हूं। यदि आपके पास इसके साथ कोई समस्या है, तो मुझे आपको कुछ सहायता प्रदान करने में खुशी होगी।

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


बढ़िया जानकारी, मैं आपके संदर्भों की जाँच करूँगा और देखूँगा कि क्या समझ में आता है।
एरिक फनकेनबस

3

मैंने केविन के समाधान का उपयोग किया लेकिन वह कहता है कि समाधान में संदेशों के पुन: विकास के लिए कोड का अभाव है। संदेश के पुन: उपयोग के लिए डेवलपर इस कोड का उपयोग कर सकते हैं:

private static void ReceiveCallback(IAsyncResult asyncResult )
{
    ClientInfo cInfo = (ClientInfo)asyncResult.AsyncState;

    cInfo.BytesReceived += cInfo.Soket.EndReceive(asyncResult);
    if (cInfo.RcvBuffer == null)
    {
        // First 2 byte is lenght
        if (cInfo.BytesReceived >= 2)
        {
            //this calculation depends on format which your client use for lenght info
            byte[] len = new byte[ 2 ] ;
            len[0] = cInfo.LengthBuffer[1];
            len[1] = cInfo.LengthBuffer[0];
            UInt16 length = BitConverter.ToUInt16( len , 0);

            // buffering and nulling is very important
            cInfo.RcvBuffer = new byte[length];
            cInfo.BytesReceived = 0;

        }
    }
    else
    {
        if (cInfo.BytesReceived == cInfo.RcvBuffer.Length)
        {
             //Put your code here, use bytes comes from  "cInfo.RcvBuffer"

             //Send Response but don't use async send , otherwise your code will not work ( RcvBuffer will be null prematurely and it will ruin your code)

            int sendLenghts = cInfo.Soket.Send( sendBack, sendBack.Length, SocketFlags.None);

            // buffering and nulling is very important
            //Important , set RcvBuffer to null because code will decide to get data or 2 bte lenght according to RcvBuffer's value(null or initialized)
            cInfo.RcvBuffer = null;
            cInfo.BytesReceived = 0;
        }
    }

    ContinueReading(cInfo);
 }

private static void ContinueReading(ClientInfo cInfo)
{
    try 
    {
        if (cInfo.RcvBuffer != null)
        {
            cInfo.Soket.BeginReceive(cInfo.RcvBuffer, cInfo.BytesReceived, cInfo.RcvBuffer.Length - cInfo.BytesReceived, SocketFlags.None, ReceiveCallback, cInfo);
        }
        else
        {
            cInfo.Soket.BeginReceive(cInfo.LengthBuffer, cInfo.BytesReceived, cInfo.LengthBuffer.Length - cInfo.BytesReceived, SocketFlags.None, ReceiveCallback, cInfo);
        }
    }
    catch (SocketException se)
    {
        //Handle exception and  Close socket here, use your own code 
        return;
    }
    catch (Exception ex)
    {
        //Handle exception and  Close socket here, use your own code 
        return;
    }
}

class ClientInfo
{
    private const int BUFSIZE = 1024 ; // Max size of buffer , depends on solution  
    private const int BUFLENSIZE = 2; // lenght of lenght , depends on solution
    public int BytesReceived = 0 ;
    public byte[] RcvBuffer { get; set; }
    public byte[] LengthBuffer { get; set; }

    public Socket Soket { get; set; }

    public ClientInfo(Socket clntSock)
    {
        Soket = clntSock;
        RcvBuffer = null;
        LengthBuffer = new byte[ BUFLENSIZE ];
    }   

}

public static void AcceptCallback(IAsyncResult asyncResult)
{

    Socket servSock = (Socket)asyncResult.AsyncState;
    Socket clntSock = null;

    try
    {

        clntSock = servSock.EndAccept(asyncResult);

        ClientInfo cInfo = new ClientInfo(clntSock);

        Receive( cInfo );

    }
    catch (SocketException se)
    {
        clntSock.Close();
    }
}
private static void Receive(ClientInfo cInfo )
{
    try
    {
        if (cInfo.RcvBuffer == null)
        {
            cInfo.Soket.BeginReceive(cInfo.LengthBuffer, 0, 2, SocketFlags.None, ReceiveCallback, cInfo);

        }
        else
        {
            cInfo.Soket.BeginReceive(cInfo.RcvBuffer, 0, cInfo.BytesReceived, SocketFlags.None, ReceiveCallback, cInfo);

        }

    }
    catch (SocketException se)
    {
        return;
    }
    catch (Exception ex)
    {
        return;
    }

}


1

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

रूपरेखा कई संगामिति मॉडल की एक विस्तृत श्रृंखला से संबंधित है और संभवत: आपके आउट ऑफ बॉक्स के लिए उपयुक्त है। इससे सिस्टम को डिबग करना आसान हो जाता है क्योंकि ज्यादातर नॉटी समसामयिक मुद्दों को पहले ही सुलझा लिया गया है। यहां व्यापार-बंद यह है कि फ्रेमवर्क C ++ में लिखा गया है और कोड बेस का सबसे गर्म और शराबी नहीं है। दूसरी ओर, आप परीक्षण किया जाता है, औद्योगिक ग्रेड नेटवर्क के बुनियादी ढांचे और बॉक्स के बाहर एक अत्यधिक स्केलेबल वास्तुकला है।


2
यह एक अच्छा सुझाव है, लेकिन सवाल के टैग से मुझे विश्वास है कि ओपी सी # का उपयोग कर रहा होगा
जेपीसीओस्टा

मैंने गौर किया; सुझाव यह था कि यह C ++ के लिए उपलब्ध है और मुझे C # के समकक्ष कुछ भी ज्ञात नहीं है। इस तरह की प्रणाली को डीबग करना सबसे अच्छे समय में आसान नहीं है और आपको इस ढांचे में जाने से भी वापसी मिल सकती है, भले ही इसका मतलब सी ++ में स्विच करना हो।
कंसर्नडऑफटुनब्रिजवेल्स

हां, यह C # है। मैं अच्छे। नेट आधारित समाधानों की तलाश कर रहा हूं। मुझे और अधिक स्पष्ट होना चाहिए था, लेकिन मुझे लगता था कि लोग टैग
पढ़ेंगे


1

खैर, .NET सॉकेट्स सेलेक्ट () प्रदान करते हैं - जो इनपुट से निपटने के लिए सबसे अच्छा है। आउटपुट के लिए मेरे पास सॉकेट-लेखक थ्रेड्स का एक पूल होता है, जो काम की कतार में काम करने वाले हिस्से के रूप में सॉकेट डिस्क्रिप्टर / ऑब्जेक्ट को स्वीकार करता है, इसलिए आपको सॉकेट के लिए एक थ्रेड की आवश्यकता नहीं है।


1

मैं .net 3.5 में जोड़े गए AcceptAsync / ConnectAsync / ReceiveAsync / SendAsync विधियों का उपयोग करेगा। मैंने एक बेंचमार्क किया है और वे लगभग 35% तेज (प्रतिक्रिया समय और बिटरेट) हैं, जिसमें 100 उपयोगकर्ता लगातार डेटा भेजते और प्राप्त करते हैं।


1

लोग स्वीकार किए गए उत्तर को चिपकाते हुए, आप _CververSocket.BeginAccept (नए AsyncCallback (स्वीकारकॉलबैक), _serverSocket) की सभी कॉल को हटाते हुए, स्वीकार कर सकते हैं। और इसे अंत में {} क्लाज में डालें, इस तरह से:

private void acceptCallback(IAsyncResult result)
    {
       xConnection conn = new xConnection();
       try
       {
         //Finish accepting the connection
         System.Net.Sockets.Socket s = (System.Net.Sockets.Socket)result.AsyncState;
         conn = new xConnection();
         conn.socket = s.EndAccept(result);
         conn.buffer = new byte[_bufferSize];
         lock (_sockets)
         {
           _sockets.Add(conn);
         }
         //Queue recieving of data from the connection
         conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), conn);
       }
       catch (SocketException e)
       {
         if (conn.socket != null)
         {
           conn.socket.Close();
           lock (_sockets)
           {
             _sockets.Remove(conn);
           }
         }
       }
       catch (Exception e)
       {
         if (conn.socket != null)
         {
           conn.socket.Close();
           lock (_sockets)
           {
             _sockets.Remove(conn);
           }
         }
       }
       finally
       {
         //Queue the next accept, think this should be here, stop attacks based on killing the waiting listeners
         _serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);       
       }
     }

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


0

मैं इन किताबों को ACE पर पढ़ने की सलाह दूंगा

पैटर्न के बारे में विचार प्राप्त करने के लिए आपको एक कुशल सर्वर बनाने की अनुमति देता है।

हालाँकि ACE को C ++ में लागू किया जाता है, लेकिन किताबें बहुत सारे उपयोगी पैटर्न को कवर करती हैं जिनका उपयोग किसी भी प्रोग्रामिंग भाषा में किया जा सकता है।


-1

स्पष्ट होने के लिए, मुझे .net आधारित समाधानों की तलाश है (यदि संभव हो तो C #, लेकिन कोई भी .net भाषा काम करेगी)

यदि आप शुद्ध रूप से .NET के साथ जाते हैं, तो आपको उच्चतम स्तर का स्केलेबिलिटी नहीं मिलने वाला है। जीसी ठहराव विलंबता में बाधा डाल सकता है।

मुझे सेवा के लिए कम से कम एक धागा शुरू करने की आवश्यकता है। मैं Asynch API (BeginRecieve, आदि ..) का उपयोग करने पर विचार कर रहा हूं क्योंकि मुझे नहीं पता कि मैं किसी भी समय (संभवत: सैकड़ों) कितने ग्राहकों से जुड़ा होगा। मैं निश्चित रूप से प्रत्येक कनेक्शन के लिए एक धागा शुरू नहीं करना चाहता हूं।

ओवरलेप्ड IO को आमतौर पर नेटवर्क संचार के लिए विंडोज का सबसे तेज एपीआई माना जाता है। मुझे नहीं पता कि क्या यह आपके Asynch API के समान है। सक्रिय सॉकेट पर कॉलबैक होने के बजाय खुले प्रत्येक सॉकेट की जांच करने के लिए प्रत्येक कॉल की आवश्यकता के अनुसार चयन का उपयोग न करें।


1
मुझे आपकी GC पॉज़ टिप्पणी समझ में नहीं आती है .. मैंने कभी भी स्केलेबिलिटी समस्याओं वाली प्रणाली नहीं देखी है जो सीधे GC से संबंधित थी।
Markt

4
यह कहीं अधिक संभावना है कि आप एक ऐसे ऐप का निर्माण करें जो खराब आर्किटेक्चर के कारण स्केल नहीं कर सकता क्योंकि जीसी मौजूद है। विशाल स्केलेबल + परफॉर्मेंट सिस्टम को .NET और Java दोनों के साथ बनाया गया है। आपके द्वारा दिए गए दोनों लिंक में, कारण सीधे कचरा संग्रह नहीं था .. लेकिन हीप स्वैपिंग से संबंधित है। मुझे लगता है कि वह वास्तव में है कि टाला जा सकता था .. आप मुझे एक भाषा को दिखा सकते हैं, तो यह है कि यह संभव एक प्रणाली है कि स्केल नहीं कर सकते हैं का निर्माण करने की नहीं है, मैं खुशी के लिए उपयोग करेंगे वास्तुकला के साथ एक समस्या है;)
Markt

1
मैं इस टिप्पणी से सहमत नहीं हूं। अज्ञात, आपके द्वारा संदर्भित प्रश्न जावा हैं, और वे विशेष रूप से बड़े मेमोरी आवंटन के साथ काम कर रहे हैं और मैन्युअल रूप से जीसी को बल देने की कोशिश कर रहे हैं। मैं वास्तव में स्मृति आवंटन की भारी मात्रा में नहीं जा रहा हूँ। यह सिर्फ एक मुद्दा नहीं है। लेकिन धन्यवाद। हां, अतुल्यकालिक प्रोग्रामिंग मॉडल आमतौर पर ओवरलैप्ड IO के शीर्ष पर लागू किया जाता है।
एरिक फुन्केनबस

1
दरअसल, जीसी को इकट्ठा करने के लिए सबसे अच्छा अभ्यास लगातार मैन्युअल रूप से नहीं होना है। यह बहुत अच्छी तरह से आपके ऐप को खराब प्रदर्शन कर सकता है। .NET जीसी एक जेनरेशनल जीसी है जो आपके ऐप के उपयोग के लिए ट्यून करेगा। क्या तुम सच में लगता है कि आप मैन्युअल रूप से बुला किए जाने की GC.Collect की जरूरत है, मैं कहूँगा कि अपने कोड की संभावना सबसे अधिक जरूरत है एक और तरीका लिखा होना चाहिए ..
Markt

1
@मार्क, यह उन लोगों के लिए एक टिप्पणी है जो वास्तव में कचरा संग्रह के बारे में कुछ भी नहीं जानते हैं। यदि आपके पास निष्क्रिय समय है, तो मैन्युअल संग्रह करने में कुछ भी गलत नहीं है। यह खत्म होने पर आपके एप्लिकेशन को बदतर बनाने वाला नहीं है। अकादमिक कागजात बताते हैं कि जेनेशनल जीसी काम करते हैं क्योंकि यह आपकी वस्तुओं के जीवनकाल का एक अनुमान है। जाहिर है यह एक सही प्रतिनिधित्व नहीं है। वास्तव में, एक विरोधाभास है जहां "सबसे पुरानी" पीढ़ी में अक्सर कचरे का उच्चतम अनुपात होता है क्योंकि यह कभी भी कचरा एकत्र नहीं होता है।
अज्ञात

-1

उच्च प्रदर्शन सर्वर विकास के लिए आप पुश फ्रेमवर्क ओपन सोर्स फ्रेमवर्क का उपयोग कर सकते हैं। यह IOCP पर बनाया गया है और यह पुश परिदृश्यों और संदेश प्रसारण के लिए उपयुक्त है।

http://www.pushframework.com


1
इस पोस्ट को C # और .net टैग किया गया था। आपने C ++ फ्रेमवर्क का सुझाव क्यों दिया?
एरिक फुकेनबस जूल

शायद इसलिए कि उन्होंने इसे लिखा था। potatosoftware.com/…
क्विलब्रेकर

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