ESP32でソケット通信をしてみる(Micropython)


# ESP32同士でsocket通信をしたい
実装がかなり大変だったため，備忘録として．

## はじめに：ソケット通信って？
他の方で非常に良い記事がありましたのでそちらを参照ください．
https://qiita.com/megadreams14/items/32a3eed4661e55419e1c

## プログラム：何はともあれまずはソースコード

環境：

```py
>>> os.uname()
(sysname='esp32', nodename='esp32', release='1.17.0', version='v1.17 on 2021-09-02', machine='ESP32 module with ESP32')
```

### TCPサーバ

```server.py
import socket #モジュールのインポート

port = 80 #ポート指定
listenSocket = None #初期化


ip = wifi.ifconfig()[0] #自分のipアドレスを取得
listenSocket = socket.socket() #socketを作成
listenSocket.bind((ip, port)) # ソケットを特定のIPアドレスとポートに紐付け
listenSocket.listen(5) # 接続の待受を開始
listenSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,  1) #指定されたソケットオプションの値を設定

while True:
    print("accepting.....") 
    conn, addr = listenSocket.accept() #接続を受信
    print(addr, "connected") #接続した相手のipアドレスを表示　(xxx.xxx.xxx.xxx connected)

    while True:
        data = conn.recv(1024) #一度に受け取るデータのサイズを指定
        if(len(data) == 0):
            print("close socket")
            conn.close() #接続を切断
            break
        print(data)
```

### TCPクライアント

```client.py
import socket              

s = socket.socket() #ソケットの作成
host = '192.168.0.18' #接続先のipアドレス
port = 80 #ポート指定

s.connect(socket.getaddrinfo(host, port)[0][-1])　#接続確立

if __name__ == '__main__':
    while True:
        msg = input('--->>> ') 
        s.sendall(msg) #入力された文字列を送信
```

## 実際に動作させてみる
先にサーバ側のプログラムを実行させ，ソケットの待受を開始する．

・クライアント側
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459889/2062fdf5-e514-cff4-374a-9152d26ed44f.png)



・サーバ側
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459889/a0f81710-ea34-627c-092b-10d9974f335c.png)

こんな感じでクライアント側で入力した文字列がサーバ側でリアルタイムで確認できるようになる．


## ソースコード：解説していくよ
### TCPサーバ
micropythonにはソケット通信を行うためのモジュールが標準的に組み込まれています．
解説はmicropythonの[公式ドキュメント](https://docs.micropython.org/en/latest/library/socket.html#socket.socket.setsockopt)を参照しつつ行います．

---

#### `listenSocket = socket.socket() #socketを作成`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459889/55ab5486-17c7-a574-af04-9d4386727e69.png)

簡単に言えばここでソケットを生成します．オプションも付けられます．

---

#### `listenSocket.bind((ip, port)) # ソケットを特定のIPアドレスとポートに紐付け`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459889/30011444-5b01-a184-be6a-94b9928aba24.png)


ソケットを特定のIPアドレスとポートに紐付けます．
引数addressと書いてありますが，ソースコード例のようにタプルで指定しないとダメです．
基本的にはIPアドレスとポートを渡せばOK

---

#### `listenSocket.listen(5) # 接続の待受を開始`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459889/6c5af874-d149-f120-4d42-4be4e57fd803.png)

接続の待ち受けを設定します．
接続可能数を指定します．今回の場合では最大で5接続まで行えます．

---

#### `listenSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,  1) #指定されたソケットオプションの値を設定`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459889/0ae2a605-6a3c-ef2b-1a70-e0a7f16da95b.png)


ここが結構めんどくさい
ドキュメントに書いてある通りですが，引数のlevelとoptnameがチップボードによって異なるため，使うボードによって確認が必要です．
> https://docs.micropython.org/en/latest/reference/glossary.html#term-MicroPython-port

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459889/6a28209c-8105-a58a-383a-90cbb1c4bebb.png)
↑参照

---

#### `conn, addr = listenSocket.accept() #接続を受信`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459889/50c61714-af59-4ae1-65af-bb2ae5e5f5d4.png)


書いてある通りですが，戻り値はconnとaddressの2つです．
connは接続でデータを送受信するために使用できる新しいソケットオブジェクト．
addressは接続のもう一方の端のソケットにバインドされたアドレス．

---

#### `data = conn.recv(1024) #一度に受け取るデータのサイズを指定`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459889/92870ac6-b9dc-2c66-d7a0-fa80f3c3f6b9.png)

たまたまpythonの公式ドキュメントで見つけたんですが，[こんなもの](https://docs.python.org/ja/3/library/socket.html#:~:text=recv(bufsize%5B%2C%20flags%5D)-,%C2%B6,-%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88%E3%81%8B%E3%82%89%E3%83%87%E3%83%BC%E3%82%BF)がありました

> 注釈 ハードウェアおよびネットワークの現実に最大限マッチするように、 bufsize の値は比較的小さい2の累乗、たとえば 4096、にすべきです。

ということですので，$2^{10} = 1024$でひとまず設定しておきます．(もっと小さくてもいいと思います)

---

#### `conn.close() #接続を切断`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459889/3cdd0bc0-0432-8375-f3c8-27a8f8787be2.png)


接続を切断します．


---

### TCPクライアント
#### `s.connect(socket.getaddrinfo(host, port)[0][-1])　#接続確立`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459889/364e9e22-19b9-681c-0b99-3878f1646ada.png)


と簡単に書いてあったのですが，，，

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459889/e6b7b9cc-5c4c-71c3-33a2-5dcab0d45c5c.png)


よくよく読むと，こうやって使ってね，と推奨されるやり方があったのでこれを真似しました．

---

#### `s.sendall(msg) #入力された文字列を送信`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459889/a0c43ab8-3326-8153-3146-02d30dfb713b.png)

ソケットにデータを送信する．

## おわりに
公式ドキュメント，
英語だけどめげずに読むことが大切だということですね