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.