套接字:如何在接收/解析数据时将数据发送到客户端而不“等待”它们

时间:2009-07-15 23:34:32

标签: c++ sockets asynchronous boost-asio

我有一个套接字服务器,使用boost :: asio用C ++编写,我正在向客户端发送数据。

服务器以数据块的形式发送数据,客户端在收到数据块时会对其进行解析。两者现在都是单线程。

我应该在服务器上使用什么样的设计来确保服务器只是尽可能快地写出数据,而不是等待客户端解析它?我想我需要在服务器上做异步。

我想可以在客户端上进行更改以实现此目的,但理想情况下,无论客户端是如何编写的,服务器都不应该在客户端上等待。

我正在将数据写入套接字:

size_t bytesWritten = m_Socket.Write( boost::asio::buffer(buffer, bufferSize));

更新

我将尝试使用Boost的机制异步写入套接字。见http://www.boost.org/doc/libs/1_36_0/doc/html/boost_asio/tutorial/tutdaytime3/src.html

e.g。

 boost::asio::async_write(socket_, boost::asio::buffer(message_),
        boost::bind(&tcp_connection::handle_write, shared_from_this(),
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  • 亚历

5 个答案:

答案 0 :(得分:1)

您可以通过不通过TCP而是通过UDP传输数据来确保异步通信。 但是,如果您需要使用TCP,请让客户端快速存储数据并在不同的线程中处理它,或者与cron作业异步处理。

答案 1 :(得分:1)

将数据传递给套接字时,它不会等待接收方处理它。它甚至不等待数据传输。数据被放入由后台操作系统处理的出站队列中。写入函数返回排队等待传输的字节数,而不是实际传输的字节数。

答案 2 :(得分:1)

如果将套接字设置为非阻塞,则写入会失败,否则会阻塞。然后,您可以根据需要对数据进行排队,并安排稍后再次尝试编写数据。我不知道如何在boost socket API中设置套接字选项,但这正是你要找的。

但这可能比它的价值更麻烦。您需要选择一个可以写入的套接字,可能是从几个同时打开的套接字,将更多数据推入其中直到它已满,然后重复。我不知道boost套接字API是否具有等效的select,因此您可以一次等待多个套接字,直到任何套接字准备好写入。

服务器通常每个客户端连接启动一个线程(或生成一个进程)的原因恰恰是因为他们可以在等待I / O时继续为其他客户端提供服务,同时避免实现自己的队列。 “安排稍后再次尝试”的最简单方法就是在专用线程中阻止I / O.

除非boost在其套接字API中做了一些不寻常的事情,否则你不能做的是要求操作系统或套接字库为你排队任意数量的数据而不会阻塞。可能存在异步API,在写入数据时会回调您。

答案 3 :(得分:1)

继续对Stefan的帖子发表评论:

绝对可以在客户端或服务器端缓冲。但请务必考虑尼尔所写的内容。如果我们开始盲目地缓冲数据并且如果处理永远不能跟上发送,那么我们的缓冲区将以我们可能不想要的方式增长。

现在我最近实现了一个简单的“NetworkPipe”,它意味着作为单个客户端/服务器,服务器/客户端之间的连接,外部用户不知道/关心Pipe是客户端还是服务器。我实现了类似于你所询问的缓冲情况,怎么样?那个类是线程化的,这是我可以想出干净地缓冲数据的唯一方法。这是我遵循的基本过程,请注意我在管道上设置了最大尺寸:

  1. 进程1启动管道,默认为服务器。现在内部线程等待客户端。
  2. 进程2启动管道,已经是服务器,默认为客户端。
  3. 我们现已连接,首先要做的是交换最大缓冲区大小。
  4. 进程1写入数据(它注意到另一端有一个空缓冲区[见#3])
  5. 进程2的内部线程(现在正在等待套接字的select())看到数据被发送并读取它,缓冲它。进程2现在将新的缓冲大小发送回P1。
  6. 所以这是一个非常简化的版本但基本上通过线程化它我总是可以等待阻塞选择调用,一旦数据到达我就可以读取并缓冲它,我发回新的缓冲大小。你可以做类似的事情,盲目地缓冲数据,它实际上相当简单,因为你不必交换缓冲区大小,但可能是一个坏主意。因此,上面的示例允许外部用户在不阻塞其线程的情况下读取/写入数据(除非另一端的缓冲区已满)。

答案 4 :(得分:0)

我使用boost :: asio :: async_write方法实现了一个解决方案。

基本上:

  • 每个客户端有一个线程(我的线程正在进行CPU绑定工作)
  • 当每个线程累积一些数据时,它会使用async_write将其写入套接字,而不是关注以前的写入是否已完成
  • 代码小心管理套接字的生命周期和写出的数据缓冲区,因为CPU处理在所有数据写完之前完成

这适合我。这使服务器线程能够在CPU完成其工作后立即完成。

总体而言,客户端接收和解析其所有数据的时间已经缩短。同样,服务器在每个客户端上花费的时间(挂机时间上的时间)也会下降。

代码段:

void SocketStream::Write(const char* data, unsigned int dataLength)
{
    // Make a copy of the data
    // we'll delete it when we get called back via HandleWrite
    char* dataCopy = new char[dataLength];
    memcpy( dataCopy,  data, dataLength );

    boost::asio::async_write
        (
        *m_pSocket,
        boost::asio::buffer(dataCopy, dataLength),
        boost::bind
            (
            &SocketStream::HandleWrite,                     // the address of the method to callback when the write is done
            shared_from_this(),                             // a pointer to this, using shared_from_this to keep us alive
            dataCopy,                                       // first parameter to the HandleWrite method
            boost::asio::placeholders::error,               // placeholder so that async_write can pass us values
            boost::asio::placeholders::bytes_transferred
            )
        );
}

void SocketStream::HandleWrite(const char* data, const boost::system::error_code& error, size_t bytes_transferred)
{
    // Deallocate the buffer now that its been written out
    delete data;

    if ( !error )
    {
        m_BytesWritten += bytes_transferred;
    }
    else
    {
        cout << "SocketStream::HandleWrite received error: " << error.message().c_str() << endl;
    }
}