О буфере записи в общем сетевом программировании
-
11-09-2019 - |
Вопрос
Я пишу сервер, используя 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
таким образом, вам гарантируется, что весь буфер будет записан до того, как будет вызван ваш обратный вызов.
Другие советы
Извините, но у вас есть два варианта:
Сериализуйте инструкцию write либо с помощью блокировок, либо лучше запустите отдельный поток записи, который считывает запросы из очереди, другие потоки затем могут складывать запросы в очередь без слишком большого конфликта (потребуется некоторое мьютексирование).
Дайте каждому потоку записи свой собственный сокет!На самом деле это лучшее решение, если программа на другом конце провода может его поддерживать .
Вы могли бы поставить свои изменения в очередь и выполнить их с данными в обработчике записи.
Сеть, скорее всего, была бы самой медленной частью канала (при условии, что ваши модификации не являются дорогостоящими в вычислительном отношении), так что вы могли бы выполнять модификации, пока уровень сокетов отправляет предыдущие данные.
Если вы работаете с большим количеством клиентов с частым подключением / отключением, взгляните на Порты завершения ввода-вывода или аналогичный механизм.