17 changed files with 854 additions and 304 deletions
@ -1,27 +1,39 @@
|
||||
#pragma once |
||||
#include "sodium.h" |
||||
#include <sodium.h> |
||||
#include <asio/ts/buffer.hpp> |
||||
#include <asio/ts/internet.hpp> |
||||
#include <asio/ts/io_context.hpp> |
||||
#include <asio/ts/net.hpp> |
||||
|
||||
namespace dvlnet { |
||||
typedef void(__stdcall *snet_event_func)(struct _SNETEVENT*); |
||||
class dvlnet_exception : public std::exception {}; |
||||
|
||||
class dvlnet { |
||||
public: |
||||
virtual int create(std::string addrstr, std::string passwd) = 0; |
||||
virtual int join(std::string addrstr, std::string passwd) = 0; |
||||
virtual bool SNetReceiveMessage(int *sender, char **data, int *size) = 0; |
||||
virtual bool SNetSendMessage(int dest, void *data, unsigned int size) = 0; |
||||
virtual bool SNetReceiveTurns(char **data, unsigned int *size, DWORD *status) = 0; |
||||
virtual bool SNetSendTurn(char *data, unsigned int size) = 0; |
||||
virtual int SNetGetProviderCaps(struct _SNETCAPS *caps) = 0; |
||||
virtual void *SNetRegisterEventHandler(event_type evtype, void(__stdcall *func)(struct _SNETEVENT *)) = 0; |
||||
virtual void *SNetUnregisterEventHandler(event_type evtype, void(__stdcall *func)(struct _SNETEVENT *)) = 0; |
||||
virtual bool SNetReceiveMessage(int* sender, char** data, |
||||
int* size) = 0; |
||||
virtual bool SNetSendMessage(int dest, void* data, |
||||
unsigned int size) = 0; |
||||
virtual bool SNetReceiveTurns(char** data, unsigned int* size, |
||||
DWORD* status) = 0; |
||||
virtual bool SNetSendTurn(char* data, unsigned int size) = 0; |
||||
virtual int SNetGetProviderCaps(struct _SNETCAPS* caps) = 0; |
||||
virtual void* SNetRegisterEventHandler(event_type evtype, |
||||
snet_event_func func) = 0; |
||||
virtual void* SNetUnregisterEventHandler(event_type evtype, |
||||
snet_event_func func) = 0; |
||||
virtual bool SNetLeaveGame(int type) = 0; |
||||
virtual ~dvlnet() {} |
||||
}; |
||||
} |
||||
|
||||
#include "dvlnet/packet.h" |
||||
#include "dvlnet/frame_queue.h" |
||||
#include "dvlnet/loopback.h" |
||||
#include "dvlnet/base.h" |
||||
#include "dvlnet/tcp_server.h" |
||||
#include "dvlnet/tcp_client.h" |
||||
#include "dvlnet/udp_p2p.h" |
||||
|
||||
@ -0,0 +1,74 @@
|
||||
#include "../types.h" |
||||
|
||||
using namespace dvlnet; |
||||
|
||||
size_t frame_queue::size() |
||||
{ |
||||
return current_size; |
||||
} |
||||
|
||||
buffer_t frame_queue::read(size_t s) |
||||
{ |
||||
if(current_size < s) |
||||
throw frame_queue_exception(); |
||||
buffer_t ret; |
||||
while(s > 0 && s >= buffer_deque.front().size()) { |
||||
s -= buffer_deque.front().size(); |
||||
current_size -= buffer_deque.front().size(); |
||||
ret.insert(ret.end(), |
||||
buffer_deque.front().begin(), |
||||
buffer_deque.front().end()); |
||||
buffer_deque.pop_front(); |
||||
} |
||||
if(s > 0) { |
||||
ret.insert(ret.end(), |
||||
buffer_deque.front().begin(), |
||||
buffer_deque.front().begin()+s); |
||||
buffer_deque.front().erase(buffer_deque.front().begin(), |
||||
buffer_deque.front().begin()+s); |
||||
current_size -= s; |
||||
} |
||||
return std::move(ret); |
||||
} |
||||
|
||||
void frame_queue::write(buffer_t buf) |
||||
{ |
||||
current_size += buf.size(); |
||||
buffer_deque.push_back(std::move(buf)); |
||||
} |
||||
|
||||
bool frame_queue::packet_ready() |
||||
{ |
||||
if(!nextsize) { |
||||
if(size() < sizeof(framesize_t)) |
||||
return false; |
||||
auto szbuf = read(sizeof(framesize_t)); |
||||
nextsize = *(reinterpret_cast<framesize_t*>(&szbuf[0])); |
||||
if(!nextsize) |
||||
throw frame_queue_exception(); |
||||
} |
||||
if(size() >= nextsize) |
||||
return true; |
||||
else |
||||
return false; |
||||
} |
||||
|
||||
buffer_t frame_queue::read_packet() |
||||
{ |
||||
if(!nextsize || (size() < nextsize)) |
||||
throw frame_queue_exception(); |
||||
auto ret = std::move(read(nextsize)); |
||||
nextsize = 0; |
||||
return std::move(ret); |
||||
} |
||||
|
||||
buffer_t frame_queue::make_frame(buffer_t packetbuf) |
||||
{ |
||||
buffer_t ret; |
||||
if(packetbuf.size() > max_frame_size) |
||||
ABORT(); |
||||
frame_queue::framesize_t size = packetbuf.size(); |
||||
ret.insert(ret.end(), packet_out::begin(size), packet_out::end(size)); |
||||
ret.insert(ret.end(), packetbuf.begin(), packetbuf.end()); |
||||
return std::move(ret); |
||||
} |
||||
@ -0,0 +1,24 @@
|
||||
#pragma once |
||||
|
||||
namespace dvlnet { |
||||
class frame_queue_exception : public dvlnet_exception {}; |
||||
|
||||
class frame_queue { |
||||
public: |
||||
typedef uint32_t framesize_t; |
||||
constexpr static framesize_t max_frame_size = 0xFFFF; |
||||
private: |
||||
size_t current_size = 0; |
||||
std::deque<buffer_t> buffer_deque; |
||||
size_t nextsize = 0; |
||||
|
||||
size_t size(); |
||||
buffer_t read(size_t s); |
||||
public: |
||||
bool packet_ready(); |
||||
buffer_t read_packet(); |
||||
void write(buffer_t buf); |
||||
|
||||
static buffer_t make_frame(buffer_t packetbuf); |
||||
}; |
||||
} |
||||
@ -0,0 +1,79 @@
|
||||
#include "../types.h" |
||||
|
||||
using namespace dvlnet; |
||||
|
||||
tcp_client::tcp_client(buffer_t info) : |
||||
base(std::move(info)) |
||||
{ |
||||
} |
||||
|
||||
int tcp_client::create(std::string addrstr, std::string passwd) |
||||
{ |
||||
local_server = std::make_unique<tcp_server>(ioc, addrstr, |
||||
default_port, passwd); |
||||
return join(local_server->localhost_self(), passwd); |
||||
} |
||||
|
||||
int tcp_client::join(std::string addrstr, std::string passwd) |
||||
{ |
||||
setup_password(passwd); |
||||
auto ipaddr = asio::ip::make_address(addrstr); |
||||
sock.connect(asio::ip::tcp::endpoint(ipaddr, default_port)); |
||||
start_recv(); |
||||
{ // hack: try to join for 5 seconds
|
||||
randombytes_buf(reinterpret_cast<unsigned char*>(&cookie_self), |
||||
sizeof(cookie_t)); |
||||
auto pkt = pktfty->make_packet<PT_JOIN_REQUEST>(PLR_BROADCAST, |
||||
PLR_MASTER, cookie_self, |
||||
game_init_info); |
||||
send(*pkt); |
||||
for (auto i = 0; i < 5; ++i) { |
||||
poll(); |
||||
if (plr_self != PLR_BROADCAST) |
||||
break; // join successful
|
||||
sleep(1); |
||||
} |
||||
} |
||||
return (plr_self == PLR_BROADCAST ? -1 : plr_self); |
||||
} |
||||
|
||||
void tcp_client::poll() |
||||
{ |
||||
ioc.poll(); |
||||
} |
||||
|
||||
void tcp_client::handle_recv(const asio::error_code& error, size_t bytes_read) |
||||
{ |
||||
if(error) |
||||
throw std::runtime_error(""); |
||||
if(bytes_read == 0) |
||||
throw std::runtime_error(""); |
||||
recv_buffer.resize(bytes_read); |
||||
recv_queue.write(std::move(recv_buffer)); |
||||
recv_buffer.resize(frame_queue::max_frame_size); |
||||
while(recv_queue.packet_ready()) { |
||||
auto pkt = pktfty->make_packet(recv_queue.read_packet()); |
||||
recv_local(*pkt); |
||||
} |
||||
start_recv(); |
||||
} |
||||
|
||||
void tcp_client::start_recv() |
||||
{ |
||||
sock.async_receive(asio::buffer(recv_buffer), |
||||
std::bind(&tcp_client::handle_recv, this, |
||||
std::placeholders::_1, std::placeholders::_2)); |
||||
} |
||||
|
||||
void tcp_client::handle_send(const asio::error_code& error, size_t bytes_sent) |
||||
{ |
||||
// empty for now
|
||||
} |
||||
|
||||
void tcp_client::send(packet& pkt) |
||||
{ |
||||
auto frame = frame_queue::make_frame(pkt.data()); |
||||
asio::async_write(sock, asio::buffer(frame), |
||||
std::bind(&tcp_client::handle_send, this, |
||||
std::placeholders::_1, std::placeholders::_2)); |
||||
} |
||||
@ -0,0 +1,27 @@
|
||||
#pragma once |
||||
|
||||
namespace dvlnet { |
||||
class tcp_client : public base { |
||||
public: |
||||
tcp_client(buffer_t info); |
||||
int create(std::string addrstr, std::string passwd); |
||||
int join(std::string addrstr, std::string passwd); |
||||
|
||||
static constexpr unsigned short default_port = 6112; |
||||
|
||||
virtual void poll(); |
||||
virtual void send(packet& pkt); |
||||
private: |
||||
std::unique_ptr<tcp_server> local_server; |
||||
|
||||
frame_queue recv_queue; |
||||
buffer_t recv_buffer = buffer_t(frame_queue::max_frame_size); |
||||
|
||||
asio::io_context ioc; |
||||
asio::ip::tcp::socket sock = asio::ip::tcp::socket(ioc); |
||||
|
||||
void handle_recv(const asio::error_code& error, size_t bytes_read); |
||||
void start_recv(); |
||||
void handle_send(const asio::error_code& error, size_t bytes_sent); |
||||
}; |
||||
} |
||||
@ -0,0 +1,200 @@
|
||||
#include "../types.h" |
||||
|
||||
using namespace dvlnet; |
||||
|
||||
tcp_server::tcp_server(asio::io_context& ioc, std::string bindaddr, |
||||
unsigned short port, std::string pw) : |
||||
ioc(ioc), pktfty(pw) |
||||
{ |
||||
auto addr = asio::ip::address::from_string(bindaddr); |
||||
auto ep = asio::ip::tcp::endpoint(addr, port); |
||||
acceptor = std::make_unique<asio::ip::tcp::acceptor>(ioc, ep); |
||||
start_accept(); |
||||
} |
||||
|
||||
std::string tcp_server::localhost_self() |
||||
{ |
||||
auto addr = acceptor->local_endpoint().address(); |
||||
if(addr.is_unspecified()) { |
||||
if(addr.is_v4()) { |
||||
return asio::ip::address_v4::loopback().to_string(); |
||||
} else if(addr.is_v6()) { |
||||
return asio::ip::address_v6::loopback().to_string(); |
||||
} else { |
||||
ABORT(); |
||||
} |
||||
} else { |
||||
return addr.to_string(); |
||||
} |
||||
} |
||||
|
||||
tcp_server::scc tcp_server::make_connection() |
||||
{ |
||||
return std::make_shared<client_connection>(ioc); |
||||
} |
||||
|
||||
plr_t tcp_server::next_free() |
||||
{ |
||||
for(plr_t i = 0; i < MAX_PLRS; ++i) |
||||
if(!connections[i]) |
||||
return i; |
||||
return PLR_BROADCAST; |
||||
} |
||||
|
||||
bool tcp_server::empty() |
||||
{ |
||||
for(plr_t i = 0; i < MAX_PLRS; ++i) |
||||
if(connections[i]) |
||||
return false; |
||||
return true; |
||||
} |
||||
|
||||
void tcp_server::start_recv(scc con) |
||||
{ |
||||
con->socket.async_receive(asio::buffer(con->recv_buffer), |
||||
std::bind(&tcp_server::handle_recv, this, con, |
||||
std::placeholders::_1, |
||||
std::placeholders::_2)); |
||||
} |
||||
|
||||
void tcp_server::handle_recv(scc con, const asio::error_code& ec, |
||||
size_t bytes_read) |
||||
{ |
||||
if(ec || bytes_read == 0) { |
||||
drop_connection(con); |
||||
return; |
||||
} |
||||
con->recv_buffer.resize(bytes_read); |
||||
con->recv_queue.write(std::move(con->recv_buffer)); |
||||
con->recv_buffer.resize(frame_queue::max_frame_size); |
||||
while(con->recv_queue.packet_ready()) { |
||||
try { |
||||
auto pkt = pktfty.make_packet(con->recv_queue.read_packet()); |
||||
if(con->plr == PLR_BROADCAST) { |
||||
handle_recv_newplr(con, *pkt); |
||||
} else { |
||||
con->timeout = timeout_active; |
||||
handle_recv_packet(*pkt); |
||||
} |
||||
} catch (dvlnet_exception e) { |
||||
drop_connection(con); |
||||
return; |
||||
} |
||||
} |
||||
start_recv(con); |
||||
} |
||||
|
||||
void tcp_server::send_connect(scc con) |
||||
{ |
||||
auto pkt = pktfty.make_packet<PT_CONNECT>(PLR_MASTER, PLR_BROADCAST, |
||||
con->plr); |
||||
send_packet(*pkt); |
||||
} |
||||
|
||||
void tcp_server::handle_recv_newplr(scc con, packet& pkt) |
||||
{ |
||||
auto newplr = next_free(); |
||||
if(newplr == PLR_BROADCAST) |
||||
throw server_exception(); |
||||
if(empty()) |
||||
game_init_info = pkt.info(); |
||||
auto reply = pktfty.make_packet<PT_JOIN_ACCEPT>(PLR_MASTER, PLR_BROADCAST, |
||||
pkt.cookie(), newplr, |
||||
game_init_info); |
||||
start_send(con, *reply); |
||||
con->plr = newplr; |
||||
connections[newplr] = con; |
||||
con->timeout = timeout_active; |
||||
send_connect(con); |
||||
} |
||||
|
||||
void tcp_server::handle_recv_packet(packet& pkt) |
||||
{ |
||||
send_packet(pkt); |
||||
} |
||||
|
||||
void tcp_server::send_packet(packet& pkt) |
||||
{ |
||||
if(pkt.dest() == PLR_BROADCAST) { |
||||
for(auto i = 0; i < MAX_PLRS; ++i) |
||||
if(i != pkt.src() && connections[i]) |
||||
start_send(connections[i], pkt); |
||||
} else { |
||||
if(pkt.dest() < 0 || pkt.dest() >= MAX_PLRS) |
||||
throw server_exception(); |
||||
if((pkt.dest() != pkt.src()) && connections[pkt.dest()]) |
||||
start_send(connections[pkt.dest()], pkt); |
||||
} |
||||
} |
||||
|
||||
void tcp_server::start_send(scc con, packet& pkt) |
||||
{ |
||||
auto frame = frame_queue::make_frame(pkt.data()); |
||||
asio::async_write(con->socket, asio::buffer(frame), |
||||
std::bind(&tcp_server::handle_send, this, con, |
||||
std::placeholders::_1, std::placeholders::_2)); |
||||
|
||||
} |
||||
|
||||
void tcp_server::handle_send(scc con, const asio::error_code& ec, |
||||
size_t bytes_sent) |
||||
{ |
||||
// empty for now
|
||||
} |
||||
|
||||
void tcp_server::start_accept() |
||||
{ |
||||
auto nextcon = make_connection(); |
||||
acceptor->async_accept(nextcon->socket, |
||||
std::bind(&tcp_server::handle_accept, |
||||
this, nextcon, |
||||
std::placeholders::_1)); |
||||
} |
||||
|
||||
void tcp_server::handle_accept(scc con, const asio::error_code& ec) |
||||
{ |
||||
if(next_free() == PLR_BROADCAST) { |
||||
drop_connection(con); |
||||
} else { |
||||
con->timeout = timeout_connect; |
||||
start_recv(con); |
||||
start_timeout(con); |
||||
} |
||||
start_accept(); |
||||
} |
||||
|
||||
void tcp_server::start_timeout(scc con) |
||||
{ |
||||
con->timer.expires_after(std::chrono::seconds(1)); |
||||
con->timer.async_wait(std::bind(&tcp_server::handle_timeout, this, con, |
||||
std::placeholders::_1)); |
||||
} |
||||
|
||||
void tcp_server::handle_timeout(scc con, const asio::error_code& ec) |
||||
{ |
||||
if(ec) { |
||||
drop_connection(con); |
||||
return; |
||||
} |
||||
if(con->timeout > 0) |
||||
con->timeout -= 1; |
||||
if(con->timeout < 0) |
||||
con->timeout = 0; |
||||
if(!con->timeout) { |
||||
drop_connection(con); |
||||
return; |
||||
} |
||||
start_timeout(con); |
||||
} |
||||
|
||||
void tcp_server::drop_connection(scc con) |
||||
{ |
||||
if(con->plr != PLR_BROADCAST) { |
||||
auto pkt = pktfty.make_packet<PT_DISCONNECT>(PLR_MASTER, PLR_BROADCAST, |
||||
con->plr, 0); |
||||
connections[con->plr] = nullptr; |
||||
send_packet(*pkt); |
||||
} |
||||
con->timer.cancel(); |
||||
con->socket.close(); |
||||
} |
||||
@ -0,0 +1,52 @@
|
||||
#pragma once |
||||
|
||||
namespace dvlnet { |
||||
class server_exception : public dvlnet_exception {}; |
||||
|
||||
class tcp_server { |
||||
public: |
||||
tcp_server(asio::io_context& ioc, std::string bindaddr, |
||||
unsigned short port, std::string pw); |
||||
std::string localhost_self(); |
||||
|
||||
private: |
||||
static constexpr int timeout_connect = 30; |
||||
static constexpr int timeout_active = 60; |
||||
|
||||
struct client_connection { |
||||
frame_queue recv_queue; |
||||
buffer_t recv_buffer = buffer_t(frame_queue::max_frame_size); |
||||
plr_t plr = PLR_BROADCAST; |
||||
asio::ip::tcp::socket socket; |
||||
asio::steady_timer timer; |
||||
int timeout; |
||||
client_connection(asio::io_context& ioc) : |
||||
socket(ioc), timer(ioc) {} |
||||
}; |
||||
|
||||
typedef std::shared_ptr<client_connection> scc; |
||||
|
||||
asio::io_context& ioc; |
||||
packet_factory pktfty; |
||||
std::unique_ptr<asio::ip::tcp::acceptor> acceptor; |
||||
std::array<scc, MAX_PLRS> connections; |
||||
buffer_t game_init_info; |
||||
|
||||
scc make_connection(); |
||||
plr_t next_free(); |
||||
bool empty(); |
||||
void start_accept(); |
||||
void handle_accept(scc con, const asio::error_code& ec); |
||||
void start_recv(scc con); |
||||
void handle_recv(scc con, const asio::error_code& ec, size_t bytes_read); |
||||
void handle_recv_newplr(scc con, packet& pkt); |
||||
void handle_recv_packet(packet& pkt); |
||||
void send_connect(scc con); |
||||
void send_packet(packet& pkt); |
||||
void start_send(scc con, packet& pkt); |
||||
void handle_send(scc con, const asio::error_code& ec, size_t bytes_sent); |
||||
void start_timeout(scc con); |
||||
void handle_timeout(scc con, const asio::error_code& ec); |
||||
void drop_connection(scc con); |
||||
}; |
||||
} |
||||
Loading…
Reference in new issue