src/proto2/NetworkTCP.cc
author terom
Mon, 10 Nov 2008 16:49:09 +0000
branchno-netsession
changeset 31 d0d7489d4e8b
child 32 2ff929186c90
permissions -rw-r--r--
add initial code written so far

#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);

}