О буфере записи в общем сетевом программировании

StackOverflow https://stackoverflow.com/questions/1237259

Вопрос

Я пишу сервер, используя boost.asio.У меня есть буфер чтения и записи для каждого соединения и я использую асинхронную функцию чтения / записи (async_write_some / async_read_some).

С буфером чтения и async_read_some, здесь нет никаких проблем.Просто вызывая async_read_some функция в порядке, потому что буфер чтения доступен только для чтения в обработчике чтения (обычно означает в том же потоке).

Но доступ к буферу записи необходимо осуществлять из нескольких потоков, поэтому он должен быть заблокирован для изменения.

ПЕРВЫЙ ВОПРОС!

Есть ли какой-нибудь способ избежать БЛОКИРОВКИ буфера записи?

Я записываю свой собственный пакет в буфер стека и копирую его в буфер записи.Затем позвоните async_write_some функция для отправки пакета.Таким образом, если я отправлю два пакета последовательно, можно ли вызвать async_write_some функционировать два раза?

ВТОРОЙ ВОПРОС!

Каков распространенный способ асинхронной записи в программировании сокетов?

Спасибо за чтение.

Это было полезно?

Решение

Ответ № 1:

Вы правы в том, что блокировка является жизнеспособным подходом, но есть гораздо более простой способ сделать все это.У Boost есть симпатичная маленькая конструкция в ASIO, называемая strand.Любой обратный вызов, который был обернут с использованием цепочки, будет гарантированно сериализован, независимо от того, какой поток выполняет обратный вызов.По сути, он обрабатывает любую блокировку за вас.

Это означает, что у вас может быть столько авторов, сколько вы хотите, и если все они будут обернуты то же самое strand (итак, разделите вашу единственную строку между всеми вашими авторами), они будут выполняться последовательно.Одна вещь, на которую следует обратить внимание, - это убедиться, что вы не пытаетесь использовать один и тот же фактический буфер в памяти для выполнения всех операций записи.Например, вот чего следует избегать:

char buffer_to_write[256];  // shared among threads

/* ... in thread 1 ... */
memcpy(buffer_to_write, packet_1, std::min(sizeof(packet_1), sizeof(buffer_to_write)));
my_socket.async_write_some(boost::asio::buffer(buffer_to_write, sizeof(buffer_to_write)), &my_callback);

/* ... in thread 2 ... */
memcpy(buffer_to_write, packet_2, std::min(sizeof(packet_2), sizeof(buffer_to_write)));
my_socket.async_write_some(boost::asio::buffer(buffer_to_write, sizeof(buffer_to_write)), &my_callback);

Там вы делитесь своим фактическим буфером записи (buffer_to_write).Если бы вы сделали что-то подобное вместо этого, с вами все было бы в порядке:

/* A utility class that you can use */
class PacketWriter
{
private:
  typedef std::vector<char>  buffer_type;

  static void WriteIsComplete(boost::shared_ptr<buffer_type> op_buffer, const boost::system::error_code& error, std::size_t bytes_transferred)
  {
    // Handle your write completion here
  }

public:
  template<class IO>
  static bool WritePacket(const std::vector<char>& packet_data, IO& asio_object)
  {
    boost::shared_ptr<buffer_type> op_buffer(new buffer_type(packet_data));

    if (!op_buffer)
    {
      return (false);
    }

    asio_object.async_write_some(boost::asio::buffer(*op_buffer), boost::bind(&PacketWriter::WriteIsComplete, op_buffer, boost::asio::placeholder::error, boost::asio::placeholder::bytes_transferred));
  }
};

/* ... in thread 1 ... */
PacketWriter::WritePacket(packet_1, my_socket);

/* ... in thread 2 ... */
PacketWriter::WritePacket(packet_2, my_socket);

Здесь было бы полезно, если бы вы также передали свою строку в WritePacket.Однако вы уловили идею.

Ответ № 2:

Я думаю, что вы уже применяете очень хороший подход.Одно из предложений, которое я бы предложил, заключается в использовании async_write вместо того , чтобы async_write_some таким образом, вам гарантируется, что весь буфер будет записан до того, как будет вызван ваш обратный вызов.

Другие советы

Извините, но у вас есть два варианта:

  1. Сериализуйте инструкцию write либо с помощью блокировок, либо лучше запустите отдельный поток записи, который считывает запросы из очереди, другие потоки затем могут складывать запросы в очередь без слишком большого конфликта (потребуется некоторое мьютексирование).

  2. Дайте каждому потоку записи свой собственный сокет!На самом деле это лучшее решение, если программа на другом конце провода может его поддерживать .

Вы могли бы поставить свои изменения в очередь и выполнить их с данными в обработчике записи.

Сеть, скорее всего, была бы самой медленной частью канала (при условии, что ваши модификации не являются дорогостоящими в вычислительном отношении), так что вы могли бы выполнять модификации, пока уровень сокетов отправляет предыдущие данные.

Если вы работаете с большим количеством клиентов с частым подключением / отключением, взгляните на Порты завершения ввода-вывода или аналогичный механизм.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top