Possibili modi di implementare un proxy HTTP con meccanismo di priorità

2

Sto lavorando su un proxy HTTP TCP: è necessario trasformarlo in un proxy prioritario.

Il proxy standard utilizzato per gestire ogni connessione con fork e il processo figlio gestisce la connessione. Ora ho implementato un meccanismo di accodamento di priorità in cui i pacchetti provenienti da entrambi i lati delle connessioni vengono accodati e rimossi dalla coda: i pacchetti provenienti da client e server remoti vengono accodati nel mio proxy, mentre i pacchetti vengono rimossi dalle code e inviati ai client o ai server remoti. I pacchetti vengono inseriti con un algoritmo prioritario di mine e i pacchetti rimossi sono quelli con la priorità più alta.

Ho pensato tre possibili implementazioni del meccanismo receive packets from clients and servers and send packets with high priority to destination , ma nessuna delle tre mi soddisfa o ha qualche problema concettuale.

1) Il proxy di fork genera i processi figli, ognuno dei quali deve "parlare" con le code di priorità, nel processo main : ciò richiederebbe un qualche tipo di IPC e per uno scambio veloce di dati come una connessione TCP IPC non sembra una buona idea.

2) Potrei conservare tutto in main : loop con select su tutti i descrittori di file socket (lato client e server) memorizzati in una lettura fd_set (in modo che possa ricevere i pacchetti che arrivano al proxy e conservarli in code prioritarie). I pacchetti che devono essere inviati al client o al server vengono prima rimossi dalle code, ma il socket che userò per inviare il pacchetto potrebbe bloccarsi: quindi, rendere il socket non bloccante o generare un thread che invierà solo un pacchetto e poi morire?

3) La terza opzione consiste nell'utilizzare un thread per connessione: ancora una volta, ogni thread dovrà parlare con le code prio, inserendo pacchetti ricevuti da client e server in code e inviando pacchetti presi dalle code alla sua destinazione. Ma una richiesta HTTP CONNECT non è un semplice ciclo di ricezione dal server fd: richiede un ciclo select in cui i pacchetti ricevuti dall'esterno vengono accodati e nel frattempo devo inviare i pacchetti rimossi dalla coda alla destinazione.

Non sono il tipo faccio le mie cose per me , ma ho trovato solo tre soluzioni e non riesco a capire altri modi.

Lavorare su Ubuntu 14.04, C ++ 11.

Modifica Fornirò ulteriori informazioni: il seguente metodo viene chiamato dal thread di rimozione dopo aver rimosso il pacchetto con la massima priorità dalle code prio:

void dealPacket(Packet p) {
    int client = p.clientFD;
    int server;
    Request req = p.request;
    std::string payload = p.payload;

    // if there's no server file descriptor,
    // this is the request packet coming from clients
    if (p.server == -1) {
        // open remote server fd, connect to it 
        // and return the server fd
        server = openBindConnect(p.address);
        if (req == GET) {
            // send GET request to server
            if (sendGETRequest(server, payload)) {
                char data[1500];
                // while server sends packets, 
                // queue them up
                do {
                    Packet newPacket = craftPacket(data);
                    insertPacket(newPacket);
                } while (recv(server, data));
            }
        }
        else if (req == CONNECT) {
            // if connection was successful, must send 
            // a "200 OK" packet to client, then client and  
            // server will start sending and receiving
            if (send200OKtoClient(client)) {
                while (true) {
                    char data[1500];
                    FD_SET(client, &readset);
                    FD_SET(server, &readset);
                    // select() loop is used to check if 
                    // client or server sent packets; if so, 
                    // queue them up
                    select(maxFD, &readset);
                    if (FD_ISSET(client, &readset)) || if (FD_ISSET(server, &readset)) {
                        // receive Packets from client or server;
                        Packet newPacket = craftPacket(data);
                        // insert in queues
                        insertPacket(newPacket);
                    }
                }
            }
        }
    }
    // if this packets belong to an already existing connection, 
    // just send it (or close the socket pair <client,server> if 
    // this packet is the last one of one connection)
    else {
        server = p.serverFD;
        if (p.lastPacket) {
            close(client);
            close(server);
        }
        else {
            // send packet to destination
        }
    }
}

Come puoi vedere, è un metodo abbastanza discutibile: ci sono send e recv , e questo syscalls può bloccare il thread, che è il thread di rimozione, e nel frattempo le code di priorità potrebbero essere state riempite mentre il thread di rimozione è bloccato a causa di send o recv .

Potrebbe essere più comodo se il thread di rimozione fosse in grado di rimuovere pacchetti e lasciare che qualcos'altro li gestisse : per ogni pacchetto dequeued, il thread di rimozione può generare un thread che aprirà una nuova connessione (se il pacchetto è una richiesta HTTP) o invierà semplicemente il pacchetto alla sua destinazione. Ma così facendo, molti thread nascono e muoiono molto rapidamente, quindi hanno più interruttori di contesto del solito.

    
posta elmazzun 07.11.2016 - 17:05
fonte

1 risposta

0

Packets are inserted with a priority algorithm of mine, and packets removed are guaranteed to be the ones with highest priority.

Se ricevi sempre pacchetti basati sulla massima priorità, ci sarà un limite di carico in cui i pacchetti a bassa priorità non verranno mai elaborati, si accumuleranno e porteranno alla carenza di risorse nel proxy (sebbene ciò dipenda dal carico e dallo scenario di utilizzo - potrebbe non essere un problema che deve essere risolto nel tuo caso).

Le soluzioni che proponi presuppongono che il ciclo di elaborazione sia comune tra le implementazioni di client e server e gestiscono i pacchetti in modo trasparente (indipendentemente dal fatto che siano pacchetti client o server e indipendentemente dalla priorità del pacchetto) che non è visibile / utilizzato nel codice hai usato).

Considera le seguenti modifiche (questa è tutta speculativa e soggettiva):

  • usa un approccio basato su attività invece di uno basato su thread.

  • separa le priorità di esecuzione in più code di elaborazione, per priorità (pacchetti ad alta priorità, pacchetti a bassa priorità, attività di pulizia, come le connessioni di chiusura, qualunque sia).

  • queste code dovrebbero accettare i functors come input e restituire risultati futuri (vedi std :: future, std :: packaged_task, std :: promise).

  • Implementa l'esecuzione / priorità delle code separatamente dall'implementazione delle operazioni.

Possibile API (comune alle code di tutte le priorità):

/// perform operations passed through pointers on an internal thread
/// and when they are finished, set a promised result into a returned future
class queue
{
public:
    template<typename R, typename F> // F() should return a R instance
    auto add(F&& functor) -> std::future<R>;
};

Possibile codice cliente (ispirato al tuo codice sopra):

auto cleanupQueue = queue{ /* ??? priority arguments */ };
auto highPriorityQueue = queue{ /* high priority arguments */ };
auto lowPriorityQueue = queue{ /* low priority arguments */ };

/// snippet of code adapted from your question, for an example
void onClientConnect(Package p)
{
    // if connection was successful, must send 
    // a "200 OK" packet to client, then client and  
    // server will start sending and receiving

    auto response = highPriorityQueue.add([&]() {
        try
        {
            select(maxFD, &readset);
            ...
        }
        catch(const std::exception&)
        {
            cleanupQueue.add([]{ FD_Close( ??? ); }); // your socket here
            throw; // will get transmitted to "response" (this is the
                   // responsibility of the queue implementation you
                   // write; if you use std::packaged_task, you get
                   // this functionality already implemented
        }
    });

    // handle the promised response
    if(response.get()) // get blocks here until highPriorityQueue is finished
                       // will throw if the queued function threw
    {
        ...
    }
}

Un'altra modifica da prendere in considerazione per il tuo codice:

  • segue l'SRP: il ciclo nel tuo codice fa troppe cose (come la gestione di entrambi i pacchetti server e client allo stesso tempo). Se lo separi, suggerirà un design diverso e sarà più facile ragionare.

Un approccio basato sulle attività (cioè usando std :: asynch e / o futures) dovrebbe essere superiore a un approccio basato su thread qui, perché ti dà una divisione più naturale per i tuoi compiti (e non necessariamente dare la priorità al intera esecuzione dell'algoritmo, o priorità in base ai dati).

    
risposta data 09.11.2016 - 16:41
fonte

Leggi altre domande sui tag