Go: Programação Distribuída

De Aulas
Revisão de 17h25min de 4 de novembro de 2021 por Admin (discussão | contribs) (→‎UDP Servidor)

TCP Cliente

Efetua uma conexão TCP com um servidor. Aguarda o usuário digitar mensagens e dar ENTER. Para saír, basta digitar EXIT.

 1package main
 2
 3import (
 4	"bufio"
 5	"fmt"
 6	"net"
 7	"os"
 8	"strings"
 9)
10
11func main() {
12	arguments := os.Args     // Pega os argumentos da linha de comando
13	if len(arguments) == 1 { // Se não tiver argumentos retorna erro
14		fmt.Println("Enter with arguments host:port.")
15		return
16	}
17
18	// Usa os argumentos e se conecta ao servidor host:port
19	c, err := net.Dial("tcp", arguments[1])
20	if err != nil {
21		fmt.Println(err)
22		return
23	}
24	defer c.Close()
25	fmt.Print("Digit your message and press key ENTER to send.")
26	fmt.Println("To exit, digit EXIT and press key exit.")
27	for {
28		reader := bufio.NewReader(os.Stdin) // Prepara o buffer de leitura
29		fmt.Print("MSG: ")
30		text, _ := reader.ReadString('\n') // Le um texto do teclado
31		fmt.Fprintf(c, text+"\n")          // Envia o texto pela conexão
32
33		message, _ := bufio.NewReader(c).ReadString('\n') // Aguarda resposta do servidor
34		fmt.Print("RCV: " + message)
35		// Se a resposta for EXIT, fecha a conexão e o cliente
36		if strings.ToUpper(strings.TrimSpace(string(text))) == "EXIT" {
37			fmt.Println("TCP client exiting...")
38			return
39		}
40	}
41}

TCP Servidor Simples

Inicia o servidor e fica aguardando um cliente. Recebe as mensagens do cliente. Quando o cliente envia EXIT, fecha o servidor e a conexão.

 1package main
 2
 3import (
 4	"bufio"
 5	"fmt"
 6	"net"
 7	"os"
 8	"strings"
 9	"time"
10)
11
12func main() {
13	arguments := os.Args     // Pega a porta como argumento da linha de comando
14	if len(arguments) == 1 { // Se não for passado a porta dá erro
15		fmt.Println("Enter with port number in argument")
16		return
17	}
18	l, err := net.Listen("tcp", ":"+arguments[1]) // Inicia a conexão TCP na porta
19	if err != nil {
20		fmt.Println(err)
21		return
22	}
23	defer l.Close() // Ao final, fecha a conexão.
24	fmt.Println("TCP Server initialized at port", arguments[1])
25
26	c, err := l.Accept() // Fica aguardando um cliente se conectar
27	if err != nil {
28		fmt.Println(err)
29		return
30	}
31	fmt.Println("Client connected...")
32
33	for { // Laço eterno
34		// Recebe informações no buffer de leitura
35		netData, err := bufio.NewReader(c).ReadString('\n')
36		if err != nil {
37			fmt.Println(err)
38			return
39		}
40		// Se for EXT, fecha o sevidor, mas fecha a conexão antes
41		if strings.ToUpper(strings.TrimSpace(string(netData))) == "EXIT" {
42			fmt.Println("TCP Server terminated...")
43			return
44		}
45
46		// Mostra a mensagem na tela e envia de volta o horário
47		fmt.Print(": ", string(netData))
48		t := time.Now()
49		myTime := t.Format(time.RFC3339) + "\n"
50		c.Write([]byte(myTime)) // Escreve no buffer de escrita para o cliente
51	}
52}

TCP Servidor com múltiplas conexões

Observe que agora temos um servidor diferente. No laço for do main ficamos aguardando um cliente se conectar. Quando houver uma conexão, essa conexão é transferida para uma função child que é chamada concorrentemente como gorotine e continua o laço, aguardando uma nova conexão.

Cada gorotine, ora chamada child, vai ficar responsável por gerenciar uma conexão específica. Quando o cliente mandar a palavra exit, não fecha o servidor, apenas aquela conexão.

Dessa forma, nosso servidor pode trabalhar com múltiplas conexões simultâneas, respondendo concorrentemente há vários clientes.

Veja que no código, quando um cliente manda uma mensagem, escrevemos qual o endereço da conexão para identificar a mensagem e diferenciá-la das mensagens dos outros clientes.

package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
	"time"
)

func child(conn net.Conn) {
	addr := conn.RemoteAddr() // Pega o endereço do cliente
	fmt.Println(addr, ": connected...")
	defer fmt.Println(addr, ": disconnected...")
	defer conn.Close() // Ao final, fecha a conexão

	for {
		// Recebe informações no buffer de leitura
		netData, err := bufio.NewReader(conn).ReadString('\n')
		if err != nil {
			fmt.Println(err)
			return
		}
		// Se for EXIT, fecha fecha a conexão
		if strings.ToUpper(strings.TrimSpace(string(netData))) == "EXIT" {
			fmt.Println(addr, ": exit...")
			return
		}
		// Mostra a mensagem na tela e envia de volta o horário
		fmt.Print(addr, " : ", string(netData))
		t := time.Now()
		myTime := t.Format(time.RFC3339) + "\n"
		conn.Write([]byte(myTime)) // Escreve no buffer de escrita para o cliente
	}
}

func main() {
	arguments := os.Args     // Pega a porta como argumento da linha de comando
	if len(arguments) == 1 { // Se não for passado a porta dá erro
		fmt.Println("Enter with port number in argument")
		return
	}
	l, err := net.Listen("tcp", ":"+arguments[1]) // Configura uma porta TCP
	if err != nil {
		fmt.Println(err)
		return
	}
	defer l.Close() // Ao final, fecha a conexão.
	fmt.Println("TCP Server initialized at port", arguments[1])

	for {
		c, err := l.Accept() // Aguarda o próximo cliente se conectar
		if err != nil {
			fmt.Println(err)
			return
		}
		go child(c) // Passa a conexão para uma gorotine gerenciar
	}
}

Daria pra fazer um chat de grupo com isso, não? É um bom exercício.

UDP Cliente

Veja que a conexão com um servidor UPD é diferente, então o cliente UDP também precisa ser específico pra isso. Por isso, não tente usar um cliente TCP para se conectar a um servidor UDP, ou melhor, tente pra ver o que acontece (¬‿¬)

Veja que aqui temos uma troca de mensagens via buffer de bytes.

 1package main
 2
 3import (
 4	"bufio"
 5	"fmt"
 6	"net"
 7	"os"
 8	"strings"
 9)
10
11func main() {
12	arguments := os.Args
13	if len(arguments) == 1 {
14		fmt.Println("Please provide a host:port string")
15		return
16	}
17	CONNECT := arguments[1]
18
19	s, err := net.ResolveUDPAddr("udp4", CONNECT)
20	c, err := net.DialUDP("udp4", nil, s)
21	if err != nil {
22		fmt.Println(err)
23		return
24	}
25
26	fmt.Printf("The UDP server is %s\n", c.RemoteAddr().String())
27	defer c.Close()
28
29	for {
30		reader := bufio.NewReader(os.Stdin)
31		fmt.Print(">> ")
32		text, _ := reader.ReadString('\n')
33		data := []byte(text + "\n")
34		_, err = c.Write(data)
35		if strings.ToUpper(strings.TrimSpace(string(data))) == "EXIT" {
36			fmt.Println("Exiting UDP client!")
37			return
38		}
39
40		if err != nil {
41			fmt.Println(err)
42			return
43		}
44
45		buffer := make([]byte, 1024)
46		n, _, err := c.ReadFromUDP(buffer)
47		if err != nil {
48			fmt.Println(err)
49			return
50		}
51		fmt.Printf("Reply: %s\n", string(buffer[0:n]))
52	}
53}

UDP Servidor

E então, nosso servidor UDP. É muito parecido com o TCP, mas configurado de forma diferente e agora trabalha com mensagens na forma de buffer de bytes.

 1package main
 2
 3import (
 4	"fmt"
 5	"math/rand"
 6	"net"
 7	"os"
 8	"strconv"
 9	"strings"
10	"time"
11)
12
13func random(min, max int) int {
14	return rand.Intn(max-min) + min
15}
16
17func main() {
18	arguments := os.Args
19	if len(arguments) == 1 {
20		fmt.Println("Please provide a port number!")
21		return
22	}
23	PORT := ":" + arguments[1]
24
25	s, err := net.ResolveUDPAddr("udp4", PORT)
26	if err != nil {
27		fmt.Println(err)
28		return
29	}
30
31	connection, err := net.ListenUDP("udp4", s)
32	if err != nil {
33		fmt.Println(err)
34		return
35	}
36
37	defer connection.Close()
38	buffer := make([]byte, 1024)
39	rand.Seed(time.Now().Unix())
40
41	for {
42		/*
43			Veja que estamos trabalhando com leitura em buffer, em bytes.
44			A função ReadFromUDP retorna também o tamanho da mensagem que
45			estamos recebendo no buffer e transformamos em string
46		*/
47		n, addr, err := connection.ReadFromUDP(buffer)
48		fmt.Print(addr, " -> ", string(buffer[0:n-1]))
49
50		// Se a mensagem for exit, fechamos a conexão e o servidor
51		if strings.ToUpper(strings.TrimSpace(string(buffer[0:n]))) == "EXIT" {
52			fmt.Println("Exiting UDP server!")
53			return
54		}
55
56		data := []byte(strconv.Itoa(rand.Intn(1000)))
57		fmt.Println(addr, "<-", string(data))
58		_, err = connection.WriteToUDP(data, addr)
59		if err != nil {
60			fmt.Println(err)
61			return
62		}
63	}
64}

Exemplo de Computação Distribuída

Servidor

 1package main
 2
 3import (
 4	"bufio"
 5	"encoding/json"
 6	"fmt"
 7	"net"
 8	"os"
 9	"strconv"
10)
11
12type Message struct {
13	Begin int `json:"begin"`
14	End   int `json:"end"`
15}
16
17func child(conn net.Conn) {
18	defer conn.Close()
19	netData, err := bufio.NewReader(conn).ReadString('\n')
20	if err != nil {
21		fmt.Println(err)
22		return
23	}
24	msg := Message{}
25	json.Unmarshal([]byte(netData), &msg)
26	out := 1
27	for i := msg.Begin; i <= msg.End; i++ {
28		out *= i
29	}
30	fmt.Println(msg.Begin, "->", msg.End, ":", out)
31	conn.Write([]byte(strconv.Itoa(out)))
32}
33
34func main() {
35	arguments := os.Args
36	if len(arguments) == 1 {
37		fmt.Println("Enter with port number in argument")
38		return
39	}
40	l, err := net.Listen("tcp", ":"+arguments[1])
41	if err != nil {
42		fmt.Println(err)
43		return
44	}
45	defer l.Close()
46	fmt.Println("TCP Server initialized at port", arguments[1])
47
48	for {
49		c, err := l.Accept()
50		if err != nil {
51			fmt.Println(err)
52			return
53		}
54		go child(c)
55	}
56}

Cliente

 1package main
 2
 3import (
 4	"bufio"
 5	"encoding/json"
 6	"fmt"
 7	"net"
 8	"os"
 9	"strconv"
10	"sync"
11)
12
13type Message struct { // Estrutura da mensagem
14	Begin int `json:"begin"`
15	End   int `json:"end"`
16}
17
18func connect_server(wg *sync.WaitGroup, ch chan int, server string, begin, end int) {
19	defer wg.Done()
20	c, err := net.Dial("tcp", server) // Conecta ao servidor
21	if err != nil {
22		fmt.Println(err)
23		return
24	}
25	defer c.Close()
26	msg := Message{begin, end}                        // cria uma mensagem na estrutura definida
27	msgJson, _ := json.Marshal(msg)                   // transforma em string json
28	fmt.Fprintf(c, string(msgJson)+"\n")              // envia ao servidor
29	message, _ := bufio.NewReader(c).ReadString('\n') // aguarda a resposta
30	result, err := strconv.Atoi(message)              // transforma a resposta para inteiro
31	if err == nil {
32		fmt.Println(begin, "->", end, ":", result)
33		ch <- result // manda o resultado para o programa principal
34	}
35}
36
37/*
38	EXECUÇÃO:
39	go run fat_dist_cli.go [value] [rotines] [server:port] ...
40
41	1: Valor para calcular o fatorial
42	2: Quantidade de gorotines, ou conexões a servidores
43	3 em diante: servidores para se conectar no formato servidor:porta
44
45	EXEMPLO:
46
47	go run fat_dist_cli.go 20 5 localhost:4400 localhost:4402 localhost:4401
48
49	O exemplo acima pede para calcular o fatorial de 20, dividindo em 5
50	subrotinas. Em sequência temos 3 servidores para utilizar. Como o número
51	de servidores é menor do que a quantidade de subrotinas, dois deles vão receber
52	duas requisições de cálculos
53*/
54
55func main() {
56	args := os.Args                        // pega os argumentos
57	value, _ := strconv.Atoi(args[1])      // pega o valor
58	rotines, _ := strconv.Atoi(args[2])    // pega a quantidade de rotinas
59	size := int(value / rotines)           // calcula o tamanho do bloco a separar
60	servers := make([]string, len(args)-3) // cria o vetor de servidores
61	j := 0                                 //
62	for i := 3; i < len(args); i++ {       // coloca os parâmetros como servidores
63		servers[j] = args[i]
64		j++
65	}
66
67	var wg sync.WaitGroup
68	wg.Add(rotines)
69	ch := make(chan int, 10)
70
71	j = 0
72	end := 0
73	for i := 0; i < rotines; i++ { // cria as subrotinas
74		begin := end + 1   // separa o inicio
75		end = begin + size // e o fim do bloco de cálculo
76		if end > value {   // Não pode passar do valor total
77			end = value
78		}
79		go connect_server(&wg, ch, servers[j], begin, end) // envia para um servidor
80		j++
81		if j >= len(servers) {
82			j = 0
83		}
84	}
85	wg.Wait() // aguarda os servidores terminarem
86	close(ch)
87	// Pega os resultados e multiplica-os
88	result := 1
89	for v := range ch {
90		result = result * v
91	}
92	// apresenta o resultado final
93	fmt.Println("Fatorial de", value, "é", result)
94}