Как стать автором
Поиск
Написать публикацию
Обновить

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

Уровень сложностиПростой

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
		}
	}
}

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

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

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

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.