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++程序员的门槛会越低,做上层开发的程序员可以不用知道协程的细节,只要知道如何正确使用库即可。
好了,真正介绍协程细节的文章有一大堆,不用我来写,我直接放代码,有兴趣的可以参考我的实现以及那些细节文章自己做:
- #pragma once
- #include <WinSock2.h>
- #include <MSWSock.h>
- #include <ws2tcpip.h>
- #pragma comment(lib, "ws2_32.lib")
- #include <coroutine>
- #include <string>
- #include <functional>
- #include "logger.hpp"
- /**
- * 最近花了点时间学习了一下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
- 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 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;
- 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);
- }
- 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;
- };
- /*直接继承一个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) {
- return s->send(data, len);
- }
- int send(const void* data, int len) {
- return s->send(data, len);
- }
- /**
- * 存在多线程异步写需求时,就应该所有的写操作全部用safe_send
- *
- */
- int safe_send(void* data, int len) {
- return s->safe_send(data, len);
- }
- int safe_send(const void* data, int len) {
- return s->safe_send(data, len);
- }
- void close() {
- s->close();
- }
- bool isactivated() { return s->isactivated(); }
- operator bool() {
- return (s != nullptr);
- }
- sockaddr_in& getsockaddr() {
- return s->getsockaddr();
- }
- private:
- friend class netio;
- friend class coio;
- friend class sock;
- sock* s = nullptr;
- };
- struct recv_awaitable {
- recv_awaitable(sock* s) : data(s) { }
- __declspec(noinline) bool await_ready() {
- // 我当前的vs版本是: vs 2022 17.0.1
- // 这里发现一个编译bug,只要await_ready与await_suspend同时被inline优化
- // 会编译成如下这样的汇编:
- /*
- * 这段反汇编处于协程函数内部,在删除__declspec(noinline)之后大致就是如下的样子。
- 00007FF69D8A60F8 call aqx::coio::recv (07FF69D8A2C70h)
- // 这里直接用rbx保存了recv_awaitable对象,它是被优化到寄存器的
- 00007FF69D8A60FD mov rbx,rax
- // 但是在经过IOCP工作线程跳回来之后,发现它并没有维护好rbx寄存器
- // 最后导致rbx = __coro_frame_ptr.__resume_address
- 00007FF69D8A6100 mov rax,qword ptr [rax]
- 00007FF69D8A6103 test dword ptr [rax+20h],400h
- 00007FF69D8A610A je `main'::`2'::<lambda_1>$_ResumeCoro$1::operator()+448h (07FF69D8A6318h)
- 00007FF69D8A6110 lea rcx,[rax+0B8h]
- 00007FF69D8A6117 cmp qword ptr [rax+0D0h],10h
- 00007FF69D8A611F jb `main'::`2'::<lambda_1>$_ResumeCoro$1::operator()+258h (07FF69D8A6128h)
- 00007FF69D8A6121 mov rcx,qword ptr [rax+0B8h]
- 00007FF69D8A6128 mov qword ptr [rax+0C8h],r14
- 00007FF69D8A612F mov byte ptr [rcx],0
- 00007FF69D8A6132 mov rax,qword ptr [rbx]
- 00007FF69D8A6135 and dword ptr [rax+20h],0FFFFFBFFh
- 00007FF69D8A613C mov r8,qword ptr [rbx]
- 00007FF69D8A613F mov rdx,rbx
- */
- // 我目前尚未完全搞清楚导致这个问题的机制(还没有真正闲出屁去帮微软找bug)。
- // 所以我没上报这个问题。
- // 有兴趣的同学可以仔细跟踪一下。
- // 我目前的缓解措施是,让await_ready强制noinline,也总比在await_suspend里面resume()强。
- 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;
- };
- struct safe_buffer {
- sock* s = nullptr;
- sock_buffer buf;
- };
- public:
- using opcode = net_status;
- sock(net_base* _v) {
- fd = INVALID_SOCKET;
- v = _v;
- st = 0;
- 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() {
- 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) {
- 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) {
- std::lock_guard<std::mutex> lg(mtx);
- return send(data, len);
- }
- int safe_send(const void* data, int len) {
- std::lock_guard<std::mutex> lg(mtx);
- return send(data, len);
- }
- void close() {
- if (INVALID_SOCKET == fd)
- return;
- 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, 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, reuse_sock;
- sock_buffer ibuf, obuf, obacklog;
- HANDLE hTimer;
- aqx::clock64_t rtime;
- net_task_t<sock> co;
- int syncsendlen;
- std::mutex mtx;
- };
- // 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);
- }
- send_awaitable safe_send(asyncsock _s, void* buf, int len) {
- _s.s->syncsendlen = _s.safe_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 safe_send(asyncsock _s, const void* buf, int len) {
- _s.s->syncsendlen = _s.safe_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 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 ¶m) {
- 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*)¶m.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 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) {
- if (p->st & opcode::t_await)
- p->co.destroy();
- delete p;
- }
- a_list.clear();
- for (auto p : c_list) {
- if (p->st & opcode::t_await)
- p->co.destroy();
- 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() + nwb.Timeout;
- 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.ibuf.preset_length(s.ibuf.length() + st.transferred);
- break;
- case opcode::s_write:
- if (!st.transferred) {
- op = 0;
- break;
- }
- 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 __start() {
- thd = std::thread([this]() {
- nwb.WorkerThreadId = GetCurrentThreadId();
- srand((unsigned int)aqx::now() + nwb.WorkerThreadId);
- 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) {
- sock* psock = (sock*)st.key;
- if (aqx::now() > psock->rtime && (psock->st & opcode::t_activated)) {
- psock->close();
- if (error_resume(*psock))
- return;
- on_reset(*psock);
- }
- }
- else if (st.transferred == opcode::s_connect) {
- sock* psock = (sock*)st.key;
- if (psock->connect()) {
- psock->close();
- if (error_resume(*psock))
- return;
- on_reset(*psock);
- }
- }
- else if (st.transferred == -1)
- break;
- continue;
- }
- on_completion(st);
- }
- //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();
}
C++20协程实例:携程化的IOCP服务端/客户端的更多相关文章
- PHP-Socket服务端客户端发送接收通信实例详解
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://fighter.blog.51cto.com/1318618/1533957 So ...
- 【gRPC】C++异步服务端客户端API实例及代码解析
对于同步API而言,程序的吞吐量并不高.因为在每次发送一个gRPC请求时,会阻塞整个线程,必须等待服务端的ack回到客户端才能继续运行或者发送下一个请求,因此异步API是提升程序吞吐量的必要手段. g ...
- [精华][推荐]CAS SSO单点登录服务端客户端实例
1.修改server.xml文件,如下: 注意: 这里使用的是https的认证方式,需要将这个配置放开,并做如下修改: <Connector port="8443" prot ...
- WebService 服务端客户端 实例 HTTPRIO (一) SOAP WSDL
Delphi中WebService包含的组件解释(有7个) (1) THTTPRIO-------:使用Http消息来调用远程使用SOAP的接口对象 (2) THTTPReqResp- ...
- Android native进程间通信实例-socket本地通信篇之——服务端进程异常退出解决办法
导读: 好难受啊,为什么服务端说挂就挂,明明只是客户端关闭而已,服务端怎么能挂呢? 想想,如果手机上使用一个聊天程序的时候,手机端关闭了聊天程序,那么远端服务器程序总不能说挂就挂吧!所以一定要查明真相 ...
- Netty 的基本简单实例【服务端-客户端通信】
Netty是建立在NIO基础之上,Netty在NIO之上又提供了更高层次的抽象. 在Netty里面,Accept连接可以使用单独的线程池去处理,读写操作又是另外的线程池来处理. Accept连接和读写 ...
- 2014 CodingTrip - 携程编程大赛 (预赛第二场)
1001: 食物链(poj1182),直接贴代码,稍作可过 并查集 // // main.cpp // 携程1 // // Created by zhang on 14-4-11. // Copyri ...
- python 携程asyncio实现高并发示例1
import asyncio #携程(携程不是函数) async def print_hello(): while True: print("hello world") await ...
- ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存
分享 最近在公司成功落地了一个用ASP.NET Core 开发前台的CMS项目,虽然对于表层的开发是兼容MVC5的,但是作为爱好者当然要用尽量多的ASP.NET Core新功能了. 背景 在项目开发的 ...
随机推荐
- Huffman算法
一.Huffman算法介绍 霍夫曼编码(英语:Huffman Coding),又译为哈夫曼编码.赫夫曼编码,是一种用于无损数据压缩的熵编码(权编码)算法.在计算机数据处理中,霍夫曼编码使用变长编码表对 ...
- 它说你的代码有 Bug「GitHub 热点速览 v.21.44」
作者:HelloGitHub-小鱼干 本周热点上的榜单大多数提升工作效率的实用工具,像是一个 API 管理所有通知消息(包括推送.邮件-)的 notifire,再是高速解析 JSON 文件的 simd ...
- JS控制文本框禁止输入特殊字符
JS 控制不能输入特殊字符<input type="text" class="domain" onkeyup="this.value=this. ...
- 开发笔记-----Ajax 基础使用
一.GET 方式的用法: 1 <!--html --> 2 <div class="layui-form"> 3 <div class="l ...
- 干货分享之spring框架源码分析02-(对象创建or生命周期)
记录并分享一下本人学习spring源码的过程,有什么问题或者补充会持续更新.欢迎大家指正! 环境: spring5.X + idea 之前分析了Spring读取xml文件的所有信息封装成beanDef ...
- Vue 之 Mixins (混入)的使用
是什么 混入 (mixins): 是一种分发 Vue 组件中可复用功能的非常灵活的方式.混入对象可以包含任意组件选项.当组件使用混入对象时,所有混入对象的选项将被合并到组件本身,也就是说父组件调用混入 ...
- 🏆【Alibaba中间件技术系列】「RocketMQ技术专题」帮你梳理RocketMQ或Kafka的选择理由以及二者PK
前提背景 大家都知道,市面上有许多开源的MQ,例如,RocketMQ.Kafka.RabbitMQ等等,现在Pulsar也开始发光,今天我们谈谈笔者最常用的RocketMQ和Kafka,想必大家早就知 ...
- 9组-Ahlpa-6/3
一.基本情况 队名:不行就摆了吧 组长博客:https://www.cnblogs.com/Microsoft-hc/p/15546622.html 小组人数: 8 二.冲刺概况汇报 卢浩玮 过去两天 ...
- 美妙绝伦面向node引用-zico图标(逐浪矢量全真图标)1.9发布
15年前,那个农村小伙初入广告行业被讥笑没有审美 于是他狠下决心,积极研发,缔就技术之核, 再后来,那些PPT和美工er们随便怎么自好,无法让其心怵. 因为他是中华人民共和国唯一具备web.cms.o ...
- 3、使用ListOperations操作redis(List列表)
文章来源:https://www.cnblogs.com/shiguotao-com/p/10560354.html 方法 c参数 s说明 List<V> range(K key, l ...