Goネットワークプログラミングの詳しい解説

インターネットプロトコルの概要

1.1 インターネット階層化モデル

インターネットの論理実装はいくつかの層に分かれています。各階にはそれぞれの機能があり、建物と同じように各階は下の階によって支えられています。ユーザーが触れるのは最上層のみで、下層はまったく感じられません。インターネットを理解するには、各層の機能をボトムアップで理解する必要があります。
ここに画像の説明を挿入

上図のように、インターネットはモデルに応じていくつかのレイヤーに分割されますが、どのモデルで分割しても、上位のレイヤーほどユーザーに近く、下位のレイヤーになるほど異なります。 、ハードウェアに近づくほど。ソフトウェア開発において、私たちがよく使うのは、上図のインターネットを5つの層に分割するモデルです。

次に、各レイヤーを下から上にレイヤーごとに紹介します。

物理層

私たちのコンピュータは外部のインターネットと通信する必要があり、まずコンピュータをネットワークに接続する必要があります。ツイストペア、光ファイバ、電波などの方法を使用できます。これは「実際の物理層」と呼ばれ、コンピューターを相互に接続する物理的な手段です。主にネットワークのいくつかの電気的特性を指定し、0 と 1 の電気信号の送信を担当します。

データリンク層

純粋な 0 と 1 には意味がないため、ユーザーはそれらにいくつかの特定の意味を与え、電気信号の解釈方法を規定します。たとえば、「グループとして数えられる電気信号はいくつですか?」各信号ビットの意味は何ですか? これは「データリンク層」の機能で、「物理層」の上位にあり、物理層が送信する0と1のグループ化方法や代表的な意味を決定します。初期の頃、各企業は電気信号をグループ化する独自の方法を持っていました。徐々に、「イーサネット」(イーサネット)と呼ばれるプロトコルが主導権を握るようになりました。

イーサネットでは、電気信号の集まりが「フレーム」と呼ばれるデータパケットを構成すると規定されています。各フレームは、ヘッダー (Head) とデータ (Data) の 2 つの部分に分かれています。このうち「ヘッダ」には送信者、受信者、データタイプなどのデータパケットの記述項目が含まれ、「データ」はデータパケットの具体的な内容です。「ヘッダ」の長さは18バイト固定です。「データ」の長さは、最短で46バイト、最長で1500バイトです。したがって、「フレーム」全体は、短くて 64 バイト、長くて 1518 バイトになります。データが非常に長い場合は、複数のフレームに分割して送信する必要があります。

では、送信者と受信者はどのように識別されるのでしょうか? イーサネットでは、ネットワークに接続されているすべてのデバイスが「ネットワーク カード」インターフェイスを備えている必要があると規定しています。パケットは 1 つの NIC から別の NIC に送信される必要があります。ネットワークカードのアドレスはデータパケットの送信アドレスと受信アドレスであり、MACアドレスと呼ばれます。各ネットワーク カードは工場から出荷される時点で、世界で一意の MAC アドレスを持ちます。長さは 48 バイナリ ビットで、通常は 12 個の 16 進数で表されます。最初の 6 桁の 16 進数はメーカーのシリアル番号、最後の 6 桁はメーカーのネットワーク カードのシリアル番号です。MAC アドレスを使用すると、ネットワーク カードとデータ パケットのパスを見つけることができます。

ARP プロトコルを通じて受信者の MAC アドレスを取得しますが、MAC アドレスを取得した後、データを受信者に正確に送信するにはどうすればよいでしょうか? 実は、ここでのイーサネットは非常に「原始的」な方法を採用しており、データパケットを受信者に正確に送信するのではなく、ネットワーク内のすべてのコンピューターに送信するため、各コンピューターはパケットの「ヘッダー」を読み取ることができます。受信者の MAC アドレスを見つけて、それを自分自身の MAC アドレスと比較し、両者が同じであればさらなる処理のためにパケットを受け入れ、そうでなければパケットを破棄します。この送信方法を「ブロードキャスト」と呼びます。

ネットワーク層

イーサネット プロトコルの規則に従って、MAC アドレスを信頼してデータを送信できます。理論的には、MAC アドレスに依存して、コンピュータのネットワーク カードが世界の別の隅にあるコンピュータのネットワーク カードを見つけることができます。しかし、このアプローチには大きな欠陥があります。それは、イーサネットがブロードキャストを使用してデータ パケットを送信し、すべてのメンバーが手を取り合って送信することです。 .パケット」は非効率であるだけでなく、送信されるデータは送信者がいるサブネットにのみ制限されます。つまり、2 台のコンピュータが同じサブネット内にない場合、ブロードキャストは送信できません。インターネット上のすべてのコンピュータがインターネット上で送受信されるすべてのデータ パケットを受信するのは非現実的であるため、この設計は合理的かつ必要です。

したがって、どの MAC アドレスが同じサブネットに属しているか、どの MAC アドレスが属していないかを区別する方法を見つける必要があります。同じサブネットであればブロードキャストで送信され、そうでない場合は「ルーティング」で送信されます。これが「ネットワーク層」の誕生につながりました。その機能は、新しいアドレスのセットを導入して、異なるコンピュータが同じサブネットに属しているかどうかを区別できるようにすることです。このアドレスのセットは「ネットワーク アドレス」、または略して「URL」と呼ばれます。

「ネットワーク層」の登場以降、各コンピュータは MAC アドレスとネットワーク アドレスの 2 種類のアドレスを持つようになりました。2 つのアドレス間には関連性はなく、MAC アドレスはネットワーク カードにバインドされており、ネットワーク アドレスはネットワーク管理者によって割り当てられます。ネットワーク アドレスはコンピュータがどのサブネット上にあるかを判断するのに役立ち、MAC アドレスはそのサブネット内のターゲット NIC にパケットを送信します。したがって、最初にネットワーク アドレスを処理し、次に MAC アドレスを処理する必要があると論理的に推測できます。

ネットワークアドレスを指定するプロトコルをIPプロトコルといいます。定義されたアドレスは IP アドレスと呼ばれます。現在、IPv4 と呼ばれる IP プロトコルの 4 番目のバージョンが広く使用されています。このバージョンの IPv4 では、ネットワーク アドレスが 32 の 2 進数で構成されることが規定されており、通常、IP アドレスを表すには、0.0.0.0 ~ 255.255.255.255 の 4 つのセグメントに分割された 10 進数が使用されます。

IPプロトコルに従って送信されるデータをIPパケットと呼びます。また、IP パケットは「ヘッダー」と「データ」の 2 つの部分に分かれています。「ヘッダー」部分には主にバージョン、長さ、IP アドレスなどの情報が含まれ、「データ」部分はパケットの特定の内容です。 IP データ パケット。IP データパケットの「ヘッダ」部分の長さは 20 ~ 60 バイトであり、データパケット全体の長さの合計は最大 65535 バイトです。

トランスポート層

MAC アドレスと IP アドレスがあれば、インターネット上の任意の 2 つのホスト間の通信をすでに確立できます。しかし問題は、データの送受信にネットワークを使用する必要があるプログラムが同じホスト上に多数存在することです。たとえば、QQ とブラウザの両方のプログラムがインターネットに接続してデータを送受信する必要があります。特定のデータパケットがどのプログラムに属しているかを区別できますか? 言い換えれば、このデータ パケットがどのプログラム (プロセス) 用であるかを示すパラメーターも必要です。このパラメータは「ポート」(ポート)と呼ばれ、実際にはネットワーク カードを使用する各プログラムの番号です。各データ パケットはホストの特定のポートに送信されるため、さまざまなプログラムが必要なデータを取得できます。

「ポート」は 0 ~ 65535 の整数で、正確に 16 バイナリ ビットです。0 ~ 1023 のポートはシステムによって占有されており、ユーザーは 1023 より大きいポートのみを選択できます。IPとポートを利用することで、インターネット上のプログラムを一意に決定し、ネットワーク間のプログラム通信を実現します。

パケットにポート情報を追加する必要があるため、新しいプロトコルが必要になります。最も単純な実装は UDP プロトコルと呼ばれ、その形式はデータのほぼ前にポート番号を加えたものになります。UDP パケットも「ヘッダー」と「データ」の 2 つの部分で構成されます。「ヘッダー」部分は主に送信ポートと受信ポートを定義し、「データ」部分は特定のコンテンツです。UDP パケットは非常に単純で、「ヘッダー」部分は合計 8 バイトのみで、全長は 65,535 バイトを超えず、IP パケットに収まります。

UDP プロトコルは、比較的シンプルで実装が容易なことが利点ですが、欠点は、一度データパケットを送信すると相手が受信したかどうかが分からないため、信頼性が低いことです。この問題を解決し、ネットワークの信頼性を向上させるために、TCP プロトコルが誕生しました。TCP プロトコルにより、データが失われないことが保証されます。欠点は、プロセスが複雑で、実装が難しく、リソースの消費量が多いことです。TCP パケットの長さに制限はなく、理論的には無限に長くすることができますが、ネットワークの効率を確保するために、通常、TCP パケットの長さは IP パケットの長さを超えてはなりません。単一の TCP パケットを分割する必要がないことを確認します。

アプリケーション層

アプリケーションは「トランスポート層」からデータを受け取り、次のステップはデータを解凍することです。インターネットはオープンなアーキテクチャであり、さまざまなデータソースが存在するため、通信のデータ形式を事前に指定しておかないと、受信側は送信された実際のデータ内容を取得できません。「アプリケーション層」の役割は、TCP プロトコルに加えて一般的な電子メール、HTTP、FTP、その他のプロトコルなど、アプリケーションで使用されるデータ形式を指定することです。これらのプロトコルは、インターネット プロトコルのアプリケーション層を構成します。

以下の図に示すように、送信者の HTTP データは、インターネットを介した HTTP データの送信プロセス中にプロトコルの各層のヘッダー情報を順番に追加し、受信者はその後、プロトコルに従ってデータを解凍します。データパケットを受信して​​います。
ここに画像の説明を挿入

2ソケットプログラミング

ソケットは、BSD UNIX のプロセス通信メカニズムです。一般に「ソケット」とも呼ばれ、IP アドレスとポートを記述するために使用され、通信チェーンのハンドルです。ソケットは、多くの関数やルーチンを定義する TCP/IP ネットワークの API として理解でき、プログラマはそれらを使用して TCP/IP ネットワーク上のアプリケーションを開発できます。コンピュータ上で実行されているアプリケーションは通常、「ソケット」を介してネットワークにリクエストを送信するか、ネットワークリクエストに応答します。

ソケット図

ソケットは、アプリケーション層と TCP/IP プロトコル ファミリ間の通信のための中間ソフトウェア抽象化層です。デザイン モードでは、Socket は実際にはファサード モードであり、複雑な TCP/IP プロトコル ファミリを Socket の背後に隠します。ユーザーは、Socket で指定された関連関数を呼び出すだけで、Socket が指定されたプロトコルに準拠するデータを整理できるようになり、その後、コミュニケーションを続けます。
ここに画像の説明を挿入

  • ソケットは「ソケット」とも呼ばれ、アプリケーションは通常、「ソケット」を通じてネットワークにリクエストを送信したり、ネットワーク リクエストに応答したりします。
  • 一般的に使用されるソケット タイプは、ストリーミング ソケットとデータグラム ソケットの 2 つです。ストリーミングは、コネクション型 TCP サービス アプリケーション用のコネクション型ソケットであり、データグラム ソケットは、コネクションレス型 UDP サービス アプリケーション用のコネクションレス型ソケットです。
  • TCP: 信頼性が高く、接続指向であり、速度が遅い
  • UDP: あまり信頼性がありませんが、高速です

例: TCP は代金引換速達のようなもので、完全な手続きとしてカウントするには、家に到着したら会いに行く必要があります。UDP は某宅急便のキャビネットのようなもので、捨てるだけでは受け取れなくなりますが、一般的にライブ配信には UDP が使われます。

3 つの TCP プログラミング

Go言語はTCP通信を実装します

TCPプロトコル

TCP/IP (伝送制御プロトコル/インターネット プロトコル)、つまり伝送制御プロトコル/インターネット プロトコルは、コネクション指向 (コネクション指向) で信頼性の高い、バイト ストリーム ベースのトランスポート層 (トランスポート層) 通信プロトコルです。これは接続指向のプロトコルであり、データは水の流れのように送信され、スティッキー パケットが存在します。

TCPサーバー

TCP サーバーは同時に多くのクライアントに接続できます。たとえば、世界中のユーザーがコンピュータのブラウザを使用して Taobao.com にアクセスします。Go 言語で複数の goroutine を作成して同時実行性を実現するのは非常に便利で効率的であるため、リンクが確立されるたびに処理する goroutine を作成できます。

TCPサーバープログラムの処理フローは次のとおりです。

    1.监听端口
    2.接收客户端请求建立链接
    3.创建goroutine处理链接。

Go 言語の net パッケージで実装される TCP サーバー コードは次のとおりです。

// tcp/server/main.go

// TCP server端

// 处理函数
func process(conn net.Conn) {
    defer conn.Close() // 关闭连接
    for {
        reader := bufio.NewReader(conn)
        var buf [128]byte
        n, err := reader.Read(buf[:]) // 读取数据
        if err != nil {
            fmt.Println("read from client failed, err:", err)
            break
        }
        recvStr := string(buf[:n])
        fmt.Println("收到client端发来的数据:", recvStr)
        conn.Write([]byte(recvStr)) // 发送数据
    }
}

func main() {
    listen, err := net.Listen("tcp", "127.0.0.1:20000")
    if err != nil {
        fmt.Println("listen failed, err:", err)
        return
    }
    for {
        conn, err := listen.Accept() // 建立连接
        if err != nil {
            fmt.Println("accept failed, err:", err)
            continue
        }
        go process(conn) // 启动一个goroutine处理连接
    }
}

上記のコードを保存し、server または server.exe 実行可能ファイルにコンパイルします。

TCPクライアント

TCP クライアントの TCP 通信の流れは次のとおりです。

    1.建立与服务端的链接
    2.进行数据收发
    3.关闭链接

Go 言語の net パッケージを使用して実装された TCP クライアント コードは次のとおりです。

// tcp/client/main.go

// 客户端
func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:20000")
    if err != nil {
        fmt.Println("err :", err)
        return
    }
    defer conn.Close() // 关闭连接
    inputReader := bufio.NewReader(os.Stdin)
    for {
        input, _ := inputReader.ReadString('\n') // 读取用户输入
        inputInfo := strings.Trim(input, "\r\n")
        if strings.ToUpper(inputInfo) == "Q" { // 如果输入q就退出
            return
        }
        _, err = conn.Write([]byte(inputInfo)) // 发送数据
        if err != nil {
            return
        }
        buf := [512]byte{}
        n, err := conn.Read(buf[:])
        if err != nil {
            fmt.Println("recv failed, err:", err)
            return
        }
        fmt.Println(string(buf[:n]))
    }
}

上記のコードを client または client.exe 実行可能ファイルにコンパイルし、最初にサーバーを起動してからクライアントを起動し、クライアントで任意のコンテンツを入力して Enter キーを押すと、サーバー上でクライアントから送信されたデータを確認できます。 TCP通信。

4 つの UDP プログラミング

Go言語はUDP通信を実装します

UDPプロトコル

UDP プロトコル (User Datagram Protocol) の中国語名は User Datagram Protocol で、OSI (Open System Interconnection、Open System Interconnection) 参照モデルのコネクションレス型トランスポート層プロトコルであり、ネットワークを確立せずに直接データを送受信できます。受信は信頼性が低く、順序が正しくない通信ですが、UDP プロトコルはリアルタイム パフォーマンスに優れており、通常はライブ ビデオに関連する分野で使用されます。

UDPサーバー

Go 言語の net パッケージを使用して実装された UDP サーバー コードは次のとおりです。

// UDP/server/main.go

// UDP server端
func main() {
    listen, err := net.ListenUDP("udp", &net.UDPAddr{
        IP:   net.IPv4(0, 0, 0, 0),
        Port: 30000,
    })
    if err != nil {
        fmt.Println("listen failed, err:", err)
        return
    }
    defer listen.Close()
    for {
        var data [1024]byte
        n, addr, err := listen.ReadFromUDP(data[:]) // 接收数据
        if err != nil {
            fmt.Println("read udp failed, err:", err)
            continue
        }
        fmt.Printf("data:%v addr:%v count:%v\n", string(data[:n]), addr, n)
        _, err = listen.WriteToUDP(data[:n], addr) // 发送数据
        if err != nil {
            fmt.Println("write to udp failed, err:", err)
            continue
        }
    }
}

UDPクライアント

Go 言語の net パッケージを使用して実装された UDP クライアント コードは次のとおりです。

// UDP 客户端
func main() {
    socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
        IP:   net.IPv4(0, 0, 0, 0),
        Port: 30000,
    })
    if err != nil {
        fmt.Println("连接服务端失败,err:", err)
        return
    }
    defer socket.Close()
    sendData := []byte("Hello server")
    _, err = socket.Write(sendData) // 发送数据
    if err != nil {
        fmt.Println("发送数据失败,err:", err)
        return
    }
    data := make([]byte, 4096)
    n, remoteAddr, err := socket.ReadFromUDP(data) // 接收数据
    if err != nil {
        fmt.Println("接收数据失败,err:", err)
        return
    }
    fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n)
}

5 つの http プログラミング

ウェブワークフロー

Web サーバーの動作原理は次のように簡単に要約できます。

  • クライアントは、TCP/IP プロトコルを通じてサーバーへの TCP 接続を確立します。
  • クライアントは、HTTP プロトコル要求パケットをサーバーに送信し、サーバー内のリソース ドキュメントを要求します。
  • サーバーは HTTP プロトコル応答パケットをクライアントに送信します。要求されたリソースに動的言語コンテンツが含まれている場合、サーバーは動的言語の解釈エンジンを呼び出して「動的コンテンツ」を処理し、処理されたデータをクライアントに返します。
  • クライアントがサーバーから切断されました。HTML ドキュメントはクライアントによって解釈され、グラフィック結果がクライアント画面にレンダリングされます。

HTTPプロトコル

ハイパーテキスト転送プロトコル (HTTP、HyperText Transfer Protocol) は、インターネット上で最も広く使用されているネットワーク プロトコルです。ブラウザと World Wide Web サーバー間の相互通信のルールを指定します。World Wide Web ドキュメントを送信するためのデータ転送プロトコル HTTP プロトコルです。インターネット経由
。通常は TCP プロトコルの上で実行されます。

HTTPサーバー

package main

import (
    "fmt"
    "net/http"
)

func main() {
    //http://127.0.0.1:8000/go
    // 单独写回调函数
    http.HandleFunc("/go", myHandler)
    //http.HandleFunc("/ungo",myHandler2 )
    // addr:监听的地址
    // handler:回调函数
    http.ListenAndServe("127.0.0.1:8000", nil)
}

// handler函数
func myHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println(r.RemoteAddr, "连接成功")
    // 请求方式:GET POST DELETE PUT UPDATE
    fmt.Println("method:", r.Method)
    // /go
    fmt.Println("url:", r.URL.Path)
    fmt.Println("header:", r.Header)
    fmt.Println("body:", r.Body)
    // 回复
    w.Write([]byte("www.5lmh.com"))
}

HTTPサーバー

package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    //resp, _ := http.Get("http://www.baidu.com")
    //fmt.Println(resp)
    resp, _ := http.Get("http://127.0.0.1:8000/go")
    defer resp.Body.Close()
    // 200 OK
    fmt.Println(resp.Status)
    fmt.Println(resp.Header)

    buf := make([]byte, 1024)
    for {
        // 接收服务端信息
        n, err := resp.Body.Read(buf)
        if err != nil && err != io.EOF {
            fmt.Println(err)
            return
        } else {
            fmt.Println("读取完毕")
            res := string(buf[:n])
            fmt.Println(res)
            break
        }
    }
}

6 つの WebSocket プログラミング

WebSocketとは何ですか

  • WebSocket は、単一の TCP 接続を介した全二重通信用のプロトコルです。
  • WebSocket により、クライアントとサーバー間のデータ交換が容易になり、サーバーがアクティブにデータをクライアントにプッシュできるようになります。
  • WebSocket APIでは、ブラウザとサーバーがハンドシェイクを完了するだけで、両者間で直接永続的な接続を確立し、双方向のデータ送信を行うことができます。
  • サードパーティのパッケージをインストールする必要があります:
    cmd で: go get -u -v github.com/gorilla/websocket

http サーバーを起動し、WebSocket 通信のクライアントをシミュレートするために使用される HTML ページにルーティングするルート パスを指定します。このページには、WebSocket 通信を実行する js セクションをトリガーするボタンが提供されます。サーバーは WebSocket リクエストを受信し、リクエストの内容をブラウザーに完全に応答します。

WebSocket リクエストを受信するサーバー:

package main

import (
	"fmt"
	"net/http"

	"github.com/gorilla/websocket"
)

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

func main() {
	http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
		conn, _ := upgrader.Upgrade(w, r, nil) // error ignored for sake of simplicity

		for {
			// Read message from browser
			msgType, msg, err := conn.ReadMessage()
			if err != nil {
				return
			}

			// Print the message to the console
			fmt.Printf("%s sent: %s\n", conn.RemoteAddr(), string(msg))

			// Write message back to browser
			if err = conn.WriteMessage(msgType, msg); err != nil {
				return
			}
		}
	})

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "H:\\go\\main\\websockets.html")
	})

	http.ListenAndServe(":8080", nil)
}

WebSocket リクエストを送信するクライアント:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSockets</title>
</head>
<body>
<input id="input" type="text" />
<button onclick="send()">Send</button>
<pre id="output"></pre>
<script>
    var input = document.getElementById("input");
    var output = document.getElementById("output");
    var socket = new WebSocket("ws://localhost:8080/echo");

    socket.onopen = function () {
        output.innerHTML += "Status: Connected\n";
    };

    socket.onmessage = function (e) {
        output.innerHTML += "Server: " + e.data + "\n";
    };

    function send() {
        socket.send(input.value);
        input.value = "";
    }
</script>
</body>
</html>

https://www.cnblogs.com/qi66/p/16773296.htmlより転載

おすすめ

転載: blog.csdn.net/lingshengxiyou/article/details/130210580