Недавно на ru.SO проскочил вопрос: как можно подключиться к SSM ( Source Specific Multicast) группе?
Нюанс в том, что для этого используется опция MCAST_JOIN_SOURCE_GROUP
для которой нет объекта-обёртки. Но, как оказалось, такой объект пишется самостоятельно на раз-два. Под катом я продублирую
свой ответ, как пример подхода реализации нужного функционала. Пример не самый идеологически правильный, но, как оказалось, рабочий. Автор сам предложил
свой вариант с захватом сырого хендла. Такой подход тоже имеет смысл в некоторых ситуациях((у меня случилось однажды так подружить сетевые абстракции Asio и libev, реализовав, тем самым, реактор на Asio :simple_smile:)).
Что-бы установить опцию при помощи socket.set_option()
(или прочитать текущее значение), нужно создать класс со следующим публичным интерфейсом:
template<typename Protocol> int level(const Protocol&) const
- определяет уровень (IPPROTO_IP
,IPPROTO_TCP
,SOL_SOCKET
и так далее)template<typename Protocol> int name(const Protocol&) const
- замысловато, но это ID опции (TCP_CORK
,MCAST_JOIN_SOURCE_GROUP
)template<typename Protocol> SOME_DATA_T* data(const Protocol&)
иtemplate<typename Protocol> const SOME_DATA_T* data(const Protocol&)
const - сами данные опцииtemplate<typename Protocol> std::size_t size(const Protocol&) const
- вернёт размер данныхtemplate<typename Protocol> void resize(const Protocol&, std::size_t s)
- не совсем понимаю, просто проверятьsizeof(data) == s
и бросать исключение, если не выполняется.
SOME_DATA_T
может быть специфичен для опции или быть просто void *
(всё равно передаётся в setsockopt()
). Для MCAST_JOIN_SOURCE_GROUP
это будет struct group_source_req
.
Собственно реализация для MCAST_JOIN_SOURCE_GROUP
будет такой:
// Просто для помощи ради
using group_source_req_t = struct group_source_req;
// Костыль для ASIO_OS_DEF, что бы можно было на разных системах примерно одинаково работать
// типа если только опция отличается, но не данные. В данном случае можно обойтись вообще без этого
#define ASIO_OS_DEF_MCAST_JOIN_SOURCE_GROUP MCAST_JOIN_SOURCE_GROUP
struct mcast_join_source_group
{
mcast_join_source_group()
: m_data()
{}
mcast_join_source_group(std::string &source_ip, std::string &group_ip, int port, std::string &interface_name)
: mcast_join_source_group()
{
int ifname_number = if_nametoindex(interface_name.c_str());
struct sockaddr_in group_addr;
group_addr.sin_addr.s_addr = inet_addr(group_ip.c_str());
group_addr.sin_port = htons(port);
group_addr.sin_family = AF_INET;
struct sockaddr_in source_addr;
source_addr.sin_addr.s_addr = inet_addr(source_ip.c_str());
source_addr.sin_port = htons(port);
source_addr.sin_family = AF_INET;
memcpy(&(m_data.gsr_source), (struct sockaddr_storage *) &source_addr, sizeof(struct sockaddr_in));
memcpy(&(m_data.gsr_group), (struct sockaddr_storage *) &group_addr, sizeof(struct sockaddr_in));
m_data.gsr_interface = ifname_number;
}
// Можно объявить вспомогательные методы для правки отдельных значений или всех полей
template<typename Protocol>
int level(const Protocol&) const
{
// Протокол, к слову, может быть и IPv6, для них обоих она работает одинаково. За исключением пары нюансов
#ifdef ASIO_OS_DEF
return ASIO_OS_DEF(IPPROTO_IP);
#else
return IPPROTO_IP;
#endif
}
template<typename Protocol>
int name(const Protocol&) const
{
#ifdef ASIO_OS_DEF
return ASIO_OS_DEF(MCAST_JOIN_SOURCE_GROUP);
#else
return MCAST_JOIN_SOURCE_GROUP;
#endif
}
template<typename Protocol>
group_source_req_t* data(const Protocol&)
{
return &m_data;
}
template<typename Protocol>
const group_source_req_t* data(const Protocol&) const
{
return &m_data;
}
template<typename Protocol>
std::size_t size(const Protocol&) const
{
return sizeof(m_data);
}
template<typename Protocol>
void resize(const Protocol&, std::size_t s)
{
if (s != sizeof(m_data))
{
std::length_error ex("mcast_join_source_group socket option resize");
asio::detail::throw_exception(ex);
}
}
private:
group_source_req_t m_data;
};
Пользоваться как-то так:
socket.set_option(mcast_join_source_group("91.203.255.225", "239.195.1.131", 17003, "vlan389"));
Данную реализацию можно улучшить, применив абстракции Asio типа asio::ip:address
и/или asio::ip::endpoint
.
Для простых опций уровня Вкл/Выкл делать придётся ещё меньше:
#ifndef ASIO_OS_DEF
# define ASIO_OS_DEF(x) x
#endif
#define ASIO_OS_DEF_SO_TIMESTAMP SO_TIMESTAMP
typedef asio::detail::socket_option::boolean<
ASIO_OS_DEF(SOL_SOCKET), ASIO_OS_DEF(SO_TIMESTAMP)> so_timestamp;
#define ASIO_OS_DEF_IP_RECVTTL IP_RECVTTL
typedef asio::detail::socket_option::boolean<
ASIO_OS_DEF(IPPROTO_IP), ASIO_OS_DEF(IP_RECVTTL)> ip_recvttl;
#define ASIO_OS_DEF_TCP_CORK TCP_CORK
typedef asio::detail::socket_option::boolean<
ASIO_OS_DEF(IPPROTO_IP), ASIO_OS_DEF(TCP_CORK)> tcp_cork;
и использовать как-то так:
socket.set_option(tcp_cork(true));