Go言語でTCP通信を実装する

# はじめに

TCP。普段よく聞きますが、実際にコードで直接触ったことはありますか？ゲームサーバーやチャットサーバーの実装を通じてソケット通信を学んできましたが、生のTCPの仕組みをもっと深く理解したいと思い、実際にコードを書きながら学んだ内容をまとめた記事です。

# 前提知識
この記事では以下の前提知識が必要です

- Go言語の基本的な文法
- ネットワークの基本的な概念
- 並行処理の基本的な理解

# 実装するもの
今回作成するアプリケーションは、以下の機能を持つシンプルなチャットサーバーです

- 複数のクライアントが接続可能
- 新しいクライアントの参加と退出を全員に通知
- クライアント間でメッセージをブロードキャスト

# サーバー側の実装

### プレイヤー管理
```server.go
// プレイヤー情報を格納する構造体
type Player struct {
	ID   string
	Conn net.Conn
}

var (
	players  = make(map[string]Player) // プレイヤーの管理
	mutex    = sync.Mutex{}            // 排他的制御のためのMutex
	playerID = 0                       // プレイヤーIDを生成するカウンタ
)
```
このコードでは、接続されたクライアント（プレイヤー）を管理するための構造体と変数を定義しています。`sync.Mutex`を使用して、並行アクセス時のデータ競合を防止します。

:::note info
### 1. sync.Mutex
sync.Mutex は 「排他的制御」 を行うためのものです。

複数のゴルーチン（並行実行される処理）が共有データを操作する場合、データの競合（データが壊れたり予期しない値になること）が起こる可能性があります。
**sync.Mutex を使うことで 1つのゴルーチンだけ がデータを操作できるようにします。**

`mutex.Lock()`: ロックをかけて、他のゴルーチンがデータに触れないようにする。
`mutex.Unlock()`: ロックを解除して、他のゴルーチンにデータを操作する権限を渡す。

### 2. net.Conn
クライアントとの通信のために必要な接続情報の保持

`conn.Write([]byte(message))`: `message`（文字列）を `[]byte` に変換し、クライアントに送信します。
`conn.Read(buffer)`: クライアントから送信されたデータを受け取ります。
`conn.Close()`: 通信が終了した後、接続を閉じます。

:::


### クライアント接続の処理とメッセージ処理
```server.go
// クライアントの接続を処理する
func handleClient(conn net.Conn) {
	defer conn.Close()

	// プレイヤーIDを割り当てる
	mutex.Lock()
	playerID++
	id := fmt.Sprintf("Player%d", playerID)
	players[id] = Player{ID: id, Conn: conn}
	mutex.Unlock()

	fmt.Printf("%s connected\n", id)
	broadcastMessage(fmt.Sprintf("%s has joined the game!\n", id), id)

	reader := bufio.NewReader(conn)
	for {
		// クライアントからのメッセージを読み取る
		message, err := reader.ReadString('\n')
		if err != nil {
			fmt.Printf("%s disconnected\n", id)
			removePlayer(id)
			broadcastMessage(fmt.Sprintf("%s has left the game.\n", id), "")
			return
		}

		// メッセージを処理して他のプレイヤーに通知
		message = strings.TrimSpace(message)
		fmt.Printf("[%s]: %s\n", id, message)
		broadcastMessage(fmt.Sprintf("[%s]: %s\n", id, message), id)
	}
}

```
各クライアント接続に対して、一意のIDを割り当て、接続情報を保存します。

:::note info
### 1. `defer`関数
`defer`は、関数が終了する直前に実行される遅延関数呼び出しを定義するキーワードです。

:::

### プレイヤーを削除
```server.go
// プレイヤーを削除する
func removePlayer(id string) {
	mutex.Lock()
	defer mutex.Unlock()
	delete(players, id)
}
```

### メッセージのブロードキャスト
```server.go
// 他のプレイヤーにメッセージを送信する
func broadcastMessage(message string, senderID string) {
	mutex.Lock()
	defer mutex.Unlock()
	for id, player := range players {
		if id != senderID { // 送信者には送り返さない
			player.Conn.Write([]byte(message))
		}
	}
}
```
送信者以外の全クライアントにメッセージを送信する関数です。

### サーバーのメイン処理
```server.go
func main() {
    listener, err := net.Listen("tcp", ":8000")
    if err != nil {
        log.Fatal("Server start error:", err)
    }
    defer listener.Close()

    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Println("Connection error:", err)
            continue
        }
        go handleClient(conn)
    }
}
```
TCP接続をリッスンし、新しい接続が来るたびに別のゴルーチンで処理します。

:::note info
### 1. `net.Listen("tcp", ":8000")`
- サーバーがクライアント接続を待ち受けるためのリスナーを作成
- 引数: プロトコル（"tcp"）とアドレス（":8000" は全てのインターフェースの8000番ポート）

`listener.Accept()`:  新しい接続を受け入れる メソッド
`listener.Close()`: リスナーを閉じる メソッド(Acceptメソッドを以降呼び出せない)

:::

# クライアントの実装
```client.go
func main() {
	// サーバーに接続
	conn, err := net.Dial("tcp", "localhost:8000")
	if err != nil {
		fmt.Println("Connection error:", err)
		return
	}
	defer conn.Close()

	// 受信を別ゴルーチンで処理
	go func() {
		for {
			message, _ := bufio.NewReader(conn).ReadString('\n')
			fmt.Print(message)
		}
	}()

	// メッセージ送信ループ
	reader := bufio.NewReader(os.Stdin)
	fmt.Print("Enter message: ")
	for {
		text, _ := reader.ReadString('\n')
		conn.Write([]byte(text))
	}
}
```
クライアントは、受信と送信を別々のゴルーチンで処理することで、効率的な通信を実現しています。

# コードの完成形
### tree
```
.
├── client
│   └── client.go
├── go.mod
├── go.sum
└── server
    └── server.go
```

### server.go
```go
package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"strings"
	"sync"
)

// プレイヤー情報を格納する構造体
type Player struct {
	ID   string
	Conn net.Conn
}

var (
	players  = make(map[string]Player) // プレイヤーの管理
	mutex    = sync.Mutex{}            // 排他的制御のためのMutex
	playerID = 0                       // プレイヤーIDを生成するカウンタ
)

// クライアントの接続を処理する
func handleClient(conn net.Conn) {
	defer conn.Close()

	// プレイヤーIDを割り当てる
	mutex.Lock()
	playerID++
	id := fmt.Sprintf("Player%d", playerID)
	players[id] = Player{ID: id, Conn: conn}
	mutex.Unlock()

	fmt.Printf("%s connected\n", id)
	broadcastMessage(fmt.Sprintf("%s has joined the game!\n", id), id)

	reader := bufio.NewReader(conn)
	for {
		// クライアントからのメッセージを読み取る
		message, err := reader.ReadString('\n')
		if err != nil {
			fmt.Printf("%s disconnected\n", id)
			removePlayer(id)
			broadcastMessage(fmt.Sprintf("%s has left the game.\n", id), "")
			return
		}

		// メッセージを処理して他のプレイヤーに通知
		message = strings.TrimSpace(message)
		fmt.Printf("[%s]: %s\n", id, message)
		broadcastMessage(fmt.Sprintf("[%s]: %s\n", id, message), id)
	}
}

// 他のプレイヤーにメッセージを送信する
func broadcastMessage(message string, senderID string) {
	mutex.Lock()
	defer mutex.Unlock()
	for id, player := range players {
		if id != senderID { // 送信者には送り返さない
			player.Conn.Write([]byte(message))
		}
	}
}

// プレイヤーを削除する
func removePlayer(id string) {
	mutex.Lock()
	defer mutex.Unlock()
	delete(players, id)
}

func main() {
	// サーバーの起動
	listener, err := net.Listen("tcp", ":8000")
	if err != nil {
		log.Fatal("Server start error:", err)
	}
	defer listener.Close()
	fmt.Println("Game server listening on :8000...")

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println("Connection error:", err)
			continue
		}
		go handleClient(conn)
	}
}

```

### client.go
```go
package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
)

func main() {
	// サーバーに接続
	conn, err := net.Dial("tcp", "localhost:8000")
	if err != nil {
		fmt.Println("Connection error:", err)
		return
	}
	defer conn.Close()

	// 受信を別ゴルーチンで処理
	go func() {
		for {
			message, _ := bufio.NewReader(conn).ReadString('\n')
			fmt.Print(message)
		}
	}()

	// メッセージ送信ループ
	reader := bufio.NewReader(os.Stdin)
	fmt.Print("Enter message: ")
	for {
		text, _ := reader.ReadString('\n')
		conn.Write([]byte(text))
	}
}

```

### 依存関係
```bash
go mod init demo
go mod tidy
```

### 実行
```
cd server
go run server.go
```
```
cd client
go run client.go
```

# 参考
https://pkg.go.dev/net