開發環境 vs2019 c++"latest" with asio2
TODO
+++確定NAK,ACK之類的意義是否混亂
+++添加隨機 誤碼/丟包/NAK
++調整延時與同步策略,刪除不必要的對象組
+使用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;
enum class NetworkLayerStatus {
PHYSICAL_LAYER_READY,
NETWORK_LAYER_READY,
FRAME_RECEIVED,
DATA_TIMEOUT,
ACK_TIMEOUT
};
constexpr byte FRAME_DATA = 0;
constexpr byte FRAME_ACK = 1;
constexpr byte FRAME_NAK = 2;
constexpr double PACKAGE_LOSING_RATE = 1e-5;
constexpr double ERR_BYTE_RATE = 1e-6;
constexpr uint32 PACKAGE_SENDING_DELAY = 270;
constexpr uint32 MAX_SEQ = 31;
constexpr uint32 PKT_LEN = 256;
constexpr uint32 CACHE_SIZE = (MAX_SEQ + 1) / 2;
constexpr uint32 ACK_TIMEOUT_LMT = 1000;
constexpr uint32 DATA_TIMEOUT_LMT = 3000;
#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();
}
void StartTimer(int interval, std::function<void()> task) {
if (_expired == false) {
return;
}
_expired = false;
std::thread([this, interval, task]() {
while (!_tryToExpire) {
std::this_thread::sleep_for(std::chrono::milliseconds(interval));
task();
}
std::lock_guard<std::mutex> locker(_mutex);
_expired = true;
_expiredcond.notify_one();
}).detach();
}
void Expire() {
if (_expired) {
return;
}
if (_tryToExpire) {
return;
}
_tryToExpire = true;
{
std::unique_lock<std::mutex> locker(_mutex);
_expiredcond.wait(locker, [this] {
return _expired == true;
});
if (_expired == true) {
_tryToExpire = false;
}
}
}
template<typename Call, class... Args>
void SyncWait(int after, Call&& f, Args&&... args) {
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;
auto task = std::bind(
std::forward<Call>(f),
std::forward<Args>(args)...
);
std::thread([after, task, this] {
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();
}
void StopAsyncWait() {
_asyncRunning = false;
}
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;
};
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
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;
_server->bind_recv([this](auto& session_ptr, strView s)
{
std::random_device r;
std::default_random_engine gen(r());
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
)
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)) {
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;
}
}
UpperLayerCall(_sBufIndex, count);
{
ACKFrame ack;
ack.ack = temp.ack;
ack.crc32 = GetCRC32Tail(&ack, sizeof(ACKFrame));
ack.type = FRAME_ACK;
std::this_thread::sleep_for(SENDING_DELAY);
_server->send(
asio::buffer(&ack, sizeof(ACKFrame))
);
printf("SEND ACK FRAME %d FINISHED\n", ack.ack);
}
if (count != 0) {
_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);
}
else {
NAKFrame nak;
nak.nak = temp.ack;
nak.crc32 = GetCRC32Tail(&nak, sizeof(NAKFrame));
nak.type = FRAME_NAK;
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);
_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)
{
{
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);
}
}
}
break;
default:
break;
}
}
});
}
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);
sendDataFrame(index);
}
}
{
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);
}
}
});
}
void ProtocolApp::sendDataFrame(const size_t index) {
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
);
}
);
}
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) {
}
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