什么需求会放弃直接使用 boost::asio::ip::tcp::acceptor?来监听并接受来自客户端的 socket 实例?
假定场景:
我们需要让已知道 IPEndPoint 地址端点的 socket,都工作在同一个 context 下面,因为这组 socket 会相互交叉的复制数据,如果我们有多个 context,就意味着会存在线程安全问题,如果我们频繁的跨线程 post(投递到事件队列),那么额外成本并不低。
如果我们可以让这些 socket,放在同一个线程上面就不需要考虑线程安全问题,可以直接避免锁同步互斥。
我们知道 boost:asio::ip::tcp::socket 提供一个 release 函数可以释放被附着的 socket-fd,但这有个问题,在 Windows 8.1 发行版以下的操作系统,该函数无法正确执行(平台不兼容),那么考虑到兼容时,就需要采用上述解决方案。
注意:这只是为了解决平台细节差异而选用的一种替用方案,并不代表太多实际意义,仅供出现类似场景需求的童靴参考用途。
头文件:
#pragma once
#include <ppp/net/SocketAcceptor.h>
namespace ppp
{
namespace net
{
class Win32SocketAcceptor final : public ppp::net::SocketAcceptor
{
public:
Win32SocketAcceptor() noexcept;
Win32SocketAcceptor(const std::shared_ptr<boost::asio::io_context>& context) noexcept;
virtual ~Win32SocketAcceptor() noexcept;
public:
virtual bool IsOpen() noexcept;
virtual bool Open(const char* localIP, int localPort, int backlog) noexcept;
virtual void Close() noexcept;
virtual int GetHandle() noexcept;
private:
bool Next() noexcept;
void Finalize() noexcept;
private:
int listenfd_;
void* hEvent_;
std::shared_ptr<void*> afo_;
std::shared_ptr<boost::asio::io_context> context_;
};
}
}
源文件:
#include <ppp/net/SocketAcceptor.h>
#include <ppp/net/IPEndPoint.h>
#include <ppp/net/Socket.h>
#include <ppp/threading/Executors.h>
#include <windows/ppp/win32/Win32Native.h>
#include <windows/ppp/net/Win32SocketAcceptor.h>
#include <Windows.h>
#include <Iphlpapi.h>
typedef ppp::net::IPEndPoint IPEndPoint;
static struct WINDOWS_SOCKET_INITIALIZATION
{
public:
WINDOWS_SOCKET_INITIALIZATION() noexcept
{
int err = WSAStartup(MAKEWORD(2, 2), &wsadata_);
assert(err == ERROR_SUCCESS);
}
~WINDOWS_SOCKET_INITIALIZATION() noexcept
{
WSACleanup();
}
private:
WSADATA wsadata_;
} __WINDOWS_SOCKET_INITIALIZATION__;
/*
* 用尽三生三世的真情 换来一世的美丽
* 用尽三生三世的真心 换来一世的回忆
*/
namespace ppp
{
namespace net
{
// 红尘中浮浮沉沉的爱 而我在原处停留
Win32SocketAcceptor::Win32SocketAcceptor(const std::shared_ptr<boost::asio::io_context>& context) noexcept
: listenfd_(INVALID_SOCKET)
, hEvent_(NULL)
, afo_(NULL)
, context_(context)
{
}
Win32SocketAcceptor::Win32SocketAcceptor() noexcept
: Win32SocketAcceptor(ppp::threading::Executors::GetDefault()) {
}
Win32SocketAcceptor::~Win32SocketAcceptor() noexcept
{
Finalize();
}
bool Win32SocketAcceptor::IsOpen() noexcept
{
bool b = NULL != hEvent_ && NULL != afo_ && NULL != context_;
if (b)
{
b = listenfd_ != INVALID_SOCKET;
}
return b;
}
bool Win32SocketAcceptor::Open(const char* localIP, int localPort, int backlog) noexcept
{
if (localPort < IPEndPoint::MinPort || localPort > IPEndPoint::MaxPort)
{
return false;
}
if (NULL == localIP || *localIP == '\x0')
{
return false;
}
if (listenfd_ != INVALID_SOCKET)
{
return false;
}
if (NULL != hEvent_)
{
return false;
}
if (NULL != afo_)
{
return false;
}
if (NULL == context_)
{
return false;
}
boost::system::error_code ec;
boost::asio::ip::address bindIP = boost::asio::ip::address::from_string(localIP, ec);
if (ec)
{
return false;
}
if (backlog < 1)
{
backlog = PPP_LISTEN_BACKLOG;
}
if (bindIP.is_v6())
{
struct sockaddr_in6 in6;
memset(&in6, 0, sizeof(in6));
in6.sin6_family = AF_INET6;
in6.sin6_port = htons(localPort);
if (inet_pton(AF_INET6, localIP, &in6.sin6_addr) < 1)
{
return false;
}
listenfd_ = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (listenfd_ == INVALID_SOCKET)
{
return false;
}
if (!ppp::net::Socket::ReuseSocketAddress(listenfd_, true))
{
return false;
}
BOOL bEnable = FALSE;
if (setsockopt(listenfd_, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char*>(&bEnable), sizeof(bEnable)) < 0)
{
return false;
}
if (bind(listenfd_, reinterpret_cast<sockaddr*>(&in6), sizeof(in6)) < 0)
{
return false;
}
}
elif(bindIP.is_v4())
{
struct sockaddr_in in4;
memset(&in4, 0, sizeof(in4));
in4.sin_family = AF_INET;
in4.sin_port = htons(localPort);
if (inet_pton(AF_INET, localIP, &in4.sin_addr) < 1)
{
return false;
}
listenfd_ = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (listenfd_ == INVALID_SOCKET)
{
return false;
}
if (!ppp::net::Socket::ReuseSocketAddress(listenfd_, true))
{
return false;
}
if (bind(listenfd_, reinterpret_cast<sockaddr*>(&in4), sizeof(in4)) < 0)
{
return false;
}
}
else
{
return false;
}
if (listen(listenfd_, backlog) < 0)
{
return false;
}
hEvent_ = WSACreateEvent();
if (hEvent_ == WSA_INVALID_EVENT)
{
return false;
}
if (WSAEventSelect(listenfd_, hEvent_, FD_ACCEPT | FD_CLOSE) != NOERROR)
{
return false;
}
afo_ = make_shared_void_pointer<boost::asio::windows::object_handle>(*context_, hEvent_);
return Next();
}
void Win32SocketAcceptor::Close() noexcept
{
std::shared_ptr<boost::asio::io_context> context = context_;
if (NULL != context)
{
auto self = shared_from_this();
context->post(
[self, this]() noexcept
{
Finalize();
});
}
}
bool Win32SocketAcceptor::Next() noexcept
{
boost::asio::windows::object_handle* afo = reinterpret_cast<boost::asio::windows::object_handle*>(afo_.get());
if (NULL == afo)
{
return false;
}
int listenfd = listenfd_;
if (listenfd == INVALID_SOCKET)
{
return false;
}
void* hEvent = hEvent_;
if (NULL == hEvent)
{
return false;
}
std::shared_ptr<SocketAcceptor> self = shared_from_this();
afo->async_wait(
[self, this, hEvent, listenfd](const boost::system::error_code& ec) noexcept
{
if (ec == boost::system::errc::operation_canceled) /* WSAWaitForMultipleEvents */
{
return;
}
WSANETWORKEVENTS events;
if (WSAEnumNetworkEvents(listenfd, hEvent, &events) == NOERROR)
{
if (events.lNetworkEvents & FD_ACCEPT)
{
if (events.iErrorCode[FD_ACCEPT_BIT] == 0)
{
struct sockaddr address = { 0 };
int address_size = sizeof(address);
int sockfd = accept(listenfd_, &address, &address_size);
if (sockfd != INVALID_SOCKET)
{
AcceptSocketEventArgs e = { sockfd };
OnAcceptSocket(e);
}
}
}
elif(events.lNetworkEvents & FD_CLOSE)
{
if (events.iErrorCode[FD_ACCEPT_BIT] == 0) /* event is operation_canceled. */
{
return;
}
}
}
Next();
});
return true;
}
int Win32SocketAcceptor::GetHandle() noexcept
{
return listenfd_;
}
void Win32SocketAcceptor::Finalize() noexcept
{
boost::asio::windows::object_handle* afo = reinterpret_cast<boost::asio::windows::object_handle*>(afo_.get());
if (NULL != afo)
{
boost::system::error_code ec;
try
{
afo->cancel(ec);
}
catch (std::exception&) {}
try
{
afo->close(ec);
}
catch (std::exception&) {}
afo_ = NULL;
hEvent_ = NULL;
}
void* hEvent = hEvent_;
if (NULL != hEvent)
{
ppp::win32::Win32Native::WSACloseEvent(hEvent);
}
int listenfd = listenfd_;
if (listenfd != INVALID_SOCKET)
{
closesocket(listenfd);
}
AcceptSocket.reset();
afo_ = NULL;
hEvent_ = NULL;
context_ = NULL;
listenfd_ = INVALID_SOCKET;
}
}
}