Cliente e servidor socket em Golang, um exemplo de chat.

Esta é a quarta versão do nosso cliente e servidor socket. Nessa versão, fiz algumas melhorias interessantes. Agora, temos um sistema de chat completo com comandos úteis e algumas otimizações.

O objetivo desta versão é aprimorar o sistema. Removi um dos canais de escrita para enviar os dados diretamente, aumentando a eficiência. Também eliminei a reconexão automática para deixar o código mais limpo, já que adicionei várias novidades.

Servidor

Vamos às mudanças no servidor.

O handle que lê os dados da conexão com o cliente agora usa um buffer de tamanho fixo. Isso evita esperar por um enter, tornando a leitura mais eficiente.

func handleReadConn(conn net.Conn, msgReadCh chan string, errCh chan error) {
	for {
		if msgReadCh == nil || conn == nil {
			return
		}
		buf := make([]byte, 1024)
		n, err := conn.Read(buf)
		if err != nil {
			errCh <- err
			return
		}
		m := string(buf[:n])
		msgReadCh <- m
	}
}

Para enviar mensagens do servidor para os clientes, substituímos o uso de canais em uma goroutine por uma única função. Isso simplifica o envio, já que a leitura dos dados está separada, permitindo leitura e escrita concorrentes.

Para evitar duplicação de código, o tratamento de erro de envio ficou na função send, que retorna um boolean indicando se o envio foi bem-sucedido.

func send(conn net.Conn, msg string) bool {
	_, err := conn.Write([]byte(msg))
	if err != nil {
		if err == io.EOF {
			fmt.Printf("%v Connection closed\n", conn.RemoteAddr())
			return false
		}
		fmt.Printf("%v Error: %v\n", conn.RemoteAddr(), err)
		return false
	}
	return true
}

Além disso, fiz várias mudanças no handler de conexões. Agora, ele interpreta comandos básicos inspirados no protocolo do IRC. Por exemplo, /nick muda o apelido do usuário e /who lista os usuários conectados.

Cliente

No cliente, a função que lê os dados da conexão também usa um buffer de tamanho fixo. Isso torna a leitura mais rápida, como no servidor.

func readConn(conn net.Conn) {
	for {
		buf := make([]byte, 1024)
		n, err := conn.Read(buf)
		if err != nil {
			errorChan <- err
			return
		}
		m := string(buf[:n])
		output <- m
	}
}

Assim como no servidor, o cliente usa uma função send para enviar dados. Se ocorrer um erro, registramos o erro e fechamos a conexão.

func send(conn net.Conn, m string) {
	if conn == nil {
		return
	}
	_, err := conn.Write([]byte(m))
	if err != nil {
		fmt.Println(err)
		conn.Close()
		conn = nil
	}
}

No cliente, temos comandos importantes como /connect, que estabelece uma conexão com o servidor, e /help, que mostra os comandos disponíveis. Se o cliente estiver conectado, o comando /help também chama o /help do servidor, criando uma única lista de ajuda.

Código-fonte

Aqui está o código-fonte do nosso servidor e cliente.

Vídeos com explicação

Conclusão

Com essas mudanças, adicionamos muitas funcionalidades. Agora, temos um sistema de chat utilizável, mais rápido e confiável. Os comandos entre cliente e servidor exemplificam uma boa comunicação e um protocolo simples. As mesmas funcionalidades poderiam ser implementadas com protocolos mais sofisticados, como gRPC ou JSON.

Cesar Gimenes

Última modificação