środa, 9 grudnia 2009

Implementacja recvall() i sendall()

Na chwilę obecną żadna z bibliotek Go nie posiada metod, lub odpowiedników metod recvall() i sendall() dla połączeń strumieniowych (takich, jak np. TCP). Dzięki temu, że język ten jest bardzo zbliżony do C, zaimplementowanie własnych funkcji nie stanowi większego problemu.


Cała praca sprowadza się do utworzenia struktury przyjmującej obiekt zainicjowanego połączenia (dowolnego typu strumieniowego, np. TCPConn) i podpięcia do niej funkcji z pętlami odpowiedzialnymi za odebranie/wysłanie całego pakietu danych w strumieniu. Nasza przykładowa paczka może wyglądać np. tak:

package stream

import "bytes"
import "net"  
import "os"   


const BUFSIZE = 1024; // Zalecana wielokrotność 8                                                                                                                                                                                                                                       


// Definiowanie interfejsu nie jest konieczne
type IStreamSession interface {
    Close();                   
    RecvAll() ([]byte, os.Error);                                                                                                                                                                                                                     
    SendAll() os.Error;                                                                          
};                                                                                                                                                                                                        

type StreamSession struct {
    Conn                 *net.TCPConn;
};                                                                                                             

func (p *StreamSession) RecvAll() ([]byte, os.Error) {                                                    
    var amnt             int = 0;           
    var err              os.Error;                                                                                                  
    var buffer           [BUFSIZE]byte;                                                                                                                                                                                                                                          
    var data             []byte;                                               

    if p.Conn == nil {
        return nil, os.EINVAL;
    };                                                                                                                                                   

    for {
        amnt, err = p.Conn.Read(&buffer);                                                                                                                                                                                        
        if amnt > 0 {
            data = bytes.Join([][]byte{data, buffer[0:amnt]},
                              nil);
        };

        if err == os.EAGAIN {
            continue;
        } else if err != nil {
            return nil, err;
        };

        if amnt < BUFSIZE { break };
    };

    return data, nil;
};

func (p *StreamSession) SendAll(data []byte) os.Error {
    var amnt, data_size  int = 0, len(data);
    var sent             int = 0;
    var err              os.Error;

    if p.Conn == nil {
        return os.EINVAL;
    };

    for {
        amnt, err = p.Conn.Write(data[sent:data_size]);
        if err == os.EAGAIN {
            continue;
        } else if err != nil {
            return err;
        } else if amnt < (data_size - sent) {
            return os.EPIPE;
        } else {
            sent += amnt;
        };

        if sent == data_size { break };
    };

    return nil;
};

func (p *StreamSession) Close() os.Error {
    err := p.Conn.Close();
    p.Conn = nil;
    return err;
};

Zgodnie z zasadą "biblioteka nie gada z użytkownikiem", obsługa błędów jest delegowana do kodu wywołującego, dlatego trzeba pamiętać o sprawdzaniu wyniku. W przypadku braku problemów zmienna przechowująca błąd będzie miała wartość nil.

Przykładowe użycie można zademonstrować na prostym programie wysyłającym żądanie HTTP i odbierającym wynik:

package main

import "./stream"
import "fmt"
import "net"
import "os"
import "strings"


const HELLO = "GET / HTTP/1.0\n\n";

func perror(err *os.Error) {
    /* Tutaj należałoby się pobawić. Dla uproszczenia
       przykładu drukujemy tylko prosty komunikat.
     */
    fmt.Println(*err);
};


func main() {
    var data   []byte;
    var err    os.Error;
    var raddr  net.TCPAddr = net.TCPAddr{net.IPv4(127, 0, 0, 1),
                                          80};

    conn, err := net.DialTCP("ipv4", nil, &raddr);
    if err != nil {
        perror(&err);
        os.Exit(1);
    }

    var sess  stream.StreamSession = stream.StreamSession{conn};

    err = sess.SendAll(strings.Bytes(HELLO));
    if err != nil {
        perror(&err);
        sess.Close();
        os.Exit(1);
    };

    data, err = sess.RecvAll();
    if err != nil {
        perror(&err);
        sess.Close();
        os.Exit(1);
    } else {
        fmt.Print(string(data));
    };
    sess.Close();
};

Paczkę możemy rozbudować o funkcje fabryczne tworzące obiekty StreamSession na podstawie przekazanych danych znakowych (np. adresu IP, czy ścieżki do pliku gniazda), co pozwoli na zwolnienie kodu wywołującego z dowiązania do biblioteki net.
Have fun :)

Brak komentarzy:

Prześlij komentarz

Uwaga. Komentarze są moderowane i mogą nie pojawić się natychmiast po utworzeniu. Autor niniejszego bloga zastrzega sobie prawo do niedopuszczenia komentarzy będących SPAMem i/lub nie odnoszących się do komentowanego wpisu i/lub łamiących zasady kulturalnej wymiany opinii.