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