Windows 下 VC++ 使用 boost::asio + WSAEventSelect + WSACreateEvent 实现 async-listen-accept socket-fd

什么需求会放弃直接使用 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;
        }
    }
}

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值