summaryrefslogblamecommitdiff
path: root/src/common/socket.cpp
blob: e79f27acbd99f0430e52d2ae1d6a53320c0b939b (plain) (tree)
1
2
3
4
5
6
7
8





                                                               
                  
 




                        



                   


                                                            
 
               

                
 

                                  


                                        



                                                     
 
                                                  
 
                                      

 

                                 
 
                         
               
 

                                                                         
 


                                       
                                   
     
        
     

                             

 
                                   
 
                         
               
 
                                                                          
 

                

                                                   

                                                                  
                                              
         
                                   


        

                             

 
                               
 

                                     

 
 
                                          
 
                                      
                                            
 

                                                                           
     

                          
     
                     
     
                        

                     

                                                                        
                            
               

     






                                                                            
                                                 
                                                                
 









                                                                            
                          
 
                                    
 
                                                

                                                     
 

                                        





                                                 
 
                 

 
                                    
 
                                      

                                               
     
                          

                  

                        
 











                                                                            




                                                        

                                                      



                        
                             








                                                





                                            

 
                                                
 
                                      

                                               



                          

                        
 









                                                                            
 


                                           
 
                                    
 



                                                                
 
                          
 
                                                

                                                     
 

                                        




                                                 
 

                 

 
                            
 
                                   





                                                                              


                          


                                         


                           

                                                     




                             
                                                                         

                       
           

 
                                                                
 


                                                                 
                                                 



                                                                 
                                                 

                                  

 
                                  
 





                                                                              



                                                                     

 
                                
 
                              
                   
                                    
     
                                                 

                             
                           

                                         


                                                         




                               





                                          

                                      
                                       


                                          

 
                          
 
                                    








                                                                   
                                                        



                                       
                                                       


                            
                                                  

                       

 
                     
 
                       
                   

 
                                   
 
                                        
                        
 
                                     

                                            
                 
     

 
                        
 


                                    

 
                                                 
 
                                 
          
                     
             

 
                    
 
                                   
 
// $Id: socket.c,v 1.1.1.1 2004/09/10 17:44:49 MagicalTux Exp $
// original : core.c 2003/02/26 18:03:12 Rev 1.7

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <errno.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/time.h>
#include <unistd.h>

#include <fcntl.h>
#include <string.h>

#include "mmo.hpp"                // [Valaris] thanks to fov
#include "socket.hpp"
#include "utils.hpp"

fd_set readfds;
int  fd_max;
int  currentuse;

const uint32_t RFIFO_SIZE = 65536;
const uint32_t WFIFO_SIZE = 65536;

struct socket_data *session[FD_SETSIZE];

/// Discard all input
static void null_parse (int fd);
/// Default parser for new connections
static void (*default_func_parse) (int) = null_parse;

void set_defaultparse (void (*defaultparse) (int))
{
    default_func_parse = defaultparse;
}

/// Read from socket to the queue
static void recv_to_fifo (int fd)
{
    if (session[fd]->eof)
        return;

    ssize_t len = read (fd, session[fd]->rdata + session[fd]->rdata_size,
                        RFIFOSPACE (fd));

    if (len > 0)
    {
        session[fd]->rdata_size += len;
        session[fd]->connected = 1;
    }
    else
    {
        session[fd]->eof = 1;
    }
}

static void send_from_fifo (int fd)
{
    if (session[fd]->eof)
        return;

    ssize_t len = write (fd, session[fd]->wdata, session[fd]->wdata_size);

    if (len > 0)
    {
        session[fd]->wdata_size -= len;
        if (len < (ssize_t)session[fd]->wdata_size)
        {
            memmove (session[fd]->wdata, session[fd]->wdata + len,
                     session[fd]->wdata_size);
        }
        session[fd]->connected = 1;
    }
    else
    {
        session[fd]->eof = 1;
    }
}

static void null_parse (int fd)
{
    printf ("null_parse : %d\n", fd);
    RFIFOSKIP (fd, RFIFOREST (fd));
}


static void connect_client (int listen_fd)
{
    struct sockaddr_in client_address;
    socklen_t len = sizeof (client_address);

    int fd = accept (listen_fd, (struct sockaddr *) &client_address, &len);
    if (fd == -1)
    {
        perror ("accept");
        return;
    }
    if (fd_max <= fd)
    {
        fd_max = fd + 1;
    }
    if (!free_fds ())
    {
        fprintf (stderr, "softlimit reached, disconnecting : %d\n", fd);
        delete_session (fd);
        return;
    }

    const int yes = 1;
    /// Allow to bind() again after the server restarts.
    // Since the socket is still in the TIME_WAIT, there's a possibility
    // that formerly lost packets might be delivered and confuse the server.
    setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
    /// Send packets as soon as possible
    /// even if the kernel thinks there is too little for it to be worth it!
    /// Testing shows this is indeed a good idea.
    setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes);

    // Linux-ism: Set socket options to optimize for thin streams
    // See http://lwn.net/Articles/308919/ and
    // Documentation/networking/tcp-thin.txt .. Kernel 3.2+
#ifdef TCP_THIN_LINEAR_TIMEOUTS
    setsockopt(fd, IPPROTO_TCP, TCP_THIN_LINEAR_TIMEOUTS, &yes, sizeof yes);
#endif
#ifdef TCP_THIN_DUPACK
    setsockopt(fd, IPPROTO_TCP, TCP_THIN_DUPACK, &yes, sizeof yes);
#endif

    FD_SET (fd, &readfds);

    fcntl (fd, F_SETFL, O_NONBLOCK);

    CREATE (session[fd], struct socket_data, 1);
    CREATE (session[fd]->rdata, uint8_t, RFIFO_SIZE);
    CREATE (session[fd]->wdata, uint8_t, WFIFO_SIZE);

    session[fd]->max_rdata = RFIFO_SIZE;
    session[fd]->max_wdata = WFIFO_SIZE;
    session[fd]->func_recv = recv_to_fifo;
    session[fd]->func_send = send_from_fifo;
    session[fd]->func_parse = default_func_parse;
    session[fd]->client_addr = client_address;
    session[fd]->created = time (NULL);
    session[fd]->connected = 0;

    currentuse++;
}

int make_listen_port (uint16_t port)
{
    struct sockaddr_in server_address;
    int  fd = socket (AF_INET, SOCK_STREAM, 0);
    if (fd == -1)
    {
        perror ("socket");
        return -1;
    }
    if (fd_max <= fd)
        fd_max = fd + 1;

    fcntl (fd, F_SETFL, O_NONBLOCK);

    const int yes = 1;
    /// Allow to bind() again after the server restarts.
    // Since the socket is still in the TIME_WAIT, there's a possibility
    // that formerly lost packets might be delivered and confuse the server.
    setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
    /// Send packets as soon as possible
    /// even if the kernel thinks there is too little for it to be worth it!
    // I'm not convinced this is a good idea; although in minimizes the
    // latency for an individual write, it increases traffic in general.
    setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes);

    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htonl (INADDR_ANY);
    server_address.sin_port = htons (port);

    if (bind (fd, (struct sockaddr *) &server_address,
              sizeof (server_address)) == -1)
    {
        perror ("bind");
        exit (1);
    }
    if (listen (fd, 5) == -1)
    {                           /* error */
        perror ("listen");
        exit (1);
    }

    FD_SET (fd, &readfds);

    CREATE (session[fd], struct socket_data, 1);

    session[fd]->func_recv = connect_client;
    session[fd]->created = time (NULL);
    session[fd]->connected = 1;

    currentuse++;
    return fd;
}

int make_connection (uint32_t ip, uint16_t port)
{
    struct sockaddr_in server_address;
    int  fd = socket (AF_INET, SOCK_STREAM, 0);
    if (fd == -1)
    {
        perror ("socket");
        return -1;
    }
    if (fd_max <= fd)
        fd_max = fd + 1;

    const int yes = 1;
    /// Allow to bind() again after the server restarts.
    // Since the socket is still in the TIME_WAIT, there's a possibility
    // that formerly lost packets might be delivered and confuse the server.
    setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
    /// Send packets as soon as possible
    /// even if the kernel thinks there is too little for it to be worth it!
    // I'm not convinced this is a good idea; although in minimizes the
    // latency for an individual write, it increases traffic in general.
    setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes);

    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = ip;
    server_address.sin_port = htons (port);

    fcntl (fd, F_SETFL, O_NONBLOCK);

    /// Errors not caught - we must not block
    /// Let the main select() loop detect when we know the state
    connect (fd, (struct sockaddr *) &server_address,
             sizeof (struct sockaddr_in));

    FD_SET (fd, &readfds);

    CREATE (session[fd], struct socket_data, 1);
    CREATE (session[fd]->rdata, uint8_t, RFIFO_SIZE);
    CREATE (session[fd]->wdata, uint8_t, WFIFO_SIZE);

    session[fd]->max_rdata = RFIFO_SIZE;
    session[fd]->max_wdata = WFIFO_SIZE;
    session[fd]->func_recv = recv_to_fifo;
    session[fd]->func_send = send_from_fifo;
    session[fd]->func_parse = default_func_parse;
    session[fd]->created = time (NULL);
    session[fd]->connected = 1;

    currentuse++;
    return fd;
}

void delete_session (int fd)
{
    if (fd < 0 || fd >= FD_SETSIZE)
        return;
    // If this was the highest fd, decrease it
    // We could add a loop to decrement fd_max further for every null session,
    // but this is cheap and good enough for the typical case
    if (fd == fd_max - 1)
        fd_max--;
    FD_CLR (fd, &readfds);
    if (session[fd])
    {
        free (session[fd]->rdata);
        free (session[fd]->wdata);
        free (session[fd]->session_data);
        free (session[fd]);
    }
    session[fd] = NULL;

    // just close() would try to keep sending buffers
    shutdown (fd, SHUT_RDWR);
    close (fd);
    currentuse--;
    if (currentuse < 0)
    {
        fprintf (stderr, "delete_session: current sessions negative!\n");
        currentuse = 0;
    }
    return;
}

void realloc_fifo (int fd, size_t rfifo_size, size_t wfifo_size)
{
    struct socket_data *s = session[fd];
    if (s->max_rdata != rfifo_size && s->rdata_size < rfifo_size)
    {
        RECREATE (s->rdata, uint8_t, rfifo_size);
        s->max_rdata = rfifo_size;
    }
    if (s->max_wdata != wfifo_size && s->wdata_size < wfifo_size)
    {
        RECREATE (s->wdata, uint8_t, wfifo_size);
        s->max_wdata = wfifo_size;
    }
}

void WFIFOSET (int fd, size_t len)
{
    struct socket_data *s = session[fd];
    if (s->wdata_size + len + 16384 > s->max_wdata)
    {
        realloc_fifo (fd, s->max_rdata, s->max_wdata << 1);
        printf ("socket: %d wdata expanded to %d bytes.\n", fd, s->max_wdata);
    }
    if (s->wdata_size + len + 2048 < s->max_wdata)
        s->wdata_size += len;
    else
        fprintf (stderr, "socket: %d wdata lost !!\n", fd), abort ();
}

void do_sendrecv (uint32_t next)
{
    fd_set rfd = readfds, wfd;
    FD_ZERO (&wfd);
    for (int i = 0; i < fd_max; i++)
    {
        if (session[i] && session[i]->wdata_size)
            FD_SET (i, &wfd);
    }
    struct timeval timeout;
    timeout.tv_sec = next / 1000;
    timeout.tv_usec = next % 1000 * 1000;
    if (select (fd_max, &rfd, &wfd, NULL, &timeout) <= 0)
        return;
    for (int i = 0; i < fd_max; i++)
    {
        if (!session[i])
            continue;
        if (FD_ISSET (i, &wfd))
        {
            if (session[i]->func_send)
                //send_from_fifo(i);
                session[i]->func_send (i);
        }
        if (FD_ISSET (i, &rfd))
        {
            if (session[i]->func_recv)
                //recv_to_fifo(i);
                //or connect_client(i);
                session[i]->func_recv (i);
        }
    }
}

void do_parsepacket (void)
{
    for (int i = 0; i < fd_max; i++)
    {
        if (!session[i])
            continue;
        if (!session[i]->connected
            && time (NULL) - session[i]->created > CONNECT_TIMEOUT)
        {
            printf ("Session #%d timed out\n", i);
            session[i]->eof = 1;
        }
        if (!session[i]->rdata_size && !session[i]->eof)
            continue;
        if (session[i]->func_parse)
        {
            session[i]->func_parse (i);
            /// some func_parse may call delete_session
            if (!session[i])
                continue;
        }
        /// Reclaim buffer space for what was read
        RFIFOFLUSH (i);
    }
}

void do_socket (void)
{
    FD_ZERO (&readfds);
    currentuse = 3;
}

void RFIFOSKIP (int fd, size_t len)
{
    struct socket_data *s = session[fd];
    s->rdata_pos += len;

    if (s->rdata_size < s->rdata_pos)
    {
        fprintf (stderr, "too many skip\n");
        abort ();
    }
}

void fclose_ (FILE * fp)
{
    if (fclose (fp))
        perror ("fclose"), abort ();
    currentuse--;
}

FILE *fopen_ (const char *path, const char *mode)
{
    FILE *f = fopen (path, mode);
    if (f)
        currentuse++;
    return f;
}

bool free_fds (void)
{
    return currentuse < SOFT_LIMIT;
}