网络客户端模拟器设计

时间:2011-01-22 11:58:43

标签: c++ multithreading network-programming boost-asio

我试图用c ++设计一个软件,它将使用** UDP协议**发送请求字节(遵循标准的**应用程序级别**协议,其中要填充的字段将从文本文件中获取)。

现在这个客户端必须能够以非常高的速率发送这些请求..每秒** 2000个事务**并且如果它在指定的超时范围内也应该收到响应,否则不会收到它

我将使用boost库来处理所有套接字的东西,但我不确定它的设计如此高速应用:(

我想我必须使用高度多线程的应用程序(将再次使用Boost)。我对吗 ?我是否必须为每个请求创建一个单独的线程?但我认为只有一个线程必须等待接收响应,否则如果许多线程正在等待响应,我们如何区分哪些线程请求我们得到了响应!!

希望这个问题很清楚。我只是需要一些关于我可能面临的设计要点和可疑问题的帮助。

2 个答案:

答案 0 :(得分:2)

我现在正在通过我自己的网络客户端,所以也许我可以传授一些建议和一些资源来看待。在这个领域有很多经验丰富的人,希望他们能够参与其中:)

首先,你是关于提升。一旦习惯了它们如何挂起,boost::asio就是编写网络代码的绝佳工具包。基本上,您创建io_service并调用run执行直到所有工作完成,或runOne执行单个IO操作。就他们自己而言,这没有用。权力来自于你在自己的循环中运行runOne

boost::asio::io_service myIOService;
while(true)
{
    myIOService.runOne();
}

,或在一个(或多个)线程上运行run函数:

boost::thread t(boost::bind(&boost::asio::io_service::run, &myIOService));

然而,值得注意的是run一旦没有工作要做就会返回(所以你可以告别那个线程)。正如我在Stackoverflow上发现的那样,诀窍是确保它总是有事可做。解决方案在boost::asio::io_service::work

boost::asio::io_service::work myWork(myIOService);   // just some abstract "work"

以上行确保您的线程不会在任何事情发生时停止。我认为这是一种保持活力的手段:)

在某些时候,您将要创建一个套接字并将其连接到某个地方。我创建了一个通用的Socket类(并从中派生了一个text-socket来创建缓冲输入)。我还想要一个基于事件的系统,它非常像C#。我在下面概述了这些东西:

第一步,我们需要一种传递参数的通用方法,因此,EventArgs

<强> eventArgs.h

 class EventArgs : boost::noncopyable
 {
 private:

 public:
  EventArgs();
  virtual ~EventArgs() = 0;
 }; // eo class EventArgs:

现在,我们需要一个人们可以订阅/取消订阅的事件类:

<强> event.h

// STL
#include <functional>
#include <stack>

// Boost
#include <boost/bind.hpp>
#include <boost/thread/mutex.hpp>

 // class Event
 class Event : boost::noncopyable
 {
 public:
  typedef std::function<void(const EventArgs&)> DelegateType;
  typedef boost::shared_ptr<DelegateType> DelegateDecl;

 private:
  boost::mutex m_Mutex;
  typedef std::set<DelegateDecl> DelegateSet;
  typedef std::stack<DelegateDecl> DelegateStack;
  typedef DelegateSet::const_iterator DelegateSet_cit;
  DelegateSet m_Delegates;
  DelegateStack m_ToRemove;

 public:
  Event()
  {
  }; // eo ctor


  Event(Event&& _rhs) : m_Delegates(std::move(_rhs.m_Delegates))
  {
  }; // eo mtor

  ~Event()
  {
  }; // eo dtor

  // static methods
  static DelegateDecl bindDelegate(DelegateType _f)
  {
   DelegateDecl ret(new DelegateType(_f));
   return ret;
  }; // eo bindDelegate

  // methods
  void raise(const EventArgs& _args)
  {
   boost::mutex::scoped_lock lock(m_Mutex);

   // get rid of any we have to remove
   while(m_ToRemove.size())
   {
    m_Delegates.erase(m_Delegates.find(m_ToRemove.top()));
    m_ToRemove.pop();
   };

   if(m_Delegates.size())
   std::for_each(m_Delegates.begin(),
        m_Delegates.end(),
        [&_args](const DelegateDecl& _decl) { (*_decl)(_args); });
  }; // eo raise

  DelegateDecl addListener(DelegateDecl _decl)
  {
   boost::mutex::scoped_lock lock(m_Mutex);
   m_Delegates.insert(_decl);
   return _decl;
  }; // eo addListener

  DelegateDecl addListener(DelegateType _f)
  {
   DelegateDecl ret(bindDelegate(_f));
   return addListener(ret);
  }; // eo addListener


  void removeListener(const DelegateDecl _decl)
  {
   boost::mutex::scoped_lock lock(m_Mutex);
   DelegateSet_cit cit(m_Delegates.find(_decl));
   if(cit != m_Delegates.end())
    m_ToRemove.push(_decl);
  }; // eo removeListener

  // operators

  // Only use operator += if you don't which to manually detach using removeListener
  Event& operator += (DelegateType _f)
  {
   addListener(_f);
   return *this;
  }; // eo op +=

 }; // eo class Event

然后,是时候创建一个套接字类了。下面是标题:

<强> socket.h中

(有些说明:ByteVectortypedef std::vector<unsigned char>

#pragma once

#include "event.h"

// boost
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/buffer.hpp>
  // class Socket
  class MORSE_API Socket : boost::noncopyable
  {
  protected:
   typedef boost::shared_ptr<boost::asio::ip::tcp::socket> SocketPtr;

  private:
   ByteVector      m_Buffer;   // will be used to read in

   SocketPtr        m_SocketPtr;
   boost::asio::ip::tcp::endpoint      m_RemoteEndPoint;
   bool         m_bConnected;

   // reader
   void _handleConnect(const boost::system::error_code& _errorCode, boost::asio::ip::tcp::resolver_iterator _rit);
   void _handleRead(const boost::system::error_code& _errorCode, std::size_t read);
  protected:

   SocketPtr socket() { return m_SocketPtr; };
  public:
   Socket(ByteVector_sz _bufSize = 512);
   virtual ~Socket();

   // properties
   bool isConnected() const { return m_bConnected; };
   const boost::asio::ip::tcp::endpoint& remoteEndPoint() const {return m_RemoteEndPoint; };

   // methods
   void connect(boost::asio::ip::tcp::resolver_iterator _rit);
   void connect(const String& _host, const Port _port);
   void close();

   // Events
   Event onRead;
   Event onResolve;
   Event onConnect;
   Event onClose;
  }; // eo class Socket

而且,现在实施。您会注意到它调用另一个类来执行DNS解析。之后我会说明。还有一些EventArg - 衍生物我已经省略了。当套接字事件发生时,它们只是作为EventArg参数传递。

<强> socket.cpp

#include "socket.h"


// boost
#include <boost/asio/placeholders.hpp>

namespace morse
{
 namespace net
 {
  // ctor
  Socket::Socket(ByteVector_sz _bufSize /* = 512 */) : m_bConnected(false)
  {
   m_Buffer.resize(_bufSize);
  }; // eo ctor

  // dtor
  Socket::~Socket()
  {
  }; // eo dtor


  // _handleRead
  void Socket::_handleRead(const boost::system::error_code& _errorCode,
            std::size_t _read)
  {
   if(!_errorCode)
   {
    if(_read)
    {
     onRead.raise(SocketReadEventArgs(*this, m_Buffer, _read));
     // read again
     m_SocketPtr->async_read_some(boost::asio::buffer(m_Buffer), boost::bind(&Socket::_handleRead, this, _1, _2));
    };
   }
   else
    close();
  }; // eo _handleRead


  // _handleConnect
  void Socket::_handleConnect(const boost::system::error_code& _errorCode,
         boost::asio::ip::tcp::resolver_iterator _rit)
  {
   m_bConnected = !_errorCode;
   bool _raise(false);
   if(!_errorCode)
   {
    m_RemoteEndPoint = *_rit;
    _raise = true;
    m_SocketPtr->async_read_some(boost::asio::buffer(m_Buffer), boost::bind(&Socket::_handleRead, this, _1, _2));
   }
   else if(++_rit != boost::asio::ip::tcp::resolver::iterator())
   {
    m_SocketPtr->close();
    m_SocketPtr->async_connect(*_rit, boost::bind(&Socket::_handleConnect, this, boost::asio::placeholders::error, _rit));
   }
   else
    _raise = true; // raise complete failure

   if(_raise)
    onConnect.raise(SocketConnectEventArgs(*this, _errorCode));

  }; // eo _handleConnect


  // connect
  void Socket::connect(boost::asio::ip::tcp::resolver_iterator _rit)
  {
   boost::asio::ip::tcp::endpoint ep(*_rit);
   m_SocketPtr.reset(new boost::asio::ip::tcp::socket(Root::instance().ioService()));
   m_SocketPtr->async_connect(ep, boost::bind(&Socket::_handleConnect, this, boost::asio::placeholders::error, _rit));
  };


  void Socket::connect(const String& _host, Port _port)
  {
   // Anon function for resolution of the host-name and asynchronous calling of the above
   auto anonResolve = [this](const boost::system::error_code& _errorCode, 
           boost::asio::ip::tcp::resolver_iterator _epIt)
   {
    // raise event
    onResolve.raise(SocketResolveEventArgs(*this, !_errorCode ? (*_epIt).host_name() : String(""), _errorCode));

    // perform connect, calling back to anonymous function
    if(!_errorCode)
     this->connect(_epIt);
   };

   // Resolve the host calling back to anonymous function
   Root::instance().resolveHost(_host, _port, anonResolve);

  }; // eo connect


  void Socket::close()
  {
   if(m_bConnected)
   {
    onClose.raise(SocketCloseEventArgs(*this));
    m_SocketPtr->close();
    m_bConnected = false;
   };
  } // eo close

正如我所说的DNS解析,行Root::instance().resolveHost(_host, _port, anonResolve);调用它来执行异步DNS:

  // resolve a host asynchronously
  template<typename ResolveHandler>
  void resolveHost(const String& _host, Port _port, ResolveHandler _handler)
  {
   boost::asio::ip::tcp::endpoint ret;
   boost::asio::ip::tcp::resolver::query query(_host, boost::lexical_cast<std::string>(_port));
   m_Resolver.async_resolve(query, _handler);
  }; // eo resolveHost

最后,我需要一个基于文本的套接字,每次收到行(然后处理)时都会引发一个事件。我这次会省略头文件,只显示实现文件。毋庸置疑,它声明了一个名为Event的{​​{1}},它在每次收到整个行时都会触发:

onLine

有关上述课程的一些注意事项......它使用// boost #include <boost/asio/buffer.hpp> #include <boost/asio/write.hpp> #include <boost/asio/placeholders.hpp> namespace morse { namespace net { String TextSocket::m_DefaultEOL("\r\n"); // ctor TextSocket::TextSocket() : m_EOL(m_DefaultEOL) { onRead += boost::bind(&TextSocket::readHandler, this, _1); }; // eo ctor // dtor TextSocket::~TextSocket() { }; // eo dtor // readHandler void TextSocket::readHandler(const EventArgs& _args) { auto& args(static_cast<const SocketReadEventArgs&>(_args)); m_LineBuffer.append(args.buffer().begin(), args.buffer().begin() + args.bytesRead()); String::size_type pos; while((pos = m_LineBuffer.find(eol())) != String::npos) { onLine.raise(SocketLineEventArgs(*this, m_LineBuffer.substr(0, pos))); m_LineBuffer = m_LineBuffer.substr(pos + eol().length()); }; }; // eo readHandler // writeHandler void TextSocket::writeHandler(const boost::system::error_code& _errorCode, std::size_t _written) { if(!_errorCode) { m_Queue.pop_front(); if(!m_Queue.empty()) // more to do? boost::asio::async_write(*socket().get(), boost::asio::buffer(m_Queue.front(), m_Queue.front().length()), boost::bind(&TextSocket::writeHandler, this, _1, _2)); } else close(); }; // eo writeHandler void TextSocket::sendLine(String _line) { Root::instance().ioService().post(boost::bind(&TextSocket::_sendLine, this, _line)); }; // eo sendLine // _sendLine void TextSocket::_sendLine(String _line) { // copy'n'queue _line.append(m_EOL); m_Queue.push_back(_line); if(m_Queue.size() == 1) // previously an empty queue, must start write! boost::asio::async_write(*socket().get(), boost::asio::buffer(m_Queue.front(), m_Queue.front().length()), boost::bind(&TextSocket::writeHandler, this, _1, _2)); }; // eo sendLine 发送行。这允许它以线程安全的方式在ASIO管理的线程上发生,并允许我们排队要在何时发送的行。这使它具有很高的可扩展性。

我确信还有更多问题,也许我的代码没有帮助。我花了好几天把它拼凑起来并理解它,我怀疑它实际上是好的。希望一些更好的头脑会瞥一眼它然后去“HOLY CRAP,这个

答案 1 :(得分:2)

我不确定你需要去“重”多线程。大多数高速应用程序使用操作系统的轮询机制,通常比线程更好地扩展。

架构将在很大程度上取决于您的应用程序需要的响应能力,包括负责生成输入和输出的组件以及进行实际处理。

使用boost :: asio解决问题的方法是使用运行boost :: asio :: io_service :: run方法的通信线程。 io_service侦听各种UDP套接字,并在消息到达时处理它们,可能将它们发送到队列中,以便应用程序可以在主线程中处理它们。从主线程,您可以将消息发布到io_services,以便它们由主系统发送。

这应该允许你每秒爬上2000条消息而没有太多困难。

另一种方法是通过从多个线程多次调用boost :: asio :: io_service :: run方法来启动多个通信线程,允许消息通过其通信线程并行处理。

与Asio的一个建议是:由于它的异步架构,如果你进入其逻辑并按照它的意图使用它,它会更好。如果您发现自己使用了大量锁并自行管理多个线程,那么您可能做错了。仔细研究各种方法的线程安全保障,并研究提供的例子。