Raspberry Piでソケット通信事始め。送信側を作ろう(Python、socket)
前回Raspberry Pi上で動くソケット通信の受信側アプリをPythonで作りました:
今回は送信側です。一応前回の記事を一通り見て頂いた前提になっておりますので、まだの方は是非ご一読を。
Pythonでの送信側プログラム
送信側(クライアント)の場合、socketオブジェクト生成後にconnectメソッドで送信したい相手のIPアドレスとポート番号を指定します:
client = socket.socket( socket.AF_INET )
client.connect( ("192.168.11.16", 15769) )
IPアドレスが合っていてもポート番号がサーバー側で指定されている物と異なるとデータが伝わりませんのでご注意ください。
connectしたら、もうsendメソッドで任意のデータ(バイナリデータ)を送信できてしまいます:
client.send( "Hello".encode( "utf-8" ) )
上の例では"Hello"という文字列をUTF-8形式のバイナリにしてサーバーへ送信しています。
で、送信し終わったらcloseメソッドを呼んで通信の終了とリソースの解放を行います:
client.close()
clientオブジェクトは再利用できませんので、sendしたらcloseがワンセットとお考え下さい。closeには別に大きな意味がありまして、これを呼ぶ事でサーバー側も「クライアントとの通信が終わった」と判断できるんです。
何も考えずに送信するだけならマジでこれだけです(^-^;。一つだけメッセージを送信して終わるコードはこんな感じになります:
import socket
client = socket.socket( socket.AF_INET )
client.connect( ("192.168.11.16", 15769) )
client.send( "Hello!".encode( "utf-8" ) )
client.close()
みじかw
コンソール入力をサーバーに送るアプリを作ってみよう
さて、上の激短な送信コードだと1回メッセージを送信したらアプリケーションが終わってしまいます。それじゃ流石にねぇという話なので、コンソールで入力した文字列をサーバーに送信するアプリを作ってみましょう。
実行したら文字列入力待ちになり、何か適当に入力してEnterするとそれがサーバーに送信される。何も入力せずにEnterすると送信側は終了。そんなのでどうでしょうか。
コンソール入力待ち
Pythonでコンソールを入力状態にするにはinput関数を使います。例えば、
msg = input( "Input your message: " )
client.send( msg.encode( "utf-8" ) )
こうすると、ターミナルに「Input your message: 」という表示が出て入力待ちになります。入力した文字列は戻り値のmsgに格納されますので、それをUTF-8形式のバイナリに変換してサーバーへ送ればOKです。ちなみにここでUTF-8にするのはサーバー側がそれを想定しているためです。
クライアントsocketオブジェクトは使い捨て!
先程も申したように一度sendしたクライアントのsocketオブジェクトは使い回しが出来ません。2回目のsendをすると例外が発生してしまいます。なのでsend済みのsocketオブジェクトはcolseして破棄し、次の送信ではクライアントsocketオブジェクトを都度作り直します。
以上を考慮したアプリの全コードはこういう感じです:
import socket
while True:
client = socket.socket( socket.AF_INET )
client.connect( ("192.168.11.16", 15769) )
msg = input( "Input your message: " )
if ( len( msg ) == 0 ):
break
client.send( msg.encode( "utf-8" ) )
client.close()
これでターミナルからサーバーに文字列をガシガシ送信できますw
前回のサーバー側も起動し実際に動作テストしてみました:
左側がクライアント側でWindows上でPythonを実行しています。右側はRaspberry Piでサーバー側を担当しています。どちらもVS Codeで実行しています。クライアントで打ち込んだ文字列がサーバーのコンソールに表示されていますね(^-^)
簡単に例外発生します
さてさて、こんな感じで送信はさっくり簡単ですが、落とし穴が沢山あります。
例えばサーバー側を起動せず、待ち受けが無い状態でクライアント側を動かすと…
connectメソッドで相手のIPアドレスを指定した所で例外が発生してアプリケーションが落ちてしまいます。その住所が無いって言われているんですね。こんな感じでネットワーク通信は様々な所で例外が発生し、プログラムが落ちます。その為実用するにはこの例外対処を「絶対に」する必要があります。
try~exceptで例外を補足
例外に対処するにはtry~catch構文で上のコードを包んであげます。それ自体は簡単。問題はその対処をどうするかです。勿論それはアプリケーションの仕様によりますので定まったルールはありません。今回はconnectに失敗した場合input入力に「Failed connection. Press enter to retry.」と表示して、Enterを押したら再connectを試みるという仕様にしてみます:
import socket
while True:
client = socket.socket( socket.AF_INET )
try:
client.connect( ("192.168.11.16", 15769) )
except:
input( "Failed connection. Press enter to retry." )
continue
msg = input( "Input your message: " )
if ( len( msg ) == 0 ):
break
client.send( msg.encode( "utf-8" ) )
client.close()
例外が発生するconnectメソッド呼び出しの所をtryで包み、exeptに例外発生時の対処を記述しています。
こうすると少なくともconnectメソッドの所で例外が起こっても再接続を促す事が出来る為プログラムは止まらずに進行できます。実際のソケット通信(ネットワーク通信全般)はこういう例外との戦いです。
実用に向けて
今回はソケット通信の送信側のサンプルを作ってみました。受信側よりもさらに短いコードで送信する事が可能なのはソケット通信のありがたい所です。
ただし前回のサーバー側、そして今回の送信側はあくまでサンプルでして、このままでは実用するに厳しい所が沢山あります。例外処理もその一つですが、実際に実用する時には最低限「サーバーからのコールバック」が必要になります。
サーバーからデータを得るコールバック
クライアントからサーバーへコマンドを送信する時、メッセージを投げっぱなしにする場合もありますが、大抵は「受け取った結果どうだった?」と結果を返してもらいたくなります。例えば「現在のロボットの位置を返して」とか「○○センサーの現在の値を教えて」みたいにRaspberry Piに搭載された外部機器の状態を得たい事だって沢山ある訳です。でもソケット通信は基本一方通行なので、これを実現するには次のようなやり取りを実装しなければならなくなります:
クライアント側から「データをくれ」というコマンドをサーバーへsend
クライアント側は即座にサーバーからの返答を受信するモードに以降
サーバー側で要求を受信。コマンドを解釈して指定データをクライアントにsend(コールバック)
クライアント側でコールバック受信。データを得る。
こういう双方向の同調と通信のやり取りが必要になります。しかもネットを介した通信には遅延が絶対生じます。その時acceptでブロッキングした状態だど、ソケットを受信するまでプログラムが止まってしまいます。それはまずい訳です。最悪クライアント→サーバ及びサーバ→クライアントのどこかでエラーが発生して通信が行われない場合もあります。そうすると永久にブロッキング…となってしまいます。
その他にも対処が必要な事は色々あります。そういう所をクリアしていって、初めて使い物になるソケット通信になっていきます。巷にはきっとその辺りをラップしてくれている上位ライブラリが落ちているとは思いますので、それを探して組み込んでしまうのも正直ありかなとは思いますが、ガリガリ作りたい(僕もそうw)人や可能な限りホワイトボックスでありたいという人もいるはず。という事で次回は上の「コールバック」を実現してみましょう。
ではまた(^-^)/
<次回>
この記事が気に入ったらサポートをしてみませんか?