程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 使用.net3.5的緩存池和SocketAsyncEventArgs類創建socket服務器

使用.net3.5的緩存池和SocketAsyncEventArgs類創建socket服務器

編輯:關於.NET

在.NET 3.5裡System.Net.Sockets空間下有一組增強功能的類,提供可供專用的高性能套接字應用程 序使用的可選異步模式,SocketAsyncEventArgs 類就是這一組增強功能的一部分。該類專為需要高性能 的網絡服務器應用程序而設計。應用程序可以完全使用增強的異步模式,也可以僅僅在目標熱點區域(例 如,在接收大量數據時)使用此模式。以下是關於此類的介紹(摘自MSDN)

http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx

其實主要是改進了異步模式,讓一些類可以重用,可能用的對象池的原理,不像以前的異步傳輸模式 ,每個數據來了,new一個新的iasyncresult,這樣可能會引起GC線程CPU很高。下面是我找的一篇介 紹.net 3.5裡增強的socket api使用的文章,我翻譯了一下,大家了解一下,貌似性能增強了不少。當然 大家肯定說還不如看原文呢,但怎麼說也是俺花了好幾天,問了好多人才翻譯出來的,大家沒事賞個臉看 看也沒啥壞處,對吧。原文鏈接如下(未經作者同意翻譯的,我不知道怎麼聯系作者,也不會用英語和作 者交流,如果作者會中文,我就會問問他是否讓我翻譯)

http://www.flawlesscode.com/post/2008/01/Socket-Server-with-Net35-using- SocketAsyncEventArgs.aspx

In a previous post I talked about the System.Net.Sockets enhancements in .NET 3.5, and if you haven't read it I suggest you do before tucking in to this as some of that code is important to understand what's happening here.

在前面的帖子裡,我談到了System.Net.Sockets在.NET 3.5裡的增強,如果你還沒有讀過,我建議你 深入本帖之前先讀一下,因為那些代碼對理解本帖很重要。

Before I start, in essence this is just a result of my experimentation and while it seems it does a pretty good job,

在開始之前(我有必要提一下),雖然它看起來不錯,但這本質上只是我實驗的一個結果,。

I'm not going to claim it's bullerproof or that it's a good example of how to write a socket server all it does is demonstrate the techniques of working with the new classes and methods.

我不想聲稱這是bullerproof的,或者是一個很好的關於如何編寫socket服務器的例子,所有這些用於 演示使用新的類和方法工作的技術。

The sample solution you can see on the right there contains three projects.

你可以在右邊看到示例解決方案裡有三個項目。

FlawlessCode contains all the classes we'll need to build ourselves a socket server.

FlawlessCode項目包含了創建Socket服務器所需的所有的類。

TestLoadGenerator is a console application which generates load for us by connecting lots of sockets to our server and sending it random data.

TestLoadGenerator項目用於向我們的Socket服務器產生大量的socket連接負載,並向服務器發送隨機 數據。

TestSocketServer is a small socket server implementation using the classes in FlawlessCode.

TestSocketServer項目是一個使用FlawlessCode項目實現的一個簡單的Socket服務端。

TcpSocketListener

We'll begin by looking at the FlawlessCode project and in particular, the TcpSocketListener.

我們將從FlawlessCode項目開始,特別是TcpSocketListener類。

It should be fairly obvious from the name what this class is meant to achieve, it sits in a loop listening for socket connections and lets us know when one arrives.

從名稱上很容易看出這個類的功能,它在一個循環裡監聽socket連接,並在連接到達時通知我們。

The public interface is very simple and looks like this:

公共的接口非常簡單,如下

public void Start();
public void Stop();
public event EventHandler<SocketEventArgs> SocketConnected;

The only thing we'll take a closer look at here is the internal loop which accepts the client sockets.

這裡唯一需要我們注意的一件事就是內部循環,在內部循環中接收客戶端的socket連接。

Here you can see the first usage of the new SocketAsyncEventArgs and we're calling AcceptAsync, in our callback we check the SocketError property to see if we had any errors.

你可以看到首次使用一個新的SocketAsyncEventArgs類,我們調用AcceptAsync方法,在回調裡檢查 SocketError屬性以便知道是否發生錯誤。

private void ListenForConnection(SocketAsyncEventArgs args)
{
    args.AcceptSocket = null;


    listenerSocket.InvokeAsyncMethod(
        new SocketAsyncMethod(listenerSocket.AcceptAsync)
        , SocketAccepted, args);
}
private void SocketAccepted(object sender, SocketAsyncEventArgs e)
{
    SocketError error = e.SocketError;
    if (e.SocketError == SocketError.OperationAborted)
        return; //Server was stopped

    if (e.SocketError == SocketError.Success)
    {
        Socket handler = e.AcceptSocket;
        OnSocketConnected(handler);
    }
    lock (this)
    {
        ListenForConnection(e);
    }
}

ServerConnection

Next we're going to take a look at the ServerConnection class, this class encapsulates the concept of a connected client.

下面我們將仔細看看ServerConnection類,它封裝一個已連接的客戶端。

Depending on what you wanted to do with your server you may decide to extend this class, rewrite it or maybe completely replace it with something derived from NetworkStream.

根據你對自己服務器的要求不同,可以選擇擴展這個類,重寫或者干脆用從NetworkStream派生出來的 類替換掉它

For our purposes today, this class when created will begin listening for data from the network, it has two public methods, one to disconnect the client and one to send data synchronously back to the client.

僅就我們今天的用途而言,這個類在被創建以後就開始監聽網絡上的數據,它有兩個方法,一個方法 用來斷開客戶端連接,另一個用來以同步的方式向客戶端發送數據。

ServerConnection also fires two callbacks, one when data is received and one when the client is disconnected. Here is a rundown of the interesting parts:

ServerConnection類也可以出發兩個回調,一個是接受數據時,一個是當客戶端連接斷開時,這是一 個有趣的經過裁剪的片段:

public void Disconnect()
{
    lock (this)
    {
        CloseConnection(eventArgs);
    }
}
public void SendData(Byte[] data, Int32 offset, Int32 count)
{
    lock (this)
    {
        State state = eventArgs.UserToken as State;
        Socket socket = state.socket;
        if (socket.Connected)
            socket.Send(data, offset, count, SocketFlags.None);
    }
}
private void ListenForData(SocketAsyncEventArgs args)
{
    lock (this)
    {
        Socket socket = (args.UserToken as State).socket;
        if (socket.Connected)
        {
            socket.InvokeAsyncMethod(socket.ReceiveAsync,
                ReceivedCompleted, args);
        }
    }
}
private void ReceivedCompleted(Object sender,
    SocketAsyncEventArgs args)
{
    if (args.BytesTransferred == 0)
    {
        CloseConnection(args); //Graceful disconnect
        return;
    }
    if (args.SocketError != SocketError.Success)
    {
        CloseConnection(args); //NOT graceful disconnect
        return;
    }
    State state = args.UserToken as State;
    Byte[] data = new Byte[args.BytesTransferred];
    Array.Copy(args.Buffer, args.Offset, data, 0, data.Length);
    OnDataReceived(data, args.RemoteEndPoint as IPEndPoint,
        state.dataReceived);
    ListenForData(args);
}
private void CloseConnection(SocketAsyncEventArgs args)
{
    State state = args.UserToken as State;
    Socket socket = state.socket;
    try
    {
        socket.Shutdown(SocketShutdown.Both);
    }
    catch { } // throws if client process has already closed
    socket.Close();
    socket = null;
    args.Completed -= ReceivedCompleted; //MUST Remember This!
    OnDisconnected(args, state.disconnectedCallback);
}

Taking it from the top, we can see the public Disconnect method, this simply calls our internal CloseConnection method which shuts down the socket and fires our disconnected callback.

在代碼的頂部,我們可以看到一個Disconnect的public方法,他簡單的調用了內部的CloseConnection 方法來關閉socket並引發disconnected回調。

An interesting point to note here is that when this class is instanciated we subscribe to the SocketAsyncEventArgs.

值得注意的一點是,當這個類實例化以後,我們向SocketAsyncEventArgs類訂閱消息。

Completed event, when a client disconnects we need to remember to unhook this event because when we're reusing objects and pooling resources like this it's a bad idea to leave these references hanging around.

完成事件,當客戶端斷開時,我們要記著拆除,因為用資源池的方式重用對象的時候,讓這些(無關 的)引用就那麼留在外面是個糟糕的主意,

Moving down we have the public SendData method, nothing interesting here really, just a standard synchrounous call.

接下來我們有一個SendData的public方法,這裡沒什麼特別的,就是一個標准的同步調用。

Next we get to the internal loop which listens for data from the client, notice how we check SocketAsyncEventArgs.BytesTransferred, if this is zero, the client has closed the connection and disconnected gracefully.

再下面我們有一個內部的循環來監聽客戶端的數據,注意下我們如何檢查 SocketAsyncEventArgs.BytesTransferred屬性,當它是0是,是客戶端已經關閉了連接並正常的斷開。

We check the value of SocketError here also to make sure there was no error anywhere, after that we make a copy of the bytes we received and inform any interested parties we have new data.

我們檢查SocketError的值以確保沒有發生任何錯誤,然後為接收到的所有字節建立一個副本,並通知 有關各方我們收到了新的數據。

BufferPool and SocketArgsPool

These two classes help us with pooling our resources and are not really very interesting, they're also almost identical to the MSDN examples so you can either look there or just check out the code.

這兩個類幫助我們池化資源,也沒有什麼非常特別的地方,他們和MSDN的例子幾乎相同,所以你可以 看看它們或者簡單的只獲取代碼。

BufferPool: http://msdn2.microsoft.com/en-us/library/bb517542.aspx

SocketArgsPool: http://msdn2.microsoft.com/en-us/library/bb551675.aspx

TestSocketServer

Now that we've sen to main functionality in the FlawlessCode project we're going to look at a simple socket server implementation using these classes.

現在,我們已經看到了FlawlessCode項目的主要功能,我們將看到一個使用這些類實現的一個簡單的 Socket服務器。

TcpSocketListener socketListener = new TcpSocketListener(IPAddress.Any, 12345, 10);

socketListener.SocketConnected += socketListener_SocketConnected;

socketListener.Start();

Fairly straight forward, we fire up out listener on port 12345 and give the listening socket an allowed connection backlog of 10.

前面的代碼非常直接,我們在12345端口上建立了監聽器,並得到了一個已經監聽的socket,它允許10 個請求隊列。

static void socketListener_SocketConnected(object sender, SocketEventArgs e)
{
    SocketAsyncEventArgs args = socketArgsPool.CheckOut();
    bufferManager.CheckOut(args);

    ServerConnection connection = new ServerConnection(e.Socket, args,
        new DataReceivedCallback(DataReceived),
        new DisconnectedCallback(Disconnected));
}

When a client connects we get an SocketAsyncEventArgs and some free buffer space for our client and then we create an instance of ServerConnection.

在客戶端連接進來的時候我們會得到一個SocketAsyncEventArgs和一些為客戶端准備的空閒的緩沖區 ,然後我們創建一個ServerConnection的實例。

Note that we are passing delegates into the constructor, this is because the ServerConnection begins listening for data immediately and we have to have the callbacks hooked up before hand.

注意一點我們需要向構造函數裡傳入委托,這是因為ServerConnection會立刻監聽數據,我們在這之 前必須准備好回調函數。

If we let the call to the constructor complete and the we hooked to standard events we may have already missed the first batch of data!

如果我們已經調用了構造函數並且連接了標准的事件,我們可能已經錯過了首批數據。

static void DataReceived(ServerConnection sender, DataEventArgs e)
{
    //Do whatever we want here
}
static void Disconnected(ServerConnection sender, SocketAsyncEventArgs e)
{
    bufferManager.CheckIn(e);
    socketArgsPool.CheckIn(e);
}

Here we do whatever processing is necessary when a client sends us data.

我們可以在客戶端發送數據時做任何我們想做的事情。

When a client disconnects we just check our buffer space and SocketAsyncEventArgs back into their respective pools to fight another day.

當客戶端斷開時,我們僅僅檢出緩存區並且讓SocketAsyncEventArgs返回他們各自的池裡,以便以後 再用。

TestLoadGenerator

I'm not going to go into how the load generation works for now, the code is all very straight forward if you've managed to follow the post this far I would imagine.

我現在不想講load generation是如何工作的,如果你已經一直看完本帖,我想你會發現代碼非常的簡 單。

One thing to note is that if you want to test this code and open thousands of connections you need to tweak a registry setting or windows wont give you enough ports.

需要注意的一點那是,如果你想測試這些代碼,並打開成千上萬個連接的話,你需要調整注冊表設置 以便windows給你足夠的端口。

You will need to add the DWORD MaxUserPort to HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters and give it a high enough value that windows won't run out of ports (reboot required, sorry)! Here is a quick examaple of how the load generation classes are used:

你需要在HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters下添加一個MaxUserPort的 DWORD的值,並設置成一個足夠高的值,否則windows不會打開那麼多端口(需要重啟,切記),這是使用 這些類產生負載的一個簡單示例:

static void Main(string[] args)
{
    LoadGenerator generator = new LoadGenerator(15000);
    generator.BytesPerDelivery = 2048;
    generator.DeliveriesPerSecond = 2;
    generator.SocketCount = 15000;
    generator.SocketDelay = 5;
    generator.SocketsPerDelivery = 3;
    generator.Start(IPAddress.Parse("127.0.0.1"), 12345);
    Console.ReadLine();
    generator.Stop();
}

Pretty easy to use, right?

相當容易使用,不是嗎?

We create an instance of the LoadGenerator class, telling it we'd like 15,000 connections maximum.

我們創建一個LoadGenerator類的實例,告訴它我們最大要壓倒15000個連接。

Then we set some properties saying that we'd like each connected socket to deliver 2K of data twice per second.

然後我們設置一些屬性告訴它我們希望每個socket連接上每秒發送兩個大小為2k的數據包。(這句翻譯 的可能不准確)

We'd like 15,000 sockets and we'd like them to connect 5ms apart and that we want on average 3% of sockets to send data in each delivery. Then we just aim and fire! Check it out:

我們希望15000個socket在5毫秒內連接,並且有每個迭代裡有百分之3的socket發送數據。然後我們啟 動它,我們看看效果(這句翻譯的可能不准確):

圖(我的機器,服務端初始化5000個SocketArgsPool,和一個大小為5000的BufferPool,每個Buffer大 小為1024,啟動後大約占500多M內存,我的機器是雙核32位。客戶端初始化5000個到服務端的連接,每個 連接每秒發2個包,每個包的大小為2048 bytes,跑了半小時,GC、線程及鎖爭用截圖如下):

When this was taken, the server executable was using 109MB or RAM and 1% CPU on my desktop machine so I think at 15,000 connections we've got some pretty damned good performance out of this thing!

當它執行起來,在我的台式機服務器使用了109M的內存及1%的CPU,所以我想15000個連接達到如此好 的性能是相當漂亮的。

Obviously when we start implementing the server logic and actually processing each packet this will go up, but for a bare socket server, I'm pretty pleased.

當然,當我們開始實現自己的服務端邏輯時候我們會處理更多的包,但對一個簡單的socket來說,我 覺得非常好了。(後半句翻譯的可能不准確)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved