• Как реализовать обмен сообщениями между двумя пользователями на Golang?

    @alex__brin
    Программист-безопасник
    Давайте представим ситуацию. У нас есть некий очень простой протокол, а передавать все мы будем с помощью JSON

    Привел в качестве примера server.go и client.go
    server.go содержит в себе примерно то, что должно быть в обычном сервере
    client.go просто для демонстрации подключения (хотя можно подключаться и через telnet)
    Надеюсь, получилось не слишком сложно и вполне понятное
    Если останутся вопросы или нужна будет помощь -- можете писать в личные сообщения ВКонтакте, ссылочка в профиле, что будет более оперативно (либо тут в комментариях) :)

    server.go
    package main
    
    import (
    	"io"
    	"strings"
    	"encoding/json"
    	"bufio"
    	"time"
    	"log"
    	"net"
    )
    
    // Это наш сервер
    type Server struct {
    	Addr string
    	IdleTimeout time.Duration
    	MaxReadBytes int64
    }
    func (s Server) ListenAndServe() error {
    	if s.Addr == "" {
    		s.Addr = ":3000"
    	}
    
    	log.Printf("starting server on %v\n", s.Addr)
    
    	listener, err := net.Listen("tcp", s.Addr)
    	if err != nil {
    		return err
    	}
    	defer listener.Close()
    
    	for {
    		conn, err := listener.Accept()
    		if err != nil {
    			log.Printf("error accption connection %v", err)
    			continue
    		}
    
    		connection := &Conn{
    			Conn: conn,
    			IdleTimeout: s.IdleTimeout,
    			MaxReadBytes: s.MaxReadBytes,
    		}
    		connection.SetDeadline(time.Now().Add(connection.IdleTimeout))
    
    		log.Printf("accepted connection from %v", conn.RemoteAddr())
    
    		connection.Write([]byte("Content-Type: appliaction/json\n  action:\n    ping - ping\n    scream - upper the text\n  body - content\n"))
    		go handle(connection) // запускаем обработчик в новой горутине, чтобы быть готовым принять еще одно соединение 
    	}
    }
    
    type Conn struct {
    	net.Conn
    	IdleTimeout time.Duration // Ожидание, когда сервер что-нибудь скажет или прочитает
    	MaxReadBytes int64 // максимальный объем передаваемых данных, чтобы клиент вдруг не захотел передать нам гигабайты данных
    }
    func (c *Conn) Write(p []byte) (int, error) {
    	c.updateDeadline()
    	return c.Conn.Write(p)
    }
    func (c *Conn) Read(b []byte) (int, error) {
    	c.updateDeadline()
    	r := io.LimitReader(c.Conn, c.MaxReadBytes)
    	return r.Read(b)
    }
    func (c *Conn) updateDeadline() {
    	c.Conn.SetDeadline(time.Now().Add(c.IdleTimeout))
    }
    
    // Это будет наш запрос
    type Request struct {
    	Action string `json:"action"` // наше действие
    	Body   string `json:"body"` // и само тело запроса
    }
    
    func handle(conn *Conn) {
    	defer func() { // Функция выполнится после return этой функции
    		log.Printf("closing connection from %v", conn.RemoteAddr())
    		conn.Close()
    	}()
    
    	r := bufio.NewReader(conn.Conn)
    	w := bufio.NewWriter(conn.Conn)
    	scaner := bufio.NewScanner(r)
    	for {
    		if !scaner.Scan() { // если произошло что-то неладное
    			if err := scaner.Err(); err != nil {
    				log.Printf("%v(%v)", err, conn.RemoteAddr())
    				return // то выйдем из функции (и, благодаря defer, закроем соединение)
    			}
    		}
    
    		req := Request{}
    		scanned := scaner.Text() // читаем текст из сети
    		log.Println(scanned)
    		err := json.Unmarshal([]byte(scanned), &req) // парсим json в нашу структуру
    		if err != nil {
    			log.Printf("error parsing json: ", err)
    		}
    
    		log.Println("New request. Action:", req.Action, "|", "Message:", req.Body)
    
    		response := ""
    
    		switch req.Action {
    		case "ping": // будем отвечать pong
    			response = "pong"
    
    		case "scream": // кричать
    			response = strings.ToUpper(req.Body)
    
    		case "bye": // и прощаться
    			response = "Bye!"
    
    		default: // иначе будем ругаться, что не знаем такого действия
    			response = "Unknown Action"
    		}
    
    		w.WriteString(response + "\n")
    		w.Flush()
    
    		if req.Action == "bye" { // если клиент попрощался, то закрываем соединение
    			return
    		}
    	}
    }
    
    func main() {
    	srv := Server{":3030", time.Minute, 1024}
    	srv.ListenAndServe()
    }
    client.go
    package main
    
    import (
    	"encoding/json"
    	"os"
    	"fmt"
    	"net"
    )
    
    type Request struct {
    	Action string `json:"action"`
    	Body   string `json:"body"`
    }
    
    func main() {
    	conn, err := net.Dial("tcp", "localhost:3030") // Подключаемся к порту
    	checkErr(err)
    	defer conn.Close()
    
    	hello := make([]byte, 1024)
    	conn.Read(hello)
    	fmt.Println(string(hello))
    
    	req := Request{}
    	for {
    		fmt.Print("Action: ")
    		fmt.Fscan(os.Stdin, &req.Action)
    		fmt.Print("Body: ")
    		fmt.Fscan(os.Stdin, &req.Body)
    
    		jsonData, err := json.Marshal(req)
    		if err != nil {
    			fmt.Println(err)
    			continue
    		}
    		jsonData = append(jsonData, byte('\n'))
    		fmt.Println(string(jsonData))
    		i, err := conn.Write(jsonData)
    		if err != nil {
    			panic(err)
    		}
    		fmt.Println("write", i, "bytes")
    
    		response := make([]byte, 1024)
    		i, err = conn.Read(response)
    		if err != nil {
    			panic(err)
    		}
    		fmt.Println("read", i, "bytes")
    		fmt.Println(string(response))
    	}
    
    }
    
    func checkErr(err error) {
    	if err != nil {
    		panic(err)
    	}
    }
    Ответ написан
    Комментировать