Pull to refresh

Создание простого чата на Golang с использованием Websockets

Level of difficultyEasy

Websockets предоставляют удобный способ установления постоянного соединения между клиентом и сервером, что делает их отличным выбором для реализации чата. В этой статье я покажу, как создать простой чат на языке программирования Golang, используя пакет gorilla/websocket. Под "простым" чатом будет подразумеваться только функционал получения и отправки сообщений. Для

Первым шагом будет установка библиотеки, которая поможет нам работать с websockets в Golang. Установим ранее упомянутый пакет с помощью следующей команды: go get github.com/gorilla/websocket

Работа сервера будет следующей: клиент отправляет запрос на рукопожатие серверу, сервер обновляет необработанное HTTP-соединение до соединения на основе веб-сокета, далее соединение добавляется в слайс :)). Далее начинается цикл событий: при получении сообщения от клиента сервер проходится по всем остальным соединениям и отправляет им данное сообщение, а клиент его обрабатывает.

Сервер

Создадим обработчик для запроса:

func main() {
	http.HandleFunc("/start", handlers.SocketHandler)
	log.Fatal(http.ListenAndServe("localhost:8080", nil))
}

Далее создадим экземпляр структуры Upgrader (он нужен для обновления HTTP соединения до WebSocket соединения), слайс соединений и связная с ним функция для удаления соединения:

var upgrader = websocket.Upgrader{} // оставим без изменений
var connections = []*websocket.Conn{}

func removeConn(slice []*websocket.Conn, val *websocket.Conn) []*websocket.Conn {
	index := -1
	for i, v := range slice {
		if v == val {
			index = i
			break
		}
	}

	if index != -1 {
		if index < len(slice)-1 {
			copy(slice[index:], slice[index+1:])
		}
		slice = slice[:len(slice)-1]
	}

	return slice
}

Далее напишем простую функцию для обработки запроса на рукопожатие, включающую в себя бесконечный цикл получения сообщений от пользователя:

func SocketHandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print("Error during connection upgradation:", err)
		return
	}
	defer conn.Close()

	connections = append(connections, conn)

	for {
		messageType, message, err := conn.ReadMessage()
		if err != nil {
			connections = removeConn(connections, conn)
			log.Println("Error during message reading:", err)
			break
		}
		log.Printf("Server: %s", message)
		for _, c := range connections {
			if c != conn {
				err = c.WriteMessage(messageType, message[:len(message)-1])
				if err != nil {
					connections = removeConn(connections, conn)
					log.Println("Error during message writing:", err)
					break
				}
			}
		}
	}
	connections = removeConn(connections, conn)
}

Клиент

var done chan interface{}

func main() {
	done = make(chan interface{})

	socketUrl := "ws://localhost:8080" + "/start"
	conn, _, err := websocket.DefaultDialer.Dial(socketUrl, nil)
	if err != nil {
		log.Fatal("Error connecting to websocket server:", err)
	}
	defer conn.Close()

	internal.ClearScreen()
	internal.RunChat(conn, done) // К ней вернемся чуть позже
}

Функция для ввода сообщения пользователем в консоль представлена ниже.

func userInput(inputChan chan string) {
	reader := bufio.NewReader(os.Stdin)
	msg, _ := reader.ReadString('\n')
	inputChan <- msg
}

Она используется в функции, которая отправляет данного сообщения на сервер.

func chatInputHandler(conn *websocket.Conn, done chan interface{}) {
	for {
		inputChan := make(chan string)
		go userInput(inputChan)
		select {
		case input := <-inputChan:
			if strings.TrimSpace(input) == "" {
				continue
			}
			err := conn.WriteMessage(websocket.TextMessage, []byte(input))
			if err != nil {
				log.Println("Error during writing to websocket:", err)
				return
			}
			PrintColoredText("message has been sent", "\033[32m")

		case <-interrupt:
			log.Println("Interrupt signal received. Closing all pending connections")

			err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
			if err != nil {
				log.Println("Error during closing websocket:", err)
				return
			}

			select {
			case <-done:
				log.Println("Receiver Channel Closed")
			case <-time.After(time.Duration(1) * time.Second):
				log.Println("Timeout in closing receiving channel")
			}
			return
		}
	}
}

Теперь посмотрим, как работает написанный код. Запустим сервер и несколько клиентов.

Два запущенных клиента
Два запущенных клиента
Сервер
Сервер

Хочется верить, что данный пример кому-то сможет помочь.

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.