Setarea unui timeout
În interacțiunea dintre client și server, putem seta un timeout, după care conexiunea între server și client va fi închisă în cazul în care nu există nicio interacțiune. Pentru aceasta, tipul net.Conn definește următoarele metode:
- SetDeadline(t time.Time) error: setează un timeout pentru toate operațiile de intrare-ieșire. Pentru a seta timpul se folosește structura time.Time
- SetReadDeadline(t time.Time) error: setează un timeout pentru operațiile de citire din flux
- SetWriteDeadline(t time.Time) error: setează un timeout pentru operațiile de scriere în flux
În ce situație sunt utile aceste metode? În tema anterioară am discutat despre interacțiunea dintre server și client. Pentru citirea datelor de la client, serverul folosea un buffer de dimensiune fixă:
input := make([]byte, (1024 * 4))
n, err := conn.Read(input)
Totuși, în anumite situații aceasta nu este cea mai bună abordare, mai ales atunci când dimensiunea datelor transmise depășește dimensiunea buffer-ului. Este posibil să nu știm exact câte date va returna serverul. Prin urmare, să definim următorul cod al clientului:
package main
import (
"fmt"
"net"
"time"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:4545")
if err != nil {
fmt.Println(err)
return
}
defer conn.Close()
for {
var source string
fmt.Print("Introduceți cuvântul: ")
_, err := fmt.Scanln(&source)
if err != nil {
fmt.Println("Intrare incorectă", err)
continue
}
// trimitem mesajul serverului
if n, err := conn.Write([]byte(source)); n == 0 || err != nil {
fmt.Println(err)
return
}
// primim răspunsul
fmt.Print("Traducere:")
conn.SetReadDeadline(time.Now().Add(time.Second * 5))
for {
buff := make([]byte, 1024)
n, err := conn.Read(buff)
if err != nil { break }
fmt.Print(string(buff[0:n]))
conn.SetReadDeadline(time.Now().Add(time.Millisecond * 700))
}
fmt.Println()
}
}
Acum obținerea datelor este gestionată într-un ciclu for separat:
for {
buff := make([]byte, 1024)
n, err := conn.Read(buff)
if err != nil { break }
fmt.Print(string(buff[0:n]))
conn.SetReadDeadline(time.Now().Add(time.Millisecond * 700))
}
Prin urmare, chiar dacă serverul trimite mai mult de 1024 de octeți, aceștia vor fi totuși procesați. Mai mult, aici se setează și un timeout pentru citirea datelor. Înainte de ciclul propriu-zis se setează un timeout de 5 secunde:
conn.SetReadDeadline(time.Now().Add(time.Second * 5))
Aceasta înseamnă că clientul va aștepta datele de la server pentru citire timp de 5 secunde. După această perioadă, operația de citire va genera o eroare și, prin urmare, va ieși din ciclul în care încercăm să citim datele de la server. 5 secunde reprezintă o perioadă destul de mare, dar la început, înainte de prima interacțiune, este mai bine să setăm un interval mai mare.
După citirea primilor 1024 de octeți, timeout-ul este resetat la 700 milisecunde. Astfel, dacă serverul nu trimite date în următoarele 700 de milisecunde, ciclul va fi întrerupt și citirea va înceta.
Este important să înțelegem rolul acestor întârzieri, deoarece ele permit generarea unei erori la citirea datelor. Astfel, putem primi această eroare și o putem trata corespunzător, de exemplu, ieșind din ciclul infinit.
Dacă nu am fi folosit setarea unui timeout, ar putea apărea o situație în care serverul așteaptă date de la client într-o operație de citire, iar clientul așteaptă date de la server într-o operație de citire, ceea ce ar duce la un blocaj.
Codul serverului rămâne același ca și în tema anterioară:
package main
import (
"fmt"
"net"
)
var dict = map[string]string{
"red": "красный",
"green": "зеленый",
"blue": "синий",
"yellow": "желтый",
}
func main() {
listener, err := net.Listen("tcp", ":4545")
if err != nil {
fmt.Println(err)
return
}
defer listener.Close()
fmt.Println("Server is listening...")
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println(err)
conn.Close()
continue
}
go handleConnection(conn) // lansăm o gorutină pentru a procesa cererea
}
}
// procesarea conexiunii
func handleConnection(conn net.Conn) {
defer conn.Close()
for {
// citim datele primite în cerere
input := make([]byte, (1024 * 4))
n, err := conn.Read(input)
if n == 0 || err != nil {
fmt.Println("Eroare la citire:", err)
break
}
source := string(input[0:n])
// pe baza datelor primite, obținem traducerea din dicționar
target, ok := dict[source]
if ok == false { // dacă datele nu sunt găsite în dicționar
target = "nedefinit"
}
// afișăm informații de diagnostic pe consola serverului
fmt.Println(source, "-", target)
// trimitem datele clientului
conn.Write([]byte(target))
}
}