본문 바로가기

Go

Go 채팅서버

https://www.youtube.com/watch?v=R_v4fxoNKMg&list=PLy-g2fnSzUTDALoERcKDniql16SAaQYHF&index=13

 

 

Websocket 을 이용한 Echo Server

  • go 서버
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/pat"
	"github.com/gorilla/websocket"
	"github.com/urfave/negroni"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

type Message struct {
	Type string      `json:"type"`
	Data interface{} `json:"data"`
}


func wshandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}

	for {
		m := &Message{}
		err := conn.ReadJSON(m)
		if err != nil {
			log.Println(err)
			return
		}

		err = conn.WriteJSON(m)
		if err != nil {
			log.Println(err)
			return
		}
	}
}

func main() {
	mux := pat.New()
	mux.Get("/ws", wshandler)

	n := negroni.Classic()
	n.UseHandler(mux)

	http.ListenAndServe(":3000", n)
}

 

 

  • index.html
<html>
<head>
<title>WebSocket Chatting program</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="chat.js"></script>
</head>
<body>
<div id="chat-log"></div>
<div id="user-name"></div>
<form id="input-form">
    <input type="text" id="chat-msg" size="64" autofocus />
    <input type="submit" value="Send" />
</form>
</body>
</html>

 

 

  • chat.js
$(function(){
    if (!window.WebSocket) {
        alert("No WebSocket!")
        return
    }

    var $chatlog = $('#chat-log')
    var $chatmsg = $('#chat-msg')

    addmessage = function(data) {
        $chatlog.prepend("<div><span>"+data+"</span></div>");
    }

    connect = function() {
        ws = new WebSocket("ws://" + window.location.host + "/ws");
        ws.onopen = function(e) {
            console.log("onopen", arguments);
        };
        ws.onclose = function(e) {
            console.log("onclose", arguments);
        };
        ws.onmessage = function(e) {
            addmessage(e.data);
        };
    }

    connect();


    var isBlank = function(string) {
        return string == null || string.trim() === "";
    };
    var username;
    while (isBlank(username)) {
        username = prompt("What's your name?");
        if (!isBlank(username)) {
            $('#user-name').html('<b>' + username + '</b>');
        }
    }

    $('#input-form').on('submit', function(e){
        if (ws.readyState === ws.OPEN) {
            ws.send(JSON.stringify({
                type: "msg",
                data: $chatmsg.val()
            }));
        }
        $chatmsg.val("");
        $chatmsg.focus();
        return false;
    });

})

 

 

 

 

 

 

 

 

EventSource를 이용한 채팅서버

https://yari-demos.prod.mdn.mozit.cloud/ko/docs/Web/API/EventSource

  • main.go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/antage/eventsource"
	"github.com/gorilla/pat"
	"github.com/urfave/negroni"
)

func postMessageHandler(w http.ResponseWriter, r *http.Request) {
	msg := r.FormValue("msg")
	name := r.FormValue("name")
	sendMessage(name, msg)
}

func addUserHandler(w http.ResponseWriter, r *http.Request) {
	username := r.FormValue("name")
	sendMessage("", fmt.Sprintf("add user: %s", username))
}

func leftUserHandler(w http.ResponseWriter, r *http.Request) {
	username := r.FormValue("username")
	sendMessage("", fmt.Sprintf("left user: %s", username))
}

type Message struct {
	Name string `json:"name"`
	Msg  string `json:"msg"`
}

var msgCh chan Message

func sendMessage(name, msg string) {
	// send message to every clients
	msgCh <- Message{name, msg}
}

func processMsgCh(es eventsource.EventSource) {
	for msg := range msgCh {
		data, _ := json.Marshal(msg)
		es.SendEventMessage(string(data), "", strconv.Itoa(time.Now().Nanosecond()))
	}
}

func main() {
	msgCh = make(chan Message)
	es := eventsource.New(nil, nil)
	defer es.Close()

	go processMsgCh(es)

	mux := pat.New()
	mux.Post("/messages", postMessageHandler)
	mux.Handle("/stream", es)
	mux.Post("/users", addUserHandler)
	mux.Delete("/users", leftUserHandler)

	n := negroni.Classic()
	n.UseHandler(mux)

	http.ListenAndServe(":3000", n)
}

 

  • index.html
<html>
<head>
<title>Simple Chat</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script type="text/javascript" src="chat.js"></script>
</head>
<body>
<div id="chat-log"></div>
<div id="user-name"></div>
<form id="input-form">
    <input type="text" id="chat-msg" size="64" autofocus />
    <input type="submit" value="Send" />
</form>
</body>
</html>

 

  • chat.js
$(function(){
    if (!window.EventSource) {
        alert("No EventSource!")
        return
    }

    var $chatlog = $('#chat-log')
    var $chatmsg = $('#chat-msg')

    var isBlank = function(string) {
        return string == null || string.trim() === "";
    };
    var username;
    while (isBlank(username)) {
        username = prompt("What's your name?");
        if (!isBlank(username)) {
            $('#user-name').html('<b>' + username + '</b>');
        }
    }

    $('#input-form').on('submit', function(e){
        $.post('/messages', {
            msg: $chatmsg.val(),
            name: username
        });
        $chatmsg.val("");
        $chatmsg.focus();
        return false;
    });

    var addMessage = function(data) {
        var text = "";
        if (!isBlank(data.name)) {
            text = '<strong>' + data.name + ':</strong> ';
        }
        text += data.msg;
        $chatlog.prepend('<div><span>' + text + '</span></div>');
    };

    var es = new EventSource('/stream');
    es.onopen = function(e) {
        $.post('users/', {
            name: username
        });
    }
    es.onmessage = function(e) {
        var msg = JSON.parse(e.data);
        addMessage(msg);
    };

    window.onbeforeunload = function() {
        $.ajax({
            url: "/users?username=" + username,
            type: "DELETE"
        });
        es.close()
    };

})

 

 

 

 

Websocket을 사용한 다중사용자 채팅

 

 

package main

import (
	"container/list"
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/pat"
	"github.com/gorilla/websocket"
	"github.com/urfave/negroni"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

type Message struct {
	name string `json:"username"`
	Type string `json:"type"`
	Data string `json:"data"`
	//	Data interface{} `json:"data"`
}

// ================== Queue ==============================

type Queue struct {
	v *list.List
}

func (q *Queue) Push(val interface{}) {
	q.v.PushBack(val)
}

func (q *Queue) Pop() interface{} {
	front := q.v.Front()
	if front != nil {
		return q.v.Remove(front)
	}
	return nil
}

func NewQueue() *Queue {
	return &Queue{list.New()}
}

// =======================================================

// ================== Client ==============================

type WClient struct {
	idx   int
	name  string
	state int
	queue *Queue
}

func (user *WClient) setState(s int) {
	user.state = s
}

func (user *WClient) addMsg(msg *Message) {
	user.queue.Push(msg)
}

// =======================================================

type Manager struct {
	m   map[int]*WClient
	idx int
}

func (manager *Manager) getIndex() int {
	manager.idx++
	return manager.idx
}

func (manager *Manager) broadcast(m *Message) {
	for _, value := range manager.m {
		value.queue.Push(m)
	}
}

func (manager *Manager) wisper(m *Message, str string) {
	for _, value := range manager.m {
		if value.name == str {
			value.addMsg(m)
		}
	}
}

var m *Manager

func newManager() *Manager {
	return &Manager{m: make(map[int]*WClient), idx: 0}
}

func getManager() *Manager {
	if m == nil {
		m = newManager()
	}
	return m
}

// =======================================================

func funcClient(w http.ResponseWriter, r *http.Request, conn *websocket.Conn, user *WClient) {
	defer fmt.Println("funcClient exit")
	defer delete(getManager().m, user.idx)

	for {
		if user.state == 0 {
			return
		}

		msg := user.queue.Pop()
		if msg != nil {
			err := conn.WriteJSON(msg)
			if err != nil {
				log.Println(err)
				return
			}
		}
	}
}

func wshandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}

	fmt.Println("Client Creatd")
	user := &WClient{idx: getManager().getIndex(), name: "noname", state: 1, queue: NewQueue()}
	getManager().m[user.idx] = user

	defer user.setState(0)

	go funcClient(w, r, conn, user)
	for {
		m := &Message{}
		err := conn.ReadJSON(m)
		if err != nil {
			log.Println(err)
			return
		}

		mtype := m.Type
		m.Type = user.name

		if mtype == "name" && user.name == "noname" {
			user.name = m.Data
			continue
		}

		if mtype == "msg" {
			getManager().broadcast(m)
			continue
		}

		getManager().wisper(m, mtype)
	}
}

func main() {
	getManager()

	mux := pat.New()
	mux.Get("/ws", wshandler)

	n := negroni.Classic()
	n.UseHandler(mux)

	http.ListenAndServe(":3000", n)
}

 

 

 

 

 

 

index.html

<html>
<head>
<title>WebSocket Chatting program</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="chat.js"></script>
</head>
<body>
<div id="chat-log"></div>
<div id="user-name"></div>
<form id="input-form">
    <input type="hidden" id="username"  value="noname" />
    <input type="text" id="chat-type" size="64" value="msg" />
    <input type="text" id="chat-msg" size="64" autofocus />
    <input type="submit" value="Send" />
</form>
</body>
</html>

 

 

chat.js

$(function(){
    if (!window.WebSocket) {
        alert("No WebSocket!")
        return
    }

    var $chatlog = $('#chat-log')
    var $chatmsg = $('#chat-msg')
    var $chattype = $('#chat-type')

    addmessage = function(data) {
        obj = JSON.parse(data)
        str = obj.type + ":" + obj.data;
        $chatlog.prepend("<div><span>"+str+"</span></div>");
    }

    connect = function() {
        ws = new WebSocket("ws://" + window.location.host + "/ws");
        ws.onopen = function(e) {
            console.log("onopen", arguments);
            ws.send(JSON.stringify({
                type: "name",
                data: username
            }));

        };
        ws.onclose = function(e) {
            console.log("onclose", arguments);
        };
        ws.onmessage = function(e) {
            addmessage(e.data);
        };
    }

    connect();


    var isBlank = function(string) {
        return string == null || string.trim() === "";
    };
    var username;
    while (isBlank(username)) {
        username = prompt("What's your name?");
        if (!isBlank(username)) {
            $('#user-name').html('<b>' + username + '</b>');

        }
    }

    $('#input-form').on('submit', function(e){

        if (ws.readyState === ws.OPEN) {
            ws.send(JSON.stringify({
                name: username,
                type: $chattype.val(), // "msg",
                data: $chatmsg.val()
            }));
        }
        $chatmsg.val("");
        $chatmsg.focus();
        return false;
    });

})