Пиринговую сеть BitTorrent протокола


Я пишу клиент BitTorrent. Часть протокол обмена длина-префикс сообщения между коллегами. Формы этих сообщений описаны в официальной спецификации , а также в неофициальной спецификация , которая поддерживается сообществом. Это мой код для записи этого сообщения в поток. Я в настоящее время с использованием C++11 поддерживается с помощью GCC 4.6.

У меня есть заголовочный файл:

#ifndef MESSAGE_HPP
#define MESSAGE_HPP

#include <ostream>
#include <string>
#include <type_traits>

namespace greed {
    // This type defines all the message types and the matching codes
    // It uses C++11's ability to define an enum's underlying type
    enum message_type : signed char
    {
    // this keep_alive as negative is a hack I'm not too happy with
        keep_alive      = -1,
        choke           = 0,
        unchoke         = 1,
        interested      = 2,
        not_interested  = 3,
        have            = 4,
        bitfield        = 5,
        request         = 6,
        piece           = 7,
        cancel          = 8
    };

    // There are three basic kinds of messages
    // These three basic templates are used in CRTP below
    //  - messages with no payload: these contain only the length
    //         and the type code
    template <message_type Type>
        struct no_payload_message
        {
            std::ostream& put(std::ostream& os) const;
        };

    //  - messages with a fixed-length payload: these contain the length,
    //         the type code, and a payload of a fixed-length, that is
    //         determined by the message type.
    template <typename Message>
        struct fixed_payload_message
        {
            std::ostream& put(std::ostream& os) const;
        };

    //  - messages with a payload of variable length: these contain the length,
    //         the type code, and the payload.
    template <typename Message>
        struct variable_payload_message
        {
            std::ostream& put(std::ostream& os) const;
        };

    // A template definition for messages, templated on the message type.
    template <message_type Type>
        struct message {};

    // A specialization for keep-alives, which are special messages that
    // consist of a single zero byte.
    template<>
        struct message<keep_alive>
        {
        public:
            std::ostream& put(std::ostream& os) const;
        };

    // Specializations for the no-payload messages types
    // The only difference between these is the message type.
    template <>
        struct message<choke> : public no_payload_message<choke> {};

    template <>
        struct message<unchoke> : public no_payload_message<unchoke> {};

    template <>
        struct message<interested> : public no_payload_message<interested> {};

    template <>
        struct message<not_interested> : public no_payload_message<not_interested> {};

    // The specializations for fixed-length payload messages contain:
    //  - appropriate constructors
    //  - a type that defines the format of the message (data_type)
    //  - a function that returns the data (data)
    // These are used by the fixed_payload_message template
    template<>
        struct message<have> : public fixed_payload_message<message<have>>
        {
        public:
            message() = delete;
            explicit message(unsigned index);

            struct data_type;
            data_type data() const;

        private:
            unsigned index;
        };

    // The specializations for variable-length payload messages contain:
    //  - appropriate constructors
    //  - a type that defines the format of the message (data_type)
    //  - a function that returns the variable payload (payload)
    //  - a function that writes out the parts of the message that are not variable (init)
    // These are used by the variable_payload_message template
    template<>
        struct message<bitfield> : public variable_payload_message<message<bitfield>>
        {
        public:
            message() = delete;
            explicit message(std::string bits);

            const std::string payload() const;

            struct data_type;
            void init(char* ptr) const;

        private:
            std::string bits;
        };

    template<>
        struct message<request> : public fixed_payload_message<message<request>>
        {
        public:
            message() = delete;
            message(unsigned index, unsigned begin, unsigned length);

            struct data_type;
            data_type data() const;

        private:
            unsigned index;
            unsigned begin;
            unsigned length;
        };

    template<>
        struct message<piece> : public variable_payload_message<message<piece>>
        {
        public:
            static const message_type id = piece;

            message() = delete;
            message(unsigned index, unsigned begin, std::string block);

            const std::string payload() const;

            struct data_type;
            void init(char* ptr) const;

        private:
            unsigned index;
            unsigned begin;
            std::string block;
        };

    template<>
        struct message<cancel> : public fixed_payload_message<message<cancel>>
        {
        public:
            message() = delete;
            message(unsigned index, unsigned begin, unsigned length);

            struct data_type;
            data_type data() const;

        private:
            unsigned index;
            unsigned begin;
            unsigned length;
        };

    // A simple type trait that determines if a type is a message type
    template <typename NonMesssage>
        struct is_message : std::false_type {};
    template <message_type Type>
        struct is_message<message<Type>> : std::true_type {};

    // Implementation of operator<< for all message types
    // This requires the put member function
    template <typename Message>
        std::ostream& operator<<(std::ostream& os, typename std::enable_if<is_message<Message>::value,const Message&>::type message);
}

#endif

И файл реализации:

#include "message.hpp"
// this header contains the hton function
#include "util.hpp"

#include <memory>
#include <new>

namespace greed {
    // simple implementation of operator<< for messages
    template <typename Message>
        std::ostream& operator<<(std::ostream& os, const Message& message) {
            return message.put(os);
        }

    // implementation of no-payload message base class
    template <message_type Type>
        std::ostream& no_payload_message<Type>::put(std::ostream& os) const {
            // make sure this struct is not padded or anything
            // this is a gcc attribute, I'll change this when there is
            // support for the C++11 attribute alignas
            struct __attribute__ ((packed)) data_type {
                unsigned len;
                message_type type;
            };
            // hton is a function that converts from host-endianness to network-endianness
            data_type buffer = { hton(sizeof(data_type)-sizeof(data_type::len)), Type }; // lay out the length and the message type
            return os.write(reinterpret_cast<char*>(&buffer), sizeof(data_type)); // write it
        }

    // implementation of fixed-length payload message base class
    template <typename Message>
        std::ostream& fixed_payload_message<Message>::put(std::ostream& os) const {
            auto buffer = static_cast<const Message*>(this)->data(); // get the data from the derived class
            return os.write(reinterpret_cast<char*>(&buffer), sizeof(buffer)); // write it
        }

    // implementation of variable-length payload message base class
    template <typename Message>
        std::ostream& variable_payload_message<Message>::put(std::ostream& os) const {
            typedef typename Message::data_type header_type;

            auto m = static_cast<const Message*>(this);
            const auto payload = m->payload(); // get the payload
            const auto data_type_size = sizeof(header_type)+payload.size(); // get the total size
            std::unique_ptr<char[]> mem(new char[data_type_size]); // allocate a buffer for it
            m->init(mem.get()); // write out the fixed-length portion
            std::copy(payload.begin(), payload.end(), mem.get()+sizeof(header_type)); // copy the payload to the buffer
            return os.write(mem.get(), data_type_size); // write it
        }

    std::ostream& message<keep_alive>::put(std::ostream& os) const {
        return os.put(0); // keep-alives are just a simple zero byte
    }

    message<have>::message(unsigned index) : index(index) {}
    struct __attribute__ ((packed)) message<have>::data_type{
        unsigned len;
        message_type type;
        unsigned index;
    };
    // have message data
    message<have>::data_type message<have>::data() const {
        return data_type{ hton(sizeof(data_type)-sizeof(data_type::len)), have, hton(index) };
    }

    message<bitfield>::message(std::string bits) : bits(bits) {}
    struct __attribute__ ((packed)) message<bitfield>::data_type {
        unsigned len;
        message_type type;
    };
    // bitfield message payload
    const std::string message<bitfield>::payload() const {
        return bits;
    }
    void message<bitfield>::init(char* ptr) const {
        // construct a new message<bitfield>::data_type in place
        new(ptr) data_type{ hton(sizeof(data_type)-sizeof(data_type::len)+bits.size()), bitfield };
    }

    message<request>::message(unsigned index, unsigned begin, unsigned length) : index(index), begin(begin), length(length) {}
    struct __attribute__ ((packed)) message<request>::data_type {
        unsigned len;
        message_type type;
        unsigned index;
        unsigned begin;
        unsigned length;
    };
    // request message data
    message<request>::data_type message<request>::data() const {
        return data_type{ hton(sizeof(data_type)-sizeof(data_type::len)), request, hton(index), hton(begin), hton(length) };
    }

    message<piece>::message(unsigned index, unsigned begin, std::string block) : index(index), begin(begin), block(block) {}
    struct __attribute__ ((packed)) message<piece>::data_type {
        unsigned len;
        message_type type;
        unsigned index;
        unsigned begin;
    };
    const std::string message<piece>::payload() const {
        return block;
    }
    void message<piece>::init(void* ptr) const {
        // construct a new message<piece>::data_type in place
        new(ptr) data_type{ hton(sizeof(data_type)-sizeof(data_type::len)+block.size()), piece, index, begin };
    }

    message<cancel>::message(unsigned index, unsigned begin, unsigned length) : index(index), begin(begin), length(length) {}
    struct __attribute__ ((packed)) message<cancel>::data_type {
        unsigned len;
        message_type type;
        unsigned index;
        unsigned begin;
        unsigned length;
    };
    // cancel message data
    message<cancel>::data_type message<cancel>::data() const {
        return data_type{ hton(sizeof(data_type)-sizeof(data_type::len)), cancel, hton(index), hton(begin), hton(length) };
    }

    // explicit template instantiations for all message types
    template struct message<keep_alive>;
    template struct message<choke>;
    template struct message<unchoke>;
    template struct message<interested>;
    template struct message<not_interested>;
    template struct message<have>;
    template struct message<bitfield>;
    template struct message<request>;
    template struct message<piece>;
    template struct message<cancel>;

    template std::ostream& operator<<(std::ostream& os, const message<keep_alive>& message);
    template std::ostream& operator<<(std::ostream& os, const message<choke>& message);
    template std::ostream& operator<<(std::ostream& os, const message<unchoke>& message);
    template std::ostream& operator<<(std::ostream& os, const message<interested>& message);
    template std::ostream& operator<<(std::ostream& os, const message<not_interested>& message);
    template std::ostream& operator<<(std::ostream& os, const message<have>& message);
    template std::ostream& operator<<(std::ostream& os, const message<bitfield>& message);
    template std::ostream& operator<<(std::ostream& os, const message<request>& message);
    template std::ostream& operator<<(std::ostream& os, const message<piece>& message);
    template std::ostream& operator<<(std::ostream& os, const message<cancel>& message);
}

Я немного не уверены о том, как я расправился с переменной длиной сообщения, особенно использование размещение новой, без вызова деструктора.

Какие будут мнения?



Комментарии
2 ответа

    enum message_type : signed char

У вас есть конкретные причины (например, требования к протоколу?) что это будут подписаны? Бит-уровень обычно включает беззнаковые типы. Протокол спецификациях также явно упомянуть размер поля тип сообщения: один байт. Поэтому я рекомендую СТД::uint8_t здесь.

    // A template definition for messages, templated on the message type.
template <message_type Type>
struct message {};

Если предназначены для использования только специализации (я не мог сказать, глядя на ваш код), я обычно 'запретить' базовый шаблон, чтобы поймать ошибки. Ошибок о том, как сообщение<...> не ставят член путаете и не обязательно возле кода, который создается шаблон. Самый простой способ сделать это, чтобы оставить шаблон неопределены, но в последнее время я использую такой трюк: static_assert( dependent_false::стоимость", только специализации должны быть использованы" );, где dependent_false::ценность - это всегда ложь , но не начинают утверждать до создания (в то время как static_assert( false, то ... ) всегда срабатывает и не дает компиляции, когда-нибудь.)

    template<>
struct message<bitfield> : public variable_payload_message<message<bitfield>>
{
public:
message() = delete;
explicit message(std::string bits);

const std::string payload() const;

Я бы предпочел возвращение СТД::строка здесь. (Так же для сообщений::груз.)

    // A simple type trait that determines if a type is a message type
template <typename NonMesssage>
struct is_message : std::false_type {};
template <message_type Type>
struct is_message<message<Type>> : std::true_type {};

В последнее время я делаю мой характер более удобным в использовании, добавив перенаправление специализаций: шаблон структура is_message: is_message {};и еще один для константной. Они помогают с точной пересылки, потому что если у вас, например, шаблон пустота perfectly_forwarded(м&&); тогда М может быть т как const& для некоторых Т. Поскольку вы не делаете, что в вашем коде я не думаю, что вам это нужно-просто голова.

    // Implementation of operator<< for all message types
// This requires the put member function
template <typename Message>
std::ostream& operator<<(std::ostream& os, typename std::enable_if<is_message<Message>::value,const Message&>::type message);

Дедукция не может работать здесь. В C++03-стиле код использует enable_if на тип возвращаемого значения, или когда нет возвращаемого типа (например, строителями) в качестве аргумента по умолчанию. Может быть, вы пытались "коллапс" аргумент по умолчанию с реальными, интересными параметрами, но вы не можете сделать это. В C++0х-стиль кода может положить enable_if в дефолт с параметрами шаблона, но это вопрос спорный, поскольку вы действительно хотите (кредит CatPlusPlus):

template<message_type M>
std::ostream&
operator<<(std::ostream& os, message<M> const& m);


    // simple implementation of operator<< for messages

Определения изменяются, чтобы соответствовать предыдущей декларации.

    // implementation of no-payload message base class
template <message_type Type>
std::ostream& no_payload_message<Type>::put(std::ostream& os) const {
// make sure this struct is not padded or anything
// this is a gcc attribute, I'll change this when there is
// support for the C++11 attribute alignas
struct __attribute__ ((packed)) data_type {
unsigned len;
message_type type;
};
// hton is a function that converts from host-endianness to network-endianness
data_type buffer = { hton(sizeof(data_type)-sizeof(data_type::len)), Type }; // lay out the length and the message type
return os.write(static_cast<char*>(&buffer), sizeof(data_type)); // write it
}

Опять же, по протоколу спецификации, я бы лен с использованием std::uint32_t. В неофициальных спецификаций использовать 1 , как длина, я не уверен, что ты вычислений здесь. Маленькое замечание: я всегда пишу ОС.писать(метод static_cast(&буфера), размер буфера) в будущем (вряд ли дело здесь, но все же).

    // implementation of fixed-length payload message base class
template <typename Message>
std::ostream& fixed_payload_message<Message>::put(std::ostream& os) const {
auto buffer = static_cast<const Message*>(this)->data(); // get the data from the derived class
return os.write(static_cast<char*>(&buffer), sizeof(buffer)); // write it
}

А ведь здесь вы принимаете размер объекта!

    // implementation of variable-length payload message base class
template <typename Message>
std::ostream& variable_payload_message<Message>::put(std::ostream& os) const {
typedef typename Message::data_type header_type;

auto m = static_cast<const Message*>(this);
const auto payload = m->payload(); // get the payload
const auto data_type_size = sizeof(header_type)+payload.size(); // get the total size
std::unique_ptr<char[]> mem(new char[data_type_size]); // allocate a buffer for it
m->init(mem.get()); // write out the fixed-length portion
std::copy(payload.begin(), payload.end(), mem.get()+sizeof(header_type)); // copy the payload to the buffer
return os.write(mem.get(), data_type_size); // write it
}

Я не вижу необходимости копировать последнее сообщение в буфер. Почему бы не использовать два вызова поток::писать? Вы могли бы заменить инициализации членов (которые вам уже не нравится) с данным участником, как с фиксированной длиной сообщения.

// Lots of definitions/instantiations

Опять же, я не проверила соответствие спецификаций. Я также уже говорил вам, как наиболее явного инстанцирования не нужны.

Я бы также написать размер field0 + размер поле1 + ... для расчета различных размеров, которые вам нужны, а не вычитания из общего размера. Я сделаю это для ясности, и я не думаю, однако, это вопрос корректности.


Заключительные замечания об общей конструкции:

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

Также я лично обычно использовать СТД::вектор для двоичных вещи, а не СТД::строка, но я не думаю, что действительно имеет значение (плюс возможно использовать для некоторых СТД::строка-конкретные вещи, которые не в код, который вы представили).

6
ответ дан 1 августа 2011 в 12:08 Источник Поделиться

Не может действительно жаловаться на это.

Единственное, что мне не нравится-это злоупотребление автомобиль:

        auto m = static_cast<const Message*>(this);
const auto payload = m->payload(); // get the payload
const auto data_type_size = sizeof(header_type)+payload.size(); // get the total size

Вы знаете, что эти типы так почему бы не быть конкретными (лично я думаю, что сделает код более удобным для чтения).

0
ответ дан 1 августа 2011 в 05:08 Источник Поделиться