この記事の内容はRaspberry Pi 4と3の両方で動作確認済みです
ブログ管理者のP.Hです!
今回はRaspberry PiでSocket通信を行う方法を紹介したいと思います。ネットワークが繋がっていれば、Socket通信で制御やデータのやり取りが出来るので、とても便利です。最近はデバイスやコントローラを全てLANで接続して、制御することが多くなっていますので、このSocket通信を使う機会がかなり増えていると思います。多くの機器に対応した通信方式ですので、マスターしておくことをお勧めします。
それではSocket通信の仕方を紹介します。Socket通信はサーバー⇔クライアント間で、通信する方式になっています。今回はクライアント側のサンプルコードの説明となります。
※下記のサーバー側の記事と合わせてお読みください。
サンプルコードの内容
今回紹介するサンプルコードは以下のように動作します。
- クライアントから文字を入力
- 入力した文字列をサーバーに送信
- サーバーがデータを受け取る
- 受け取ったデータをそのままクライアントに送信
構成
- サーバー:Raspberry Pi IPアドレス 192.168.1.10 ポート:12345
- クライアント:Raspberry Pi IPアドレス 192.168.1.15 ポート:12345
クライアント側のサンプルコード
今回、サンプルコードを2パターン紹介したいと思います。
- 通信する毎にソケットのオープン/クローズをする方法
- はじめの1回だけソケットのオープン/クローズをする方法
現在のハードウェアスペックであれば、毎回ソケットのオープン/クローズをしても全く問題ありませんが、こちらのほうが処理は重たいです。
クライアント側のサンプルコード ①
通信する毎にソケットのオープン/クローズをするサンプルコードです。コメントも記載していますので、まずざっと目を通してみてください。
import socket import time from datetime import datetime HOST_IP = "192.168.0.10" # 接続するサーバーのIPアドレス PORT = 12345 # 接続するサーバーのポート DATESIZE = 1024 # 受信データバイト数 class SocketClient(): def __init__(self, host, port): self.host = host self.port = port self.socket = None def send_recv(self, input_data): # sockインスタンスを生成 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: # ソケットをオープンにして、サーバーに接続 sock.connect((self.host, self.port)) print('[{0}] input data : {1}'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), input_data) ) # 入力データをサーバーへ送信 sock.send(input_data.encode('utf-8')) # サーバーからのデータを受信 rcv_data = sock.recv(DATESIZE) rcv_data = rcv_data.decode('utf-8') print('[{0}] recv data : {1}'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), rcv_data) ) if __name__ == '__main__': client = SocketClient(HOST_IP, PORT) while True: input_data = input("send data:") # ターミナルから入力された文字を取得 client.send_recv(input_data)
それではコードの説明をしてきます。
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
⇒ sockモジュールをインスタンス化。with文を抜けると自動的にソケットをcloseする。sock.connect( (self.host, self.port) )
⇒ サーバー側のIPアドレスとポートを引数に指定。ソケットをオープンにして、サーバーに接続する。
あとは、send関数でデータをサーバーへ送信、recv関数でサーバーからデータを受信しています。
クライアント側のサンプルコード ②
はじめの1回だけソケットのオープンをするサンプルコードです。
import socket import time from datetime import datetime HOST_IP = "192.168.0.10" # 接続するサーバーのIPアドレス PORT = 12345 # 接続するサーバーのポート DATESIZE = 1024 # 受信データバイト数 INTERVAL = 3 # ソケット接続時のリトライ待ち時間 RETRYTIMES = 5 # ソケット接続時のリトライ回数 class SocketClient(): def __init__(self, host, port): self.host = host self.port = port self.socket = None def connect(self): client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # サーバーとの接続 RETRYTIMESの回数だけリトライ for x in range(RETRYTIMES): try: client_socket.connect((self.host, self.port)) self.socket = client_socket print('[{0}] server connect -> address : {1}:{2}'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), self.host, self.port) ) break except socket.error: # 接続を確立できない場合、INTERVAL秒待ってリトライ time.sleep(INTERVAL) print('[{0}] retry after wait{1}s'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), str(INTERVAL)) ) # サーバーへデータ送信関数 def send(self): # ターミナルから入力された文字を取得 input_data = input("send data:") print('[{0}] input data : {1}'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), input_data) ) input_data = input_data.encode('utf-8') self.socket.send(input_data) # データ送信 # サーバーからデータ受信関数 def recv(self): rcv_data = self.socket.recv(DATESIZE) # データ受信 rcv_data = rcv_data.decode('utf-8') print('[{0}] recv data : {1}'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), rcv_data) ) return rcv_data # 上記の送信/受信関数を順番に行う def send_rcv(self): self.send() return self.recv() # ソケットをクローズする関数 def close(self): self.socket.close() # ソケットクローズ self.socket = None if __name__ == '__main__': client = SocketClient(HOST_IP, PORT) client.connect() # はじめの1回だけソケットをオープン while True: if client.socket is not None: # quitが戻ってくる(自分でquitと入力する)とソケットをクローズして終了 if client.send_rcv() == 'quit': client.close() else: break
- def connect(self):
⇒ client_socket.connectでサーバーとの接続を試みます。接続ができない場合は、INTERVALの値だけ待った後に、RETRYTIMESの回数だけリトライをします。
あとは、send関数でデータをサーバーへ送信、recv関数でサーバーからデータを受信しています。ターミナルで"quit"と入力すると、quitの文字列がサーバーに送られ、サーバーからquitの文字列がクライアントに返ってきます。この操作を行うと、client.close()が呼ばれ、ソケットがクローズされます。
以上でSocket通信のクライアント側の説明は終了です。