开发环境 vs2019 c++"latest" with asio2
T O D O \color{red}{TODO} TODO
+ + + 确 定 N A K , A C K 之 类 的 意 义 是 否 混 乱 \color{red}{+++确定NAK, ACK之类的意义是否混乱} +++确定NAK,ACK之类的意义是否混乱
+ + + 添 加 随 机 误 码 / 丢 包 / N A K \color{red}{+++添加随机~误码/丢包/NAK} +++添加随机 误码/丢包/NAK
+ + 调 整 延 时 与 同 步 策 略 , 删 除 不 必 要 的 对 象 组 \color{red}{++调整延时与同步策略,删除不必要的对象组} ++调整延时与同步策略,删除不必要的对象组
+ 使 用 s p d l o g 生 成 运 行 日 志 \color{pink}{+使用spdlog生成运行日志} +使用spdlog生成运行日志
config.h
#pragma once
#include <array>
#include <memory>
#include <vector>
#include <random>
#include <cstdint>
#include <timer.h>
#include <type_traits>
#include <sdkddkver.h>
#include <asio2/udp/udp_cast.hpp>
#include <asio2/udp/udp_client.hpp>
#include <asio2/udp/udp_server.hpp>
#include <asio2/udp/udp_session.hpp>
#define CLIENT_MEMBER
#define SERVER_MEMBER
#define TEST(x) printf("********TEST" #x "********\n");
using byte = uint8_t;
using uint16 = uint16_t;
using uint32 = uint32_t;
using uint64 = uint64_t;
constexpr uint32 MAX_BUFFER_SIZE = 1024;
constexpr uint64 CRC32_SETENCE = 0x104C11DB7;
constexpr uint32 CRC32_GENERATE = CRC32_SETENCE & 0xFFFF; //0x4C11DB7
// Get memory layout info: /d1 reportSingleClassLayoutXXX
enum class NetworkLayerStatus {
PHYSICAL_LAYER_READY,
NETWORK_LAYER_READY,
FRAME_RECEIVED,
DATA_TIMEOUT,
ACK_TIMEOUT
};
//enum class FrameType: byte {
//};
constexpr byte FRAME_DATA = 0; //type+seqNum+ack+data+crc32
constexpr byte FRAME_ACK = 1; //type+ack+crc32
constexpr byte FRAME_NAK = 2; //type+nak+crc32
constexpr double PACKAGE_LOSING_RATE = 1e-5; //factor
constexpr double ERR_BYTE_RATE = 1e-6;
constexpr uint32 PACKAGE_SENDING_DELAY = 270; //ms
constexpr uint32 MAX_SEQ = 31; //2^n-1 窗口序号
constexpr uint32 PKT_LEN = 256; //248; 总长度应小于udp各端缓冲区的大小?
constexpr uint32 CACHE_SIZE = (MAX_SEQ + 1) / 2;
constexpr uint32 ACK_TIMEOUT_LMT = 1000; //ms
constexpr uint32 DATA_TIMEOUT_LMT = 3000; //ms
#pragma pack(2)
struct Frame {
byte type = FRAME_DATA;
byte ack = 0;
byte seqNum = 0;
byte _ = 0; //_定义内存对齐位
std::array<byte, PKT_LEN> data = {};
uint32 crc32 = 0x0;
};
struct ACKFrame {
byte type = FRAME_ACK;
byte ack = 0;
uint32 crc32 = 0x0;
};
struct NAKFrame {
byte type = FRAME_NAK;
byte nak = 0;
uint32 crc32 = 0x0;
};
#pragma pack()
timer.h(弃用?)
#pragma once
#include <mutex>
#include <chrono>
#include <thread>
#include <atomic>
#include <memory>
#include <functional>
#include <type_traits>
#include <condition_variable>
class Timer {
public:
Timer() :_expired(true), _tryToExpire(false) {}
Timer(const Timer& t) {
_expired = t._expired.load();
_tryToExpire = t._tryToExpire.load();
}
~Timer() {
Expire();
// std::cout << "timer destructed!" << std::endl;
}
void StartTimer(int interval, std::function<void()> task) {
if (_expired == false) {
// std::cout << "timer is currently running, please expire it first..." << std::endl;
return;
}
_expired = false;
std::thread([this, interval, task]() {
while (!_tryToExpire) {
std::this_thread::sleep_for(std::chrono::milliseconds(interval));
task();
}
// std::cout << "stop task..." << std::endl; {
std::lock_guard<std::mutex> locker(_mutex);
_expired = true;
_expiredcond.notify_one();
}).detach();
}
void Expire() {
if (_expired) {
return;
}
if (_tryToExpire) {
// std::cout << "timer is trying to expire, please wait..." << std::endl;
return;
}
_tryToExpire = true;
{
std::unique_lock<std::mutex> locker(_mutex);
_expiredcond.wait(locker, [this] {
return _expired == true;
});
if (_expired == true) {
// std::cout << "timer expired!" << std::endl;
_tryToExpire = false;
}
}
}
template<typename Call, class... Args>
void SyncWait(int after, Call&& f, Args&&... args) {
//using retType = typename std::invoke_result<Call, Args....>::type;
auto task = std::bind(
std::forward<Call>(f),
std::forward<Args>(args)...
);
std::this_thread::sleep_for(std::chrono::milliseconds(after));
task();
}
template<typename Call, class... Args>
void AsyncWait(int after, Call&& f, Args&&... args) {
if (_asyncRunning)
throw std::runtime_error(
"forbidden to start a new async event now"
);
_asyncRunning = true;
//if(_asyncId._Id)
/*using retType =
typename std::invoke_result<Call, Args...>::type;
std::function<retType(Args...)> */
auto task = std::bind(
std::forward<Call>(f),
std::forward<Args>(args)...
);
std::thread([after, task, this] {
//_asyncRunning = true;
std::chrono::milliseconds limit(after);
std::chrono::milliseconds interval(50);
auto start = std::chrono::steady_clock::now();
while (
_asyncRunning &&
std::chrono::steady_clock::now()-start < limit
) std::this_thread::sleep_for(interval);
if (_asyncRunning) {
task();
}
_asyncRunning = false;
}).detach();
/*_asyncThrdInfo._Hnd = ((_Thrd_t*)(_asyncThrd))->_Hnd;
_asyncThrdInfo._Id = ((_Thrd_t*)(_asyncThrd))->_Id;
_asyncThrd->detach();*/
}
void StopAsyncWait() {
_asyncRunning = false;
/*TerminateThread(_asyncThrdInfo._Hnd, 0);
delete _asyncThrd;
_asyncThrd = nullptr;*/
}
private:
std::mutex _mutex;
std::atomic<bool> _expired;
std::thread::id _asyncId;
std::atomic<bool> _tryToExpire;
std::condition_variable _expiredcond;
std::atomic_bool _asyncRunning = false;
/*std::thread* _asyncThrd = nullptr;
_Thrd_t _asyncThrdInfo { 0,0 };*/
};
main.cpp
#include "ProtocolApp/ProtocolApp.h"
int main(int argc, char* argv[]) {
ProtocolApp app("1919");
app.startServer().detach();
app.startClient().detach();
while (true) {
std::this_thread::sleep_for(
std::chrono::seconds(1)
);
}
return 0;
}
ProtocolApp.h
#pragma once
#include <config.h>
// 目前只有一对一广播模式
class ProtocolApp {
public:
using strView = std::string_view;
explicit ProtocolApp(const strView& port):
_port(port), _running(true),
_client(new asio2::udp_client),
_server(new asio2::udp_server),
_servCache(new std::array<Frame, CACHE_SIZE>),
_sChecked(new std::array<bool, CACHE_SIZE>),
_clntCache(new std::array<Frame, CACHE_SIZE>),
_cChecked(new std::array<bool, CACHE_SIZE>)
{
std::thread(&ProtocolApp::InitServer, this).detach();
std::thread(&ProtocolApp::InitClient, this).detach();
}
virtual ~ProtocolApp() {
_server->stop();
_client->stop();
delete _server;
delete _client;
delete _servCache; _servCache = nullptr;
delete _sChecked; _sChecked = nullptr;
delete _clntCache; _clntCache = nullptr;
delete _cChecked; _cChecked = nullptr;
}
static uint32
GetCRC32Tail(void* data, size_t length);
static uint32
GenerateRandomFrameData(void* data, size_t length);
inline static bool
CheckCRC32Tail(void* data, size_t length, uint32 crcTail);
static void //aborted
buildFrame(std::array<byte, sizeof(Frame)>& data, Frame& srcFrame);
static void
UpperLayerCall(const size_t index, const size_t frameCnt);
void
InitServer();
void
InitClient();
void
sendDataFrame(const size_t index);
std::thread
startServer();
std::thread
startClient();
private:
strView _port;
bool _running;
private CLIENT_MEMBER:
asio2::udp_server* _server;
std::mutex _servNetMutex;
std::array<Frame, CACHE_SIZE>* _servCache;
std::array<bool, CACHE_SIZE>* _sChecked;
uint32 _sBufIndex = 0;
uint32 _sIndexL = 0;
uint32 _sIndexR = 0;
unsigned long long _sTotalRecv = 0;
unsigned long long _sTotalSend = 0;
private SERVER_MEMBER:
asio2::udp_client* _client;
std::mutex _clntNetMutex;
std::array<Frame, CACHE_SIZE>* _clntCache;
std::array<bool, CACHE_SIZE>* _cChecked;
uint32 _cBufIndex = 0;
uint32 _cIndexL = 0;
uint32 _cIndexR = 0;
unsigned long long _cTotalRecv = 0;
unsigned long long _cTotalSend = 0;
};
protocolApp.cpp
#include "ProtocolApp.h"
#define _servCache (*_servCache)
#define _sChecked (*_sChecked)
#define _sFrameTimer (*_sFrameTimer)
#define _clntCache (*_clntCache)
#define _cChecked (*_cChecked)
#define _cFrameTimer (*_cFrameTimer)
template<typename A, typename B, typename C>
bool isBetween(A&& left, B&& b, C&& right) {
return
((left <= b) && (b <= right)) ||
((left <= b) && (right < left)) ||
((b <= right) && (right < left));
}
template<typename T>
bool isPerfectTure(T&& data) {
for (auto i = 0; i < data.size(); i++) {
if (!data[i])
return false;
}
return true;
}
void ProtocolApp::InitServer() {
_sBufIndex = 0;
_sIndexL = 0;
_sIndexR = CACHE_SIZE-1;
// typeof session_ptr: std::shared_ptr<asio2::udp_session>&
// 可使用连续.bind链,但会影响vs代码结构化导致操作麻烦
_server->bind_recv([this](auto& session_ptr, strView s)
{
/*static */std::random_device r; // 同下
/*static */std::default_random_engine gen(r()); // 觉得不够随机,取消这两个static
static std::uniform_real_distribution<double> zero_one_distr(
0, 1
);
static std::chrono::milliseconds SENDING_DELAY(PACKAGE_SENDING_DELAY);
if (
zero_one_distr(gen) > PACKAGE_LOSING_RATE
)
//TODO:
try {
if (s.size() == sizeof(Frame)) {
Frame temp;
memcpy(&temp, s.data(), s.size());
uint16 errCnt = 0;
for (int i = 0; i < sizeof(Frame); i++) {
if (zero_one_distr(gen) < ERR_BYTE_RATE) {
errCnt++;
}
}
for (uint16 i = 0; i < errCnt; i++) {
byte* pErrByte =
reinterpret_cast<byte*>(&temp) + gen() % sizeof(ACKFrame);
*pErrByte = ~(*pErrByte);
}
if (isBetween(_sIndexL, temp.ack, _sIndexR)) {
if (temp.type == FRAME_DATA && CheckCRC32Tail(temp.data.data(), PKT_LEN, temp.crc32)) {
//TEST(1);
//std::this_thread::sleep_for(std::chrono::seconds(1));
//TEST(2);
uint32 sub = ((temp.ack + MAX_SEQ + 1) - _sIndexL) % (MAX_SEQ + 1);
memcpy(&_servCache[(_sBufIndex + sub) % CACHE_SIZE], &temp, sizeof(Frame));
printf("COPY FRAME %d INTO _servCache[%d]\n", temp.ack, (_sBufIndex + sub) % CACHE_SIZE);
_sChecked[(_sBufIndex + sub) % CACHE_SIZE] = true;
uint32 count = CACHE_SIZE;
for (uint32 i = _sBufIndex; i < _sChecked.size() + _sBufIndex; i++) {
if (!_sChecked[i % CACHE_SIZE]) {
count = static_cast<byte>(i - _sBufIndex);
break;
}
else {
_sChecked[i % CACHE_SIZE] = false;
}
}
//emit signal for upper layer start handling
UpperLayerCall(_sBufIndex, count);
{
ACKFrame ack;
ack.ack = temp.ack;
ack.crc32 = GetCRC32Tail(&ack, sizeof(ACKFrame));
ack.type = FRAME_ACK;
// emulate data sending delay
std::this_thread::sleep_for(SENDING_DELAY);
_server->send(
asio::buffer(&ack, sizeof(ACKFrame))
);
printf("SEND ACK FRAME %d FINISHED\n", ack.ack);
}
//std::thread(
// [this](byte ack_) {
//
// }, temp.ack
//).detach();
if (count != 0) { /*&& count != CACHE_SIZE*/
_sIndexL = (_sIndexL + count) % (MAX_SEQ + 1);
_sIndexR = (_sIndexR + count) % (MAX_SEQ + 1);
_sBufIndex = (_sBufIndex + count) % CACHE_SIZE;
}
printf("---------------SERVER FORWARD STEP COUNT: %d---------------\n", count);
}
// crc check error, send NAK Frame
else {
NAKFrame nak;
nak.nak = temp.ack;
nak.crc32 = GetCRC32Tail(&nak, sizeof(NAKFrame));
nak.type = FRAME_NAK;
// emulate data sending delay
std::this_thread::sleep_for(SENDING_DELAY);
_server->send(
asio::buffer(&nak, sizeof(NAKFrame))
);
printf("SEND NAK FRAME %d FINISHED\n", nak.nak);
}
}
}
}
catch (std::exception& err) {
printf("\n\n%s\n\n", err.what());
}
else {
printf("————————DATA PACKAGE SEGMENTATION LOST————————\n");
}
});
_server->bind_connect([](auto& session_ptr)
{
printf("client connected: %s %u %s %u\n",
session_ptr->remote_address().c_str(),
session_ptr->remote_port(),
session_ptr->local_address().c_str(),
session_ptr->local_port()
);
});
_server->bind_disconnect([](auto& session_ptr)
{
printf("client disconnected: %s %u %s\n",
session_ptr->remote_address().c_str(),
session_ptr->remote_port(),
asio2::last_error_msg().c_str()
);
});
_server->bind_handshake([](auto& session_ptr, asio::error_code ec)
{
printf("client handshaked: %d %s\n",
asio2::last_error_val(),
asio2::last_error_msg().c_str()
);
});
_server->bind_start([this](asio::error_code ec)
{
memset(_sChecked.data(), false, CACHE_SIZE);
memset(_servCache.data(), 0, CACHE_SIZE * sizeof(Frame));
_sIndexL = 0;
_sIndexR = CACHE_SIZE-1;
if (asio2::get_last_error())
printf("start udp server failed: %d %s\n",
asio2::last_error_val(),
asio2::last_error_msg().c_str()
);
else
printf("start udp server succeed: %s %u\n",
_server->listen_address().c_str(),
_server->listen_port()
);
});
}
void ProtocolApp::InitClient() {
_client->connect_timeout(std::chrono::seconds(3));
_client->local_endpoint(asio::ip::udp::v4(), 810); //本地转发EP?
_client->bind_connect([&](asio::error_code ec)
{
if (asio2::get_last_error())
printf("connect failed: %d %s\n",
asio2::last_error_val(),
asio2::last_error_msg().c_str()
);
else
printf("connect success: %s %u\n",
_client->local_address().c_str(),
_client->local_port()
);
});
_client->bind_disconnect([](asio::error_code ec)
{
printf("disconnected: %d %s\n",
asio2::last_error_val(),
asio2::last_error_msg().c_str()
);
});
_client->bind_recv([this](strView s)
{
/* if (
ACK也要丢的话...
) */{
ACKFrame ack;
switch (s.size()) {
case sizeof(Frame) :
break;
case sizeof(ACKFrame) :
memcpy(&ack, s.data(), sizeof(ACKFrame));
if (ack.type == FRAME_ACK) {
std::lock_guard<std::mutex> lock(_clntNetMutex);
{
if (isBetween(_cIndexL, ack.ack, _cIndexR)) {
size_t arrIndex =
(ack.ack + MAX_SEQ + 1 + _cBufIndex - _cIndexL) %
(CACHE_SIZE);
_cChecked[arrIndex] = true;
printf("\t\t\t\t\tGet frame %d confirm\n", ack.ack);
}
else {
printf("\t\t\t\t\tDrop frame %d confirm\n", ack.ack);
}
}
}
else if (ack.type == FRAME_NAK) {
std::lock_guard<std::mutex> lock(_clntNetMutex);
{
if (isBetween(_cIndexL, ack.ack, _cIndexR)) {
size_t arrIndex =
(ack.ack + MAX_SEQ + 1 + _cBufIndex - _cIndexL) %
(CACHE_SIZE);
sendDataFrame(arrIndex);
printf("\t\t\t\t\tResend data frame %d\n", ack.ack);
}
else {
printf("\t\t\t\t\tDrop nak request %d\n", ack.ack);
}
}
}
//_cFrameTimer[ack.ack].StopAsyncWait();
break;
default:
break;
}
}
//asio::write(client.get_socket(), asio::buffer(s));
});
}
std::thread ProtocolApp::startClient() {
static const byte TEST_SEQ_NUM = 19;
return std::thread([this] {
if (!_client->start("127.0.0.1", _port))
printf("start failure: %d %s\n", asio2::last_error_val(), asio2::last_error_msg().c_str());
std::random_device r;
std::default_random_engine gen(r());
std::uniform_int_distribution<int> distribution(20, 50);
memset(_cChecked.data(), false, _cChecked.size() * sizeof(bool));
memset(_clntCache.data(), 0, _cChecked.size() * sizeof(Frame));
_cIndexL = 0;
_cIndexR = CACHE_SIZE-1;
_cBufIndex = 0;
while (_running) {
static std::chrono::milliseconds CHECK_INTERVAL(50);
static std::chrono::milliseconds FRAME_SEND_DELAY(270);
static std::chrono::milliseconds TIMEOUT_LIMIT(DATA_TIMEOUT_LMT);
for (byte i = 0; i < CACHE_SIZE; i++) {
byte index = (_cBufIndex + i) % CACHE_SIZE;
if (!_cChecked[index]) {
Frame& frameBuf = _clntCache[index];
frameBuf.crc32 =
GenerateRandomFrameData(
frameBuf.data.data(), PKT_LEN
);
frameBuf.type = FRAME_DATA;
frameBuf.seqNum = TEST_SEQ_NUM;
frameBuf.ack = (_cIndexL + i) % (MAX_SEQ + 1);
// async
// std::thread(&ProtocolApp::sendDataFrame, this, index).detach();
//
sendDataFrame(index);
}
}
// check and delay
{
auto startTime = std::chrono::high_resolution_clock::now();
while (
!isPerfectTure(_cChecked) &&
std::chrono::high_resolution_clock::now() - startTime < TIMEOUT_LIMIT
) {
std::this_thread::sleep_for(CHECK_INTERVAL);
}
std::lock_guard<std::mutex> lock(_clntNetMutex);
uint32 count = CACHE_SIZE;
for (byte i = 0; i < CACHE_SIZE; i++) {
if (!_cChecked[
(i + _cBufIndex) % CACHE_SIZE]
) {
count = i;
break;
}
else {
_cChecked[
(i + _cBufIndex) % CACHE_SIZE
] = false;
}
}
_cIndexL = (_cIndexL + count) % (MAX_SEQ + 1);
_cIndexR = (_cIndexR + count) % (MAX_SEQ + 1);
_cBufIndex = (_cBufIndex + count) % CACHE_SIZE;
printf("---------------CLIENT FORWARD STEP COUNT: %d---------------\n", count);
}
}
});
}
// TODO: 增加depth防止永久丢包导致的栈溢出
void ProtocolApp::sendDataFrame(const size_t index) {
// bind返回的玩意怎么变这样了
static std::chrono::milliseconds SENDING_DELAY(PACKAGE_SENDING_DELAY);
std::this_thread::sleep_for(SENDING_DELAY);
_client->send(
asio::buffer(&(_clntCache[index]), sizeof(Frame)),
[this, index] {
printf(
"Send frame %d finished\n",
_clntCache[index].ack
);
/*_cFrameTimer[index].AsyncWait(
DATA_TIMEOUT_LMT,
[this](const int index) {
sendDataFrame(index);
},
index
);*/
}
);
}
std::thread ProtocolApp::startServer() {
return std::thread([this] {
_server->start("127.0.0.1", _port);
while (_running)
std::this_thread::sleep_for(std::chrono::seconds(1));
});
}
void ProtocolApp::buildFrame(
std::array<byte, sizeof(Frame)>& data,
Frame& srcFrame
) {
data[0] = srcFrame.type;
data[1] = srcFrame.ack;
data[2] = srcFrame.seqNum;
data[3] = 0;
memcpy(
(void*)((uintptr_t)data.data() + 4),
srcFrame.data.data(), static_cast<size_t>(PKT_LEN)
);
data[sizeof(Frame) - 1] = srcFrame.crc32;
}
void ProtocolApp::UpperLayerCall(const size_t index, const size_t frameCnt) {
// (异步copy,同步直接取出)缓冲区中的(Cnt个)Frame并进行处理
/*printf(
"GET DATA FRAME! [type]: %d [ack]: %d "
"[seqNum]: %d [crc32]: 0x%08x\n\n",
temp->type, temp->ack, temp->seqNum, temp->crc32);*/
}
uint32 ProtocolApp::GetCRC32Tail(void* data, size_t length) {
uint32 crc = ~0;
size_t dataSize = PKT_LEN;
const byte* current = (byte*)data;
while (dataSize-- != 0) {
byte s = byte(crc) ^ *current++;
uint32 low = (s ^ (s << 6)) & 0xFF;
uint32 tmp = (low * ((1 << 23) + (1 << 14) + (1 << 2)));
crc = (crc >> 8) ^
(low * ((1 << 24) + (1 << 16) + (1 << 8))) ^
(tmp) ^
(tmp >> 1) ^
(low * ((1 << 20) + (1 << 12))) ^
(low << 19) ^
(low << 17) ^
(low >> 2);
}
return ~crc;
}
uint32 ProtocolApp::GenerateRandomFrameData(
void* data,
size_t length
) {
uint32 crc = ~0;
size_t dataSize = PKT_LEN;
byte* current = (byte*)data;
std::random_device r;
std::default_random_engine gen(r());
std::uniform_int_distribution<int> distribution(0, 255);
while (dataSize-- != 0) {
*current = distribution(gen);
byte s = byte(crc) ^ *current++;
uint32 low = (s ^ (s << 6)) & 0xFF;
uint32 tmp = (low * ((1 << 23) + (1 << 14) + (1 << 2)));
crc = (crc >> 8) ^
(low * ((1 << 24) + (1 << 16) + (1 << 8))) ^
(tmp) ^
(tmp >> 1) ^
(low * ((1 << 20) + (1 << 12))) ^
(low << 19) ^
(low << 17) ^
(low >> 2);
}
return ~crc;
}
inline bool ProtocolApp::CheckCRC32Tail(
void* data,
size_t length,
uint32 crcTail
) {
return GetCRC32Tail(data, length) == crcTail;
}
#undef _servCache
#undef _sChecked
#undef _sFrameTimer
#undef _clntCache
#undef _cChecked
#undef _cFrameTimer