简体   繁体   English

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

[英]Sockets: How to send data to the client without 'waiting' on them as they receive/parse it

I have a socket server, written in C++ using boost::asio, and I'm sending data to a client. 我有一个使用boost :: asio用C ++编写的套接字服务器,并且正在向客户端发送数据。

The server sends the data out in chunks, and the client parses each chunk as it receives it. 服务器将数据分块发送出去,而客户端则在接收到每个数据块时对其进行解析。 Both are pretty much single threaded right now. 两者现在几乎都是单线程的。

What design should I use on the server to ensure that the server is just writing out the data as fast as it can and never waiting on the client to parse it? 我应该在服务器上使用哪种设计来确保服务器以最快的速度写出数据,并且永远不会等待客户端解析数据? I imagine I need to do something asynchronous on the server. 我想我需要在服务器上执行异步操作。

I imagine changes could be made on the client to accomplish this too, but ideally the server should not wait on the client regardless of how the client is written. 我想也可以在客户端上进行更改以完成此操作,但是理想情况下,无论客户端如何编写,服务器都不应在客户端上等待。

I'm writing data to the socket like this: 我正在将数据写入套接字,如下所示:

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

Update: 更新:

I am going to try using Boost's mechanism to write asynchronously to the socket. 我将尝试使用Boost的机制异步写入套接字。 See http://www.boost.org/doc/libs/1_36_0/doc/html/boost_asio/tutorial/tutdaytime3/src.html 参见http://www.boost.org/doc/libs/1_36_0/doc/html/boost_asio/tutorial/tutdaytime3/src.html

eg 例如

 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));
  • Alex 亚历克斯

You can ensure asynchronous communication by transporting the data not over TCP but over UDP. 您可以通过不通过TCP而是通过UDP传输数据来确保异步通信。 However, if you need to use TCP, let the client store the data away quickly and process it in a different thread or asynchronously with a cron job. 但是,如果需要使用TCP,请让客户端快速存储数据,并在其他线程中处理该数据,或者与cron作业异步处理。

When you pass data to a socket, it does not wait for the receiver to process it. 当您将数据传递到套接字时,它不会等待接收者对其进行处理。 It does not even wait for the data to be transmitted. 它甚至不等待数据传输。 The data is put into an outbound queue that is processed by the OS in the background. 数据被放入由操作系统在后台处理的出站队列中。 The writing function returns how many bytes were queued for transmission, not how many bytes were actually transmitted. 写入函数返回排队传输的字节数,而不是实际传输的字节数。

If you set your socket to non-blocking, then writes should fail if they would otherwise block. 如果将套接字设置为非阻塞,则写入将以其他方式阻塞,否则将失败。 You can then queue up the data however you like, and arrange for another attempt to be made later to write it. 然后,您可以根据需要将数据排队,并安排以后再尝试写入。 I don't know how to set socket options in the boost socket API, but that's what you're looking for. 我不知道如何在boost socket API中设置套接字选项,但这就​​是您想要的。

But this is probably more trouble than it's worth. 但这可能比其价值更大。 You'd need to select a socket that's ready for writing, presumably from several open simultaneously, shove more data into it until it's full, and repeat. 您需要选择一个准备好进行写入的套接字,大概是同时打开几个套接字,将更多的数据推入该套接字直到充满,然后重复执行。 I don't know if the boost sockets API has an equivalent of select , so that you can wait on multiple sockets at once until any of them is ready to write. 我不知道Boost套接字API是否具有等效的select ,因此您可以一次在多个套接字上等待,直到它们中的任何一个都可以编写为止。

The reason that servers typically start a thread (or spawn a process) per client connection is precisely so that they can get on with serving other clients while they're waiting on I/O, while avoiding implementing their own queues. 服务器通常为每个客户端连接启动线程(或生成进程)的原因正是为了使它们能够在等待I / O时继续为其他客户端提供服务,同时避免实现自己的队列。 The simplest way to "arrange for another attempt later" is just to do blocking I/O in a dedicated thread. “安排以后再尝试一次”的最简单方法就是在专用线程中阻塞I / O。

What you can't do, unless boost has done something unusual in its sockets API, is require the OS or the sockets library to queue up arbitrary amounts of data for you without blocking. 除非boost对其套接字API做出了不寻常的事情,否则您将无法做的是要求操作系统或套接字库为您排队任意数量的数据而不会阻塞。 There may be an async API which will call you back when the data is written. 可能有一个异步API,在写入数据时会回叫您。

Continuing from the comments on Stefan's post: 继续关于Stefan帖子的评论:

It is definitely possible to buffer on either the client or server side. 绝对有可能在客户端或服务器端进行缓冲。 But make sure to consider what Neil wrote. 但是,请务必考虑尼尔的所作所为。 If we just begin to buffer data blindly and if the processing can never keep up with the sending then our buffer will grow in a fashion we probably don't want. 如果我们只是开始盲目地缓存数据,并且如果处理无法跟上发送的步伐,那么我们的缓冲区将以我们可能不希望的方式增长。

Now I recently implemented a straightforward 'NetworkPipe' which was meant to function as a connection between a single client/server, server/client where the outside user doesn't know/care if the Pipe is the client or the server. 现在,我最近实现了一个简单的“ NetworkPipe”,该功能旨在充当单个客户端/服务器,服务器/客户端之间的连接,外部用户不知道/不在乎管道是客户端还是服务器。 I implemented a buffering situation similar to what you are asking about, how? 我实现了与您所要询问类似的缓冲情况,如何? Well the class was threaded, this was about the only way I could figure out to cleanly buffer the data. 好了,该类是线程化的,这是我想出的唯一方法,可以干净地缓冲数据。 Here is the basic process that I followed, and note that I set a maximum size on the Pipes: 这是我遵循的基本过程,请注意,我在管道上设置了最大尺寸:

  1. Process 1 starts pipe, defaults to server. 进程1启动管道,默认为服务器。 Now internal thread waits for client. 现在内部线程正在等待客户端。
  2. Process 2 starts pipe, already a server, defaults to Client. 进程2启动管道(已经是服务器),默认为客户端。
  3. We are now connected, first thing to do is exchange maximum buffer sizes. 现在我们已经连接,要做的第一件事就是交换最大缓冲区大小。
  4. Process 1 writes data (it notes that the other end has an empty buffer [see #3]) 进程1写入数据(它注意到另一端有一个空缓冲区[请参阅#3])
  5. Process 2's internal thread (now waiting on a select() for the socket) sees that data is sent and reads it, buffers it. 进程2的内部线程(现在正在等待套接字的select())看到数据已发送并读取,然后对其进行缓冲。 Process 2 now sends back the new buffered size to P1. 现在,进程2将新的缓冲大小发送回P1。

So thats a really simplified version but basically by threading it I can always be waiting on a blocking select call, as soon as data arrives I can read and buffer it, I send back the new buffered size. 因此,这是一个真正简化的版本,但是基本上通过线程化,我总是可以等待阻塞的选择调用,一旦数据到达,我就可以读取和缓冲它,然后将新的缓冲大小发回。 You could do something similar, and buffer the data blindly, its actually quite a bit simpler because you don't have to exchange buffer sizes, but probably a bad idea. 您可以执行类似的操作,并盲目地缓冲数据,实际上它要简单得多,因为您不必交换缓冲区大小,但这可能是一个坏主意。 So the above example allowed external users to read/write data without blocking their thread (unless the buffer on the other end is full). 因此,上面的示例允许外部用户在不阻塞其线程的情况下读取/写入数据(除非另一端的缓冲区已满)。

I implemented a solution using the boost::asio::async_write method. 我使用boost :: asio :: async_write方法实现了一个解决方案。

Basically: 基本上:

  • I have one thread per client (my threads are doing CPU bound work) 每个客户端有一个线程(我的线程正在执行CPU绑定工作)
  • As each thread accumulates some amount of data, it writes it to the socket using async_write, not caring if previous writes have completed 当每个线程累积一定数量的数据时,它将使用async_write将其写入套接字,而不关心先前的写入是否完成
  • The code is careful to manage the lifetime of the socket and the data buffers being written out because the CPU processing finishes before all the data has written out 该代码谨慎管理套接字和数据缓冲区的生命周期,因为CPU处理在所有数据都被写入之前完成

This works well for me. 这对我来说很好。 This enables the server thread to finish as soon as its done its CPU work. 这使服务器线程在完成CPU工作后立即完成。

Overall the the time for the client to receive and parse all of its data went down. 总体而言,客户端接收和解析其所有数据的时间减少了。 Similarly the time (clock on the wall time) that the server spends on each client goes down. 同样,服务器在每个客户端上花费的时间(挂钟时间)也会减少。

Code snippet: 程式码片段:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM