/ /スケーラブルなTcp / Ipベースのサーバーを作成する方法-c#、. net、ネットワーキング、tcp、スケーラビリティ

スケーラブルなTcp / Ipベースのサーバの作成方法 - C#、.net、networking、tcp、スケーラビリティ

私は新しいウィンドウズを書くデザイン段階にいます長時間実行される接続のTCP / IP接続を受け入れるサービスアプリケーション(つまり、これは、短い接続が多数あるHTTPのようなものではなく、クライアントが接続して数時間、数日、または数週間も接続を維持します)。

設計するための最良の方法のアイデアを探していますネットワークアーキテクチャ。サービスに対して少なくとも1つのスレッドを開始する必要があります。同時に接続するクライアントの数(おそらく数百)がわからないため、Asynch API(BeginRecieveなど)の使用を検討しています。 )。接続ごとにスレッドを開始したくありません。

データは主にクライアントに流出します私のサーバーですが、クライアントからいくつかのコマンドが送信されることがあります。これは主に、私のサーバーが定期的にステータスデータをクライアントに送信する監視アプリケーションです。

これを可能な限りスケーラブルにするための最良の方法に関する提案はありますか?基本的なワークフローは?ありがとう。

編集:明確にするために、私は.netベースのソリューションを探しています(可能であればC#ですが、.net言語でも機能します)

バウンティノート: 賞金を獲得するには、単純な答え以上のものを期待しています。ダウンロードできるものへのポインタとして、またはインラインでの短い例として、ソリューションの実際の例が必要です。また、.netおよびWindowsベースである必要があります(.net言語であればどれでもかまいません)。

編集: 良い答えをくれた皆さんに感謝したい。残念ながら、私は1つしか受け入れることができず、よりよく知られているBegin / Endメソッドを受け入れることにしました。 Esacのソリューションの方が良いかもしれませんが、それでもまだ十分に新しいので、どのように機能するかはわかりません。

良かったと思ったすべての回答に賛成しました。再度、感謝します。

回答:

回答№1の89

私はこれに似たものを書いた過去。数年前の私の研究から、非同期ソケットを使用して独自のソケット実装を作成することが最善の策であることが示されました。つまり、実際には何も実行していないクライアントは、比較的少ないリソースしか必要としませんでした。発生することはすべて、.netスレッドプールによって処理されます。

サーバーのすべての接続を管理するクラスとして作成しました。

私は単にリストを使用してすべてのクライアント接続を保持しましたが、より大きなリストをより高速に検索する必要がある場合は、好きなように記述できます。

private List<xConnection> _sockets;

また、着信接続を実際にリッスンするソケットも必要です。

private System.Net.Sockets.Socket _serverSocket;

startメソッドは実際にサーバーソケットを開始し、着信接続の待機を開始します。

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

例外処理コードが不適切に見えることに注意したいのですが、その理由は、そこに例外抑制コードがあり、例外を抑制して返すためです。 false 設定オプションが設定されていたが、簡潔にするためにそれを削除したかった。

_serverSocket。上記のBeginAccept(new AsyncCallback(acceptCallback))、_serverSocket)は、ユーザーが接続するたびにacceptCallbackメソッドを呼び出すようにサーバーソケットを設定します。このメソッドは、.Netスレッドプールから実行されます。これは、多くのブロッキング操作がある場合、追加のワーカースレッドの作成を自動的に処理します。これにより、サーバーの負荷を最適に処理できます。

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

上記のコードは基本的に、入ってくる接続、キューの受け入れを終了しました BeginReceive これは、クライアントがデータを送信したときに実行されるコールバックであり、次に acceptCallback 入ってくる次のクライアント接続を受け入れます。

BeginReceive メソッド呼び出しは、クライアントからデータを受け取ったときに何をすべきかをソケットに伝えます。ために BeginReceive、あなたはそれにバイト配列を与える必要があります、それはクライアントがデータを送るときそれがデータをコピーするところです。の ReceiveCallback メソッドが呼び出されます。これは、データの受信を処理する方法です。

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

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

編集:このパターンでは、コードのこの領域でそれを言及するのを忘れていました:

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

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

私が一般的に行うことは、あなたが何であれコードが必要です。パケットをメッセージに再構成してから、それらをジョブとしてスレッドプールに作成します。このようにして、クライアントからの次のブロックのBeginReceiveは、メッセージ処理コードが実行されている間も遅延しません。

受け入れコールバックはデータの読み取りを終了しますend receiveを呼び出してソケット。これは、受信開始機能で提供されるバッファーを満たします。私がコメントを残した場所で好きなことをしたら、次を呼び出します BeginReceive コールバックを再度実行するメソッドクライアントはそれ以上のデータを送信します。ここが非常にトリッキーな部分です。クライアントがデータを送信すると、受信コールバックはメッセージの一部でしか呼び出されない場合があります。再構成は非常に複雑になる可能性があります。私は独自の方法を使用して、独自のプロトコルを作成しました省略しましたが、必要に応じて追加できます。このハンドラーは、私が書いたコードの中で最も複雑なものでした。

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

上記のsendメソッドは実際には同期を使用します Send 私にとっては、アプリケーションのメッセージサイズとマルチスレッドの性質により、問題はありませんでした。すべてのクライアントに送信する場合は、_socketsリストをループするだけです。

上記で参照したxConnectionクラスは、基本的には、バイトバッファーを含めるためのソケットの単純なラッパーであり、私の実装ではいくつかの追加機能があります。

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

こちらも参考にしてください using■含まれていないと常にイライラするので、含めます。

using System.Net.Sockets;

参考になれば幸いです。クリーンなコードではないかもしれませんが、機能します。変更にうんざりしなければならないコードにもいくつかのニュアンスがあります。1つには、 BeginAccept いつでも呼び出されます。これについては非常に迷惑な.netバグがありましたが、これは何年も前のことなので、詳細は思い出せません。

また、 ReceiveCallback コードでは、次の受信をキューに入れる前に、ソケットから受信したものを処理します。つまり、単一のソケットの場合、実際には ReceiveCallback いつでも一度に、そして私たちはする必要はありませんスレッド同期を使用します。ただし、データをプルした直後に次の受信を呼び出すようにこれを並べ替える場合、これは少し高速かもしれませんが、スレッドを適切に同期することを確認する必要があります。

また、私は自分のコードの多くをハッキングしましたが、何が起こっているかの本質をそのまま残しました。これは、あなたにとってデザインの良い出発点になるはずです。これに関して他に質問がある場合はコメントを残してください。


回答№2の81

ネットワーク操作を行うには多くの方法がありますC#で。それらはすべて、内部で異なるメカニズムを使用しているため、高い同時実行性を備えた大きなパフォーマンスの問題を抱えています。 Begin *操作はこれらの1つであり、多くの場合、ネットワーキングを行うためのより高速/最速の方法であると誤解されます。

これらの問題を解決するために、彼らは* Asyncメソッドセットを導入しました:MSDNから http://msdn.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.aspx

SocketAsyncEventArgsクラスはセットの一部ですSystem.Net.Sockets .. ::。Socketクラスの拡張機能の一覧です。これにより、特殊な高性能ソケットアプリケーションで使用できる代替非同期パターンが提供されます。このクラスは、高いパフォーマンスを必要とするネットワークサーバーアプリケーション用に特別に設計されました。アプリケーションは、拡張非同期パターンを排他的またはターゲットのホットエリアでのみ使用できます(たとえば、大量のデータを受信する場合)。

これらの機能強化の主な機能は大量の非同期ソケットI / O中のオブジェクトの繰り返し割り当てと同期の回避。 System.Net.Sockets .. ::。Socketクラスによって現在実装されているBegin / Endデザインパターンでは、非同期ソケット操作ごとにSystem .. ::。IAsyncResultオブジェクトを割り当てる必要があります。

内部では、* Async APIはIO完了ポートを使用します。これは、ネットワーク操作を実行する最速の方法です。 http://msdn.microsoft.com/en-us/magazine/cc302334.aspx

そしてあなたを助けるために、私は* Async APIを使用して作成したTelnetサーバーのソースコード。関連部分のみを含めています。また、データをインラインで処理する代わりに、別のスレッドで処理されるロックフリー(待機フリー)キューにデータをプッシュすることにしました。空の場合に新しいオブジェクトを作成する単純なプールである対応するプールクラスと、不確定性を受信しない限り実際には必要のない自己拡張バッファーであるバッファークラスは含まれていないことに注意してくださいデータの量。詳しい情報が必要な場合は、遠慮なくPMを送ってください。

 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("rnb".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
}
}

回答№3の43

以前は本当に良い議論がありましたCoversantのChris Mullinsによって書かれた.NETを使用したスケーラブルなTCP / IP、残念ながら彼のブログは以前の場所から消えてしまったようです。そのため、私は彼のアドバイスを思い出からまとめようと思います(このスレッドに彼の有用なコメントがいくつか表示されます: C ++とC#:非常にスケーラブルなIOCPサーバーの開発

まず第一に、両方を使用することに注意してください Begin/End そしてその Async のメソッド Socket クラスはIO Completion Ports(IOCP)を利用してスケーラビリティを提供します。これにより、ソリューションを実装するために実際に選択する2つの方法よりも、スケーラビリティにはるかに大きな違いが生じます(正しく使用した場合、以下を参照)。

クリス・マリンズ」の投稿は、 Begin/End、これは私が個人的に経験したものですと。 Chrisは、これに基づいて、2 GBのメモリを搭載した32ビットマシンで最大10,000の同時クライアント接続を拡張し、十分なメモリを搭載した64ビットプラットフォームで100,000に拡張したソリューションをまとめました。私がこのテクニックを使った自分の経験から(この種の負荷に近いところはありません)、これらの指標値を疑う理由はありません。

IOCP対接続ごとのスレッドまたは「選択」プリミティブ

を使用するメカニズムを使用する理由内部でのIOCPは、読み取りしようとしているIOチャネルに実際のデータが存在するまでスレッドを起動しない非常に低レベルのWindowsスレッドプールを使用することです(IOCPは、ファイルIOとして使用できることに注意してください)上手)。これの利点は、Windowsがデータがまだないことを見つけるためだけにスレッドに切り替える必要がないことです。これにより、サーバーが必要とする最小限必要なコンテキストスイッチの数が減ります。

コンテキストスイッチは間違いなく殺します「接続ごとのスレッド」メカニズム。ただし、これは、数十の接続しか扱っていない場合に実行可能なソリューションです。ただし、このメカニズムは、「スケーラブル」な想像力によるものではありません。

IOCPを使用する際の重要な考慮事項

メモリ

何よりもまず、実装が単純すぎると、IOCPが.NETでメモリの問題を簡単に引き起こす可能性があることを理解することが重要です。すべてのIOCP BeginReceive 呼び出しは、読み込んでいるバッファの「固定」をもたらします。これが問題である理由の適切な説明については、以下を参照してください。 ユン・ジンのブログ:OutOfMemoryExceptionとピン留め.

幸い、この問題は回避できますが、多少のトレードオフが必要です。提案された解決策は、大きな byte[] アプリケーションの起動時(または終了時)のバッファ少なくとも90KB程度(.NET 2以降では、必要なサイズはそれ以降のバージョンで大きくなる可能性があります)。これを行う理由は、大規模なメモリ割り当ては、自動的に固定される非圧縮メモリセグメント(ラージオブジェクトヒープ)に自動的に割り当てられるためです。起動時に1つの大きなバッファを割り当てることにより、この移動できないメモリのブロックが邪魔にならず、断片化を引き起こさない比較的「低いアドレス」にあることを確認します。

次に、オフセットを使用してこれを大きなセグメントに分割できます一部のデータを読み取る必要がある接続ごとに別々の領域にバッファリングします。ここでトレードオフが生じます。このバッファは事前に割り当てる必要があるため、接続ごとに必要なバッファスペースの量と、スケーリングする接続数に設定する上限を決定する必要があります(または、抽象化を実装できます)必要に応じて追加の固定バッファを割り当てることができます)。

最も簡単な解決策は、すべての接続にこのバッファ内の一意のオフセットで1バイトを割り当てることです。その後、あなたは作ることができます BeginReceive 単一バイトの読み取りを要求し、取得したコールバックの結果として残りの読み取りを実行します。

処理

からコールバックを受け取ったとき Begin コールした場合、コールバックのコードが低レベルのIOCPスレッドで実行されることを認識することが非常に重要です。絶対です 本質的な このコールバックでの長時間の操作を避けてください。複雑な処理にこれらのスレッドを使用すると、「接続ごとのスレッド」を使用するのと同じくらい効果的にスケーラビリティが失われます。

推奨される解決策は、コールバックを使用することです他のスレッドで実行される、着信データを処理するためにワークアイテムをキューに入れるためだけです。 IOCPスレッドが可能な限り迅速にそのプールに戻ることができるように、コールバック内の潜在的なブロック操作を回避します。 .NET 4.0では、最も簡単な解決策は、 Task、クライアントソケットへの参照と、すでに読み取られた最初のバイトのコピーを BeginReceive コール。次に、このタスクは、処理中の要求を表すすべてのデータをソケットから読み取り、それを実行して、新しい BeginReceive IOCPのソケットをもう一度キューに入れる呼び出し。 .NET 4.0より前のバージョンでは、ThreadPoolを使用するか、独自のスレッド化されたワークキュー実装を作成できます。

概要

基本的に、私はこのソリューションにKevinのサンプルコードを使用することをお勧めします。次の警告が追加されています。

  • 渡すバッファを確認してください BeginReceive すでに「固定」されています
  • 渡すコールバックを確認してください BeginReceive 入ってくるデータの実際の処理を処理するためにタスクをキューに入れるだけです

あなたがそれをするとき、私はあなたができることは間違いありません「Chrisを複製する」とは、数十万の潜在的な同時クライアントにスケールアップすることになります(適切なハードウェアと独自の処理コードコースの効率的な実装が与えられた場合)。


答え№422

あなたはすでに答えの大部分を経由して上記のコードサンプル。ここでは、非同期IO操作を使用することが絶対的な方法です。非同期IOは、Win32が内部的にスケーリングするように設計されている方法です。達成できる最高のパフォーマンスは、完了ポートを使用してソケットを完了ポートにバインドし、完了ポートの完了を待機するスレッドプールを使用して達成されます。一般的な知恵は、CPU(コア)ごとに2-4のスレッドが完了を待機することです。 Windows PerformanceチームのRick Vicikによる次の3つの記事を読むことを強くお勧めします。

  1. パフォーマンスのためのアプリケーションの設計-パート1
  2. パフォーマンスのためのアプリケーションの設計-パート2
  3. パフォーマンスのためのアプリケーションの設計-パート3

上記の記事は、ほとんどがネイティブWindowsをカバーしていますAPIですが、スケーラビリティとパフォーマンスを把握しようとする人は必読です。彼らは物事の管理された側にもいくつかのブリーフを持っています。

あなたが行う必要がある2番目のことは、 .NETアプリケーションのパフォーマンスとスケーラビリティの向上 本、それはオンラインで入手可能です。 スレッド、非同期呼び出し、およびロックの使用に関する適切で有効なアドバイスは、第5章にあります。しかし、実際の宝石は第17章にあり、スレッドプールの調整に関する実用的なガイダンスなどの便利な機能が見つかります。この章の推奨事項に従ってmaxIothreads / maxWorkerThreadsを調整するまでの問題。

あなたは純粋なTCPサーバーを作りたいと言っているので、私の次のポイントは偽物です。 しかしながら、角を曲がってWebRequestクラスとその派生クラスを使用している場合は、そのドアを守るドラゴンがあることに注意してください。 ServicePointManager。これは1つの構成クラスです人生の目的:あなたのパフォーマンスを台無しにすること。強制的に課されたServicePoint.ConnectionLimitからサーバーを解放することを確認してください。そうしないと、アプリケーションがスケーリングされません(デフォルト値が何かを発見できます...)。また、httpリクエストでExpect100Continueヘッダーを送信するというデフォルトのポリシーを再検討することもできます。

コアソケットマネージAPIについて送信側はかなり簡単ですが、受信側はかなり複雑です。高いスループットとスケールを実現するには、受信用にポストされたバッファーがないため、ソケットがフロー制御されないようにする必要があります。理想的には、高いパフォーマンスを得るには、3〜4個先のバッファーをポストし、バッファーが1つ戻ったらすぐに新しいバッファーをポストする必要があります( あなたは戻ってきたものを処理します)そのため、ソケットは常にネットワークから来るデータを置く場所があることを保証します。すぐにこれを達成することができない理由がわかります。

遊び終わったらBeginRead / BeginWrite APIを使用して本格的な作業を開始すると、トラフィックにセキュリティ、つまりNTLM / Kerberos認証とトラフィック暗号化、または少なくともトラフィック改ざん保護が必要であることがわかります。これを行う方法は、組み込みのシステムを使用することです.Net.Security.NegotiateStream(異なるドメインにまたがる必要がある場合はSslStream)これは、ストレートソケットの非同期操作に依存する代わりに、AuthenticatedStream非同期操作に依存することを意味します。ソケットを取得するとすぐ(接続からクライアント上またはサーバー上のAcceptから)ソケットにストリームを作成し、BeginAuthenticateAsClientまたはBeginAuthenticateAsServerのいずれかを呼び出して、認証のために送信します。認証が完了した後(少なくともネイティブのInitiateSecurityContext / AcceptSecurityContextマッドネスから安全です...) AuthenticatedストリームのRemoteIdentityプロパティをチェックし、製品が必要とするACL検証を行うことにより、認証を行います サポート。その後、BeginWriteを使用してメッセージを送信し、BeginReadでメッセージを受信します。これは、AuthenticateStreamクラスがこれをサポートしていないため、複数の受信バッファーをポストできないことについて以前に話していた問題です。 BeginReadオペレーションは、フレーム全体を受信するまで内部的にすべてのIOを管理します。そうしないと、メッセージ認証(フレームの復号化とフレームの署名の検証)を処理できませんでした。私の経験では、AuthenticatedStreamクラスによるジョブはかなり良好で、問題はないはずです。つまり。わずか4〜5%のCPUでGBネットワークを飽和させることができるはずです。 AuthenticatedStreamクラスは、プロトコル固有のフレームサイズ制限も課します(SSLの場合は16k、Kerberosの場合は12k)。

これで正しい軌道に乗れるでしょう。ここにコードを投稿するつもりはありません。 MSDNの完全に良い例。私はこのような多くのプロジェクトを行ってきました、そして私は問題なく接続された約1000人のユーザーに拡張できます。その上で、カーネルがより多くのソケットハンドルを使用できるようにレジストリキーを変更する必要があります。 サーバ OS、つまり、XPやVista(つまりクライアントOS)ではなくW2K3であり、大きな違いがあります。

ところで、データベース操作があるかどうかを確認してくださいサーバーまたはファイルIOで非同期フレーバーを使用するか、またはすぐにスレッドプールをドレインします。SQLServer接続の場合は、接続文字列に「Asyncronous Processing = true」を追加してください。


回答№5の11

私はいくつかのソリューションでそのようなサーバーを実行しています。.netで実行するさまざまな方法の詳細な説明は次のとおりです。 .NETの高性能ソケットでワイヤに近づく

最近、私はコードを改善する方法を探していました。これについて調べます。 "バージョン3.5でのソケットパフォーマンスの向上「特に、非同期ネットワークI / Oを使用して最高のパフォーマンスを達成するアプリケーションで使用するために」含まれていました。

「これらの機能強化の主な機能は、大量の非同期ソケットI / O中にオブジェクトの割り当てと同期が繰り返されるのを回避します。非同期ソケットI / OのSocketクラスによって現在実装されているBegin / Endデザインパターンでは、非同期ソケット操作ごとにSystem.IAsyncResultオブジェクトを割り当てる必要があります。」

リンクをたどれば読み続けることができます。私は個人的に明日彼らのサンプルコードをテストして、私が得たものに対してそれをベンチマークします。

編集: ここに クライアントとクライアントの両方の動作コードを見つけることができますサーバーは新しい3.5SocketAsyncEventArgsを使用しているため、数分以内にテストしてコードを実行できます。これは単純なアプローチですが、はるかに大規模な実装を開始するための基礎となります。また この ほぼ2年前のMSDNMagazineの記事は、興味深い読み物でした。


回答№6の9

WCFネットTCPバインディングとパブリッシュ/サブスクライブパターンの使用を検討しましたか? WCFを使用すると、配管ではなく、[ほとんど]ドメインに集中できます。

IDesignのダウンロードセクションには、WCFサンプルがたくさんあり、パブリッシュ/サブスクライブフレームワークもあります。 http://www.idesign.net


答え№7のための8

私は一つのことについて疑問に思っています:

私は絶対に始めたくない 各接続のスレッド。

何故ですか?Windowsは、少なくともWindows 2000以降、アプリケーションで数百のスレッドを処理できました。私はそれを実行しました。スレッドを同期する必要がない場合は、操作が非常に簡単です。特に、多くのことを実行していることを考えると、 I / O(つまり、CPUに縛られておらず、ディスク通信またはネットワーク通信のいずれかで多くのスレッドがブロックされる)、私はこの制限を理解していません。

マルチスレッドの方法をテストし、見つけましたかそれは何かに欠けていますか?スレッドごとにデータベース接続も行う予定ですか(データベースサーバーが停止するため、これは悪い考えですが、3層設計で簡単に解決できます)。 「数百ではなく数千のクライアントがあり、それから本当に問題が発生するのではないかと心配していますか? (私は1000スレッド、または32 GB以上のRAMがある場合は1万スレッドを試しますが、CPUに縛られていないことを考えると、スレッドの切り替え時間はまったく関係ありません。)

これがコードです-これがどのように実行されているかを確認するには、に移動してください http://mdpopescu.blogspot.com/2009/05/multi-threaded-server.html 写真をクリックしてください。

サーバークラス:

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

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

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

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

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

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

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

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

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

サーバーのメインプログラム:

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

クライアントクラス:

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

var msg = new byte[1024];

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

クライアントのメインプログラム:

using System;
using System.Threading;

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

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

回答№8のための5

.NETの統合非同期IOの使用(BeginRead、など)すべてを取得できる場合は良い考えです詳細は正しい。ソケット/ファイルハンドルを適切に設定すると、OSの基盤となるIOCP実装が使用され、スレッドを使用せずに(または、最悪の場合、カーネルからのものと思われるスレッドを使用して)操作を完了できるようになります。 .NETのスレッドプールの代わりにIOスレッドプール。スレッドプールの輻輳を緩和するのに役立ちます。)

主な落とし穴は、ソケット/ファイルを非ブロッキングモードで開いていることを確認することです。デフォルトの便利な関数のほとんど( File.OpenRead)これをしないでください、それであなたはあなた自身を書く必要があるでしょう。

他の主な懸念事項の1つは、エラー処理です。-非同期I / Oコードを作成するときにエラーを適切に処理することは、同期コードで行うよりもはるかに困難です。また、スレッドを直接使用していなくても、競合状態やデッドロックが発生しやすいため、これに注意する必要があります。

可能であれば、スケーラブルな非同期IOを実行するプロセスを容易にするために、便利なライブラリを試して使用する必要があります。

マイクロソフトの 同時実行調整ランタイム は、この種のプログラミングの難しさを緩和するように設計された.NETライブラリの一例です。見た目は素晴らしいですが、使用したことがないので、どれだけ拡張できるかについてはコメントできません。

非同期ネットワークまたはディスクI / Oを実行する必要がある個人的なプロジェクトでは、過去1年間に構築した一連の.NET同時実行/ IOツールを使用します。 Squared.Task。それは次のような図書館に触発されています imvu.task そして ねじれた、そして私はいくつかを含めました 作業例 ネットワークI / Oを実行するリポジトリ内。私はまた、私が書いたいくつかのアプリケーションでそれを使用しました-公開されている最大のアプリケーションは NDexer (スレッドレスディスクI / Oに使用します)。このライブラリは、imvu.taskでの私の経験に基づいて作成されており、かなり包括的な単体テストのセットが含まれているため、試してみることを強くお勧めします。問題が発生した場合は、サポートさせていただきます。

私の意見では、使用した私の経験に基づいて学習曲線に対処する準備ができている限り、スレッドの代わりに非同期/スレッドレスIOは、.NETプラットフォームでの価値のある取り組みです。これにより、スレッドオブジェクトのコストによって課せられるスケーラビリティの煩わしさを回避できます。場合によっては、Futures / Promisesなどの並行性プリミティブを注意深く使用することで、ロックやミューテックスの使用を完全に回避できます。


答え№9の2

あなたはでテクニックの素晴らしい概要を見つけることができます C10k問題ページ.


答え№10の2

私はKevinのソリューションを使用しましたが、ソリューションにはメッセージを再構築するためのコードがないと彼は言います。開発者はこのコードをメッセージの再構築に使用できます。

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

}

回答№11の場合は1

ACEと呼ばれるフレームワークを使用してみることができます(Adaptive Communications Environment)これはネットワークサーバー用の汎用C ++フレームワークです。これは非常に堅固で成熟した製品であり、telcoグレードまでの高信頼性で大量のアプリケーションをサポートするように設計されています。

フレームワークは非常に広範囲を扱います並行性モデルであり、おそらくすぐに使用できるアプリケーションに適したモデルがあります。これにより、厄介な同時実行の問題のほとんどがすでに解決されているため、システムのデバッグが容易になります。ここでのトレードオフは、フレームワークがC ++で記述されており、コードベースの中で最も暖かくてふわふわではないことです。一方、テスト済みの産業グレードのネットワークインフラストラクチャと、拡張性の高いアーキテクチャをすぐに利用できます。


回答№12の場合は1

私は使うだろう セダ または軽量のスレッドライブラリ(erlang以降のLinux) サーバー側のNTPLスケーラビリティを参照してください)。あなたのコミュニケーションがそうでなければ、非同期コーディングは非常に面倒です:)


回答№13の場合は1

まあ、.NETソケットは提供するようです select() -それは入力を処理するのに最適です。出力の場合、ワークキューでリッスンするソケットライタースレッドのプールがあり、ワークアイテムの一部としてソケット記述子/オブジェクトを受け入れるため、ソケットごとにスレッドは必要ありません。


答え№14のための1

私は使用します.Net3.5で追加されたAcceptAsync / ConnectAsync / ReceiveAsync / SendAsyncメソッド。私はベンチマークを実行しましたが、100人のユーザーが絶えずデータを送受信しているため、約35%高速(応答時間とビットレート)です。


回答№15については1

受け入れられた答えをコピーして貼り付ける人々に、あなたはacceptCallbackメソッドを書き換えて、のすべての呼び出しを削除できます _serverSocket.BeginAccept(new AsyncCallback(acceptCallback)、_ serverSocket); そして、次のように、finally {}句に入れます。

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

それ以来、最初のキャッチを削除することもできますコンテンツは同じですが、テンプレートメソッドであり、型付き例外を使用して例外をより適切に処理し、エラーの原因を理解する必要があるため、これらのキャッチをいくつかの便利なコードで実装するだけです。


回答№16の場合は0

ACEでこれらの本を読むことをお勧めします

効率的なサーバーを作成できるようにするパターンについてのアイデアを得る。

ACEはC ++で実装されていますが、本はあらゆるプログラミング言語で使用できる多くの便利なパターンをカバーしています。


回答のための-1№17

明確にするために、私は.netベースのソリューションを探しています(可能であればC#ですが、どの.net言語でも機能します)

純粋に.NETを使用する場合、最高レベルのスケーラビリティは得られません。 GCの一時停止は、レイテンシーを妨げる可能性があります。

少なくとも1つのスレッドを開始する必要がありますサービスのために。いつでも接続するクライアントの数(おそらく数百)がわからないため、Asynch API(BeginRecieveなど)の使用を検討しています。接続ごとにスレッドを開始したくないことは間違いありません。

重複したIO 一般的にWindows "最速と見なされますネットワーク通信用のAPI。これがAsynchAPIと同じかどうかはわかりません。各呼び出しでは、アクティブなソケットでコールバックを行う代わりに、開いているすべてのソケットをチェックする必要があるため、selectを使用しないでください。


答え-1の場合は-1

高性能サーバー開発には、PushFrameworkオープンソースフレームワークを使用できます。 IOCPに基づいて構築されており、プッシュシナリオやメッセージブロードキャストに適しています。

http://www.pushframework.com