goo에서 웹 서비스 No.9(네트워크 플러그)

34357 단어 GoWeb기본적tech
이번에는 웹 페이지 플러그인을 총결산해 봅시다.인터넷 플러그인은 여러 사용자가 상호작용을 하는 응용에 필요한 기술이다.클라이언트 서버 시스템에서 서버는 클라이언트의 요청에 응답만 할 수 있기 때문에 다른 클라이언트의 변경 사항은 우리가 요청을 보내지 않으면 알 수 없습니다.그걸 해결하는 건 인터넷 소켓이야.
이번 데이터를 주었다github.필요한 파일은 복제한 후에 사용하십시오.

주의


명령행을 사용하는 작업이 나타나는 경우가 있습니다.cd,ls,mkdir,touch 등 기본 명령은 제가 아는 전제에서 진행할 수 있도록 해 주세요.
환경에 따라 작업과 실행 결과가 달라질 수 있습니다.나의 집행 환경은 다음과 같다.
MacBook Pro (Early 2015)
macOS Catalina ver.10.15.6
go version go1.14.7 darwin/amd64
エディタはVScode

지금까지의 양방향 통신


네트워크 콘센트(websocket)가 등장하기 전에도 양방향 통신을 하는 기술이 있었다.여기 두 개 소개할게요.

폴링(폴링)


이것은 단방향 사이트에서 양방향 실현을 실현할 때 가장 먼저 떠오르는 방법으로 일정한 간격으로 끊임없이 요청을 보내는 방법이다.물론 비용이 너무 많이 들어서 인터넷 콘센트가 생겨난 지금의 좋은 방법도 아니다.

Comet(혜성)


Comet은 JavaScript 등을 사용하여 요청을 보냅니다.서버는 요청에 즉시 응답하지 않고 변경 사항이 있으면 응답을 되돌려줍니다.이렇게 하면 불필요한 요구를 줄일 수 있다.그러나 HTTP 통신에 따른 낭비는 해소되지 않았다.
위에서 열거한 두 가지는 HTTP 통신에서 양방향 통신을 실현하는 것으로 충격적이며 양방향 통신을 하기 위해서는 새로운 메커니즘이 필요하다.

웹 소켓


효율적인 양방향 통신을 위해 설치된 전용 규격은 네트워크 소켓이다.네트워크 소켓은 양방향 통신을 하는 기술이 아니라 프로토콜이다.
네트워크 소켓은 HTTP 통신을 먼저 사용하고 악수를 통해 통신을 맺는다.통신을 구축한 후 프로토콜을 네트워크 플러그인으로 옮겨 머리가 작은 통신을 한다.이 협의는 고객과 서버에서 데이터를 발송할 수 있다.
원래는 HTML5 규격의 일부였다가 개별 규격으로 절개됐다.

Go로 설치해보세요.


여기서부터 Go로 구현해 더 자세한 구조를 살펴봅시다.우선 다음 디렉터리로 파일을 준비합니다.
$ tree
.
├── client
│   ├── main.go
│   └── public
│       └── index.html
└── server
    └── main.go

코드


각 문건에 다음과 같이 기술하다.
client/main.go
// code:web9-1
package main

import (
	"log"
	"net/http"
)

func main() {
	port := "3000"
	http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("public"))))
	log.Printf("Server listening on http://localhost:%s/", port)
	log.Print(http.ListenAndServe(":"+port, nil))
}
웹 페이지 9-1은 단순히 net/http 포장을 사용하여 웹 9-2의 페이지를 대리할 뿐이다.
client/public/index.html
<!-- code:web9-2 -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        var sock = null;
        var wsuri = "ws://127.0.0.1:1234";

        window.onload = function() {

            console.log("onload");

            sock = new WebSocket(wsuri);

            sock.onopen = function() {
                console.log("connected to " + wsuri);
            }

            sock.onclose = function(e) {
                console.log("connection closed (" + e.code + ")");
            }

            sock.onmessage = function(e) {
                console.log("message received: " + e.data);
            }
        };

        function send() {
            var msg = document.getElementById('message').value;
            sock.send(msg);
        };
    </script>
    <h1>WebSocket Echo Test</h1>
    <form>
        <p>
            Message: <input id="message" type="text" value="Hello, world!">
        </p>
    </form>
    <button onclick="send();">Send Message</button>
</body>
</html>
웹 9-2에서 Go가 아닌 JavaScript로 클라이언트 측의 웹 플러그인을 설치합니다.연결sock.onopen에 지정된 URI 서버 및 소켓이 있습니다.연결이 끊기면 sock.onclose 웹 페이지 소켓이 닫힙니다.sock.onmessage 서버로부터 전송을 받습니다.이쪽으로 보내면sock.send() 보낼 수 있어요.
창에 입력한 문자열을 서버에 보냅니다.
server/main.go
// code:web9-3
package main

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

	"golang.org/x/net/websocket"
)

func echo(ws *websocket.Conn) {
	var err error

	for {
		var reply string // 受信メッセージ保持

		if err = websocket.Message.Receive(ws, &reply); err != nil {
			fmt.Println("cant recieve")
			break
		}

		fmt.Println("Recieve back from client:", reply)

		msg := "Recieved:" + reply + "at:" + string(time.Now().Format("2006/1/2 15:04:05"))
		fmt.Println("Sending to client:", msg)

		if err = websocket.Message.Send(ws, msg); err != nil {
			fmt.Println("cant send.")
			break
		}

	}
}

func main() {
	http.Handle("/", websocket.Handler(echo))

	if err := http.ListenAndServe(":1234", nil); err != nil {
		log.Fatal("Listen and Serve:", err)
	}
}
서버 측면은 Go로 구현됩니다.Go에서는 표준 포장이 네트워크 소켓을 지원하지 않기 때문에 제3자의 것을 사용한다.이번에는 정통golang.org/x/net/websocket을 사용합니다.인터넷상에서 Go 언어로 사용하는 인터넷 도구 세트를 사용하는 예가 많기 때문에 사용할 수 있다고 생각한다.
main 함수의 실현은 매우 간단하다gorilla. 지정된 포트에서 요청을 수락http.ListenAndServe하고 요청을 http.Handle에 맡긴다.
요청을 받은 후websocket.Handler echo 함수에 처리를 맡깁니다.
echo 함수에는 구체적인 처리가 있습니다.echo 함수는 websocket.Handler로부터 연결을 수신합니다.연결을 받은 시간에 연결이 되어 있기 때문에 echo 함수가 어떤 교환을 할지 기술하기만 하면 됩니다.
이번에는 echo 함수의 이름에 따라 요청한 메시지에 수신 시간을 더해서 발송합니다.

실행 시도 결과


먼저 브라우저에서 최초로 어떤 대화가 나올지 확인하세요.이것은 cherome의 개발 도구인 네트워크에서 확인할 수 있습니다.위에서 말한 바와 같이 네트워크 소켓은 처음에 HTTP를 통해 통신을 맺었다.
우선 고객의 요구입니다.여기서 중요한 것은 websocket.Handler입니다.
GET ws://127.0.0.1:1234/ HTTP/1.1
Host: 127.0.0.1:1234
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Upgrade: websocket
Origin: http://localhost:3000
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Sec-WebSocket-Key: L7XSkAaV9mTTJbtCOhsZkg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
다음은 서버의 응답입니다.여기에는 고객으로부터 받은 Sec-WebSocket-Key 생성Sec-WebSocket-Key을 사용합니다.이것을 클라이언트로 보내서 통신을 만듭니다.
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: nk/xVRZ0rn2kjrM6nr8O/elGToQ=
참고로 Sec-WebSocket-Accept의 생성 방법은 이미 공개되었으며 Go로 실시하면 다음과 같다.
// code:web9-4
package main

import (
	"crypto/sha1"
	"encoding/base64"
	"fmt"
)

func main() {
	key := "L7XSkAaV9mTTJbtCOhsZkg=="
	magicStr := "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
	key = key + magicStr
	
	sha := sha1.New()
	sha.Write([]byte(key))
	bs := sha.Sum(nil)
	accept := base64.StdEncoding.EncodeToString(bs)
	
	fmt.Println(accept)
}
/* 実行結果
//Sec-WebSocket-Acceptと同じもの
nk/xVRZ0rn2kjrM6nr8O/elGToQ=
*/
통신 수립 후 협의는 Sec-WebSocket-Accept에서 http로 옮겨졌다.고객 측의 자바스크립트 설치에서도 이걸 확인할 수 있을 것 같습니다.
var wsuri = "ws://127.0.0.1:1234";
그나저나 ws 이렇게 암호화 통신도 지원하는 경우https.
var wsuri = "wss://127.0.0.1:1234";
이후 설치에 따라 메시지를 보내면 브라우저 콘솔로 바로 돌아갑니다.
chrome console
onload
(index):20 connected to ws://127.0.0.1:1234
(index):28 Hello, world!
(index):28 
그나저나 wss 보냈지만 문자열로 판단돼 경보창이 표시되지 않았다.

설치 2


위에 설치하면 바로 답장이 와서 양방향 통신 느낌은 없어요.따라서 두 개의 클라이언트를 만들고 그 중 하나가 메시지를 보내면 연결된 다른 클라이언트도 보내진다.
server/main.go
// code:web9-5
// 接続しているクライアントを保持
var conns list.List

func echo(ws *websocket.Conn) {
    var err error
    // 新規のクライアントならconnに追加
	var conn *list.Element
	contain := false
	for c := conns.Front(); c != nil; c = c.Next() {
		if c.Value == ws {
			contain = true
		}
	}
	if !contain {
		conn = conns.PushBack(ws)
	}
	for {
		var reply string
		clientHost := ws.Config().Origin
        // 接続が途切れたらconnから削除
		if err = websocket.Message.Receive(ws, &reply); err != nil {
			conns.Remove(conn)
			ws.Close()
			fmt.Println("cant recieve", clientHost)
			break
		}
        // 送信するメッセージの生成
		fmt.Println("Recieve back from client:", reply)
		msg := "Recieved:" + reply + "from:" + clientHost.Host + "at:" + string(time.Now().Format("2006/1/2 15:04:05"))
		fmt.Println("Sending to client:", msg)
        // 保持している全てのクライアントに送信
		for c := conns.Front(); c != nil; c = c.Next() {
			if err = websocket.Message.Send(c.Value.(*websocket.Conn), msg); err != nil {
				fmt.Println("cant send.")
				break
			}
			reply = ""
		}
	}
}
여기에는 변경된 부분만 실렸다.간단하게 연결된 클라이언트의 연결을 alert("hello")에 유지하고 발송할 때 for 순환으로 저장된 모든 연결을 보냅니다.
클라이언트가 다른 포트에서 열려 있습니다.
수행 결과는 아까와 다르지 않아 보는 것보다 혼자 하는 것이 더 감동적이다.그러니 여기서 내가 사랑을 끊는 것을 허락해 주십시오.
앞으로 채팅 등 구체적인 목적에 맞춰 실복을 바꾸면 될 것 같다.

좋은 웹페이지 즐겨찾기