【TCP通信】SocketDebuggerを使ってエコーサーバーを構築してみた

# 1. はじめに
- TCPのクライアントソフトを開発したけど、ちゃんと動くか不安だ…
- 確認したいけどサーバーソフトは取引先相手が作るから確認できない💦
- デバッグ用のサーバーソフトを作ったとしても、それがバグる可能性もあるし💀

こういった経験をされたエンジニアは少なくないと思います。
そんな時、**品質の高い汎用的なエコーサーバー**があれば解決しますよね。

アプリ間のソケット通信をデバッグするための**SocketDebugger**でエコーサーバーをお手軽に構築してみたいと思います

SocketDebuggerとはなんぞや？という方は、まずはこちらをご参照ください。

https://qiita.com/umezawa_udom/items/9515884a187f06427364

# 2. ネットワーク構成
以下のようなネットワーク構成でTCPのクライアントソフトと、エコーサーバーを用意したいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4093303/bd24f070-213a-4fc8-b245-f56e367abffa.png)

# 3. クライアントソフト
クライアントソフトの要件は、以下です。
- 指定したIPとポートに接続し、ユーザーが入力した数値を送信します
```python:tcp_client.py
"""
TCP通信を行うクライアントソフト。
指定したIPとポートに接続し、ユーザーが入力した数値を送信します。
数値以外の入力があった場合は、再入力を求めます。
"""
import socket
import sys
import time

def tcp_client(host, port):
    """
    引数:
        host (str): 接続先のIPアドレス
        port (int): 接続先のポート番号
    """
    try:
        # TCP/IPソケットを作成
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        
        # サーバーに接続
        print(f"{host}:{port}に接続しています...")
        sock.connect((host, port))
        print(f"{host}:{port}に接続しました")
        
        # データの受信と処理
        while True:

            # ループの区切り線を表示
            print("-" * 50)
            
            # ユーザーに送信する値の入力を求める
            while True:
                user_input = input("送信する値を入力してください: ")
                try:
                    value_to_send = int(user_input)
                    break  # 有効な数値が入力されたらループを抜ける
                except ValueError:
                    print("エラー: 有効な数値を入力してください")
            
            print(f"送信する値: {value_to_send}")
            
            # ユーザーが入力した値を送る
            sock.sendall(str(value_to_send).encode('utf-8'))
            
            # 過負荷を防ぐための小さな遅延
            time.sleep(1)

            # サーバーからデータを受信
            data = sock.recv(1024)
            if not data:
                print("サーバーが接続を閉じました")
                break
                
            # 受信したデータを整数に変換
            received_value = int(data.decode('utf-8'))
            print(f"受信した値: {received_value}")
            
    except ValueError as e:
        print(f"エラー: 受信したデータが有効な数値ではありません - {e}")
    except ConnectionRefusedError:
        print(f"エラー: {host}:{port}への接続が拒否されました")
    except ConnectionResetError:
        print("エラー: 接続がサーバーによってリセットされました")
    except socket.timeout:
        print("エラー: 接続がタイムアウトしました")
    except socket.error as e:
        print(f"ソケットエラー: {e}")
    except KeyboardInterrupt:
        print("\nクライアントがユーザーによって終了されました")
    finally:
        print("接続を閉じています")
        sock.close()

if __name__ == "__main__":
    # コマンドライン引数からホストとポートを取得、または既定値を使用
    if len(sys.argv) >= 3:
        host = sys.argv[1]
        try:
            port = int(sys.argv[2])
        except ValueError:
            print("エラー: ポートは数値である必要があります")
            sys.exit(1)
    else:
        host = input("サーバーのIPアドレスを入力してください: ")
        try:
            port = int(input("サーバーのポート番号を入力してください: "))
        except ValueError:
            print("エラー: ポートは数値である必要があります")
            sys.exit(1)
    
    # クライアントを開始
    tcp_client(host, port)
```
開発したクライアントソフトが、正しく動作するかはサーバー側がないと確認できず、不安になります…。

# 4. SocketDebuggerをエコーサーバーに設定する
4-1. SocketDebuggerを起動します。
4-2. メニューの[設定]から[通信設定]をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4093303/97e0994a-6854-42ac-a3d2-d17cece8423b.png)

4-3. 左の項目ペインの[接続]-[ポート１]をクリックし、赤枠内を以下のように設定します。
<table>
  <thead>
    <tr>
      <th>項目</th> <th>設定値</th>
    </tr>
  </thead>
  <tr>
    <td>このポートを使用する</td><td>チェック有</td>
  </tr>
  <tr>
    <td>通信タイプ</td><td>TCPサーバ</td>
  </tr>
  <tr>
    <td>localポート番号1</td><td>1024</td>
  </tr>
</table>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4093303/c69f8e12-a75d-4432-a84b-a3a61d967747.png)

4-4. 左の項目ペインの[動作]-[ポート１]をクリックし、赤枠内を以下のように設定します。
<table>
  <thead>
    <tr>
      <th>項目</th> <th>設定値</th>
    </tr>
  </thead>
  <tr>
    <td>受信時動作</td><td>チェック有</td>
  </tr>
  <tr>
    <td>対応するポート</td><td>受信したポート</td>
  </tr>
  <tr>
    <td>受信したデータを返信</td><td>選択</td>
  </tr>
</table>

設定が終了したら、最後に「OK」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4093303/a7aaf638-20d3-4581-8a0e-1c94ca15b9fe.png)

4-5. ポート１の通信を開始し、クライアントソフトからの接続要求を待ちます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4093303/ac489d88-f387-4185-bbdf-36811bc91b93.png)

# 5. 動作検証
さっそくクライアントソフトを実行して動作検証します。
送信する値と**受信した値**に注目してください。

```:クライアントソフト実行
サーバーのIPアドレスを入力してください: 192.168.2.100
サーバーのポート番号を入力してください: 1024
192.168.2.100:1024に接続しています...
192.168.2.100:1024に接続しました
--------------------------------------------------
送信する値を入力してください: 3061
送信する値: 3061
受信した値: 3061
--------------------------------------------------
送信する値を入力してください: 227
送信する値: 227
受信した値: 227
```
**サーバーから「送信したデータ」がそのまま返ってきています！😄**

エコーサーバー（SocketDebugger）のログも確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4093303/e3289dce-256d-4501-a89d-6b64ae8da0a3.png)
期待通りに受信したデータを**エコー**（そのまま返す）していますね！

開発したTCPのクライアントソフトが、正しく指定した相手とTCP通信を行い、正常にデータを送信できていることを確認することが出来ました👍

# 6. 応用編（CountUpエコーサーバー設定）
応用編として、受信したデータをそのまま返すのではなく、サーバー側で加算して返す**CountUpエコーサーバー**に変更したいと思います。

::: note info
SocketDebuggerでは、通信処理をユーザーが自由な仕様に従ってテストが行えるよう、デフォルトの設定だけでは実現不可能なロジックを Luaスクリプト（5.1）により実現することが可能です。
:::

6-1. メニューの[設定]から[通信設定]をクリックします。
6-2. 左の項目ペインの[動作]-[ポート１]をクリックし、「スクリプト制御を行う」にチェックを入れ設定します。最後に「OK」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4093303/262cf2c5-b6a7-4e3d-9bce-40df1cf8fd3b.png)

6-3. スクリプトエディタ（スクリプト1）内の受信通知関数に以下の赤枠内の処理を追記してください。（事前にメニューの[表示]から[スクリプト(1)]を選択状態にする）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4093303/b2588327-0037-4d0d-b719-1da676007d49.png)

```lua:Luaスクリプト
---------------------------------------------
-- 受信通知
---------------------------------------------
function OnReceive(recv)
    Logput(1,'OnReceive')
    --追記する（ここから）
    Logput(1,'加算してエコーします')
    str = string.char(unpack(recv))
    num = tonumber(str) + 1
    SendData(num)
    --（ここまで）
    return 0
end
```

::: note info
SocketDebuggerで拡張したイベントと関数
OnReceive：データ受信時に発生するイベント
Logput：ログ出力関数
SendData：データ送信関数
:::

::: note info
Lua標準関数
unpack：テーブルを受け取りその要素を個別の値として返します
string.char：1つ以上のASCII数値を対応する文字に変換します
tonumber：文字列やその他の値を数値に変換します
:::


# 7. 応用編（動作検証）
再びクライアントソフトを実行して動作検証します。
送信する値と**受信した値**に注目してください。

```:クライアントソフト実行
サーバーのIPアドレスを入力してください: 192.168.2.100
サーバーのポート番号を入力してください: 1024
192.168.2.100:1024に接続しています...
192.168.2.100:1024に接続しました
--------------------------------------------------
送信する値を入力してください: 3061
送信する値: 3061
受信した値: 3062
--------------------------------------------------
送信する値を入力してください: 227
送信する値: 227
受信した値: 228
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4093303/e59f36b9-ab2d-448b-982d-84457b39d0a6.png)
**期待通り「送信したデータ」がカウントアップされて返ってきています！😲**

スクリプト制御を活用することで柔軟にサーバーの仕様を変更することができました👏

# 8. まとめ
今回は、お手軽に**エコーサーバー**を構築する方法を紹介しました。
**SocketDebugger**をエコーサーバーにすることで、アプリケーション開発における通信確認の手間を大幅に削減できます。

また、Luaスクリプトを活用することでもっと複雑な制御も可能になると思います。
是非様々な活用方法を試してみてください！

# 記事リンク
https://qiita.com/umezawa_udom/items/9515884a187f06427364

https://qiita.com/umezawa_udom/items/dba49ca754b5dcffb198

# 株式会社ユードム
株式会社ユードムはITと人間力で社会に貢献します。

https://www.udom.co.jp/

SocketDebuggerのご購入はこちら（期間限定の試用版もあります）

https://www.udom.co.jp/sdg/index.html