728x90


웹으로 양방향에 대해서는 매우 많이 논했다. 그래서 개념에 대해서 알고싶다면 참고들을 확인해라.

참고로 주로 확인해야할 것은 long pooling hidden iframe streaming부분이다.

또한 NodeJS를 할줄 안다면 WebSocket예제를 참고하는것도 좋다.

참고로 NodeJS예제 포스팅에서는 WebSocket을 어떤브라우저에서 지원을 해주는지에 대한 설명도 같이 적혀있으니 참고하기 좋다.

또한 WebSocket에 대한 간략한 설명도 위 포스팅에서 있으나 자세한 설명은 아직 없다.

추후에 업로드 되면 이 포스팅에 링크를 걸어두도록 하는게 좋을 것 같다.


이 예제는 server와 client간의 통신 브라우저를 통한통신, 즉 일반적인 웹 소켓통신을 의미한다.

추후에 브라우저를 통하지 않은 웹소켓을 이용한 통신을 포스팅할 예정이다.



go get github.com/gorilla/websocket


일단 전역으로 websocket을 설치한다.

여러 버전이 있지만 gorilla것이 가장 유명하다.


디렉터리 구조는 위와 같이 만든다.


index.html은 클라이언트 코드이다.

server.go는 서버 코드이다.


Server



서버쪽 코드가 길다. 필자가 봐도 좀 길어보이긴한다. 일단 차근차근 보도록하자.


func main() {
http.HandleFunc("/echo", echo)
http.HandleFunc("/", home)
log.Fatal(http.ListenAndServe("localhost:8080", nil))
}

main에 url을 /echo와 /를 등록하고 실행한다.


func home(w http.ResponseWriter, r *http.Request) {
path := filepath.Join("templates", "index.html")
tmpl := template.Must(template.ParseFiles(path))
tmpl.Execute(w, "ws://"+r.Host+"/echo")
}

/는 home과 연결되는데 이 때 이녀석이 할 일은 template을 뿌려주는 역활만 하면 끝난다.

변수로 웹소켓의 주소를 넘겨줘야 나중에 주소가 바뀌어도 자동으로 바뀌게 될 것이다.

여기서는 크게 설명할건 없다.


var upgrader = websocket.Upgrader{}

func echo(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
for {
mt, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
var objmap map[string]interface{}
_ = json.Unmarshal(message, &objmap)
event := objmap["event"].(string)
sendData := map[string]interface{}{
"event": "res",
"data": nil,
}
switch event {
case "open":
log.Printf("Received: %s\n", event)
case "req":
sendData["data"] = objmap["data"]
log.Printf("Received: %s\n", event)
}
refineSendData, err := json.Marshal(sendData)
err = c.WriteMessage(mt, refineSendData)
if err != nil {
log.Println("write:", err)
break
}
}
}

웹소켓 부분은 조금긴데 이녀석도 잘라서 보자.


var upgrader = websocket.Upgrader{} 

먼저 websocket.Upgrader를 만든다.

이름이 정말직관적이지 않게 만들었는데 이 struct의 기능은 http requset와 http response를 websocket에 맞게 커스터마이징 시켜준다.


c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()

golang에서 흔히 볼 수 있는 코드인데 upgrader 객체에서 Upgrade메소드를 실행시켜주면 이는 해당 요청과 응답을 http => ws로 변환된다.

문제 생기면 defer키워드로 인해서 연결은 종료된다.


var objmap map[string]interface{}
_ = json.Unmarshal(message, &objmap)
event := objmap["event"].(string)
sendData := map[string]interface{}{
"event": "res",
"data": nil,
}

사실 이거 이해할려면 클라이언트 쪽을 먼저 봐야한다.

어쩃든 데이터가 넘어오는데 필자가 json으로 마샬링해서 데이터를 서버쪽으로 던질것이다.

즉 우리는 날라오는 데이터가 json이라고 가정을 하고 시작한다.

그러면 그 json데이터를 unmarshal하고 그 값을 map에 담는다.

데이터는 interface형이므로 string으로 보기위해서 캐스팅해준다.

마지막으로 다시 전송할 데이터를 만드는데 우리는 이벤트타입을 res로, data는 아래 가공해서 붙혀줄 것이다.

사실 이렇게 안해도되니까 여러분이 데이터 형을 바꾸고 싶으면 얼마든지 바꿔서 전송하는게 좋을 것이다.


switch event {
case "open":
log.Printf("Received: %s\n", event)
case "req":
sendData["data"] = objmap["data"]
log.Printf("Received: %s\n", event)
}

데이터는 타입에 나눠서 처리할 것이다.

넘어온 이벤트 타입을 보고 행하는게 편하기 때문이다.

open이면 그냥 아무것도안한다(그냥 연결됫다는 뜻, 이것도 필자가 보내주는 이벤트이다.)

이벤트가 req였다면 데이터를 다시 담아서 data에 넣은 후 그대로 반송할 생각이다.


refineSendData, err := json.Marshal(sendData)
err = c.WriteMessage(mt, refineSendData)

그리고 다시 데이터를 담아서 써준다. 그러면 이제 서버쪽은 끝난다.


Client



이 떄까지 다른언에서도 사용했던 자주쓰는 예제이다. 사실 쉬워서 별 설명은 필요없을 수 있다.


ws.onopen = (event) => {
let sendData = {event: 'open'}
ws.send(JSON.stringify(sendData));
}

연결했을 때의 이벤트다. open이라는 이벤트를 담은후 마샬링(직렬화)해서 문자열로 서버에 전송한다.


function myOnClick() {
let sendData = {event: 'req', data: {comment: $('#user').val()}};
ws.send(JSON.stringify(sendData));
$('#user').val('');
}

전송버튼을 누르면 현재 input박스에 있는 값을 포장해서 이를 sendData의 comment에 넣고

다시 이걸 마샬링(직렬화)해서 전송한다.


ws.onmessage = (event) => {
console.log(event.data);
let recData = JSON.parse(event.data);
switch (recData.event) {
case 'res':
$('#chat').val($('#chat').val() + recData.data.comment + '\n');
break;
default:
}
}

만약 서버에서 메세지가 왔다면 이 이벤트를 수행한다.

우리는 메세지를 다시 세분화해서 보냈다.

일단 문자열로 넘어왔으므로 다시 JSON으로 파싱(언마샬링)해준다. 그 다음 해당 이벤트 타입을 확인하고 적절히 처리한다.

본 예제에서는 채팅창에 추가하게 된다.





아직까지는 브로드캐스팅(다중채팅)은 되지 않는다. 해당 예제는 추후에 업로드 하도록 하겠다.



+ Recent posts