Установка таймаута

Последнее обновление: 18.01.2018

При взаимодействии клиента и сервера мы можем устанавливать таймаут, по истечении которого соединение между сервером и клиентом при отсутствии взаимодействия будет разорвано. Для этого у типа net.Conn определены следующие методы:

  • SetDeadline(t time.Time) error: устанавливает таймаут на все операции ввода-вывода. Для установки времени применяется структура time.Time

  • SetReadDeadline(t time.Time) error: устанавливает таймаут на операции ввода в поток

  • SetWriteDeadline(t time.Time) error: устанавливает таймаут на операции вывода из потока

В каком случае они могут пригодиться? В прошлой теме было рассмотрено взаимодействие сервера и клиента. Для чтения данных от клиента сервер использовал буфер фиксированного размера:

input := make([]byte, (1024 * 4)) 
n, err := conn.Read(input)

Однако в ряде ситуаций это не лучший способ, особенно когда размер передаваемых данных превышает размер буфера. Мы можем точно не знать, сколько данных возвратит нам сервер. Поэтому определим следующий код клиента:

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("Введите слово: ") 
		_, err := fmt.Scanln(&source) 
		if err != nil { 
			fmt.Println("Некорректный ввод", err) 
			continue
		}
		// отправляем сообщение серверу
		if n, err := conn.Write([]byte(source));
		n == 0 || err != nil { 
			fmt.Println(err) 
			return 
		}
		// получем ответ
		fmt.Print("Перевод:")
		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()
	}
}

Теперь получение данных выделено в отдельный цикл for:

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))
}

Поэтому даже если сервер передал больше 1024 байт, все они все равно будут обработаны. Но кроме того, здесь также устанавливается таймаут на чтение данных. Перед самим циклом устанавливается таймаут в 5 секунд:

conn.SetReadDeadline(time.Now().Add(time.Second * 5))

Это значит, что клиент может ожидать данные на чтение от сервера в течении 5 секунд. По истечении этого времени операция чтения генерирует ошибку и соответственно происходит выход из цикла, где мы пытаемся прочитать данные от сервера. 5 секунд - довольно большой период, но в начале перед первым взаимодействием лучше устанавливать период побольше. И после прочтения первых 1024 байт таймаут сбрасывается до 700 миллисекунд. То есть если в течение последующих 700 милисекунд сервер не пришлет никаких данных, то происходит выход из цикла и соответственно чтение данных заканчивается.

Важно понимать роль подобных задержек, так как они позволяют сгенерировать ошибку при чтении данных. А значит мы можем получить эту ошибку и должным образом обработать ее, например, выйти из бесконечного цикла. Если бы мы не использовали установку таймаута, то могла бы сложиться ситуация, когда сервер ожидал данных от клиента в операции чтения, а клиент ожидал данных от сервера также в операции чтения. И была бы своего рода блокировка.

Код сервера остается тем же, что и в прошлой теме:

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)  // запускаем горутину для обработки запроса
	} 
}
// обработка подключения
func handleConnection(conn net.Conn) { 
	defer conn.Close()
	for {
		// считываем полученные в запросе данные
		input := make([]byte, (1024 * 4)) 
		n, err := conn.Read(input) 
		if n == 0 || err != nil {
			fmt.Println("Read error:", err)
			break
		}	 
		source := string(input[0:n])
		// на основании полученных данных получаем из словаря перевод 
		target, ok := dict[source]
		if ok == false{				// если данные не найдены в словаре
			target = "undefined"
		}
		// выводим на консоль сервера диагностическую информацию
		fmt.Println(source, "-", target)
		// отправляем данные клиенту
		conn.Write([]byte(target))
	}
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850