simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

Go言語の通信スケルトンコード例

■前置き

Goってのはマルチコア処理で効率的に同時アクセスをさばく、現時点で最強?のネットワークプログラミング言語ですので
その機能を最大限に発揮するスケルトンコードをチュートリアルで公開しているものと思っていました。
が、あまりにプリミティブな(基礎的な)ことしか頭に入ってこなかったので、仕方なく自作することにしました。

ゼロから作るのは非効率でしたので、参考コードとして前々回紹介した以下の二つのコードを使わせてもらいました。
golang socket server & client ping-pong demo
go server-clientアプリケーションの実装

■本題

複数クライアントからの同時アクセスを受け付けるサーバーとクライアントのコードを書いてみました。
足りない部分は公式ドキュメントを頼りに実装し、動作確認まで済ませました。

1.クライアントを起動するとサーバーに接続、クライアントで適当な文字列を打ち込むと、これをサーバーに送ります。
2.サーバーは複数のクライアントのセッションを張り続け、一つのクライアントから文字列が送られてきたら、同じ内容をそのクライアントだけに返します。
3.クライアントはサーバーから文字列を受け取ったら表示し、次の文字列の入力を待ちます。
4.サーバーはセッションが切れたら、クライアントからの送信待ちループを抜けてコネクションを閉じます(サーバーは終了せずに、次の接続を待機します)

server.go

package main

import (
	"bufio"
	"io"
	"log"
	"net"
	"strconv"
	"strings"
)

func main() {
	port := 7777
	SocketServer(port)
}

// const meesages
const (
	StopCharacter = "\r\n\r\n"
)

// SocketServer start a server
func SocketServer(port int) {
	listen, err := net.Listen("tcp4", ":"+strconv.Itoa(port))
	defer listen.Close()
	if err != nil {
		log.Fatalf("Socket listen port %d failed,%s", port, err)
	}
	log.Printf("Begin listen port: %d", port)
	for {
		conn, err := listen.Accept()
		if err != nil {
			log.Fatalln(err)
			continue
		}
		go handler(conn)
	}
}

func handler(conn net.Conn) {
	defer conn.Close()
	var (
		buf = make([]byte, 1024)
		r   = bufio.NewReader(conn)
		w   = bufio.NewWriter(conn)
	)
CLOOP:
	for {
		var received []string
	ILOOP:
		for {
			n, err := r.Read(buf)
			data := string(buf[:n])
			switch err {
			case io.EOF:
				break CLOOP
			case nil:
				hasStopCharacter := false
				if strings.HasSuffix(data, StopCharacter) {
					hasStopCharacter = true
					data = strings.TrimSuffix(data, StopCharacter)
				}
				log.Println("Receive:", data)
				received = append(received, data)
				if hasStopCharacter {
					break ILOOP
				}
			default:
				log.Fatalf("Receive data failed:%s", err)
				return
			}
		}
		echo := strings.Join(received, "")
		w.Write([]byte(echo))
		w.Flush()
		log.Printf("Send: %s", echo)
	}
}

client.go

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
)

func main() {
	var (
		ip   = "127.0.0.1"
		port = 7777
	)
	SocketClient(ip, port)
}

// const messages
const (
	Exit          = "exit\r\n"
	StopCharacter = "\r\n\r\n"
)

// SocketClient start client
func SocketClient(ip string, port int) {
	addr := strings.Join([]string{ip, strconv.Itoa(port)}, ":")
	conn, err := net.Dial("tcp", addr)
	defer conn.Close()
	if err != nil {
		log.Fatalln(err)
	}
	for {
		inputln := WaitInputln()
		log.Printf("inputln: %s", inputln)
		if 0 == strings.Compare(inputln, Exit) {
			break
		}
		SendMessage(conn, inputln)
		WaitServerMessage(conn)
	}
}

// WaitInputln wait inupt line
func WaitInputln() string {
	for {
		reader := bufio.NewReader(os.Stdin)
		fmt.Print("Input text: ")
		text, _ := reader.ReadString('\n')

		if len(text) > 1 {
			return text
		}
	}
}

// SendMessage sand message to server
func SendMessage(conn net.Conn, message string) {
	jointed := strings.Join([]string{message, StopCharacter}, "")
	conn.Write([]byte(jointed))
	log.Printf("Send: %s", message)
}

// WaitServerMessage wait server message
func WaitServerMessage(conn net.Conn) {
	buff := make([]byte, 1024)
	n, _ := conn.Read(buff)
	log.Printf("Receive: %s", buff[:n])
}

Go の GC について気になったので調べた
かなりシンプルなアルゴリズムを使って、Java などと比べるとかなり停止時間が小さく、スループットも意識して改善が続けられているとのこと
5年前にはいくつかのケースでメモリリークが見つかっていたが、最近はそれらの問題は解決されているとのこと
postd.cc