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




.net networking (12)

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

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

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

इसे यथासंभव स्केलेबल बनाने के सर्वोत्तम तरीके पर कोई सुझाव? मूल वर्कफ़्लो? धन्यवाद।

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

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

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

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


To be clear, i'm looking for .net based solutions (C# if possible, but any .net language will work)

You are not going to get the highest level of scalability if you go purely with .NET. GC pauses can hamper the latency.

I'm going to need to start at least one thread for the service. I am considering using the Asynch API (BeginRecieve, etc..) since I don't know how many clients I will have connected at any given time (possibly hundreds). I definitely do not want to start a thread for each connection.

Overlapped IO is generally considered to be Windows' fastest API for network communication. I don't know if this the same as your Asynch API. Do not use select as each call needs to check every socket that is open instead of having callbacks on active sockets.


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

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

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

आईओसीपी बनाम थ्रेड-प्रति-कनेक्शन या 'चयन' प्राइमेटिव्स बनाम

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

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

आईओसीपी का उपयोग करते समय महत्वपूर्ण विचार

याद

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

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

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

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

प्रसंस्करण

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

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

सारांश

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

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

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


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

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

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

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

कवर के तहत, * Async API आईओ पूर्णता बंदरगाहों का उपयोग करता है जो नेटवर्किंग परिचालन करने का सबसे तेज़ तरीका है, 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
    }
}

Have you considered just using a WCF net TCP binding and a publish/subscribe pattern ? WCF would allow you to focus [mostly] on your domain instead of plumbing..

There are lots of WCF samples & even a publish/subscribe framework available on IDesign's download section which may be useful : http://www.idesign.net


I used Kevin's solution but he says that solution lacks code for reassembly of messages. Developers can use this code for reassembly of messages:

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;
    }

}


I would use the AcceptAsync/ConnectAsync/ReceiveAsync/SendAsync methods that were added in .Net 3.5. I have done a benchmark and they are approximately 35% faster (response time and bitrate) with 100 users constantly sending and receiving data.


I've got such a server running in some of my solutions. Here is a very detail explanation of the different ways to do it in .net: Get Closer to the Wire with High-Performance Sockets in .NET

Lately I've been looking for ways to improve our code and will be looking into this: " Socket Performance Enhancements in Version 3.5 " that was included specifically "for use by applications that use asynchronous network I/O to achieve the highest performance".

"The main feature of these enhancements is the avoidance of the repeated allocation and synchronization of objects during high-volume asynchronous socket I/O. The Begin/End design pattern currently implemented by the Socket class for asynchronous socket I/O requires a System.IAsyncResult object be allocated for each asynchronous socket operation."

You can keep reading if you follow the link. I personally will be testing their sample code tomorrow to benchmark it against what i've got.

Edit: Here you can find working code for both client and server using the new 3.5 SocketAsyncEventArgs so you can test it within a couple minutes and go thru the code. It is a simple approach but is the basis for starting a much larger implementation. Also this article from almost two years ago in MSDN Magazine was a interesting read.


Well, .NET sockets seem to provide select() - that's best for handling input. For output I'd have a pool of socket-writer threads listening on a work queue, accepting socket descriptor/object as part of the work item, so you don't need a thread per socket.


You already got the most part of the answer via the code samples above. Using asynchronous IO operation is absolutely the way to go here. Async IO is the way the Win32 is designed internally to scale. The best possible performance you can get is achieved using Completion Ports, binding your sockets to completion ports and have a thread pool waiting for completion port completion. The common wisdom is to have 2-4 threads per CPU(core) waiting for completion. I highly recommend to go over these three articles by Rick Vicik from the Windows Performance team:

  1. Designing Applications for Performance - Part 1
  2. Designing Applications for Performance - Part 2
  3. Designing Applications for Performance - Part 3

The said articles cover mostly the native Windows API, but they are a must read for anyone trying to get a grasp at scalability and performance. They do have some briefs on the managed side of things too.

Second thing you'll need to do is make sure you go over the Improving .NET Application Performance and Scalability book, that is available online. You will find pertinent and valid advice around the use of threads, asynchronous calls and locks in Chapter 5. But the real gems are in Chapter 17 where you'll find such goodies as practical guidance on tuning your thread pool. My apps had some serious problems until I adjusted the maxIothreads/maxWorkerThreads as per the recommendations in this chapter.

You say that you want to do a pure TCP server, so my next point is spurious. However , if you find yourself cornered and use the WebRequest class and its derivatives, be warned that there is a dragon guarding that door: the ServicePointManager . This is a configuration class that has one purpose in life: to ruin your performance. Make sure you free your server from the artificial imposed ServicePoint.ConnectionLimit or your application will never scale (I let you discover urself what is the default value...). You may also reconsider the default policy of sending an Expect100Continue header in the http requests.

Now about the core socket managed API things are fairly easy on the Send side, but they are significantly more complex on the Receive side. In order to achieve high throughput and scale you must ensure that the socket is not flow controlled because you do not have a buffer posted for receive. Ideally for high performance you should post ahead 3-4 buffers and post new buffers as soon as you get one back ( before you process the one got back) so you ensure that the socket always has somewhere to deposit the data coming from the network. You'll see why you probably won't be able to achieve this shortly.

After you're done playing with the BeginRead/BeginWrite API and start the serious work you'll realize that you need security on your traffic, ie. NTLM/Kerberos authentication and traffic encryption, or at least traffic tampering protection. The way you do this is you use the built in System.Net.Security.NegotiateStream (or SslStream if you need to go cross disparate domains). This means that instead of relying on straight socket asynchronous operations you will rely on the AuthenticatedStream asynchronous operations. As soon as you obtain a socket (either from connect on client or from accept on server) you create a stream on the socket and submit it for authentication, by calling either BeginAuthenticateAsClient or BeginAuthenticateAsServer. After the authentication completes (at least your safe from the native InitiateSecurityContext/AcceptSecurityContext madness...) you will do your authorization by checking the RemoteIdentity property of your Authenticated stream and doing whatever ACL verification your product must support. After that you will send messages using the BeginWrite and you'll be receiving them with BeginRead. This is the problem I was talking before that you won't be able to post multiple receive buffers, because the AuthenticateStream classes don't support this. The BeginRead operation manages internally all the IO until you have received an entire frame, otherwise it could not handle the the message authentication (decrypt frame and validate signature on frame). Though in my experience the job done by the AuthenticatedStream classes is fairly good and shouldn't have any problem with it. अर्थात। you should be able to saturate GB network with only 4-5% CPU. The AuthenticatedStream classes will also impose on you the protocol specific frame size limitations (16k for SSL, 12k for Kerberos).

This should get you started on the right track. I'm not going to post code here, there is a perfectly good example on MSDN . I've done many projects like this and I was able to scale to about 1000 users connected without problems. Above that you'll need to modify registry keys to allow the kernel for more socket handles. and make sure you deploy on a server OS, that is W2K3 not XP or Vista (ie. client OS), it makes a big difference.

BTW make sure if you have databases operations on the server or file IO you also use the async flavor for them, or you'll drain the thread pool in no time. For SQL Server connections make sure you add the 'Asyncronous Processing=true' to the connection string.


You can use Push Framework open source framework for high-performance server development. It is built on IOCP and is suitable for push scenarios and message broadcast.

http://www.pushframework.com


You could try using a framework called ACE (Adaptive Communications Environment) which is a generic C++ framework for network servers. It's a very solid, mature product and is designed to support high-reliability, high-volume applications up to telco-grade.

The framework deals with quite a wide range of concurrency models and probably has one suitable for your applciation out of the box. This should make the system easier to debug as most of the nasty concurrency issues have already been sorted out. The trade-off here is that the framework is written in C++ and is not the most warm and fluffy of code bases. On the other hand, you get tested, industrial grade network infrastructure and a highly scalable architecture out of the box.







scalability