ろうとるがPythonを扱う、、（その22： NAT配下のクライアント通信）

#  異なるNAT配下のクライアントどうしをサーバー経由で通信
異なるNAT配下のクライアントどうしをNAT外にあるサーバーを経由して通信させてみた。

## ネットワーク
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/86e1a0bd-fb17-f1dc-8b28-74783d3c06ec.png)
異なるNAT配下にあるClient1とClient2とが、NAT外にあるサーバーを経由して通信を行う。2つのWiFiルーターがNATの役割を果たす。NAT配下のClient1のIPアドレスは192.168.111.0/24、NAT配下のClient2のIPアドレスは10.1.1.0/24、NAT外のネットワークは192.168.2.0/24となっている。

## ソースコード
Pythonによるコードである。セッションを設けづらいため、UDP通信。なお、エラー処理などは考慮していない。

### Clientからのコマンド
ここでは、テキスト（文字列）にて、Clientからのコマンドを定義する。
- 'rgst,my_id'：クライアントが自分のIDをServerに登録する。
- 'send,peer_id,txt'：クライアントが送信相手のIDにメッセージ(txt)を送る。

### Server
```python
from socket import *
import select

port = 62111				# Receive port
terminalData = {}			# Dictionary for id and client

sock = socket(AF_INET, SOCK_DGRAM)	# UDP
sock.bind(('0.0.0.0', port))		# Receive at all I/Fs
fds = set()
fds.add(sock)

while 1:
    r, w, x = select.select(list(fds), [], [], 3)
    for s in r:
        if s is sock:
            msg, ClientAddr = s.recvfrom(64)
            msg = msg.decode(encoding='utf-8')
            # msg format 'rgst,my_id' or 'send,peer_id,txt'
            cmd = msg.split(',')[0]	# command
            if cmd == 'rgst':		# 'rgst':Registration
                id = msg.split(',')[1].rstrip()
                print('rgst ', id, ClientAddr)
                terminalData[id] = ClientAddr
            else: 			# 'send':Send text from one peer
                id = msg.split(',')[1]
                txt = msg.split(',')[2].rstrip()
                #print('send "', txt, '" to ', id)
                if id in terminalData:
                    print('Found: ', terminalData[id][0], \
                          str(terminalData[id][1]), ' --> ', \
                          'send "', txt, '" to ', id)
                    # Send text to another peer
                    s.sendto(txt.encode('utf-8'), terminalData[id])
```
- 待受ポートは62111。
- クライアントを識別するIDとクライントが属するNAT外側IPアドレス情報およびソースポート情報を格納する辞書を定義（terminalData）。
- ソケット作成及び待受ポートを設定。
- selectによる待受。
- データ受信時にコマンドごとの処理を実施。
    - rgst：IDとAT外側IPアドレス情報およびソースポート情報とを辞書へ登録。
    - send：辞書に送信先IDが登録されていれば、IDに相当するIPアドレス（＋宛先ポート番号）にメッセージを送信。

### Client
```python
from socket import *
import select
import threading
import sys
import time

admin_ip = '192.168.2.1'	# Server IP Address
admin_port = 62111		# Server Receive Port

# Registration Thread #
def rgst_thread(s, my_id):
    while 1:
        rgst_msg = 'rgst,' + my_id
        s.sendto(rgst_msg.encode('utf-8'), (admin_ip, admin_port))
        time.sleep(300)

# Send Text Thread #
def send_thread(s, peer_id):
    while 1:
        txt = input()	# Waiting input
        send_msg = 'send,' + peer_id + ',' + txt # + '\n'
        s.sendto(send_msg.encode('utf-8'), (admin_ip, admin_port))

#####  Main  #####
# command: cli.py my_id peer_id
args = sys.argv		# Arguments
if len(args) != 3:
    print('Usage: python ' + args[0] + ' my_id peer_id')
    sys.exit()

# UDP socket
sock = socket(AF_INET, SOCK_DGRAM)

# Call Registration Thread
th1 = threading.Thread(target=rgst_thread, args=(sock, args[1]))
th1.start()                      
# Call Sending Text Thread
th2 = threading.Thread(target=send_thread, args=(sock, args[2]))
th2.start()                      

fds = set()
fds.add(sock)
while 1:
    # Waiting text from peer
    r, w, x = select.select(list(fds), [], [], 3)
    for s in r:
        if s is sock:
            recv_msg, serv_addr = sock.recvfrom(64)
            print(recv_msg.decode(encoding='utf-8'))
```
- サーバーIPアドレスおよびサーバー待受ポートの指定。
- NATにてポート開放を維持するため、クライアントIDを定期的に（ここでは300秒ごと）サーバーへ送信するスレッドの定義。
- キーボードからの入力をサーバー経由で送信先に送るスレッドの定義。
- 実行時の引数を取得。
    - 自分のIDおよび送信先ID
- UDPソケット作成。
- スレッド呼び出し。
    - クライアントIDの定期的な送信。
    - キーボード入力されたテキストの送信。
- 受信時メッセージ表示。

## 検証
Clientでの実行の様子である。Client1にて、「python cli.py pc1 pc2」とコマンド入力している。その後、テキストを入力し、送信後行われている。また、受信したテキストをを表示している。
![pc1のコピー.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/7903aaea-b119-c843-f89a-5b1f28a040e4.jpeg)
Client2にて、「python3 cli.py pc2 pc1」とコマンド入力している。その後は、同様。
![pc2のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/5b469d89-dc2a-f7d7-579a-581a866ce908.png)

Serverのログの状況は下記となる。
```
$ python3 srv.py 
rgst  pc1 ('192.168.2.34', 63312)
rgst  pc2 ('192.168.2.15', 50714)
Found:  192.168.2.15 50714  -->  send " Message from pc1 to pc2 " to  pc2
Found:  192.168.2.34 63312  -->  send " Received!! " to  pc1
Found:  192.168.2.34 63312  -->  send " from pc2 " to  pc1
rgst  pc1 ('192.168.2.34', 63312)
rgst  pc2 ('192.168.2.15', 50714)
```
期待どおりの結果。

EOF