Ölçeklenebilir bir Tcp / Ip tabanlı sunucu nasıl yazılır


148

Uzun süren bağlantılar için TCP / IP bağlantılarını kabul eden yeni bir Windows Hizmeti uygulaması yazmanın tasarım aşamasındayım (yani, birçok kısa bağlantı olan HTTP gibi değil, bir istemci saatlerce veya günlerce bağlanır ve bağlı kalır veya hatta haftalar).

Ağ mimarisini tasarlamanın en iyi yolu için fikirler arıyorum. Hizmet için en az bir iş parçacığı başlatmam gerekecek. Ben herhangi bir zamanda (muhtemelen yüzlerce) kaç müşteri bağlı olacak bilmiyorum çünkü Asynch API (BeginRecieve, vb ..) kullanmayı düşünüyorum. Kesinlikle her bağlantı için bir iş parçacığı başlatmak istemiyorum.

Veriler öncelikle sunucumdan istemcilere akacaktır, ancak zaman zaman istemcilerden gönderilen bazı komutlar olacaktır. Bu öncelikle sunucumun durum verilerini istemcilere düzenli olarak gönderdiği bir izleme uygulamasıdır.

Bunu mümkün olduğunca ölçeklenebilir yapmanın en iyi yolu hakkında herhangi bir öneriniz var mı? Temel iş akışı? Teşekkürler.

EDIT: Açıkça, ben .net tabanlı çözümler arıyorum (mümkünse C #, ancak herhangi bir .net dili çalışacaktır)

BÜYÜK NOT: Ödül kazanmak için basit bir cevaptan daha fazlasını bekliyorum. Bir çözümün çalışan bir örneğine ihtiyacım var, indirebileceğim bir şey için bir işaretçi veya satır içi kısa bir örnek. Ve .net ve Windows tabanlı olmalıdır (herhangi bir .net dili kabul edilebilir)

EDIT: İyi cevaplar veren herkese teşekkür etmek istiyorum. Ne yazık ki, sadece birini kabul edebilirim ve daha iyi bilinen Başlangıç ​​/ Bitiş yöntemini kabul etmeyi seçtim. Esac'ın çözümü daha iyi olabilir, ancak nasıl çalışacağından emin olamayacağım kadar yeni.

İyi olduğunu düşündüğüm tüm cevapları iptal ettim, keşke sizler için daha fazlasını yapabilseydim. Tekrar teşekkürler.


1
Uzun süren bir bağlantı olması gerektiğinden kesinlikle emin misiniz? Sağlanan sınırlı bilgilerden söylemek zor, ama bunu sadece kesinlikle gerekliyse yaparım ..
markt

Evet, uzun sürüyor olmalı. Veriler gerçek zamanlı olarak güncellenmelidir, bu nedenle periyodik yoklama yapamıyorum, veriler meydana geldiği anda istemciye iletilmelidir, bu da sabit bir bağlantı anlamına gelir.
Erik Funkenbusch

1
Bu geçerli bir sebep değil. Http destek uzun çalışan bağlantıları gayet iyi. Sadece bir bağlantı açın ve bir repsonse (durmuş anket) bekleyin. Bu birçok AJAX tarzı uygulamalar vb için iyi çalışıyor. Sizce Gmail nasıl çalışıyor :-)
TFD

2
Gmail, e-posta için düzenli olarak yoklayarak çalışır, uzun süren bir bağlantı sağlamaz. Bu, gerçek zamanlı yanıtın gerekli olmadığı e-postalar için uygundur.
Erik Funkenbusch

2
Yoklama veya çekme iyi ölçeklenir, ancak gecikmeyi hızlı bir şekilde geliştirir. İtme de ölçeklenmez, ancak gecikmeyi azaltmaya veya ortadan kaldırmaya yardımcı olur.
andrewbadera

Yanıtlar:


92

Geçmişte buna benzer bir şey yazmıştım. Yıllar önce yaptığım araştırmadan, Asenkron prizleri kullanarak kendi soket uygulamanızı yazmanın en iyi bahis olduğunu gösterdi. Bu, istemcilerin gerçekten hiçbir şey yapmamalarının aslında nispeten az kaynak gerektirdiği anlamına geliyordu. Meydana gelen her şey .net iş parçacığı havuzu tarafından işlenir.

Sunucular için tüm bağlantıları yöneten bir sınıf olarak yazdım.

Tüm istemci bağlantılarını tutmak için bir liste kullandım, ancak daha büyük listeler için daha hızlı aramalara ihtiyacınız varsa, istediğiniz gibi yazabilirsiniz.

private List<xConnection> _sockets;

Ayrıca aslında gelen bağlantıları dinlemek için sokete ihtiyacınız var.

private System.Net.Sockets.Socket _serverSocket;

Start yöntemi aslında sunucu soketini başlatır ve gelen bağlantıları dinlemeye başlar.

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

Sadece istisna işleme kodunun kötü göründüğünü belirtmek isterim, ama bunun nedeni, bir istisna bastırılmış ve falsebir yapılandırma seçeneği ayarlanmışsa geri dönecek şekilde istisna bastırma kodu vardı , ancak bunun için kaldırmak istedim. kısalık aşkına.

Yukarıdaki _serverSocket.BeginAccept (yeni AsyncCallback (acceptCallback)), _serverSocket), sunucu soketimizi kullanıcı bağladığında acceptCallback yöntemini çağırmak için ayarlar. Bu yöntem, birçok engelleme işleminiz varsa ek çalışan iş parçacıkları oluşturmayı otomatik olarak işleyen .Net iş parçacığı havuzundan çalışır. Bu, sunucudaki yükleri en iyi şekilde işlemelidir.

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

Yukarıdaki kod aslında gelen bağlantıyı kabul etmeyi tamamladı BeginReceive, istemci veri gönderdiğinde çalışacak bir geri arama olan kuyruklar ve sonra acceptCallbackgelen bir sonraki istemci bağlantısını kabul edecek bir sonraki kuyrukları sıralar .

BeginReceiveYöntem çağrısı istemciden veri aldığında ne yapacağını soketi söyler budur. Bunun için BeginReceive, bir bayt dizisi vermeniz gerekir; bu, istemci veri gönderdiğinde verileri kopyalayacağı yerdir. ReceiveCallbackYöntem alıyoruz verilere uygulayabileceğiniz olan çağrılır alacak.

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

DÜZENLEME: Bu modelde bu kod alanında şunu belirtmeyi unuttum:

//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);

Ne genel olarak ne yapmak istiyorum ne kodu, mesajların içine paketleri yeniden montaj yapmak ve daha sonra iş parçacığı havuzunda işler olarak oluşturmaktır. Bu şekilde, istemciden sonraki bloğun BeginReceive değeri, ileti işleme kodu çalışıyorsa gecikmez.

Geri arama kabulü, arama sonu alma işlemini yaparak veri soketini okumayı bitirir. Bu, başlangıç ​​alma işlevinde sağlanan arabelleği doldurur. Yorumumu bıraktığım yerde istediğiniz her şeyi yaptıktan sonra BeginReceive, istemci daha fazla veri gönderirse geri aramayı yeniden çalıştıracak bir sonraki yöntemi çağırırız . Şimdi gerçekten zor olan kısım, istemci veri gönderdiğinde, geri alma numaranız yalnızca mesajın bir kısmı ile çağrılabilir. Yeniden montaj çok karmaşık hale gelebilir. Kendi yöntemimi kullandım ve bunu yapmak için bir tür özel protokol oluşturdum. Dışarıda bıraktım, ancak talep ederseniz, ekleyebilirim. Bu işleyici aslında yazdığım en karmaşık kod parçasıydı.

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

Yukarıdaki gönderme yöntemi aslında Sendbenim için mesaj boyutları ve benim uygulama çok iş parçacıklı doğası nedeniyle iyi bir zaman uyumlu çağrı kullanır . Her istemciye göndermek istiyorsanız, _sockets Listesinde dolaşmanız yeterlidir.

Yukarıda referans olarak gördüğünüz xConnection sınıfı temel olarak bir soketin bayt arabelleğini içermesi için basit bir sarmalayıcıdır ve benim uygulamada bazı ekstralar içerir.

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

Ayrıca referans için burada usingben dahil değildir zaman her zaman rahatsız olsun ben dahil s vardır.

using System.Net.Sockets;

Umarım bu yardımcı olur, en temiz kod olmayabilir, ama işe yarıyor. Ayrıca, kodda, değiştirme konusunda yorgun olmanız gereken bazı nüanslar da vardır. Birincisi, BeginAcceptherhangi bir anda sadece bir tane çağırın. Yıllar önce bu konuda çok can sıkıcı bir .net hatası vardı, bu yüzden ayrıntıları hatırlamıyorum.

Ayrıca, ReceiveCallbackkodda, bir sonraki alma sırasına geçmeden önce soketten alınan her şeyi işleriz. Bu, tek bir soket için, ReceiveCallbackherhangi bir zamanda sadece bir kerede olduğumuzu ve iş parçacığı senkronizasyonunu kullanmamız gerekmediği anlamına gelir. Ancak, veri çekildikten hemen sonra bir sonraki alımı çağırmak için bunu yeniden sıralarsanız, bu biraz daha hızlı olabilir, iş parçacıklarını doğru şekilde senkronize ettiğinizden emin olmanız gerekir.

Ayrıca, kodumun bir sürü kesmek, ama yerinde neler olduğunu özü bıraktı. Bu, tasarımınız için iyi bir başlangıç ​​olmalıdır. Bu konuyla ilgili başka sorularınız varsa yorum bırakın.


1
Bu iyi bir cevap Kevin .. ödül almak için yolda gibi görünüyor. :)
Erik Funkenbusch

6
Bunun neden en yüksek oyu verdiğini bilmiyorum. Begin * End *, C # ile ağ oluşturmanın en hızlı yolu ya da en yüksek oranda ölçeklenebilir değildir. Senkronize olandan daha hızlıdır, ancak Windows'ta bu ağ yolunu gerçekten yavaşlatan birçok işlem vardır.
esac

6
Önceki yorumda esac'ın yazdıklarını unutmayın. Başlangıç ​​deseni muhtemelen bir noktaya kadar çalışacaktır, kodum şu anda başlangıç-bitiş kullanıyor, ancak .net 3.5'teki sınırlamalarında iyileştirmeler var. Ödül umurumda değil ama bu yaklaşımı uygulasanız bile cevabımdaki bağlantıyı okumanızı tavsiye ederim. "Sürüm
3.5'te

1
Ben sadece yeterince açık olmayabilir beri onların atmak istedim, bu .net 2.0 dönemi kodu nerede bu çok canlı bir desen olduğuna inanıyorum. Ancak, esac'ın cevabı .net 3.5'i hedefliyorsa biraz daha modern görünüyor, sahip olduğum tek nitpick olayların atılmasıdır :) ancak bu kolayca değiştirilebilir. Ayrıca, bu kod ile verim testi yaptım ve çift çekirdekli bir opteron 2Ghz 100Mbps ethernet'i en üst düzeye çıkardı ve bu kodun üstüne bir şifreleme katmanı ekledi.
Kevin Nisbet

1
@KevinNisbet Bunun oldukça geç olduğunu biliyorum, ancak bu cevabı kendi sunucularını tasarlamak için kullanan herkes için - gönderme de asenkron olmalıdır, çünkü aksi takdirde kendinizi bir kilitlenme olasılığı için açarsınız. Her iki taraf da kendi arabelleklerini dolduran veriler yazıyorsa, Sendyöntemler her iki tarafta da süresiz olarak engellenir, çünkü giriş verilerini okuyan kimse yoktur.
Luaan

83

C # 'da ağ işlemleri yapmanın birçok yolu vardır. Hepsi kaputun altında farklı mekanizmalar kullanır ve bu nedenle yüksek eşzamanlılık ile önemli performans sorunlarına maruz kalırlar. Başlangıç ​​* işlemleri, çoğu insanın ağ oluşturmanın en hızlı / en hızlı yolu olduğu için sıklıkla hata yaptığı bunlardan biridir.

Bu sorunları çözmek için, onlar yöntemlerden * zaman uyumsuz setini tanıttı: MSDN http://msdn.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.aspx

SocketAsyncEventArgs sınıfı, özel yüksek performanslı soket uygulamaları tarafından kullanılabilen alternatif bir asenkron kalıp sağlayan System.Net.Sockets .. ::. Socket sınıfının bir takım geliştirmelerinin bir parçasıdır. Bu sınıf, özellikle yüksek performans gerektiren ağ sunucusu uygulamaları için tasarlanmıştır. Bir uygulama, gelişmiş eşzamansız deseni yalnızca veya yalnızca hedeflenen sıcak alanlarda (örneğin, büyük miktarda veri alırken) kullanabilir.

Bu geliştirmelerin ana özelliği, yüksek hacimli asenkron soket G / Ç sırasında nesnelerin tekrarlanan tahsisinden ve senkronizasyonundan kaçınmaktır. Şu anda System.Net.Sockets .. ::. Socket sınıfı tarafından uygulanan Begin / End tasarım deseni, her asenkron soket işlemi için bir System .. ::. IAsyncResult nesnesi tahsis edilmesini gerektirir.

Kapakların altında * Async API, ağ işlemlerini gerçekleştirmenin en hızlı yolu olan IO tamamlama bağlantı noktalarını kullanır, bkz. Http://msdn.microsoft.com/en-us/magazine/cc302334.aspx

Ve sadece size yardımcı olmak için, * Async API'sını kullanarak yazdığım bir telnet sunucusunun kaynak kodunu ekliyorum. Sadece ilgili bölümleri dahil ediyorum. Ayrıca, veri satır içi işlemek yerine, ayrı bir iş parçacığında işlenen bir kilit serbest (ücretsiz bekleme) kuyruğuna itmek tercih. Sadece boşsa yeni bir nesne oluşturacak basit bir havuz olan karşılık gelen Pool sınıfını ve belirsiz bir alan almadığınız sürece gerçekten gerekli olmayan sadece kendi kendine genişleyen bir tampon olan Buffer sınıfını dahil etmiyorum. data miktarı. Daha fazla bilgi isterseniz, bana PM göndermekten çekinmeyin.

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

Bu oldukça basit ve basit bir örnek. Teşekkürler. Her yöntemin profesyonel ve eksilerini değerlendirmek zorunda kalacağım.
Erik Funkenbusch

Test etme şansım olmadı, ancak bir nedenden dolayı burada bir yarış durumunun belirsiz hissini alıyorum. İlk olarak, çok fazla mesaj alırsanız, olayların sırayla işleneceğini bilmiyorum (kullanıcılar uygulaması için önemli olmayabilir, ancak not edilmelidir) veya yanlış olabilirim ve olaylar sırayla işlenir. İkincisi, kaçırmış olabilirim ama uzun süre alırsa DataReceived hala çalışırken tamponun üzerine yazılma riski yok mu? Bu muhtemelen haksız endişeler giderilirse, bunun çok iyi ve modern bir çözüm olduğunu düşünüyorum.
Kevin Nisbet

1
Benim durumumda, telnet sunucum için% 100, EVET sırayla. Anahtar AcceptAsync, ReceiveAsync, vb çağırmadan önce uygun geri arama yöntemini ayarlamaktır. Benim durumumda SendAsync ayrı bir iş parçacığı üzerinde yapmak, bu nedenle bir Kabul / Gönder / Alma / Gönder / Alma / Bağlantısı kesme desen yapmak için değiştirilirse, değiştirilmesi gerekecek.
esac

1
Nokta # 2 de dikkate almanız gereken bir şeydir. 'Bağlantı' nesnesimi SocketAsyncEventArgs bağlamında saklıyorum. Bunun anlamı, bağlantı başına yalnızca bir alma arabelleğim olması. DataReceived tamamlanana kadar bu SocketAsyncEventArgs ile başka bir alma gönderme değil, bu yüzden tamamlanana kadar bu konuda daha fazla veri okunabilir. Bu veriler üzerinde uzun bir işlem yapılmadığını TAVSİYE EDERİM. Aslında kilitli olmayan bir kuyruğa alınan tüm verilerin arabelleğini taşımak ve sonra ayrı bir iş parçacığında işlemek. Bu, ağ kısmında düşük gecikme süresi sağlar.
esac

1
Bir yan notta, bu kod için birim testleri ve yük testleri yazdım ve kullanıcı yükünü 1 kullanıcıdan 250 kullanıcıya (tek bir çift çekirdekli sistemde, 4GB RAM'de) artırdığımda, 100 bayt için yanıt süresi (1 paket) ve 10000 bayt (3 paket) tüm kullanıcı yük eğrisi boyunca aynı kaldı.
esac

46

Eskiden Coversant'tan Chris Mullins tarafından yazılmış .NET'i kullanarak ölçeklenebilir TCP / IP hakkında gerçekten iyi bir tartışma vardı, maalesef blogunun önceki konumundan kaybolduğu anlaşılıyor, bu yüzden tavsiyelerini bellekten bir araya getirmeye çalışacağım (bazı yararlı yorumlar) Bu iş parçacığında görünür: C ++ vs. C #: Yüksek düzeyde ölçeklenebilir bir IOCP sunucusu geliştirme )

Her şeyden önce, sınıftaki hem kullanım hem Begin/Endde Asyncyöntemlerin Socketölçeklenebilirlik sağlamak için IO Tamamlama Bağlantı Noktalarını (IOCP) kullandığını unutmayın. Bu, çözümünüzü uygulamak için seçtiğiniz iki yöntemden hangisine göre ölçeklenebilirlik açısından (doğru kullanıldığında; aşağıya bakın) çok daha büyük bir fark yaratır.

Chris Mullins'in gönderileri Begin/Endkişisel deneyime sahip olan kullanmaya dayanıyordu . Chris'in 2 GB belleğe sahip 32 bit bir makinede 10.000'e kadar eşzamanlı istemci bağlantısını ölçeklendiren ve yeterli belleğe sahip 64 bit bir platformda 100.000'e kadar ölçeklendirilmiş bir çözüm oluşturduğunu unutmayın. Bu teknikle ilgili kendi deneyimlerime dayanarak (bu tür yüklerin yakınında hiçbir yerde) bu gösterge rakamlarından şüphe etmek için hiçbir nedenim yok.

IOCP'ye göre bağlantı başına iş parçacığı veya 'seç' ilkelleri

Kaputun altında IOCP kullanan bir mekanizma kullanmak istemenizin nedeni, okumaya çalıştığınız IO kanalında gerçek veriler olana kadar hiçbir iş parçacığını uyandırmayan çok düşük düzeyli bir Windows iş parçacığı havuzu kullanmasıdır ( IOCP'nin dosya IO için de kullanılabileceğini unutmayın). Bunun yararı, Windows'un henüz bir veri bulunmadığını bulmak için yalnızca bir iş parçacığına geçmek zorunda kalmamasıdır, bu nedenle sunucunuzun gerekli minimum değere sahip olması gereken bağlam anahtarlarının sayısını azaltır.

Bağlam anahtarları, 'bağlantı başına iplik' mekanizmasını kesinlikle öldürecek olan şeydir, ancak bu sadece birkaç düzine bağlantıyla uğraşıyorsanız geçerli bir çözümdür. Ancak bu mekanizma hayal gücünün 'ölçeklenebilir' bir gerginliği ile değildir.

IOCP kullanırken dikkat edilmesi gereken önemli noktalar

Hafıza

Her şeyden önce, uygulamanız çok safsa, IOCP'nin .NET altında kolayca bellek sorunlarına neden olabileceğini anlamak önemlidir. Her IOCP BeginReceiveçağrısı, okuduğunuz tamponun "sabitlenmesine" neden olur. Bunun neden bir sorun olduğuna dair iyi bir açıklama için bkz: Yun Jin Weblog: OutOfMemoryException ve Pinning .

Neyse ki bu problemden kaçınılabilir, ancak biraz değiş tokuş gerektirir. Önerilen çözüm, byte[]uygulama başlangıcında (veya buna yakın), en az 90 KB veya daha büyük bir tampon tahsis etmektir (.NET 2'den itibaren, sonraki sürümlerde gerekli boyut daha büyük olabilir). Bunu yapmanın nedeni, büyük bellek ayırmalarının otomatik olarak, otomatik olarak etkin bir şekilde sabitlenen sıkıştırılmamış bir bellek segmentine (Büyük Nesne Yığını) neden olmasıdır. Başlangıçta büyük bir arabellek ayırarak, bu taşınamaz bellek bloğunun, engel olmayacak ve parçalanmaya neden olmayacağı göreceli olarak 'düşük bir adreste' olduğundan emin olun.

Daha sonra, bu büyük arabelleği bazı verileri okuması gereken her bağlantı için ayrı alanlara bölümlere ayırmak için ofsetleri kullanabilirsiniz. Burada bir değiş tokuş devreye girer; bu arabellek önceden ayrılması gerektiğinden, bağlantı başına ne kadar arabellek alanına ihtiyacınız olduğunu ve ölçeklemek istediğiniz bağlantı sayısı için hangi üst sınırı ayarlamak istediğinize (veya bir soyutlama uygulayabileceğinize) karar vermeniz gerekir. ek sabitlenmiş arabellekleri ihtiyacınız olduğunda ayırabilir).

En basit çözüm, her bağlantıya bu arabellekteki benzersiz bir ofsette tek bir bayt atamak olacaktır. Ardından BeginReceive, okunması gereken tek bir bayt için arama yapabilir ve aldığınız geri arama sonucunda okunan geri kalanını gerçekleştirebilirsiniz.

İşleme

Geri arama yaptığınızda, geri Beginaramadaki kodun düşük seviyeli IOCP iş parçacığında yürütüleceğini anlamak çok önemlidir. Bu geri aramada uzun işlemlerden kaçınmanız kesinlikle önemlidir . Bu iş parçacıklarını karmaşık işlemler için kullanmak, ölçeklenebilirliğinizi 'bağlantı başına iş parçacığı' kadar etkili bir şekilde öldürür.

Önerilen çözüm, geri aramayı yalnızca, başka bir iş parçacığında yürütülecek olan gelen verileri işlemek üzere bir iş öğesini sıralamak için kullanmaktır. IOCP iş parçacığının havuzuna olabildiğince çabuk dönebilmesi için geri arama içindeki olası engelleme işlemlerinden kaçının. .NET 4.0'da, en kolay çözüm, Taskistemci soketine bir başvuru ve BeginReceiveçağrı tarafından zaten okunan ilk baytın bir kopyasını vererek, bir spawn etmektir . Bu görev daha sonra işlediğiniz isteği temsil eden soketten tüm verileri okumaktan, yürütmekten BeginReceiveve soketi bir kez daha IOCP için kuyruğa almak için yeni bir çağrı yapmaktan sorumludur . .NET 4.0 öncesi, ThreadPool kullanabilir veya kendi iş parçacığı iş kuyruğu uygulamanızı oluşturabilirsiniz.

özet

Temel olarak, aşağıdaki çözümlerle Kevin'in bu çözüm için örnek kodunu kullanmanızı öneririm:

  • Geçtiğiniz arabelleğin BeginReceivezaten 'sabitlendiğinden emin olun
  • Geçtiğiniz geri BeginReceivearamanın, gelen verilerin gerçek işlenmesini işlemek için bir görevi sıralamaktan başka bir şey yapmadığından emin olun

Bunu yaptığınızda, Chris'in sonuçlarını aynı anda yüz binlerce eşzamanlı istemciye kadar ölçeklendirebildiğinizden şüpheliyim (doğru donanım ve kendi işlem kodunuzun etkili bir şekilde uygulanması;)


1
Daha küçük bir bellek bloğunu sabitlemek için, arabelleği sabitlemek için GCHandle nesnesi Alloc yöntemi kullanılabilir. Bu yapıldıktan sonra, Marshal nesnesinin UnsafeAddrOfPinnedArrayElement değeri arabelleğe bir işaretçi elde etmek için kullanılabilir. Örneğin: GCHandle gchTheCards = GCHandle.Alloc (TheData, GCHandleType.Pinned); IntPtr pAddr = Marshal.UnsafeAddrOfPinnedArrayElement (TheData, 0); (sbyte *) pTheData = (sbyte *) pAddr.ToPointer ();
Bob Bryan

@BobBryan Yapmaya çalıştığınız ince bir noktayı kaçırmadıkça, bu yaklaşım aslında çözümümün büyük bloklar tahsis etmeye çalıştığı problemle yardımcı olmaz, yani küçük sabitlenmiş blokların tekrarlı tahsisinde bulunan dramatik bellek parçalanması potansiyeli bellek.
jerryjvl

Mesele şu ki, bellekte sabitlenmiş tutmak için büyük bir blok ayırmanız gerekmiyor. Daha küçük bloklar tahsis edebilir ve gc'nin hareket etmesini önlemek için yukarıdaki tekniği bellekte sabitlemek için kullanabilirsiniz. Küçük blokların her birine bir referans tutabilirsiniz (tıpkı tek bir büyük bloğa referans tuttuğunuz gibi) ve gerektiğinde tekrar kullanabilirsiniz. Her iki yaklaşım da geçerlidir - sadece çok büyük bir tampon kullanmak zorunda olmadığınıza işaret ediyordum. Ancak, bazen çok büyük bir tampon kullanmanın, gc'nin daha verimli davranacağından en iyi yol olduğunu söyledi.
Bob Bryan

@BobBryan, BeginReceive'ı çağırdığınızda arabelleği sabitlemek otomatik olarak gerçekleştiğinden, sabitleme gerçekten burada dikkat çekici bir nokta değildir; verimlilik;) ... ve bu özellikle ölçeklenebilir bir sunucu yazmaya çalışırken bir endişe kaynağıdır, bu nedenle tampon alanı için kullanılacak büyük bloklar tahsis etme ihtiyacı vardır.
jerryjvl

@jerryjvl Gerçekten eski bir soru ortaya koyduğum için üzgünüm, ancak yakın zamanda BeginXXX / EndXXX asenkron yöntemleri ile bu kesin sorunu keşfettim. Bu harika bir gönderi, ancak bulmak için çok fazla kazma yaptı. Önerilen çözümün hoşuma gidiyor ama bir kısmını anlamıyorum: "Sonra tek bir baytın okunması için bir BeginReceive çağrısı yapabilir ve aldığınız geri arama sonucunda okumanın geri kalanını gerçekleştirebilirsiniz." Aldığınız geri arama sonucunda hazırlığın geri kalanını gerçekleştirerek ne demek istersiniz?
Mausimo

22

Yukarıdaki kod örnekleri aracılığıyla cevabın çoğunu zaten aldınız. Eşzamansız IO işlemini kullanmak kesinlikle buraya gitmenin yoludur. Async IO, Win32'nin dahili olarak ölçeklenmek üzere tasarlandığı yoldur. Alabileceğiniz en iyi performans, Tamamlama Bağlantı Noktaları kullanılarak, yuvalarınızı tamamlama bağlantı noktalarına bağlayarak ve tamamlama bağlantı noktası tamamlanmasını bekleyen bir iş parçacığı havuzuna sahip olarak elde edilir. Ortak bilgelik, CPU (çekirdek) başına tamamlamayı bekleyen 2-4 iş parçacığına sahip olmaktır. Windows Performans ekibinden Rick Vicik'in bu üç makalesine göz atmanızı şiddetle tavsiye ederim:

  1. Performans için Uygulama Tasarlama - Bölüm 1
  2. Performans için Uygulama Tasarlama - Bölüm 2
  3. Performans için Uygulama Tasarlama - Bölüm 3

Bahsedilen makaleler çoğunlukla yerel Windows API'sini kapsamaktadır, ancak ölçeklenebilirlik ve performans konusunda kavramaya çalışan herkes için okunması gerekir. Onlar da yönetilen tarafında bazı özetleri var.

Yapmanız gereken ikinci şey, .NET Uygulama Performansını ve Ölçeklenebilirliğini Geliştirme'yi gözden geçirmektir. online olarak kitap. Bölüm 5'te iş parçacıklarının, eşzamansız aramaların ve kilitlerin kullanımı hakkında uygun ve geçerli tavsiyeler bulacaksınız. Ancak gerçek taşlar, iş parçanızın havuzunu ayarlamak için pratik rehberlik gibi yararlı özellikler bulacağınız 17. Bölümde bulunmaktadır. Uygulamalarımın maxIothreads / maxWorkerThreads ayarlarını bu bölümdeki önerilere göre ayarlayana kadar bazı ciddi sorunları vardı.

Saf bir TCP sunucusu yapmak istediğinizi söylüyorsunuz, bu yüzden bir sonraki noktam sahte. Ancak , kendinizi köşeli bulursanız ve WebRequest sınıfını ve türevlerini kullanıyorsanız, o kapıyı koruyan bir ejderha olduğu konusunda uyarınız : ServicePointManager . Bu, yaşamda bir amacı olan bir yapılandırma sınıfıdır: performansınızı bozmak. Sunucunuzu yapay yüklenen ServicePoint.ConnectionLimit'ten kurtardığınızdan emin olun, aksi takdirde uygulamanız asla ölçeklenmez (Varsayılan değerin ne olduğunu kendiniz keşfetmenize izin veririm ...). Http isteklerinde bir Expect100Continue üstbilgisi gönderme varsayılan politikasını da yeniden düşünebilirsiniz.

Şimdi çekirdek soket yönetilen API hakkında Gönder tarafında oldukça kolay, ancak Alma tarafında önemli ölçüde daha karmaşıktır. Yüksek verim ve ölçek elde etmek için, soketin akış kontrollü olmadığından emin olmalısınız, çünkü almak için bir tampon belleğe sahip değilsiniz. İdeal olarak, yüksek performans için 3-4 tamponu göndermeli ve bir tane alır almaz yeni tamponlar göndermelisiniz (geri dönmeden önce ), böylece soketin her zaman ağdan gelen verileri depolayacak bir yere sahip olduğundan emin olmalısınız. Bunu neden kısa sürede başaramayacağınızı göreceksiniz.

BeginRead / BeginWrite API ile oynamayı bitirdikten ve ciddi çalışmaya başladıktan sonra, trafiğinizde güvenliğe ihtiyacınız olduğunu fark edeceksiniz. NTLM / Kerberos kimlik doğrulaması ve trafik şifrelemesi veya en azından trafik müdahale koruması. Bunu yapmanın yolu yerleşik System.Net.Security.NegotiateStream (veya farklı etki alanlarını çaprazlamak zorunda kalırsanız SslStream) kullanmaktır. Bu, düz soketli eşzamansız işlemlere güvenmek yerine AuthenticatedStream eşzamansız işlemlerine güveneceğiniz anlamına gelir. Bir soket alır almaz (istemcide bağlan veya sunucuda kabul et) soket üzerinde bir akış oluşturur ve BeginAuthenticateAsClient veya BeginAuthenticateAsServer'ı çağırarak kimlik doğrulaması için gönderirsiniz. Kimlik doğrulama işlemi tamamlandıktan sonra (en azından yerel InitiateSecurityContext / AcceptSecurityContext delilikten güvenliğiniz ...), Yetkilendirilmiş akışınızın RemoteIdentity özelliğini kontrol ederek ve ürününüzün desteklemesi gereken her türlü ACL doğrulamasını yaparak yetkilendirmeyi yaparsınız. Bundan sonra BeginWrite kullanarak mesaj göndereceksiniz ve BeginRead ile mesaj alacaksınız. AuthenticateStream sınıfları bunu desteklemediğinden, daha önce konuştuğum sorun, birden çok alma arabelleği yayınlayamayacaksınız. BeginRead işlemi, tüm bir kareyi alana kadar tüm IO'yu dahili olarak yönetir, aksi takdirde ileti kimlik doğrulamasını işleyemedi (karenin şifresini çözme ve karedeki imzayı doğrulama). Deneyimlerime göre AuthenticatedStream sınıfları tarafından yapılan iş oldukça iyidir ve bununla ilgili herhangi bir sorun olmamalıdır. Yani. GB ağını yalnızca% 4-5 CPU ile doyurabilmeniz gerekir. AuthenticatedStream sınıfları ayrıca protokole özgü çerçeve boyutu sınırlamalarını da (SSL için 16k, Kerberos için 12k) uygular.

Bu sizi doğru yolda başlatmanızı sağlamalıdır. Burada kod göndermeyeceğim , MSDN'de mükemmel bir örnek var . Bunun gibi birçok proje yaptım ve sorunsuz bir şekilde bağlanmış yaklaşık 1000 kullanıcıya ölçeklenebildim. Bunun üzerine, çekirdeğin daha fazla soket tanıtıcısı kullanmasına izin vermek için kayıt defteri anahtarlarını değiştirmeniz gerekir. ve XP veya Vista (yani istemci işletim sistemi değil) W2K3 olan bir sunucu işletim sisteminde dağıttığınızdan emin olun , bu büyük bir fark yaratır.

BTW, sunucuda veya dosya GÇ'sinde veritabanı işlemleriniz varsa, onlar için zaman uyumsuz lezzeti de kullandığınızdan emin olun, yoksa iş parçacığı havuzunu hemen boşaltabilirsiniz. SQL Server bağlantıları için bağlantı dizesine 'Eşzamansız İşleme = true' değerini eklediğinizden emin olun.


Burada harika bilgiler var. Keşke birden fazla kişiye ödül verebilseydim. Ancak, sizi iptal ettim. Burada iyi şeyler, teşekkürler.
Erik Funkenbusch

11

Bazı çözümlerimde böyle bir sunucu var. İşte bunu yapmak için farklı yolların çok ayrıntılı bir açıklaması. Net: .NET Yüksek Performanslı Yuva ile Tel Yaklaşın

Son zamanlarda kodumuzu geliştirmenin yollarını arıyordum ve bunu inceleyeceğim: " En yüksek performansı elde etmek için eşzamansız ağ G / Ç kullanan uygulamalar tarafından kullanılmak üzere özel olarak dahil edilen" Sürüm 3.5'teki Soket Performansı Geliştirmeleri ".

"Bu geliştirmelerin ana özelliği, yüksek hacimli asenkron soket G / Ç sırasında nesnelerin tekrarlanan tahsisinden ve senkronizasyonundan kaçınılmasıdır. Şu anda Soket sınıfı tarafından asenkron soket G / Ç için uygulanan Başlangıç ​​/ Bitiş tasarım modeli bir Sistem gerektirir. Her asenkron soket işlemi için IAsyncResult nesnesi ayrılmıştır. "

Bağlantıyı takip ederseniz okumaya devam edebilirsiniz. Şahsen ben sahip olduklarını karşılaştırmak için örnek kodları yarın test edilecektir.

Düzenleme: Burada , istemci ve sunucu için yeni 3.5 SocketAsyncEventArgs kullanarak çalışma kodunu bulabilirsiniz, böylece birkaç dakika içinde test edip kodu geçebilirsiniz. Bu basit bir yaklaşımdır, ancak çok daha büyük bir uygulamaya başlamanın temelidir. Ayrıca neredeyse iki yıl önce MSDN Magazine'de bu makale ilginç bir okuma oldu.



9

Sadece bir WCF net TCP bağlaması ve bir yayınlama / abone olma modeli kullanmayı düşündünüz mü? WCF, sıhhi tesisat yerine alan adınıza odaklanmanıza izin verir.

IDesign'ın indirme bölümünde yararlı olabilecek birçok WCF örneği ve hatta bir yayınla / abone ol çerçevesi var: http://www.idesign.net


8

Bir şey merak ediyorum:

Kesinlikle her bağlantı için bir iş parçacığı başlatmak istemiyorum.

Neden? Windows, bir uygulamada en az Windows 2000'den bu yana yüzlerce iş parçacığını işleyebilir. Bunu yaptım, iş parçacıklarının senkronize edilmesi gerekmiyorsa çalışmak gerçekten kolaydır. Özellikle çok fazla G / Ç yaptığınız göz önüne alındığında (CPU'ya bağlı değilsiniz ve disk veya ağ iletişiminde çok sayıda iş parçacığı engellenir), bu kısıtlamayı anlamıyorum.

Çok iş parçacıklı yöntemi test ettiniz ve bir şeyde eksik olduğunu buldunuz mu? Ayrıca her bir iş parçacığı için bir veritabanı bağlantısı olmasını istiyor musunuz (veritabanı sunucusunu öldürür, bu kötü bir fikirdir, ancak 3 katmanlı bir tasarımla kolayca çözülür). Yüzlerce yerine binlerce müşteriniz olacağından ve gerçekten sorun yaşayacağınızdan endişe duyuyor musunuz? (32 + GB RAM'im olsaydı bin iş parçacığı, hatta on bini denememe rağmen - yine de CPU'ya bağlı olmadığınız göz önüne alındığında, iş parçacığı değiştirme süresi kesinlikle alakasız olmalıdır.)

İşte kod - bunun nasıl çalıştığını görmek için http://mdpopescu.blogspot.com/2009/05/multi-threaded-server.html adresine gidin ve resme tıklayın.

Sunucu Tipi:

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

Sunucu ana programı:

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

Müşteri sınıfı:

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

Müşteri ana programı:

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

Windows çok sayıda iş parçacığını işleyebilir, ancak .NET bunları işleyecek şekilde tasarlanmamıştır. Her .NET uygulama etki alanında bir iş parçacığı havuzu vardır ve bu iş parçacığı havuzunu tüketmek istemezsiniz. Eğer bir threadpool gelen ya da değil olsa elle bir Konu başlatmak emin değilim. Yine de, çoğu zaman hiçbir şey yapmayan yüzlerce iş parçacığı büyük bir kaynak israfıdır.
Erik Funkenbusch

1
Konuları yanlış bir şekilde gördüğünüzü düşünüyorum. İş parçacıkları yalnızca gerçekten isterseniz, iş parçacığı havuzundan gelir - normal İş Parçacıkları istemez. Hiçbir şey yapmadan yüzlerce iş parçacığı tam olarak hiçbir şey boşa harcamıyor :) (Biraz bellek, ama bellek o kadar ucuz ki artık bir sorun değil.) Bunun için birkaç örnek uygulama yazacağım, bir URL göndereceğim işim bittiğinde. Bu arada, yukarıda yazdıklarımı tekrar gözden geçirmenizi ve sorularıma cevap vermeye çalışmanızı tavsiye ederim.
Marcel Popescu

1
Oluşturulan iş parçacıklarının iş parçacığı havuzundan gelmediği için Marcel'in yorumuyla ilgili yorumunu kabul etsem de, ifadenin geri kalanı doğru değil. Bellek, bir makinede ne kadar yüklü olduğu ile ilgili değildir, pencerelerdeki tüm uygulamalar sanal adres alanında ve uygulamanız için 2 GB veri sağlayan 32bit sistemde çalışır (kutuya ne kadar ram takıldığı önemli değildir). Bunların çalışma zamanı tarafından yönetilmesi gerekir. Zaman uyumsuz GÇ yapmak, beklemek için bir iplik kullanmaz (çakışan GÇ'ye izin veren IOCP kullanır) ve daha iyi bir çözümdür ve ÇOK daha iyi ölçeklendirilir.
Brian ONeil

7
Çok sayıda iş parçacığı çalıştırırken sorun bellek değil CPU. İş parçacıkları arasındaki bağlam geçişi nispeten pahalı bir işlemdir ve ne kadar aktif iş parçacığı oluşursa o kadar çok bağlam anahtarına sahip olursunuz. Birkaç yıl önce bilgisayarımda bir C # konsol uygulamasıyla ve yaklaşık. İşlemcimin 500 iş parçacığı% 100 idi, iş parçacıkları önemli bir şey yapmıyordu. Ağ iletişimleri için, iş parçacığı sayısını düşük tutmak daha iyidir.
sipwiz

1
Ben bir Görev çözümü ile gitmek veya async / await kullanın. Görev çözümü daha basit görünürken, zaman uyumsuz / beklemede büyük olasılıkla daha ölçeklenebilir (özellikle ES'ye bağlı durumlar içindir).
Marcel Popescu

5

BeginReadTüm ayrıntıları doğru şekilde yapabiliyorsanız, .NET'in entegre Async IO'sunu ( vb.) Kullanmak iyi bir fikirdir. Soket / dosya tutamaçlarınızı doğru şekilde ayarladığınızda, işletim sisteminin temel IOCP uygulamasını kullanacak ve işlemlerinizin herhangi bir iş parçacığı kullanmadan (veya en kötü durumda, bunun yerine çekirdeğin IO iş parçacığı havuzundan geldiğine inandığım bir iş parçacığı kullanarak) tamamlanmasına izin verecektir. .NET'in iş parçacığı havuzunun tıkanıklığını azaltmaya yardımcı olan iş parçacığı havuzunun açıklaması.)

Ana sorun, soketlerinizi / dosyalarınızı engellemeyen modda açtığınızdan emin olmaktır. Varsayılan kolaylık işlevlerinin çoğu (gibi File.OpenRead) bunu yapmaz, bu yüzden kendiniz yazmanız gerekir.

Diğer ana endişelerden biri hata işleme - senkronize olmayan G / Ç kodu yazarken hataları doğru şekilde işlemek, senkron kodda yapmaktan çok, çok daha zor. Doğrudan iş parçacığı kullanmıyor olsanız bile, yarış koşulları ve kilitlenme ile sonuçlanmak çok kolaydır, bu yüzden bunun farkında olmanız gerekir.

Mümkünse, ölçeklenebilir eşzamansız IO yapma işlemini kolaylaştırmak için bir uygunluk kitaplığı kullanmayı denemelisiniz.

Microsoft'un Eşzamanlılık Koordinasyon Çalışma Zamanı , bu tür bir programlama yapmanın zorluğunu hafifletmek için tasarlanmış bir .NET kütüphanesi örneğidir. Harika görünüyor, ama kullanmadığım için, ne kadar iyi ölçeklendirileceği konusunda yorum yapamıyorum.

Eşzamansız ağ veya disk G / Ç yapması gereken kişisel projelerim için, geçen yıl Squared.Task adında oluşturduğum bir dizi .NET eşzamanlılık / G / Ç aracı kullanıyorum . Bu imvu.task ve twisted gibi kütüphanelerden esinlenerek , depoya ağ G / Ç yapan bazı çalışma örnekleri ekledim . Ayrıca yazdığım birkaç uygulamada da kullandım - en büyük kamuya açık olan biri NDexer (dişsiz disk I / O için kullanıyor). Kütüphane imvu.task ile olan tecrübelerime dayanarak yazılmıştır ve oldukça kapsamlı bir birim test setine sahiptir, bu yüzden denemenizi şiddetle tavsiye ederim. Bununla ilgili herhangi bir sorununuz varsa, size biraz yardım etmekten memnuniyet duyarız.

Benim görüşüme göre, iş parçacıkları yerine asenkron / threadless IO kullanma deneyimime dayanarak, öğrenme eğrisi ile başa çıkmaya hazır olduğunuz sürece .NET platformunda değerli bir çaba. Bu, Thread nesnelerinin maliyetinin getirdiği ölçeklenebilirlik sorunlarından kaçınmanıza izin verir ve birçok durumda, Futures / Promises gibi eşzamanlılık ilkellerini dikkatli bir şekilde kullanarak kilitlerin ve mutekslerin kullanımından tamamen kaçınabilirsiniz.


Harika bilgi, referanslarınızı kontrol edip neyin anlamlı olduğunu göreceğim.
Erik Funkenbusch

3

Kevin'in çözümünü kullandım ama çözümün mesajların yeniden birleştirilmesi için koddan yoksun olduğunu söylüyor. Geliştiriciler bu kodu mesajların yeniden birleştirilmesi için kullanabilir:

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

Ağ sunucuları için genel bir C ++ çerçevesi olan ACE (Uyarlanabilir İletişim Ortamı) adlı bir çerçeve kullanmayı deneyebilirsiniz. Çok sağlam, olgun bir üründür ve telco sınıfına kadar yüksek güvenilirlikli, yüksek hacimli uygulamaları desteklemek için tasarlanmıştır.

Çerçeve oldukça geniş bir yelpazede eşzamanlılık modelleri ile ilgileniyor ve muhtemelen kutudan çıktığı uygulama için uygun olanı var. Bu, kötü eşzamanlılık sorunlarının çoğu zaten çözülmüş olduğundan sistemin hata ayıklamasını kolaylaştırmalıdır. Buradaki denge, çerçevenin C ++ ile yazılmış olması ve kod tabanlarının en sıcak ve kabarık olmamasıdır. Öte yandan, endüstriyel sınıf ağ altyapısı ve son derece ölçeklenebilir bir mimariye sahip olursunuz.


2
Bu iyi bir öneri, ama soru etiketleri OP OP C # kullanacağına inanıyorum
JPCosta

Onu farkettim; öneri bu C ++ için kullanılabilir ve ben C # için eşdeğer bir şey farkında değilim oldu. Bu tür bir sistemde hata ayıklamak en iyi ihtimalle kolay değildir ve C ++ 'ya geçiş yapmanıza rağmen bu çerçeveye geri dönebilirsiniz.
EndişeliOfTunbridgeWells

Evet, bu C #. İyi .net tabanlı çözümler arıyorum. Daha açık olmalıydım, ama insanların etiketleri okuyacağını varsaydım
Erik Funkenbusch


1

.NET soketleri, select () işlevini sağlıyor gibi görünüyor . Çıktı için, bir iş kuyruğunu dinleyen, soket tanımlayıcısını / nesnesini iş öğesinin bir parçası olarak kabul eden soket yazıcısı iş parçacığı havuzum olurdu, böylece soket başına bir iş parçacığına ihtiyacınız yoktur.


1

Net 3.5 içinde eklenen AcceptAsync / ConnectAsync / ReceiveAsync / SendAsync yöntemlerini kullanırdım. Bir kıyaslama yaptım ve 100 kullanıcı sürekli veri gönderip alan yaklaşık% 35 daha hızlı (yanıt süresi ve bit hızı).


1

kabul edilen yanıtı yapıştırarak kopyala, _serverSocket.BeginAccept (yeni AsyncCallback (acceptCallback), _serverSocket) 'in tüm çağrılarını kaldırarak acceptCallback yöntemini yeniden yazabilirsiniz; ve sonunda bu şekilde {} yantümcesine koydu:

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

içeriği aynı olduğu için ilk yakalamayı bile kaldırabilirsiniz, ancak bir şablon yöntemidir ve istisnaları daha iyi ele almak ve hataya neyin neden olduğunu anlamak için yazılı istisna kullanmalısınız, bu yüzden bu yakalamaları bazı yararlı kodlarla uygulayın



-1

Açık olmak gerekirse, .net tabanlı çözümler arıyorum (mümkünse C #, ancak herhangi bir .net dili çalışacaktır)

Yalnızca .NET ile çalışırsanız en yüksek düzeyde ölçeklenebilirlik elde edemezsiniz. GC duraklamaları gecikmeyi engelleyebilir.

Hizmet için en az bir iş parçacığı başlatmam gerekecek. Ben herhangi bir zamanda (muhtemelen yüzlerce) kaç müşteri bağlı olacak bilmiyorum çünkü Asynch API (BeginRecieve, vb ..) kullanmayı düşünüyorum. Kesinlikle her bağlantı için bir iş parçacığı başlatmak istemiyorum.

Çakışan GÇ genellikle ağ iletişimi için Windows'un en hızlı API'si olarak kabul edilir. Bunun Asynch API'nizle aynı olup olmadığını bilmiyorum. Her çağrının aktif soketlerde geri çağrı yapmak yerine açık olan her soketi kontrol etmesi gerektiğinden select seçeneğini kullanmayın.


1
GC duraklama yorumunuzu anlamıyorum .. GC ile doğrudan ilişkili ölçeklenebilirlik sorunları olan bir sistem görmedim.
markt

4
Kötü mimari nedeniyle ölçeklenemeyen bir uygulama geliştirmeniz, GC varlığından çok daha olasıdır. Hem ölçeklenebilir hem de Java ile büyük ölçeklenebilir + performanslı sistemler oluşturulmuştur. Verdiğiniz bağlantıların her ikisinde de, neden doğrudan çöp toplama değil .. yığın değiştirme ile ilgili. Bunun gerçekten önlenebilecek bir mimari sorun olduğundan şüphelenirim .. Eğer bana bir dili gösteremediğimde ölçeklenemeyen bir sistem kurmanın mümkün olmadığını, memnuniyetle kullanacağım;)
mark

1
Bu yoruma katılmıyorum. Bilinmeyen, referans verdiğiniz sorular Java'dır ve özellikle daha büyük bellek ayırmalarıyla uğraşırlar ve gc'yi manuel olarak zorlamaya çalışırlar. Burada gerçekten büyük miktarda bellek ayırma yapmayacağım. Bu sadece bir sorun değil. Ama teşekkürler. Evet, Asenkron Programlama Modeli genellikle Çakışan G / Ç'nin üzerine uygulanır.
Erik Funkenbusch

1
Aslında, en iyi uygulama sürekli olarak GC'yi toplamaya zorlamak değildir. Bu, uygulamanızın daha kötü performans göstermesini sağlayabilir. .NET GC, uygulamanızın kullanımını ayarlayan nesil bir GC'dir. Eğer gerçekten GC.Collect'i manuel olarak aramanız gerektiğini düşünüyorsanız, kodunuzun büyük olasılıkla başka bir şekilde yazılması gerektiğini söyleyebilirim ..
markt

1
@markt, bu çöp toplama hakkında hiçbir şey bilmeyen insanlar için bir yorum. Boşta kalma süreniz varsa, elle toplama yapmakla ilgili yanlış bir şey yoktur. Bu, bittiğinde uygulamanızı daha da kötüleştirmeyecektir. Akademik makaleler, nesnel GC'lerin çalıştığını göstermektedir çünkü bu, nesnelerinizin ömrünün yaklaşık bir değeridir. Açıkçası bu mükemmel bir temsil değildir. Aslında, "en eski" neslin çoğu zaman en yüksek çöp oranına sahip olduğu bir paradoks vardır, çünkü asla çöp toplanmaz.
Bilinmiyor

-1

Yüksek performanslı sunucu geliştirme için Push Framework açık kaynak çerçevesini kullanabilirsiniz. IOCP üzerine kurulmuştur ve push senaryoları ve mesaj yayını için uygundur.

http://www.pushframework.com


1
Bu yazı C # ve .net olarak etiketlendi. Neden bir C ++ çerçevesi önerdiniz?
Erik Funkenbusch

Muhtemelen yazdığı için. potatosoftware.com/…
quillbreaker

pushframework birden çok sunucu örneğini destekliyor mu? değilse, nasıl ölçeklenir?
esskar
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.