いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-276 (TCPで通信するサンプル)

概要

以下、自分用のメモです。

たまにちょこっと通信するプログラム書くときに、GoでTCP通信どうやるんやっけ??ってよくなるので、忘れないようにここにメモメモ。。。

自分用のボイラープレートコードみたいなものです。

サンプル

大したことしてなくて、サーバとクライアントがいて、所定のプロトコルに沿ったメッセージをクライアントが送り、サーバがエコー(大文字化)して返すというものです。

server.go

// Go でのソケットプログラミング 基本 (2)
//
// 本パッケージはサーバ側の処理です。
//
// # REFERENCES
//   - https://pkg.go.dev/net@go1.19.3
//   - https://www.developer.com/languages/intro-socket-programming-go/
//   - https://stackoverflow.com/questions/13417095/how-do-i-stop-a-listening-server-in-go
//   - https://stackoverflow.com/a/237495
package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "io"
    "log"
    "net"
    "os"
    "os/signal"
    "strings"
)

const (
    PORT = 8888
)

var (
    appLog = log.New(os.Stderr, "[server] ", log.Ltime|log.Lmicroseconds)
)

func exitOnErr(err error) {
    if err != nil {
        if err != io.EOF {
            panic(err)
        }
    }
}

func main() {
    // Start
    var (
        listener net.Listener
        err      error
    )

    listener, err = net.Listen("tcp", fmt.Sprintf(":%d", PORT))
    exitOnErr(err)
    appLog.Println("server start...")

    // Regist SIGINT(Ctrl-C) handler
    var (
        quitCh = make(chan os.Signal, 1)
    )
    signal.Notify(quitCh, os.Interrupt)

    go func() {
        <-quitCh
        listener.Close()
    }()

    // Accept
    var (
        count int
    )

    for {
        var (
            conn net.Conn
        )

        conn, err = listener.Accept()
        if err != nil {
            appLog.Println("shutdown...")
            break
        }

        appLog.Printf("accept from %v", conn.RemoteAddr())
        go proc(conn)
        count++
    }

    log.Printf("COUNT=%d\n", count)
}

func proc(stream io.ReadWriteCloser) {
    defer stream.Close()

    // Recv
    //
    // Protocol:
    //         (1) length: uint32 (4-bytes)
    //         (2) data  : string (variable)
    var (
        totalBuf = new(bytes.Buffer)
        err      error
    )

    // (1) length
    var (
        buf    = make([]byte, 4)
        length uint32
    )

    _, err = stream.Read(buf)
    exitOnErr(err)

    length = binary.BigEndian.Uint32(buf[:])
    _, err = totalBuf.Write(buf)
    exitOnErr(err)

    // (2) data
    var (
        message string
    )

    buf = make([]byte, length)
    _, err = stream.Read(buf)
    exitOnErr(err)

    message = string(buf)
    _, err = totalBuf.Write(buf)
    exitOnErr(err)

    // Send
    var (
        resp    = strings.ToUpper(message)
        respBuf = []byte(resp)
    )
    _, err = stream.Write(respBuf)
    exitOnErr(err)

    appLog.Printf("\t[bytes] %v\n", totalBuf.Bytes())
}

client.go

// Go でのソケットプログラミング 基本 (2)
//
// 本パッケージはクライアント側の処理です。
//
// # REFERENCES
//   - https://pkg.go.dev/net@go1.19.3
//   - https://www.developer.com/languages/intro-socket-programming-go/
//   - https://stackoverflow.com/questions/13417095/how-do-i-stop-a-listening-server-in-go
//   - https://stackoverflow.com/a/237495
package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "io"
    "log"
    "math/rand"
    "net"
    "os"
    "time"
)

const (
    PORT = 8888
)

var (
    items = []string{
        "golang",
        "java",
        "python3",
        "C",
        "C++",
        "C#",
        "rust",
        "javascript",
    }
    appLog = log.New(os.Stderr, "[client] ", log.Ltime|log.Lmicroseconds)
)

func init() {
    rand.Seed(time.Now().UnixNano())
}

func exitOnErr(err error) {
    if err != nil {
        if err != io.EOF {
            panic(err)
        }
    }
}

func main() {
    // Connect
    var (
        conn net.Conn
        addr *net.TCPAddr
        err  error
    )

    addr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", PORT))
    exitOnErr(err)
    appLog.Printf("connect to %v", addr)

    conn, err = net.Dial("tcp", addr.String())
    exitOnErr(err)
    defer conn.Close()

    // Send
    //
    // Protocol:
    //         (1) length: uint32 (4-bytes)
    //         (2) data  : string (variable)
    var (
        message = items[rand.Intn(len(items))]
        length  = make([]byte, 4)
        buf     = new(bytes.Buffer)
    )

    binary.BigEndian.PutUint32(length, uint32(len(message)))

    _, err = buf.Write(length)
    exitOnErr(err)
    _, err = buf.Write([]byte(message))
    exitOnErr(err)

    appLog.Printf("send %v --> %v", conn.LocalAddr(), conn.RemoteAddr())
    _, err = conn.Write(buf.Bytes())
    exitOnErr(err)

    // Recv
    var (
        resp []byte
    )

    resp, err = io.ReadAll(conn)
    exitOnErr(err)

    appLog.Printf("recv %v --> %v", conn.RemoteAddr(), conn.LocalAddr())
    appLog.Printf("\t%v --> %v", message, string(resp))
}

Taskfile.yml

version: '3'

tasks:
  default:
    cmds:
      - task: server
      - task: client
  server:
    dir: server
    cmds:
      - cmd: go build -o server
      - cmd: ./server &
        ignore_error: true
  client:
    dir: client
    cmds:
      - cmd: go build -o client
      - cmd: timeout 1 bash -c 'while true; do echo '--------------------'; ./client; done;'; true
        ignore_error: true
      - cmd: pkill -SIGINT server

実行結果

実行すると以下のようになります。

gitpod /workspace/try-golang (master) $ task -d examples/socket/tcp_02_twoway_recvside_close/

task: [server] go build -o server
task: [server] ./server &
task: [client] go build -o client
[server] 04:46:47.161861 server start...
task: [client] timeout 1 bash -c 'while true; do echo '--------------------'; ./client; done;'; true
--------------------
[client] 04:46:47.449368 connect to :8888
[client] 04:46:47.449519 send 127.0.0.1:57098 --> 127.0.0.1:8888
[server] 04:46:47.449609 accept from 127.0.0.1:57098
[server] 04:46:47.449700   [bytes] [0 0 0 10 106 97 118 97 115 99 114 105 112 116]
[client] 04:46:47.449754 recv 127.0.0.1:8888 --> 127.0.0.1:57098
[client] 04:46:47.449770   javascript --> JAVASCRIPT
--------------------
[client] 04:46:47.451532 connect to :8888
[client] 04:46:47.451678 send 127.0.0.1:57100 --> 127.0.0.1:8888
[server] 04:46:47.451711 accept from 127.0.0.1:57100
[server] 04:46:47.451795   [bytes] [0 0 0 1 67]
[client] 04:46:47.451833 recv 127.0.0.1:8888 --> 127.0.0.1:57100
[client] 04:46:47.451838   C --> C
--------------------
[client] 04:46:47.453747 connect to :8888
[client] 04:46:47.453868 send 127.0.0.1:57104 --> 127.0.0.1:8888
[server] 04:46:47.453885 accept from 127.0.0.1:57104
[server] 04:46:47.453945   [bytes] [0 0 0 7 112 121 116 104 111 110 51]
[client] 04:46:47.453974 recv 127.0.0.1:8888 --> 127.0.0.1:57104
[client] 04:46:47.453986   python3 --> PYTHON3

・
・
割愛
・
・
--------------------
[client] 04:46:48.442361 connect to :8888
[client] 04:46:48.442576 send 127.0.0.1:42858 --> 127.0.0.1:8888
[server] 04:46:48.442584 accept from 127.0.0.1:42858
[server] 04:46:48.442663   [bytes] [0 0 0 4 114 117 115 116]
[client] 04:46:48.442715 recv 127.0.0.1:8888 --> 127.0.0.1:42858
[client] 04:46:48.442733   rust --> RUST
--------------------
[client] 04:46:48.444926 connect to :8888
[client] 04:46:48.445122 send 127.0.0.1:42870 --> 127.0.0.1:8888
[server] 04:46:48.445150 accept from 127.0.0.1:42870
[server] 04:46:48.445222   [bytes] [0 0 0 3 67 43 43]
[client] 04:46:48.445277 recv 127.0.0.1:8888 --> 127.0.0.1:42870
[client] 04:46:48.445294   C++ --> C++
--------------------
task: [client] pkill -SIGINT server
[server] 04:46:48.450327 shutdown...
2022/12/05 04:46:48 COUNT=453

上記のサンプルは以下にアップしています。

try-golang/examples/socket/tcp_02_twoway_recvside_close at master · devlights/try-golang · GitHub

参考情報

Go言語による並行処理

Go言語による並行処理

Amazon


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。