如何编写一个可扩展的基于TCP / IP的服务器 [英] How to write a scalable Tcp/Ip based server

查看:300
本文介绍了如何编写一个可扩展的基于TCP / IP的服务器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我以书面形式接受了长时间运行的连接的TCP / IP连接一个新的Windows服务应用程序的设计阶段(即这是不是像HTTP,那里有许多短连接,而是一个客户端连接,并保持连接数小时或数天或甚至数周)。

I am in the design phase of writing a new Windows Service application that accepts TCP/IP connections for long running connections (i.e. this is not like HTTP where there are many short connections, but rather a client connects and stays connected for hours or days or even weeks).

我在找的想法来设计网络架构的最佳途径。我将需要至少启动一个线程的服务。我现在用的非同步API考虑(BeginRecieve,等..),因为我不知道有多少客户,我会已连接在任何给定的时间(可能是数百个)。我绝对不希望启动一个线程为每个连接。

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

数据将主要从我的服务器流出到客户端,但将有来自客户端偶尔发送一些指令。这主要是在我的服务器定期发送状态数据到客户端监控应用程序了。

Data will primarily flow out to the clients from my server, but there will be some commands sent from the clients on occasion. This is primarily a monitoring applicaiton in which my server sends status data periodically to the clients.

这是最好的方法的任何建议,使这是可扩展的可能吗?基本工作流程?谢谢你。

Any suggestions on the best way to make this as scalable as possible? Basic workflow? Thanks.

编辑:要清楚,我正在寻找.NET解决方案(C#如果可能的话,但任何.NET语言来工作)

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

赏金注:被授予奖金,我希望有更多的不是一个简单的答案。我需要一个解决方案的工作的例子,无论是作为一个指针的东西,我可以下载或短的例子在网上。而基于它必须是.NET和Windows(任何.NET语言是可以接受的)

BOUNTY NOTE: To be awarded the bounty, I expect more than a simple answer. I would need a working example of a solution, either as a pointer to something I could download or a short example in-line. And it must be .net and Windows based (any .net language is acceptable)

编辑:我要感谢大家给了很好的答案。不幸的是,我只能接受一个,我选择了接受比较知名的开始/结束的方法。 ESAC的解决方案可能会更好,但它仍然是新的,以至于我不肯定知道它是如何工作了做。

I want to thank everyone that gave good answers. Unfortunately, I could only accept one, and I chose to accept the more well known Begin/End method. Esac's solution may well be better, but it's still new enough that I don't know for sure how it will work out.

我已经upvoted所有我认为是很好的答案,我希望我能为你们做的更多。再次感谢。

I have upvoted all the answers I thought were good, I wish I could do more for you guys. Thanks again.

推荐答案

我已经写了一些东西。从我的研究,几年前发现,编写自己的套接字实现是最好的选择,使用异步套接字。这意味着客户没有真正做任何实际需要相对较少的资源。凡是确实发生由.NET线程池处理。

I've written something similar to this in the past. From my research years ago showed that writing your own socket implementation was the best bet, using the Asynchronous sockets. This meant that clients not really doing anything actually required relatively little resources. Anything that does occur is handled by the .net thread pool.

我写它作为管理的服务器的所有连接的类。

I wrote it as a class that manages all connections for the servers.

我只是用一个列表来保存所有的客户端连接,但如果你的大名单需要更快的查找,你可以按你想要写吧。

I simply used a list to hold all the client connections, but if you need faster lookups for larger lists, you can write it however you want.

private List<xConnection> _sockets;

此外,你需要的插座居然一边收听对进来的连接。

Also you need the socket actually listenning for incomming connections.

private System.Net.Sockets.Socket _serverSocket;

start方法实际启动服务器套接字并开始监听所有进来的连接。

The start method actually starts the server socket and begins listening for any incomming connections.

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

我只是想指出的异常处理code不妙,但它的原因是我有例外燮pression code在那里,这样任何的例外是燮pressed并返回如果设置一个配置选项,但我想删除它简洁的缘故。

I'd just like to note the exception handling code looks bad, but the reason for it is I had exception suppression code in there so that any exceptions would be suppressed and return false if a config option was set, but I wanted to remove it for brevity sake.

在_serverSocket.BeginAccept(新的AsyncCallback(acceptCallback)),_serverSocket)以上基本设置我们的服务器套接字每当用户连接到呼叫acceptCallback方法。这种方法运行在.NET线程池,它可以自动处理创造更多的工作线程,如果你有很多阻塞操作。这应该优化服务器上​​处理任何负载。

The _serverSocket.BeginAccept(new AsyncCallback(acceptCallback)), _serverSocket) above essentially sets our server socket to call the acceptCallback method whenever a user connects. This method runs from the .Net threadpool, which automatically handles creating additional worker threads if you have many blocking operations. This should optimally handle any load on the server.

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

以上code基本上是刚刚结束接受了进来,排队的连接 BeginReceive 这是一个回调,当客户端发送的数据,将运行,然后排队接下来的 acceptCallback 将接受来自在未来的客户端连接。

The above code essentially just finished accepting the connection that comes in, queues BeginReceive which is a callback that will run when the client sends data, and then queues the next acceptCallback which will accept the next client connection that comes in.

BeginReceive 方法的调用是告诉插座,当它接收来自客户端的数据做什么。对于 BeginReceive ,你需要给它一个字节数组,这是它会在客户端发送数据的复制数据。该 ReceiveCallback 方法被调用,这是我们如何处理接收数据。

The BeginReceive method call is what tells the socket what to do when it receives data from the client. For BeginReceive, you need to give it a byte array, which is where it will copy the data when the client sends data. The ReceiveCallback method will get called, which is how we handle receiving data.

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

编辑:在这种模式我忘了提及,在code这方面的:

In this pattern I forgot to mention that in this area of code:

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

我一般会做的是,在任何你想要的code,是做包的重新组装成消息,然后创建它们作为线程池的作业。这样,客户端的下一个块的BeginReceive不被延迟,而任何消息处理code上。

What I would generally do is in the whatever you want code, is do reassembly of packets into messages, and then create them as jobs on the thread pool. This way the BeginReceive of the next block from the client isn't delayed while whatever message processing code is running.

在接受回调完成由主叫端接收读取数据插座。这填补了开始接收功能所提供的缓冲区。一旦你做任何你想在我离开的评论,我们称之为一个 BeginReceive 方法,该方法将再次运行回调,如果客户端发送任何数据。现在,这里的真正棘手的部分,当客户端发送数据时,您收到的回调可能只是调用了消息的一部分。重组可能会变得非常非常复杂。我用我自己的方法,创造了一种专有协议来做到这一点。我离开了出来,但如果你的要求,我可以添加它。这种处理实际上是最复杂的一块code我曾经写的。

The accept callback finishes reading the data socket by calling end receive. This fills the buffer provided in the begin receive function. Once you do whatever you want where I left the comment, we call the next BeginReceive method which will run the callback again if the client sends any more data. Now here's the really tricky part, when the client sends data, your receive callback might only be called with part of the message. Reassembly can become very very complicated. I used my own method and created a sort of proprietary protocol to do this. I left it out, but if you request, I can add it in. This handler was actually the most complicated piece of code I had ever written.

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

以上的发送方法实际上采用的是同步发送电话,对我来说很好,由于消息大小和我的应用程序的多线程特性。如果你想发送给每一个客户,你只需要通过_sockets列表循环。

The above send method actually uses a synchronous Send call, for me that was fine due to the message sizes and the multithreaded nature of my application. If you want to send to every client, you simply need to loop through the _sockets List.

你看到上面提到的xConnection类基本上是一个简单的包装为一个插座包括字节的缓冲区,并在我的实现一些附加功能。

The xConnection class you see referenced above is basically a simple wrapper for a socket to include the byte buffer, and in my implementation some extras.

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

此外,以供参考这里的使用的I包括,因为我总是生气不包括在内,当他们。

Also for reference here are the usings I include since I always get annoyed when they aren't included.

using System.Net.Sockets;

我希望这是有帮助的,它可能不是最干净的code,但它的工作原理。也有一些细微之处code,你应该感到厌倦有关更改。其一,只有一个 BeginAccept 称为在任何一个时间。过去有解决此一件很烦人的.NET的错误,这是几年前,所以我不记得细节。

I hope that's helpful, it may not be the cleanest code, but it works. There are also some nuances to the code which you should be weary about changing. For one, only have a single BeginAccept called at any one time. There used to be a very annoying .net bug around this, which was years ago so I don't recall the details.

此外,在 ReceiveCallback code,我们处理从插座接收之前,我们排队的下一个接收任何东西。这意味着,对于一个插座,我们实际上只在过 ReceiveCallback 一旦在任何时间点,我们并不需要使用线程同步。但是,如果你重新排列此调用拉低数据,这可能会快一点后,立即下一个接收,你需要确保你正确地同步线程。

Also, in the ReceiveCallback code, we process anything received from the socket before we queue the next receive. This means that for a single socket, we're only actually ever in ReceiveCallback once at any point in time, and we don't need to use thread synchronization. However, if you reorder this to call the next receive immediately after pulling the data, which might be a little faster, you will need to make sure you properly synchronize the threads.

另外,我砍死了很多我的code,但留下了所发生的事情在发生的本质。这应该是一个良好的开端为你设计的。发表评论,如果您有解决这个任何问题。

Also, I hacked out alot of my code, but left the essence of what's happening in place. This should be a good start for you're design. Leave a comment if you have any more questions around this.

这篇关于如何编写一个可扩展的基于TCP / IP的服务器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆