C++20协程实例:协程化的IOCP服务端/客户端

C++20协程实例:协程化的IOCP服务端/客户端

VC支持协程已经有一段时间了,之前一直想不明白协程的意义在哪里,前几天拉屎的时候突然灵光一闪:

以下是伪代码:

task server() {    for (;;) {        sock_context s = co_await io.accept();        for (;;) {            auto buf = co_await io.recv(s);            if (!buf.length())                break;            std::cout << buf.data() << std::endl;            int n = co_await io.send(s, "收到!", strlen("收到!") + 1);        }        co_await io.close(s);    }}

如果把IO库对外的接口做成上面这样,那岂不是看起来和最简单的阻塞模型相同的代码结构,但它的内在其实是异步的,用单线程相同的代码就能支撑一堆连接通信。

所以才有了接下来的研究(闲出屁才研究的),好在研究出成品了。

最终我也明白协程的意义了:

  协程化的库越多,C++程序员的门槛会越低,做上层开发的程序员可以不用知道协程的细节,只要知道如何正确使用库即可。

好了,真正介绍协程细节的文章有一大堆,不用我来写,我直接放代码,有兴趣的可以参考我的实现以及那些细节文章自己做:

2021/12/23:我最近使用了一个边缘应用试毒了这个库,一系列修修补补过后,还是很好用的。

2021/12/23:备注:最好不要用lambda函数作为协程函数,它可能会异常,也可能不会,这属于编译器bug带来的玄学。

#pragma once#include <WinSock2.h>#include <MSWSock.h>#include <ws2tcpip.h>#pragma comment(lib, "ws2_32.lib")#include <coroutine>#include <string>#include <functional>#include <thread>#include "logger.hpp"#include <random>/*** 最近花了点时间学习了一下C++20协程,初步改造实现了IOCP协程化的网络IO库* 此前基于回调分发的机制,由于上层协议解析所需的各种上下文,导致这个库是模板化的,* 现在有了协程,上层协议上下文已经可以在协程函数中实现,消除了模板化,也变得易于维护了一丢丢。* 但目前协程还有多少坑是未知的,是好是坏还得再看。* 使用协程,就意味着,这个库几乎完全失去了多线程的能力,* 要维护好一个内部是多线程,外皮是协程的IO库,我承认我没那个脑子。* 我个人当前的状态是不考虑过度设计,只追求上层代码优雅简洁,10几万并发对我而言已经满足了。* 如果这还不够用,那就意味着该放弃协程了,协程不是完全没有损耗的,根据我的测试,协程相比回调函数分发的方式,有15%左右的性能损耗。*/#pragma warning(push)#pragma warning(disable:4996)namespace aqx{    static int init_winsock() {        WSADATA wd;        return WSAStartup(MAKEWORD(2, 2), &wd);    }    static aqx::log nlog;#ifndef _nf#define _nf ((size_t)-1)#endif#ifndef __AQX_TIME_HPP#define __AQX_NOW_FUNC    using clock64_t = long long;    template<typename period = std::milli>    clock64_t now() {        const clock64_t _Freq = _Query_perf_frequency();        const clock64_t _Ctr = _Query_perf_counter();        const clock64_t _Whole = (_Ctr / _Freq) * period::den;        const clock64_t _Part = (_Ctr % _Freq) * period::den / _Freq;        return _Whole + _Part;    }#endif    /**     * 操作码与状态码定义    */    struct net_status {        static constexpr unsigned int s_accept = 0x01;        static constexpr unsigned int s_connect = 0x02;        static constexpr unsigned int s_read = 0x04;        static constexpr unsigned int s_write = 0x08;        static constexpr unsigned int s_close = 0x10;        static constexpr unsigned int s_exec = 0x20;        static constexpr unsigned int t_activated = 0x40;        static constexpr unsigned int t_acceptor = 0x0100;        static constexpr unsigned int t_connector = 0x0200;        static constexpr unsigned int t_await_undo = 0x0400;        static constexpr unsigned int t_await_accept = 0x010000;        static constexpr unsigned int t_await_connect = 0x020000;        static constexpr unsigned int t_await_read = 0x040000;        static constexpr unsigned int t_await_write = 0x080000;        static constexpr unsigned int t_await_close = 0x100000;        static constexpr unsigned int t_await = 0xFF0000;    };    /** net_base 主要负责衔接操作系统    * 不考虑过度设计,写得比较辣鸡,能用就行。    */    class net_base {    public:        net_base() {            fd = INVALID_SOCKET;            hIocp = NULL;            AcceptEx = NULL;            ConnectEx = NULL;            DisconnectEx = NULL;            StreamCapacity = 1440;            Timeout = 0;            DataBacklog = 0;            WorkerThreadId = 0;        }        static bool sockaddr_from_string(sockaddr_in& _Addr, const std::string& _Dest) {            _Addr.sin_addr.S_un.S_addr = INADDR_NONE;            size_t pos = _Dest.find(":");            if(pos == _nf) {                nlog("%s->错误的目标地址:(%s)\n", __FUNCTION__, _Dest.data());                return false;            }            auto strip = _Dest.substr(0, pos);            auto strport = _Dest.substr(pos + 1);            strport.erase(strport.find_last_not_of("\r\n\t ") + 1);            strport.erase(0, strport.find_first_not_of("\r\n\t "));            unsigned short port = (unsigned short)atoi(strport.c_str());            if (!port) {                nlog("%s->目标端口号错误:(%s)\n", __FUNCTION__, _Dest.data());                return false;            }                        strip.erase(strip.find_last_not_of("\r\n\t ") + 1);            strip.erase(0, strip.find_first_not_of("\r\n\t "));            auto it = std::find_if(strip.begin(), strip.end(), [](char c)->bool {                return ((c < '0' || c > '9') && (c != '.'));                });            _Addr.sin_family = AF_INET;            _Addr.sin_port = htons(port);            if (it != strip.end()) {                hostent* host = gethostbyname(strip.c_str());                if (!host) {                    nlog("%s->错误的目标域名:(%s)\n", __FUNCTION__, _Dest.data());                    return false;                }                _Addr.sin_addr = *(in_addr*)(host->h_addr_list[0]);            }            else {                _Addr.sin_addr.S_un.S_addr = inet_addr(strip.c_str());            }            if (_Addr.sin_addr.S_un.S_addr == INADDR_NONE) {                nlog("%s->错误的目标地址:(%s)\n", __FUNCTION__, _Dest.data());                return false;            }            return true;        }        static void sockaddr_any(sockaddr_in& _Addr, unsigned short _Port) {            _Addr.sin_family = AF_INET;            _Addr.sin_port = htons(_Port);            _Addr.sin_addr.S_un.S_addr = INADDR_ANY;        }        static void sockaddr_local(sockaddr_in& _Addr, unsigned short _Port) {            _Addr.sin_family = AF_INET;            _Addr.sin_port = htons(_Port);            _Addr.sin_addr.S_un.S_addr = INADDR_LOOPBACK;        }        static void* getmswsfunc(SOCKET s, GUID guid) {            DWORD dwBytes;            void* lpResult = nullptr;            WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid,                sizeof(guid), &lpResult, sizeof(lpResult), &dwBytes, NULL, NULL);            return lpResult;        }        static std::string sockaddr_to_string(const sockaddr_in &_Addr) {            char buf[256];            sprintf(buf, "%d.%d.%d.%d:%d", _Addr.sin_addr.S_un.S_un_b.s_b1,                _Addr.sin_addr.S_un.S_un_b.s_b2,                _Addr.sin_addr.S_un.S_un_b.s_b3,                _Addr.sin_addr.S_un.S_un_b.s_b4,                htons(_Addr.sin_port));            std::string _Result = buf;            return _Result;        }    private:        int init(int _StreamCapacity, int _DataBacklog, int _Timeout) {            if (fd != INVALID_SOCKET) {                return 0;            }            auto reterr = [this](int n) {                if (fd != INVALID_SOCKET) {                    closesocket(fd);                    fd = INVALID_SOCKET;                }                return n;            };            StreamCapacity = _StreamCapacity;            Timeout = _Timeout;            if (Timeout < 0) {                nlog("%s->Timeout必须>=0", __FUNCTION__);                return reterr(-1);            }            DataBacklog = _DataBacklog;            fd = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);            if (fd == INVALID_SOCKET) {                nlog("%s->创建套接字失败:%d", __FUNCTION__, WSAGetLastError());                return reterr(-1);            }            ConnectEx = (LPFN_CONNECTEX)getmswsfunc(fd, WSAID_CONNECTEX);            if (!ConnectEx) {                nlog("%s->获取 ConnectEx 地址失败,错误号:%d", __FUNCTION__, WSAGetLastError());                return reterr(-2);            }            AcceptEx = (LPFN_ACCEPTEX)getmswsfunc(fd, WSAID_ACCEPTEX);            if (!AcceptEx) {                nlog("%s->获取 AcceptEx 函数失败,错误号:%d", __FUNCTION__, WSAGetLastError());                return reterr(-3);            }                        // 我已经不止一次做过DisconnectEx的测试,最终结论都是DisconnectEx并不能提高并发连接数。            // DisconnectEx 在想象中会更快是因为用IOCP队列锁去换系统全局锁带来了性能提升。            // 还有一种方法是开一个线程搞个表去阻塞调用DisconnectEx,完事之后直接AcceptEx,也就最终把全局内核锁完全转嫁成你自己的锁了。            // DisconnectEx首先是不同的操作系统行为不一致,真正保险的做法只能在对方关闭连接时,调用DisconnectEx来复用。            // 对于IOCP来说,也就是在WSARecv或者WSASend 从 GetQueuedCompletionStatus 返回之后,第2个参数transferred == 0时            // 同时它受到TCP TIME_WAIT状态的影响            // 系统存在大量TIME_WAIT套接字时,最终得到的效果是,用了更多内存,去换来了更少的并发连接数。            /*DisconnectEx = (LPFN_DISCONNECTEX)getmswsfunc(fd, WSAID_DISCONNECTEX);            if (!DisconnectEx) {                nlog("%s->获取 DisconnectEx 函数失败,错误号:%d", __FUNCTION__, WSAGetLastError());                return reterr(-4);            }*/            hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);            if (!hIocp) {                nlog("%s->创建完成端口失败,错误号:%d", __FUNCTION__, GetLastError());                return reterr(-5);            }            CreateIoCompletionPort((HANDLE)fd, hIocp, 0, 0);            return 0;        }        void close() {            if (fd != INVALID_SOCKET) {                closesocket(fd);                fd = INVALID_SOCKET;            }            if (hIocp) {                CloseHandle(hIocp);                hIocp = NULL;            }        }        BOOL Accept(SOCKET s, char* _Data, LPOVERLAPPED _Overlapped) {            DWORD _Received = 0;            return AcceptEx(fd, s, _Data, 0, sizeof(SOCKADDR_IN) << 1, sizeof(SOCKADDR_IN) << 1, &_Received, _Overlapped);        }        BOOL Connect(SOCKET s, sockaddr* _Addr, int _AddrLen, LPOVERLAPPED _Overlapped) {            DWORD _Sent = 0;            return ConnectEx(s, _Addr, _AddrLen, nullptr, 0, &_Sent, _Overlapped);        }        /*BOOL Disconnect(SOCKET s, LPOVERLAPPED _Overlapped) {            return DisconnectEx(s, _Overlapped, TF_REUSE_SOCKET, 0);        }*/        /* 使用了C++11的条件变量与互斥锁实现了同步消息来保证多线程安全IO处理,本质上只是多线程Output        * 因为完成端口未实现同步消息机制,所以这种操作无论如何都至少要涉及到两个锁(一个IOCP锁,一个其他锁):        * 1、采用动态new delete,这种方式最坏的情况要经过那把系统全局的大锁,不可取。        * 2、采用一个我们自己的锁对象,当前使用的方式。        * 3、每个套接字上下文拥有一个独立的锁对象,总觉得在这种了不起就才10几万并发IO的场景,锁竞争带来的性能损耗不该发展到这一步。        */        int SafeIOMessage(DWORD dwNumberOfBytesTransferred, ULONG_PTR dwCompletionKey) {            std::unique_lock<std::mutex> lock(safeIO.mtx);            safeIO.cv.wait(lock, [this]() {                return (safeIO.s & 1);            });            if (safeIO.s == -1)                return -1;            safeIO.s = 0;            PostQueuedCompletionStatus(hIocp, dwNumberOfBytesTransferred, dwCompletionKey, 0);            safeIO.cv.wait(lock, [this]() {                return (safeIO.s & 3);            });            if (safeIO.s == -1)                return -1;            int _Result = safeIO.result;            safeIO.s = 1;            safeIO.cv.notify_all();            return _Result;        }        void InitSafeIO() {            std::lock_guard<std::mutex> lg(safeIO.mtx);            safeIO.s = 1;        }        void ExitSafeIO() {            std::lock_guard<std::mutex> lg(safeIO.mtx);            safeIO.s = -1;            safeIO.cv.notify_all();        }        void SafeIOResult(int _Result) {            // 理论上来说,IOCP工作者线程不需要在此处加锁,实际情况未知,我个人是以悲观的态度对待这个问题            std::lock_guard<std::mutex> lg(safeIO.mtx);            safeIO.result = _Result;            safeIO.s = 2;            safeIO.cv.notify_all();        }    private:        friend class sock;        friend class netio;        friend class coio;        SOCKET fd;        HANDLE hIocp;        LPFN_ACCEPTEX AcceptEx;        LPFN_CONNECTEX ConnectEx;        LPFN_DISCONNECTEX DisconnectEx;        int StreamCapacity;        int Timeout;        int DataBacklog;        DWORD WorkerThreadId;        struct safeio_send_struct {            sock* s;            void* buf;            int len;        };        struct SAFEIO {            std::mutex mtx;            std::condition_variable cv;            int s = -1;            int result = 0;        }safeIO;            };    /*直接继承一个std::string来作为套接字的各种缓冲区*/    class sock_buffer : public std::string {    public:        using _Basetype = std::string;        using _Basetype::_Basetype;        void preset_length(size_t _Length) {            // 直接在二进制层面去搞VC的std::string结构,修改std::string::length()的返回值            // 这么做的好处是,免去了std::string::resize()的拷贝问题。            // 注意这段代码仅适用于VC,G++的std::string结构和VC不一样。            struct __stlstr {                const char str[0x10];                size_t len;            };            if (this->capacity() < _Length)                this->reserve(_Length);            ((__stlstr*)this)->len = _Length;        }    };    /**    * 协程task    */    template<typename _Ty>    struct net_task_t {        struct promise_type;        using _Hty = std::coroutine_handle<promise_type>;        struct promise_type {            net_task_t get_return_object() { return { _Hty::from_promise(*this) }; }            // initial_suspend 里返回return std::suspend_always{};表示协程初始化成功之后就挂起            // 这里就挂起,是为了给set_sock留出操作的时间,否则一个空函数协程,会在创建完之后直接就销毁。            auto initial_suspend() { return std::suspend_always{}; }            auto final_suspend() noexcept {                 s->on_destroy_coroutine();                 return std::suspend_never{};             }            void unhandled_exception() { std::terminate(); }            void return_void() { }            _Ty* s = nullptr;        };        _Hty _Handle;        void resume() { _Handle.resume(); }        void destroy() { _Handle.destroy(); }        void set_sock(_Ty* _s) { _Handle.promise().s = _s; }    };    /**套接字上下文*/    class sock {        // 这是扩展OVERLAPPED结构        struct binding {            OVERLAPPED ol;            int opt;            sock* s;        };        /**        * 返回给协程recv的对象类型        */        class sock_data {            sock_data(sock* _s) : s(_s) {}        public:            char* data() { return s->ibuf.data(); }            void erase(size_t _Count) { s->ibuf.erase(0, _Count); }            size_t length() { return s->ibuf.length(); }            void clear() { s->ibuf.clear(); }        private:            friend class sock;            sock* s;        };        /**返回给协程connect和accept的对象类型        * 用于异步send与close,        * 其他线程也可以利用这个对象通信,已经处理了线程安全问题,但不太效率,因为使用了全局锁。        */        class asyncsock {        public:            /**            * send 是未加锁的发送数据            * 没有多线程需求时,send是安全的            */            int send(void* data, int len) {                if (s->v->WorkerThreadId != GetCurrentThreadId()) {                    return s->safe_send(data, len);                }                else {                    return s->send(data, len);                }            }            int send(const void* data, int len) {                if (s->v->WorkerThreadId != GetCurrentThreadId()) {                    return s->safe_send(data, len);                }                else {                    return s->send(data, len);                }            }            void close() {                if (s->v->WorkerThreadId != GetCurrentThreadId()) {                    s->safe_close();                }                else {                    s->close();                }            }            bool isactivated() { return s->isactivated(); }            operator bool() {                return (s != nullptr);            }            sockaddr_in& getsockaddr() {                return s->getsockaddr();            }            // 响应超时,这是用来给客户端发送心跳包的            // 心跳机制是基于操作系统函数 RegisterWaitForSingleObject实现的            // 会基于netio::init传入的Timeout参数的2/3的频率发送消息            // 也就是说,Timeout并不是一个绝对准确的数值,这就是为了要给客户端留出发心跳包的切入点的代价。            // 例如Timeout设置为6000, 真正超时的客户端,将会再4000-8000ms后被检查出来            void ontimeout(void(*proc)(asyncsock)) {                if (!s)                    return;                s->ontimeout = proc;            }                    private:            bool operator<(const asyncsock& as) const{                return (size_t)s < (size_t)as.s;            }            friend typename std::less<asyncsock>;        private:            friend class netio;            friend class coio;            friend class sock;            sock* s = nullptr;        };                struct recv_awaitable {            recv_awaitable(sock* s) : data(s) { }            // 当编译器自动将await_ready以及await_suspend优化为inline时,协程态引发异常            // 使await_ready强制noline时,没有异常。            __declspec(noinline) bool await_ready() {                // 我当前的vs版本是: vs 2022 17.0.1                // 这里发现一个编译bug,只要await_ready与await_suspend同时被inline优化                // 最后从流程态切换回协程态时,会获取 __coro_frame_ptr.__resume_address 做为recv_awaitable对象来使用                // 紧接着就会引发异常                // 最终我发现,目前vc的协程与lambda函数之间存在bug,                // 使用lambda作为协程函数时,如果此lambda函数inline,就可能会有各种指针错误。                // 我已向vs社区报告过此问题,得到的答复时考虑中,也不知道何时修复。                if (data.s->st & net_status::t_await_undo) {                    data.s->ibuf.clear();                    data.s->st &= (~net_status::t_await_undo);                    return true;                }                return false;            }            void await_suspend(std::coroutine_handle<> handle) { }            sock_data await_resume() const {                 return data;             }            sock_data data;        };        struct sock_awaitable {            sock_awaitable(sock* _s) { s.s = _s; }            __declspec(noinline) bool await_ready() {                if (s.s->st & net_status::t_await_undo) {                    s.s->st &= (~net_status::t_await_undo);                    return true;                }                return false;            }            void await_suspend(std::coroutine_handle<> handle) { }            sock::asyncsock await_resume() { return s; }            sock::asyncsock s;        };        struct close_awaitable {            close_awaitable(bool _IsSuspend) : IsSuspend(_IsSuspend) { }            __declspec(noinline) bool await_ready() { return (IsSuspend == false); }            void await_suspend(std::coroutine_handle<> handle) { }            void await_resume() { }            bool IsSuspend;        };        struct send_awaitable {            send_awaitable(sock* _s) : s(_s) {}            __declspec(noinline) bool await_ready() {                if (s->st & net_status::t_await_undo) {                    s->st &= (~net_status::t_await_undo);                    return true;                }                return false;            }            void await_suspend(std::coroutine_handle<> handle) { }            int await_resume() { return s->syncsendlen; }            sock* s;        };    public:        using opcode = net_status;        sock(net_base* _v) {            fd = INVALID_SOCKET;            v = _v;            st = 0;            ontimeout = nullptr;            memset(&input.ol, 0, sizeof(input.ol));            memset(&output.ol, 0, sizeof(output.ol));                        if (v->Timeout)                output.ol.hEvent = input.ol.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);            else                output.ol.hEvent = input.ol.hEvent = NULL;            output.s = input.s = this;            output.opt = opcode::s_write;            ibuf.reserve(v->StreamCapacity);            obuf.reserve(v->StreamCapacity);        }        ~sock() {            close();            if (!output.ol.hEvent)                return;            CloseHandle(output.ol.hEvent);            output.ol.hEvent = output.ol.hEvent = NULL;            if (st & opcode::t_await)                 co.destroy();        }        void on_destroy_coroutine() {            close();            st &= (~opcode::t_connector);        }        bool isactivated() {            return ((st & opcode::t_activated) != 0);        }        int send(void* data, int len) {            if (!len)                return len;            int n = (int)(obuf.capacity() - obuf.length());            if (n >= len && !obacklog.length()) {                obuf.append((char*)data, len);            }            else {                if (v->DataBacklog != 0 && obacklog.length() + len > v->DataBacklog) {                    //积压值超过限制                    close();                    return -1;                }                obacklog.append((char*)data, len);            }            return (write() == 0) ? len : -1;        }        int send(const void* data, int len) {            return send((void*)data, len);        }        int safe_send(void* data, int len) {            net_base::safeio_send_struct param = { this, data, len };            return v->SafeIOMessage(opcode::s_write, (ULONG_PTR)&param);        }        int safe_send(const void* data, int len) {            net_base::safeio_send_struct param = { this, (void*)data, len };            return v->SafeIOMessage(opcode::s_write, (ULONG_PTR)&param);        }        int safe_close() {            return v->SafeIOMessage(opcode::s_close, (ULONG_PTR)this);        }        void close() {            if (INVALID_SOCKET == fd)                return;            ontimeout = nullptr;            closesocket(fd);            fd = INVALID_SOCKET;            st &= ~opcode::t_activated;            st |= opcode::s_close;            set_timer(false);            ibuf.clear();            if (obacklog.capacity() <= 0x0F)                return;            sock_buffer tmp;            obacklog.swap(tmp);        }        sockaddr_in& getsockaddr() { return sa; }    private:        int initfd() {            if (INVALID_SOCKET != fd) {                return 0;            }                            fd = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);            if (INVALID_SOCKET == fd) {                nlog("%s->创建套接字失败,错误号:%d", __FUNCTION__, WSAGetLastError());                return -1;            }            LINGER linger = { 1, 0 };            setsockopt(fd, SOL_SOCKET, SO_LINGER, (char*)&linger, sizeof(linger));            int b = 1;            setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&b, sizeof(b));            CreateIoCompletionPort((HANDLE)fd, v->hIocp, 0, 0);            return 0;        }        int bindlocal() {            sockaddr_in local;            local.sin_family = AF_INET;            local.sin_addr.S_un.S_addr = INADDR_ANY;            local.sin_port = 0;            if (SOCKET_ERROR == bind(fd, (LPSOCKADDR)&local, sizeof(local))) {                nlog("%s->绑定本地端口失败,错误号:%d", __FUNCTION__, WSAGetLastError());                return -1;            }            return 0;        }        bool set_dest(const std::string& _Dest) {            return net_base::sockaddr_from_string(sa, _Dest);        }        void set_timer(bool _Enable) {            if (_Enable) {                if (hTimer)                    return;                RegisterWaitForSingleObject(&hTimer, output.ol.hEvent, [](void* Param, BOOLEAN TimerOrWaitFired) {                    if (!TimerOrWaitFired)                        return;                    sock* p = (sock*)Param;                    PostQueuedCompletionStatus(p->v->hIocp, 0, (ULONG_PTR)p, nullptr);                }, this, (ULONG)v->Timeout * 2 / 3, WT_EXECUTEDEFAULT);            }            else {                if (!hTimer)                    return;                std::ignore = UnregisterWaitEx(hTimer, NULL);                hTimer = NULL;            }        }        int nat() {            sockaddr_in _Addr;            int _AddrLen = sizeof(_Addr);            if (-1 == getsockname(fd, (sockaddr*)&_Addr, &_AddrLen))                return -1;            SOCKET fdNat = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);            LINGER linger = { 1, 0 };            setsockopt(fdNat, SOL_SOCKET, SO_LINGER, (char*)&linger, sizeof(linger));            CreateIoCompletionPort((HANDLE)fdNat, v->hIocp, 0, 0);            if (-1 == bind(fdNat, (sockaddr*)&_Addr, sizeof(_Addr))) {                closesocket(fdNat);                return -1;            }            close();            fd = fdNat;            return connect();        }        int accept() {            if (((st & 0xFF) | opcode::s_close) != opcode::s_close) {                nlog("%s->当前套接字未断开连接!", __FUNCTION__);                return -1;            }            if (initfd())                return -1;            DWORD _Received = 0;            input.opt = opcode::s_accept;            st &= (~opcode::s_close);            st |= opcode::s_accept;            if (!v->Accept(fd, ibuf.data(), &input.ol)) {                int _Error = WSAGetLastError();                if (_Error != ERROR_IO_PENDING) {                    st &= (~opcode::s_accept);                    nlog("%s->AcceptEx失败, 错误号:", __FUNCTION__, WSAGetLastError());                    return -1;                }            }            return 0;        }        int connect() {            if (((st & 0xFF) | opcode::s_close) != opcode::s_close) {                nlog("%s->当前套接字未断开连接!", __FUNCTION__);                return -1;            }            if (INVALID_SOCKET == fd) {                if (initfd())                    return -1;                if (bindlocal())                    return -1;            }            input.opt = opcode::s_connect;            st &= (~opcode::s_close);            st |= opcode::s_connect;            if (!v->Connect(fd, (sockaddr*)&sa, sizeof(sa), &input.ol)) {                int _Error = WSAGetLastError();                if (_Error != ERROR_IO_PENDING) {                    nlog("%s->ConnectEx失败, 错误号:", __FUNCTION__, WSAGetLastError());                    return -1;                }            }            return 0;        }        int write() {            if (!(st & opcode::t_activated)) {                return -1;            }            if (st & (opcode::s_write | opcode::s_close | opcode::s_accept | opcode::s_connect))                return 0;            if (obacklog.size()) {                size_t rl = obuf.capacity() - obuf.length();                if (rl > obacklog.length())                    rl = obacklog.length();                if (rl) {                    obuf.append(obacklog.data(), rl);                    obacklog.erase(0, rl);                }            }            WSABUF buf = { (ULONG)(obuf.length()), obuf.data() };            if (!buf.len)                return 0;            st |= opcode::s_write;            DWORD _Sent = 0;            if (SOCKET_ERROR == WSASend(fd, &buf, 1, &_Sent, 0, &(output.ol), NULL)) {                int _Error = WSAGetLastError();                if (WSA_IO_PENDING != _Error) {                    st &= (~opcode::s_write);                    return -1;                }            }            return 0;        }        int read() {            if (!(st & opcode::t_activated)) {                return -1;            }            if (st & (opcode::s_read | opcode::s_close | opcode::s_accept | opcode::s_connect))                return 0;            WSABUF buf = {                (ULONG)(ibuf.capacity() - ibuf.length()),                ibuf.data() + ibuf.length()            };            if ((int)buf.len <= 0) {                return -1;            }            DWORD _Received = 0;            DWORD _Flags = 0;            st |= opcode::s_read;            input.opt = opcode::s_read;            if (SOCKET_ERROR == WSARecv(fd, &buf, 1, &_Received, &_Flags, &(input.ol), NULL)) {                int _Error = WSAGetLastError();                if (WSA_IO_PENDING != _Error) {                    st &= ~(opcode::s_read);                    return -1;                }            }            return 0;        }    private:        friend class coio;        friend class netio;        SOCKET fd;        sockaddr_in sa;        net_base* v;        int st;        binding input, output;        sock_buffer ibuf, obuf, obacklog;        HANDLE hTimer;        aqx::clock64_t rtime;        net_task_t<sock> co;        void (*ontimeout)(asyncsock);        int syncsendlen;    };    // coio是传参给协程函数的操作对象    class coio {        coio(sock* _s) : s(_s) {}    public:        using asyncsock = sock::asyncsock;        using sock_awaitable = sock::sock_awaitable;        using close_awaitable = sock::close_awaitable;        using send_awaitable = sock::send_awaitable;        using recv_awaitable = sock::recv_awaitable;        struct nat_awaitable {            nat_awaitable(bool _ret) : ret(_ret) {  }            __declspec(noinline) bool await_ready() { return (ret == false); }            void await_suspend(std::coroutine_handle<> handle) { }            bool await_resume() { return ret; }            bool ret;        };        coio() : s(nullptr) {}        sock_awaitable connect(const std::string& _Dest) {            if (!s->set_dest(_Dest)) {                // 设置目标地址失败时,撤销等待。                s->st |= net_status::t_await_undo;                return sock_awaitable(s);            }            // 我使用的协程initial_suspend中是不挂起的,             // 所以一个套接字的首次connect操作基本都是由其他线程引发的            // 而且很可能在await_suspend之前,IOCP队列就已经完成            if (GetCurrentThreadId() == s->v->WorkerThreadId) {                if (s->connect()) {                    // 连接失败时,撤销等待。                    s->st |= net_status::t_await_undo;                    return sock_awaitable(s);                }            }            else {                // 因此,不是IOCP队列线程引发的connect就发送到IOCP队列去处理                PostQueuedCompletionStatus(s->v->hIocp, net_status::s_connect, (ULONG_PTR)s, 0);            }            s->st |= net_status::t_await_connect;            return sock_awaitable(s);        }        sock_awaitable accept() {            // 首次accept虽然也是其他线程调用的(一般是main线程)            // 但首次accept时,IOCP工作线程尚未启动,因此可以无视掉connect的那个问题。            s->st |= ((!s->accept()) ? net_status::t_await_accept : net_status::t_await_undo);            return sock_awaitable(s);        }        /**        * 以下几个成员函数中的参数asyncsock _s应该等同于私有成员s,除非强行在外部使用syncio对象        * 使用参数而不是私有成员的原因是防止在尚未连接前调用IO操作。        * 私有成员s将专用于accept与connect        */        close_awaitable close(asyncsock _s) {            _s.s->close();            if ((_s.s->st & 0xFF) == net_status::s_close) {                // 如果套接字上已经没有任何IO事件,就让awaitable直接唤醒协程                // 通常这才是正常状态,但如果有其他线程异步send时,可能就会有未决IO存在了。                return close_awaitable(false);            }            _s.s->st |= net_status::t_await_close;            return close_awaitable(true);        }        send_awaitable send(asyncsock _s, void *buf, int len) {            _s.s->syncsendlen = _s.send(buf, len);            _s.s->st |= ((_s.s->syncsendlen >= 0) ? net_status::t_await_write : net_status::t_await_undo);            return sock::send_awaitable(_s.s);        }        send_awaitable send(asyncsock _s, const void* buf, int len) {            _s.s->syncsendlen = _s.send(buf, len);            _s.s->st |= ((_s.s->syncsendlen >= 0) ? net_status::t_await_write : net_status::t_await_undo);            return sock::send_awaitable(_s.s);        }        recv_awaitable recv(asyncsock _s) {            int n = _s.s->read();            if (n < 0) {                _s.s->st |= net_status::t_await_undo;            }            else {                _s.s->st |= net_status::t_await_read;            }            return recv_awaitable(_s.s);        }        nat_awaitable nat(asyncsock _s, const std::string& _Dest) {            if ((_s.s->st & 0xFF) != net_status::t_activated) {                // nat之前必须保证所有未决IO都已经返回,与打洞服务器保持正常连接状态,否则就是失败。                // 到这里失败时,依旧与打洞服务器保持着正常连接。                return nat_awaitable(false);            }            sockaddr_in sa = _s.s->sa;            if (!_s.s->set_dest(_Dest)) {                // 设置目标地址失败                // 到这里失败时,依旧与打洞服务器保持着正常连接。                _s.s->sa = sa;                return nat_awaitable(false);            }            if (_s.s->nat()) {                // 到这一步失败时,与打洞服务器的连接就有可能会断掉                // nat失败时,本就应该直接close();                 // 都失败了,我想不出还要跟打洞服务器继续苟合的理由。                // 如果所有状态全都对,还失败,可能就是双方正好属于无法穿透的NAT类型环境下。                // 我对此研究不多,业界内真正懂行的也不多,资料更是少得可怜,我只知道TCP NAT在代码上的表现为:                //     1、与打洞服务器保持连接的这个套接字设置了SO_REUSEADDR,确保这个套接字绑定的本地端口可复用。                //          在这个库里我全都设置了可复用,但主要目的是为了缓解TIME_WAIT,并不是为了穿透。                //     2、双方通过打洞服务器沟通好各自的远端地址                //     3、双方都创建一个新的套接字,并将该套接字绑定到本地与打洞服务器进行连接的那个地址(getsockname可以获得)                //          到第 3 步处理好之后,与打洞服务器连接的那个套接字,已经废了,无法再进行通信,此时应该把它close掉。                //     4、最后双方都connect对方的地址。                _s.s->sa = sa;                return nat_awaitable(false);            }            s->st |= net_status::t_await_connect;            return nat_awaitable(true);        }        bool valid() {            return (s != nullptr);        }        operator bool () {            return valid();        }    private:        friend class netio;        sock* s;    };    /**    * 可以简单把netio看成是一个容器的作用    * 它主要用于对接net_base,创建线程,处理IO事件。    */    class netio {        struct IOCP_STATUS {            DWORD transferred;            SIZE_T key;            typename sock::binding* pb;            BOOL ok;        };    public:        /**listener 只是一种简单的参数包装,只是为了方便构造而已        * 构造参数:        * _Dest 要监听的地址和端口,格式为:"a.b.c.d:port"        * _ListenBacklog 系统函数listen的第2个参数        * _MaxClients 最多同时接受的客户端数量        */        class listener {        public:            listener() {                max_clients = 0;                listen_backlog = 0;                addr.sin_addr.S_un.S_addr = INADDR_NONE;            }            listener(const std::string& _Dest, int _ListenBacklog, size_t _MaxClients) {                max_clients = _MaxClients;                listen_backlog = _ListenBacklog;                net_base::sockaddr_from_string(addr, _Dest);            }        private:            friend class netio;            sockaddr_in addr;            int listen_backlog;            size_t max_clients;        };        using asyncsock = sock::asyncsock;        using sock_data = sock::sock_data;        using opcode = net_status;        using task = net_task_t<sock>;        int init(int _StreamCapacity = 1440, int _DataBacklog = 0, int _Timeout = 0) {            std::lock_guard<std::mutex> lg(mtx);            return nwb.init(_StreamCapacity, _DataBacklog, _Timeout);        }        int server(const std::function<task(coio)> &_func, const listener &param) {            std::lock_guard<std::mutex> lg(mtx);            if (thd.joinable()) {                nlog("%s->netio已启动, 请勿重复调用!", __FUNCTION__);                return 0;            }            if (nwb.fd == INVALID_SOCKET)                return -1;            cofunc = _func;            if (param.addr.sin_addr.S_un.S_addr != INADDR_NONE) {                if (SOCKET_ERROR == bind(nwb.fd, (SOCKADDR*)&param.addr, sizeof(SOCKADDR))) {                    nlog("%s->绑定端口失败,错误号:%d", __FUNCTION__, WSAGetLastError());                    nwb.close();                    return -1;                }                if (SOCKET_ERROR == ::listen(nwb.fd, param.listen_backlog)) {                    nlog("%s->监听失败,错误号:%d", __FUNCTION__, WSAGetLastError());                    nwb.close();                    return -1;                }                for (int i = 0; i < param.max_clients; i++) {                    sock* psock = new sock(&nwb);                    a_list.push_back(psock);                    psock->st |= opcode::t_acceptor;                    psock->co = cofunc(coio(psock));                    psock->co.set_sock(psock);                    psock->co.resume();                }            }            __start();            return 0;        }        // client是一次性的,专用于客户端        // 让它返回asyncsock对象的理由是为了给脚本语言预留的        // 例如可以使用lua去实现类似node.js的那种connect之后不管连没连上就先得到对象去绑定事件的机制。        asyncsock client(const std::function<task(coio)>& _func) {            std::lock_guard<std::mutex> lg(mtx);            coio io;            asyncsock ret;            if (!thd.joinable()) {                // 如果线程未启动,尝试启动线程,这之后如果要回收资源,是需要stop和release的                if (nwb.fd == INVALID_SOCKET)                    return ret;                __start();            }            io.s = get_connector();            ret.s = io.s;            io.s->co = _func(io);            io.s->co.set_sock(io.s);            io.s->co.resume();            return ret;        }        void exec(const std::function<void()>& _func) {            if (!thd.joinable()) {                // 如果线程未启动,尝试启动线程,这之后如果要回收资源,是需要stop和release的                if (nwb.fd == INVALID_SOCKET)                    return;                __start();            }            nwb.SafeIOMessage(opcode::s_exec, (ULONG_PTR)&_func);        }        void stop() {            std::lock_guard<std::mutex> lg(mtx);            if (thd.joinable()) {                PostQueuedCompletionStatus(nwb.hIocp, -1, 0, 0);                thd.join();            }        }        void release() {            std::lock_guard<std::mutex> lg(mtx);            if (thd.joinable()) {                nlog("%s->nio正在运行,请先stop", __FUNCTION__);                return;            }            for (auto p : a_list) {                delete p;            }            a_list.clear();            for (auto p : c_list) {                delete p;            }            c_list.clear();            nwb.close();        }    private:        sock* get_connector() {            sock* psock = nullptr;                        for (auto v : c_list) {                if ((v->st & opcode::t_connector) == 0 && ((v->st & 0xFF)| opcode::s_close) == opcode::s_close) {                    psock = v;                    break;                }            }            if (!psock) {                psock = new sock(&nwb);                c_list.push_back(psock);            }            psock->st |= opcode::t_connector;            return psock;        }        void on_connect(sock& s) {            s.ibuf.clear();            s.obuf.clear();            s.obacklog.clear();            s.rtime = aqx::now();            if (nwb.Timeout != 0)                s.set_timer(true);            s.st |= opcode::t_activated;        }                void on_accept(sock &s) {            // 懒得去调用GetAcceptExSockAddrs,有硬编码可用#ifndef _WIN64            s.sa = *(sockaddr_in*)(s.ibuf.data() + 0x26);#else            s.sa = *(sockaddr_in*)(s.ibuf.data() + 0x20);#endif            on_connect(s);        }                bool on_resume(sock& s) {            if (s.st & opcode::t_await) {                // 清除所有协程等待标志                s.st &= (~opcode::t_await);                // 唤醒协程                s.co.resume();                return true;            }            return false;        }        void on_close(sock& s) {            if ((s.st & 0xFF) == opcode::s_close) {                s.st &= ~opcode::s_close;                on_resume(s);            }        }        bool error_resume(sock &s) {            int st = s.st & opcode::t_await;            switch (st) {            case opcode::t_await_accept:            case opcode::t_await_connect:            case opcode::t_await_close:                s.st &= (~opcode::t_await);                s.co.resume();                return true;            case opcode::t_await_read:                s.ibuf.clear();                s.st &= (~opcode::t_await);                s.co.resume();                return true;            case opcode::t_await_write:                s.syncsendlen = -1;                s.st &= (~opcode::t_await);                s.co.resume();                return true;            default:                break;            }            return false;        }        void on_reset(sock &s) {            if ((s.st & 0xFF) == opcode::s_close) {                s.st &= ~opcode::s_close;                if (s.st & opcode::t_acceptor) {                    // 如果服务端协程不在一个循环里,协程返回自动销毁后就会这样                    // 此时的挽救措施就是创建一个新的协程                    s.co = cofunc(coio(&s));                }            }        }        void on_completion(IOCP_STATUS& st) {            sock& s = *(st.pb->s);            int op = st.pb->opt;            s.st &= (~op);            if (s.st & opcode::s_close)                op = 0;            //nlog("on_completion:%I64X, %d", &s, op);            switch (op) {            case 0:                break;            case opcode::s_accept:                on_accept(s);                break;            case opcode::s_connect:                if (!st.ok && WSAGetLastError() == 1225) {                    // 出现这种错误,一般是由于服务端没有在监听指定端口,直接被操作系统拒绝了。                    op = 0;                    break;                }                on_connect(s);                break;            case opcode::s_read:                if (!st.transferred) {                    op = 0;                    break;                }                s.rtime = aqx::now();                s.ibuf.preset_length(s.ibuf.length() + st.transferred);                break;            case opcode::s_write:                if (!st.transferred) {                    op = 0;                    break;                }                s.rtime = aqx::now();                s.obuf.erase(0, st.transferred);                if (s.obuf.length() || s.obacklog.length()) {                    if (s.write()) {                        op = 0;                        break;                    }                }                // write操作可能是非协程发起的,协程很可能挂起在recv,因此需要判断一下。                if (!(s.st & opcode::t_await_write))                    return;                break;            }                        //nlog("on_completion2:%I64X, %d", &s, op);            if (!op) {                if (error_resume(s))                    return;                // 只有当协程被销毁时,error_resume才会返回false                s.close();                on_reset(s);                return;            }                        on_resume(s);            if (s.st & opcode::s_close)                return on_close(s);        }        void on_msgtimeout(sock *psock) {            if (aqx::now() - psock->rtime >= nwb.Timeout && (psock->st & opcode::t_activated)) {                psock->close();                if (error_resume(*psock))                    return;                on_reset(*psock);                return;            }            if (psock->ontimeout != nullptr) {                asyncsock as;                as.s = psock;                psock->ontimeout(as);            }        }        void on_msgconnect(sock* psock) {            if (psock->connect()) {                psock->close();                if (error_resume(*psock))                    return;                on_reset(*psock);            }        }        void on_msgwrite(net_base::safeio_send_struct* pss) {            nwb.SafeIOResult(pss->s->send(pss->buf, pss->len));        }        void on_msgclose(sock* psock) {            psock->close();            nwb.SafeIOResult(0);        }        void __start() {            thd = std::thread([this]() {                nwb.WorkerThreadId = GetCurrentThreadId();                srand((unsigned int)aqx::now() + nwb.WorkerThreadId);                nwb.InitSafeIO();                IOCP_STATUS st = { 0,0,0,0 };                //nlog("netio::worker->I/O工作线程 %d 开始!", nwb.WorkerThreadId);                                for (;;) {                    st.ok = GetQueuedCompletionStatus(nwb.hIocp,                        &(st.transferred),                        &(st.key),                        (OVERLAPPED**)&(st.pb),                        INFINITE);                    if (!st.pb) {                        if (st.transferred == -1)                             break;                                                switch (st.transferred) {                        case 0:                            on_msgtimeout((sock*)st.key);                            break;                        case opcode::s_connect:                            on_msgconnect((sock*)st.key);                            break;                        case opcode::s_write:                             on_msgwrite((net_base::safeio_send_struct*)st.key);                            break;                        case opcode::s_close:                            on_msgclose((sock*)st.key);                            break;                        case opcode::s_exec:                            (*((std::function<void()>*)st.key))();                            nwb.SafeIOResult(0);                            break;                        }                        continue;                    }                    on_completion(st);                }                                nwb.ExitSafeIO();                nwb.WorkerThreadId = 0;                //nlog("netio::worker->I/O工作线程 %d 已停止!", nwb.WorkerThreadId);            });        }            private:        net_base nwb;        std::list<sock*> a_list;        std::list<sock*> c_list;        std::function<task(coio)> cofunc;        std::thread thd;        std::mutex mtx;    };}#pragma warning(pop)

这个库我已经去除了各种耦合,除了日志库,aqx::log我自己写的一个简单的格式化日志库:

logger.hpp#pragma once#include <iostream>#include <string>#include <time.h>#include <stdarg.h>#include <mutex>#include <vector>//aqx::log不与aqx其他库耦合#if defined(_WIN32) || defined(_WIN64)#ifndef _WINDOWS_#include <WinSock2.h>#endif#define __aqxlog_getpid GetCurrentProcessId#define __aqxlog_gettid GetCurrentThreadId#include <io.h>#else#if defined(__linux__)#include <unistd.h>#include <sys/syscall.h>#define __aqxlog_getpid getpid#define __aqxlog_gettid() syscall(__NR_gettid)#endif#endif#pragma warning(push)#pragma warning(disable:4996)namespace aqx {    class log {    private:        struct _format_texts {            std::string time;            std::string type;            std::string pid;            std::string tid;        };    public:        static constexpr auto hs_time{ static_cast<int>(1) };        static constexpr auto hs_type{ static_cast<int>(2) };        static constexpr auto hs_pid{ static_cast<int>(4) };        static constexpr auto hs_tid{ static_cast<int>(8) };        log() {            _stdout_fp = stdout;            fp = stdout;            _fmtts = { "%Y/%m/%d %H:%M:%S ", "{%s} ",  "[%d] ",  "(%d) " };            head_style = log::hs_time;            head_presize = _gethps();            _EnableInfo = true;            _EnableError = false;            _EnableDebug = false;            _EnableWarn = false;            _DefType = "info";            s.reserve(0x1000);        }        ~log() {            if (fp != _stdout_fp)                fclose(fp);        }        void enable(const std::string_view& _Type, bool _Enable) {            std::lock_guard<std::mutex> lg(_Mtx);            if (_Type == "info")                _EnableInfo = _Enable;            else if (_Type == "error")                _EnableError = _Enable;            else if (_Type == "debug")                _EnableDebug = _Enable;            else if (_Type == "warn")                _EnableWarn = _Enable;        }        void seths(int hs) {            std::lock_guard<std::mutex> lg(_Mtx);            head_style = hs;            head_presize = _gethps();        }        void sethfmt(int _Style, const char* _Fmt) {            std::lock_guard<std::mutex> lg(_Mtx);            switch (_Style) {            case hs_time:                _fmtts.time = _Fmt;                break;            case hs_type:                _fmtts.type = _Fmt;                break;            case hs_pid:                _fmtts.pid = _Fmt;                break;            case hs_tid:                _fmtts.tid = _Fmt;                break;            }            head_presize = _gethps();        }        bool setvfs(const char* _FileName, bool _PutStdout = false) {            std::lock_guard<std::mutex> lg(_Mtx);            FILE* _tmp = fopen(_FileName, "ab");            if (!_tmp)                return false;            if (fp != _stdout_fp)                fclose(fp);            fp = _tmp;            PutStdout = _PutStdout;            return true;        }        log& info(const char* _Fmt, ...) {            std::lock_guard<std::mutex> lg(_Mtx);            if (!_EnableInfo)                return *this;            va_list vl;            va_start(vl, _Fmt);            _build("info", _Fmt, vl);            va_end(vl);            _putlog();            return *this;        }        log& debug(const char* _Fmt, ...) {            std::lock_guard<std::mutex> lg(_Mtx);            if (!_EnableDebug)                return *this;            va_list vl;            va_start(vl, _Fmt);            _build("info", _Fmt, vl);            va_end(vl);            _putlog();            return *this;        }        log& error(const char* _Fmt, ...) {            std::lock_guard<std::mutex> lg(_Mtx);            if (!_EnableError)                return *this;            va_list vl;            va_start(vl, _Fmt);            _build("info", _Fmt, vl);            va_end(vl);            _putlog();            return *this;        }        log& warn(const char* _Fmt, ...) {            std::lock_guard<std::mutex> lg(_Mtx);            if (!_EnableWarn)                return *this;            va_list vl;            va_start(vl, _Fmt);            _build("info", _Fmt, vl);            va_end(vl);            _putlog();            return *this;        }        log& operator()(const char* _Fmt, ...) {            std::lock_guard<std::mutex> lg(_Mtx);            if (!_EnableInfo)                return *this;            va_list vl;            va_start(vl, _Fmt);            _build(_DefType.c_str(), _Fmt, vl);            va_end(vl);            _putlog();            return *this;        }    private:        void _putlog() {            fputs(s.data(), fp);            if (fp != _stdout_fp) {                //fflush(fp);                if (PutStdout)                    fputs(s.data(), _stdout_fp);            }        }        size_t _build(const char* _Type, const char* _Fmt, va_list vl) {            s.clear();            size_t n = vsnprintf(nullptr, 0, _Fmt, vl);            if (n <= 0)                return _build_head(_Type);            if (n >= s.capacity()) {                s.clear();                s.reserve(n + head_presize);            }            size_t _Pos = _build_head(_Type);            char* p = (char*)s.data();            _Pos += vsnprintf(p + _Pos, s.capacity(), _Fmt, vl);            char c = p[_Pos - 1];#ifdef _WINDOWS_            if (c != '\r' && c != '\n') {                p[_Pos++] = '\r';                p[_Pos++] = '\n';                p[_Pos] = '\0';            }#else            if (c != '\r' && c != '\n') {                p[_Pos++] = '\n';                p[_Pos] = '\0';            }#endif            return _Pos;        }        size_t _build_time(size_t _Pos) {            if (!(head_style & log::hs_time))                return _Pos;            time_t t = time(NULL);            auto _Tm = localtime(&t);            _Pos += strftime((char*)s.data() + _Pos, head_presize, _fmtts.time.c_str(), _Tm);            return _Pos;        }        size_t _build_type(size_t _Pos, const char* _Type) {            if (!(head_style & log::hs_type))                return _Pos;            _Pos += sprintf((char*)s.data() + _Pos, _fmtts.type.c_str(), _Type);            return _Pos;        }        size_t _build_pid(size_t _Pos) {            if (!(head_style & log::hs_pid))                return _Pos;            auto _Pid = __aqxlog_getpid();            _Pos += sprintf((char*)s.data() + _Pos, _fmtts.pid.c_str(), _Pid);            return _Pos;        }        size_t _build_tid(size_t _Pos) {            if (!(head_style & log::hs_tid))                return _Pos;            auto _Tid = __aqxlog_gettid();            _Pos += sprintf((char*)s.data() + _Pos, _fmtts.tid.c_str(), _Tid);            return _Pos;        }        size_t _build_head(const char* _Type) {            return _build_tid(_build_pid(_build_type(_build_time(0), _Type)));        }        size_t _gethps() {            size_t _Result = 3;            if (head_style & log::hs_time)                _Result += ((_fmtts.time.length() << 1) + 30);            if (head_style & log::hs_type)                _Result += ((_fmtts.pid.length() << 1) + 12);            if (head_style & log::hs_pid)                _Result += ((_fmtts.pid.length() << 1) + 20);            if (head_style & log::hs_tid)                _Result += ((_fmtts.pid.length() << 1) + 20);            return _Result;        }    private:        std::vector<char> s;        FILE* fp;        _format_texts _fmtts;        int head_style;        size_t head_presize;        bool PutStdout;        FILE* _stdout_fp;        std::mutex _Mtx;        std::string _DefType;        bool _EnableInfo;        bool _EnableDebug;        bool _EnableError;        bool _EnableWarn;    };}static aqx::log logger;#pragma warning(pop)

最后是测试代码:客户端和服务端放在一起了,要分离就从nio.init后面的几个地方分离一下。

// main.cpp#include <iostream>#include <aqx/netio.hpp>int main(){    aqx::init_winsock();    aqx::netio nio;    nio.init(1440, 0x10000);    // 一个简单的echo服务器例子:    nio.server([](aqx::coio io)->aqx::netio::task {        // 服务端始终应该放在一个死循环里,否则兜底逻辑会反复创建新协程。        for (;;) {            // io.accept会返回一个可用于异步send和close的对象            auto s = co_await io.accept();            logger("客户端连入:%s", aqx::net_base::sockaddr_to_string(s.getsockaddr()));            for (;;) {                auto buf = co_await io.recv(s);                if (!buf.length()) {                    logger("断开连接!");                    break;                }                puts(buf.data());                buf.clear();                // 异步发送,协程不会在这里挂起                s.send("收到!", 5);                            }            co_await io.close(s);            logger("已关闭!");        }    }, aqx::netio::listener("0.0.0.0:55554", 100, 100));    // 我已经懒到让客户端和服务端都放在一起了,要分自己分    auto sock1 = nio.client([](aqx::coio io)->aqx::netio::task {        // 客户端只有需要自动重连,才放在循环里处理        for (;;) {            auto s = co_await io.connect("127.0.0.1:55554");            if (!s) {                co_await io.close(s);                continue;            }            for (;;) {                auto buf = co_await io.recv(s);                if (!buf.length()) {                    break;                }                puts(buf.data());                buf.clear();            }                        co_await io.close(s);        }           });    // 我已经懒到让客户端和服务端都放在一起了,要分自己分    auto sock2 = nio.client([](aqx::coio io)->aqx::netio::task {        // 客户端只有需要自动重连,才放在循环里处理        for (;;) {            auto s = co_await io.connect("127.0.0.1:55554");            if (!s) {                co_await io.close(s);                continue;            }            for (;;) {                auto buf = co_await io.recv(s);                if (!buf.length()) {                    break;                }                puts(buf.data());                buf.clear();            }            co_await io.close(s);        }    });        std::string str;    for (;;) {        std::cin >> str;        if (str == "exit")            break;        std::string sd = "sock1:";        sd += str;        sock1.safe_send(sd.data(), (int)sd.length() + 1);        sd = "sock2:";        sd += str;        sock2.safe_send(sd.data(), (int)sd.length() + 1);    }    nio.stop();    nio.release();}

我还是稍微负责一点,既然发现了编译bug,还是跟踪一下吧。

如果 recv_awaitable::await_ready()是inline时,流程态remuse切换到 协程态 时,会经过以下流程
00007FF723AF6000 mov r11,rsp
00007FF723AF6003 mov qword ptr [r11+10h],rbx
00007FF723AF6007 mov qword ptr [r11+18h],rsi
00007FF723AF600B mov qword ptr [r11+20h],rdi
00007FF723AF600F mov qword ptr [r11+8],rcx
00007FF723AF6013 push r12
00007FF723AF6015 push r14
00007FF723AF6017 push r15
00007FF723AF6019 sub rsp,90h
00007FF723AF6020 mov rax,qword ptr [__security_cookie (07FF723AFA008h)]
00007FF723AF6027 xor rax,rsp
00007FF723AF602A mov qword ptr [rsp+80h],rax
00007FF723AF6032 mov rdi,rcx
00007FF723AF6035 mov qword ptr [rsp+50h],rcx
00007FF723AF603A movzx eax,word ptr [rdi+2Ch]
00007FF723AF603E mov word ptr [rsp+48h],ax
00007FF723AF6043 inc ax
00007FF723AF6046 cmp ax,0Ah
00007FF723AF604A ja `main'::`2'::<lambda_1>$_ResumeCoro$1::operator()+463h (07FF723AF6463h)
00007FF723AF6050 movsx rax,ax
00007FF723AF6054 lea rdx,[__ImageBase (07FF723AF0000h)]
00007FF723AF605B mov ecx,dword ptr [rdx+rax*4+6494h]
00007FF723AF6062 add rcx,rdx
00007FF723AF6065 jmp rcx
00007FF723AF6067 jmp `main'::`2'::<lambda_1>$_ResumeCoro$1::operator()+82h (07FF723AF6082h)
00007FF723AF6069 xor r15d,r15d
00007FF723AF606C mov dword ptr [rdi+1B0h],r15d
00007FF723AF6073 mov r12d,10000h
00007FF723AF6079 jmp `main'::`2'::<lambda_1>$_ResumeCoro$1::operator()+2E0h (07FF723AF62E0h)
00007FF723AF607E jmp `main'::`2'::<lambda_1>$_ResumeCoro$1::operator()+82h (07FF723AF6082h)
00007FF723AF6080 jmp `main'::`2'::<lambda_1>$_ResumeCoro$1::operator()+82h (07FF723AF6082h)
}
}, aqx::netio::listener("0.0.0.0:55554", 100, 100));
00007FF723AF6082 cmp word ptr [rdi+0Ah],0
00007FF723AF6087 je `main'::`2'::<lambda_1>$_ResumeCoro$1::operator()+464h (07FF723AF6464h)
00007FF723AF608D mov edx,1B4h
00007FF723AF6092 mov rcx,rdi
00007FF723AF6095 call operator delete (07FF723AF5504h)
00007FF723AF609A jmp `main'::`2'::<lambda_1>$_ResumeCoro$1::operator()+464h (07FF723AF6464h)
00007FF723AF609F xor r15d,r15d
00007FF723AF60A2 mov r12d,10000h
00007FF723AF60A8 mov rdx,qword ptr [__coro_frame_ptr] ******************************************** 在这里获取了__coro_frame_ptr.__resume_address
00007FF723AF60AD jmp `main'::`2'::<lambda_1>$_ResumeCoro$1::operator()+26Bh (07FF723AF626Bh)
00007FF723AF60B2 xor r15d,r15d
00007FF723AF60B5 mov r12d,10000h
00007FF723AF60BB jmp `main'::`2'::<lambda_1>$_ResumeCoro$1::operator()+2C4h (07FF723AF62C4h)
00007FF723AF60C0 xor r15d,r15d
00007FF723AF60C3 mov r12d,10000h

---------------------------------------------------------------------------------------------------

00007FF723AF60A8 mov rdx,qword ptr [__coro_frame_ptr]
它直接拷贝了协程帧结构下 offset=0的__resume_address

00007FF723AF60AD jmp `main'::`2'::<lambda_1>$_ResumeCoro$1::operator()+26Bh (07FF723AF626Bh)
紧接着直接跳转过去,就将rdx当作recv_awaitable去进行操作

---------------------------------------------------------------------------------------------------
这个问题我敢肯定100%是编译器bug,导致这个问题的原因,一定不是简简单单的内联因素,绝对会有更深层次的编译逻辑导致此bug,但那是微软的问题。

推荐阅读

    excel怎么用乘法函数

    excel怎么用乘法函数,乘法,函数,哪个,excel乘法函数怎么用?1、首先用鼠标选中要计算的单元格。2、然后选中单元格后点击左上方工具栏的fx公

    excel中乘法函数是什么?

    excel中乘法函数是什么?,乘法,函数,什么,打开表格,在C1单元格中输入“=A1*B1”乘法公式。以此类推到多个单元。1、A1*B1=C1的Excel乘法公式

    标准差excel用什么函数?

    标准差excel用什么函数?,函数,标准,什么,在数据单元格的下方输入l标准差公式函数公式“=STDEVPA(C2:C6)”。按下回车,求出标准公差值。详细

    excel常用函数都有哪些?

    excel常用函数都有哪些?,函数,哪些,常用,1、SUM函数:SUM函数的作用是求和。函数公式为=sum()例如:统计一个单元格区域:=sum(A1:A10)  统计多个

    设置线程名称|tomcat线程名称设置

    设置线程名称|tomcat线程名称设置,,1. tomcat线程名称设置一.tomcat的优化1.tomcat的自身调优采用动静分离调优Tomcat线程池调优Tomcat的

    记一次服务端系统性能优化

    记一次服务端系统性能优化,在线,设备, 首先简单介绍一下业务场景,物联网设备,关注公众号,免费领取环保袋。12月8号,也就是昨天上午,突然接

    多线程cpu电脑|多线程的CPU

    多线程cpu电脑|多线程的CPU,,1. 多线程的CPU四核心四线程,表示这个电脑的CPU核心是4个核心、4个线程的。电脑CPU的核心数量和线程数量越多,

    北京交警电脑版|北京交警客户端下载

    北京交警电脑版|北京交警客户端下载,,1. 北京交警客户端下载网上举报车辆违章的APP有很多,每个地方推出的举报方式不一样,有的使用微信公众