You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

201 lines
5.0 KiB

#include "pch.h"
#include "dvlnet/tcp_server.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() >= 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();
}