#include "NetworkTCP.hh"
#include <cstdlib>
#include <cassert>
NetworkBuffer::NetworkBuffer (NetworkSocket &socket, size_t size_hint) :
socket(socket), buf(0), size(0), offset(0) {
// allocate initial buffer
if ((buf = (char *) malloc(size_hint)) == NULL)
throw CL_Error("malloc failed");
// remember size
size = size_hint;
}
void NetworkBuffer::resize (size_t new_size) {
// realloc buffer
if ((buf = (char *) realloc((void *) buf, new_size)) == NULL)
throw CL_Error("realloc failed");
// update size
size = new_size;
}
void NetworkBuffer::write (const char *buf_ptr, size_t buf_size) {
int ret;
// try and short-circuit writes unless we have already buffered data
if (offset == 0) {
try {
// attempt to send something
ret = socket.send(buf_ptr, buf_size);
} catch (CL_Error &e) {
// ignore EAGAIN, detect this by setting ret to -1
if (errno != EAGAIN)
throw;
ret = -1;
}
// if we managed to send something, adjust buf/size and buffer
if (ret > 0) {
buf_ptr += ret;
buf_size -= ret;
// sanity-check
assert(buf_size >= 0);
// if that was all, we're done
if (buf_size == 0)
return;
}
}
size_t new_size = size;
// calcluate new buffer size
while (offset + buf_size > new_size)
new_size *= 2;
// grow internal buffer if needed
if (new_size != size)
resize(new_size);
// copy into our internal buffer
memcpy(buf + offset, buf_ptr, buf_size);
}
void NetworkBuffer::flush_write (void) {
int ret;
// ignore if we don't have any data buffered
if (offset == 0)
return;
// attempt to write as much as possible
try {
ret = socket.send(buf, offset)
} catch (CL_Error &e) {
// ignore EAGAIN and just return
if (errno == EAGAIN)
return;
else
throw;
}
// update offset
offset -= ret;
// shift the buffer forwards from (buf + ret) -> (buf), copying (old_offset - ret) bytes
memmove(buf, buf + ret, offset);
}
NetworkTCPTransport::NetworkTCPTransport (CL_Socket socket) :
socket(socket), in(NETWORK_TCP_INITIAL_IN_BUF), out(NETWORK_TCP_INITIAL_OUT_BUF) {
// use nonblocking sockets
socket.set_nonblocking(true);
// connect signals
slots.connect(socket.sig_read_triggered(), this, &NetworkTCPTransport::on_read);
slots.connect(socket.sig_write_triggered(), this, &NetworkTCPTransport::on_write);
slots.connect(socket.sig_disconnected_triggered(), this, &NetworkTCPTransport::on_disconnected);
}
void NetworkTCPTransport::on_read (void) {
NetworkPacket packet;
do {
size_t to_read = 0;
// guess how much data to receive based on either the given length prefix or our minimim chunk size
if (in.read_prefix<uint16_t>(to_read) == false || to_read < NETWORK_TCP_CHUNK_SIZE)
to_read = NETWORK_TCP_CHUNK_SIZE
// do the recv
if (in.recv(socket, to_read) == -1)
// read out any packets
while (in.read_prefix_packet<uint16_t>(packet)) {
handle_packet(packet);
}
} while (...);
}
void NetworkTCPTransport::on_write (void) {
// just flush the output buffer
out.flush_write();
}
void NetworkTCPTransport::on_disconnected (void) {
}
void NetworkTCPTransport::write_packet (const NetworkPacket &packet) {
// just write to the output buffer
out.write(packet.get_buf(), packet.get_size());
}
NetworkTCPServer::NetworkTCPServer (const NetworkAddress &listen_addr) :
socket(tcp, ipv4) {
// bind
socket.bind(listen_addr);
// assign slots
slots.connect(socket.sig_read_triggered(), this, &NetworkTCPServer::on_accept);
// listen
socket.listen(NETWORK_LISTEN_BACKLOG);
}
void NetworkTCPServer::on_accept (void) {
// accept a new socket
NetworkSocket client_sock = socket.accept();
// create a new NetworkTCPTransport
NetworkTCPTransport *client = new NetworkTCPTransport(client_sock);
// let our user handle it
handle_client(client);
}
NetworkTCPClient::NetworkTCPClient (const NetworkAddress &connect_addr) :
NetworkTCPTransport(NetworkSocket(tcp, ipv4)) {
// connect
socket.connect(connect_addr);
}