52/100 Dias de Golang - Chat Golang
Table of Contents
#
Chat Golang
Continuando no assunto de redes, vamos melhorar o script do dia 51 e fazer um chat, esse chat deve receber várias conexões e cada mensagem deve ser recebida por todos os usuários conectados no server - a famosa mensagem em broadcasting. Nosso server deve guardar uma lista de clientes conectados, deve ter uma função handleConnection()
para lidar com cada conexão e uma função broadcaster()
que é responsável por enviar a mensagem para todos os usuários conectados. As duas função serão goroutines
e irão se comunicar via chanels
. O nosso client será bem simples vai se conectar ao servidor, ficar recebendo e enviando as mensagens, a função que lê as mensagems deve ser uma goroutine também.
#
Client
Vamos começar com o mais simples, a função que fica recebendo as mensagens:
go func() {
reader := bufio.NewReader(conn)
for {
msg, err := reader.ReadString('\n')
if err != nil {
fmt.Println("Conexão encerrada.")
os.Exit(0)
}
fmt.Print(msg)
}
}()
Aqui criamos uma goroutine
que ficar lendo as mensagens. O \n
é usado no ReadString('\n')
porque ele indica o delimitador de fim da mensagem, no caso do nosso chat aqui quando o user apertar a tecla enter será gerado um \n
. E a função para enviarmos as mensagens segue a mesma lógica:
input := bufio.NewReader(os.Stdin)
for {
text, _ := input.ReadString('\n')
_, err := conn.Write([]byte(text))
if err != nil {
fmt.Println("Erro ao enviar mensagem.")
break
}
}
Aqui mudamos o Reader
para o os.Stdin
do terminal, ficamos lendo o input e enviamos para a conexão. Esse conn
é a conexão estabelecida com o servidor. Veja como fica o client completo:
package main
import (
"bufio"
"fmt"
"net"
"os"
)
func main() {
conn, err := net.Dial("tcp", "0.0.0.0:8000")
if err != nil {
fmt.Println("Erro ao conectar:", err)
return
}
defer conn.Close()
go func() {
reader := bufio.NewReader(conn)
for {
msg, err := reader.ReadString('\n')
if err != nil {
fmt.Println("Conexão encerrada.")
os.Exit(0)
}
fmt.Print(msg)
}
}()
input := bufio.NewReader(os.Stdin)
for {
text, _ := input.ReadString('\n')
_, err := conn.Write([]byte(text))
if err != nil {
fmt.Println("Erro ao enviar mensagem.")
break
}
}
}
##
Server
O server tem algumas funções a mais que o client. Primeiro vamos começar criando um map e um canal. O map será responsável por guardar os users conectados e o canal será a ponte de conexão entre as goroutines.
var clients = make(map[net.Conn]string)
var messages = make(chan string)
Agora vamos incrementar um pouco a função handleConnection
. Logo que a conexão é estabelecida o server pergunta o nome do client. Esse nome é salvo no map de clients
e fica em um for loop infinito recebendo mensagens e adicionado ao canal de messages
. Todas as strings enviada ao canal de messages será enviada aos clients conectados.
func handleConnection(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
// Nome do cliente
conn.Write([]byte("Digite seu nome: "))
name, _ := reader.ReadString('\n')
name = name[:len(name)-1]
clients[conn] = name
messages <- fmt.Sprintf("%s entrou no chat", name)
for {
msg, err := reader.ReadString('\n')
if err != nil {
delete(clients, conn)
messages <- fmt.Sprintf("%s saiu do chat", name)
break
}
messages <- fmt.Sprintf("%s: %s", name, msg)
}
}
A função brodcaster
é responsável por receber as mensagens através do canal messages
e enviar para todos os usuários. Caso exista um erro o client é fechado e a referência do map é deletada.
func broadcaster() {
for {
msg := <-messages
for client := range clients {
_, err := client.Write([]byte(msg))
if err != nil {
client.Close()
delete(clients, client)
}
}
}
}
No main somente criamos o servidor na porta 8000
, criamos a goroutine e ficamos em um for infinito escutando conexões. Veja o server.go
completo
package main
import (
"bufio"
"fmt"
"net"
"os"
)
var clients = make(map[net.Conn]string)
var messages = make(chan string)
func handleConnection(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
// Nome do cliente
conn.Write([]byte("Digite seu nome: "))
name, _ := reader.ReadString('\n')
name = name[:len(name)-1]
clients[conn] = name
messages <- fmt.Sprintf("%s entrou no chat", name)
for {
msg, err := reader.ReadString('\n')
if err != nil {
delete(clients, conn)
messages <- fmt.Sprintf("%s saiu do chat", name)
break
}
messages <- fmt.Sprintf("%s: %s", name, msg)
}
}
func broadcaster() {
for {
msg := <-messages
for client := range clients {
_, err := client.Write([]byte(msg))
if err != nil {
client.Close()
delete(clients, client)
}
}
}
}
func main() {
listener, err := net.Listen("tcp", "0.0.0.0:8000")
if err != nil {
fmt.Println("Erro ao iniciar o servidor:", err)
os.Exit(1)
}
defer listener.Close()
fmt.Println("Servidor iniciado na porta 8000")
go broadcaster()
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Erro ao aceitar conexão:", err)
continue
}
go handleConnection(conn)
}
}
Para rodar, temos que abrir 3 terminais. Um para o server e outros dois para os clients. Daí é só ficar digitando e ficar vendo o chat.