目的
本記事ではデバイス間
(PCとラズパイ)でのソケット通信を、wifiルーター
を用いたクローズドネットワーク内
で実装してみたいと思います。
ゆくゆくはクローズドネットワーク内で複数のドローンと通信を確立して、制御を行いたいと思っています。
使用したハード・ソフト
- MacStudio (2022) ←メイン
- MacBookPro(2016) ←サブ
- RaspberryPi 4B
- VisualStudioCode
- WiFiルーター(BUFFALO WSR-2533DHP)
アジェンダ
- WiFiルーターのクローズドネットワーク設定
- ソケット通信の実装
WiFiルーターのクローズドネットワーク設定
今回はソケット通信を用いますが、ソケット通信というのはネットワークにおいて、ソケットという「出入り口
」を作ることで、通信を行うものです。(超簡単な説明)
これを世界中と繋がるオープン
なネットワークで行うこともできますが、セキュリティの面や自分のやりたいことは手元のデバイスを接続できれば良いため、インターネットには接続していないネットワーク(クローズドネットワーク
)を作成して使用していきたいと思います。
当初私はルーターをデフォルトの状態で起動し、接続を試みましたが、設定されているIPアドレスが自動取得になっていたりすることでうまく接続が確立できない経験があるため以下の手順を踏む必要がありました。
上記のサイトにある方法でWiFiルーターをクローズ状態にできました。
行ったことは
- ルーター機能の使用
- マニュアル動作に変更
- IPアドレス、サブネットマスクの固定
になります。
ソケット通信の実装
ソケット通信の実装をするにあたって私はMac
とRaspberry Pi
を使用します。
Raspberry Pi
を使用するために私はMacからSSH接続
によってRaspberryPi
を扱いたいと思います。
SSH接続自体はターミナル
から簡単に行うことができますが、本開発ではVSCode
を用いたいと思います。
Mac
からSSH接続
をするためにMac
、RaspberryPi
の両方を先ほど設定したクローズドネットワークに接続します。
※Macをお使いの方で、ターミナルからのSSH接続はできるのにVSCodeで接続しようとした際にうまくいかない(XHRエラーなど)場合には以下の記事を参考にしてみてください。(私も以前陥ってしまい、参考にさせていただきました。)
VSCodeでSSH接続を行なって開発を行う際に役立つのが「Remote SSH」という拡張機能です。
私はこちらをインストールして利用しています。以下の記事でわかりやすい説明をされていたので、導入を行う際は参考にしてみてください。
私は新規SSH接続
を行う際は下の写真のようにリモートメニュー
からSSHの「+
」ボタンを選択し、
ssh接続
のコマンドを入力しましょう。
(ssh ユーザ名@IPアドレス のようなものです。)
その後接続先のOS
やパスワード
の入力画面が出ることがありますが、提示された通りに入力します。
これにて左下の表示が接続先の名前に変更されれば接続が完了しています。
さて、やっとソケット通信
による通信を行うところまで来ました。
本来ならばソケット通信
とは?いつ使うの?などと言った解説を行うべきかとは思うのですが、他の知識を持った方が詳しく解説されている記事がたくさんあるのでそちらを参考にしてください…
↑こちらが個人的にはわかりやすかったです。
(やっと)コードを書いていきましょう。
今回はRaspberryPi
を使用してソケット通信
を行うため、(私の場合は)python
にてプログラムを書いていきます。
仕様としては
- MacStudio (サーバ)
- RaspberryPi (クライアント)
にて行います。
サーバ側
それではコードを書いていきましょう。
今回のシステムではターミナル
からのプログラム起動時にオプションとしてIP
やポート
の指定ができるようになっていますが、本記事ではファイルを通常実行し、宣言されているIP
とポート
に接続します。
それらの設定
を行なっているのが以下の部分です。
import sys
import time
SERVER = 'localhost'
WAITING_PORT = 8765
LOOP_WAIT = 3
この箇所ではインタプリタや実行環境を扱うためにsys
、待機時間を作るためにtime
をインポートしています。
また、パブリックな変数としてサーバとして使用するIPを格納するSERVER
、受信を待機するポート番号を格納するWAITING_PORT
、ループする際の待機時間を格納するLOOP_WAIT
を宣言しています。
今回はIPアドレスにlocalhost
を使用していますが、有線で直結をしていない限りはIP
を指定してあげた方が良いです。(できるならIPを調べることもやってみましょう。)
IPアドレス
の調べ方はいろいろありますが、ターミナル等
で検索できると思いますのでwindows
の方はipconfig
、Mac
やLinux
の方はifconfig
と入力して検索してあげましょう。(どこを見れば良いかわからない方は以下の記事を参考にしてみると良さそうです。)
上記のようにCUI
から操作(というほどかっこいいものでもありませんが)する必要なく設定画面などで簡単に確認できますが、せっかくなのでコマンドも打ってみましょう〜
さて話を戻しまして、続きに参ります。
def server_test(server_v1=SERVER, waiting_port_v1=WAITING_PORT):
import socket
# socoket for waiting of the requests.
# AF_INET : IPv4
# SOCK_STREAM : TCP
socket_w = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_w.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
node_s = server_v1
port_s = waiting_port_v1
socket_w.bind((node_s, port_s))
BACKLOG = 5
socket_w.listen(BACKLOG)
print('Waiting for the connection from the client(s). '
+ 'node: ' + node_s + ' '
+ 'port: ' + str(port_s))
try:
while True:
socket_s_r, client_address = socket_w.accept()
print('Connection from '
+ str(client_address)
+ " has been established.")
data_r = socket_s_r.recv(1024)
data_r_str = data_r.decode('utf-8')
print('I (the server) have just received the data __'
+ data_r_str + '__ from the client. '
+ str(client_address))
time.sleep(LOOP_WAIT)
print("Now, closing the data socket.")
socket_s_r.close()
except KeyboardInterrupt:
print("Ctrl-C is hit!")
print("Now, closing the data socket.")
socket_s_r.close()
print("Now, closing the waiting socket.")
socket_w.close()
いきなり長いコードに見えますが、ここがソケット通信
を行なっている関数
です。難しいことをいろいろ行なっているようですが、ソケット通信を学んだみなさんならわかるところが多いはずです。
- ソケットのインポート
- ソケットの設定
- クライアントからの受信待ち許可
- クライアントからの受信待ち開始
- クライアントから受信
- ソケット通信送信受信終了
やっていることはこれくらいですね。
ここまできたらあとはソケット通信関数を扱えるメインプログラムを書くだけです。
if __name__ == '__main__':
print("Start if __name__ == '__main__'")
sys_argc = len(sys.argv)
count = 1
hostname_v = SERVER
waiting_port_v = WAITING_PORT
while True:
print(count, "/", sys_argc)
if(count >= sys_argc):
break
option_key = sys.argv[count]
# print(option_key)
if ("-h" == option_key):
count = count + 1
hostname_v = sys.argv[count]
# print(option_key, hostname_v)
if ("-p" == option_key):
count = count + 1
waiting_port_v = int(sys.argv[count])
# print(option_key, waiting_port_v)
count = count + 1
print(hostname_v)
print(waiting_port_v)
server_test(hostname_v, waiting_port_v)
上の方で少し言及していましたが、コマンドラインからプログラムを起動した際に付け加えられるオプション項目の部分を取得するための部分が大半になっていますね。(Whileのあたり)
実際にソケット通信の関数を呼び出しているのは一番下のserver_test()
の部分だけです。なのでコマンドラインからの処理オプションをオミットすれば
if __name__ == '__main__':
print("Start if __name__ == '__main__'")
sys_argc = len(sys.argv)
count = 1
hostname_v = SERVER
waiting_port_v = WAITING_PORT
print(hostname_v)
print(waiting_port_v)
server_test(hostname_v, waiting_port_v)
たったこれだけってことになります。
サーバ側のコードは以上になります。(以下サーバコード)
import sys
import time
SERVER = 'localhost'
WAITING_PORT = 8765
LOOP_WAIT = 3
def server_test(server_v1=SERVER, waiting_port_v1=WAITING_PORT):
import socket
# socoket for waiting of the requests.
# AF_INET : IPv4
# SOCK_STREAM : TCP
socket_w = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_w.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
node_s = server_v1
port_s = waiting_port_v1
socket_w.bind((node_s, port_s))
BACKLOG = 5
socket_w.listen(BACKLOG)
print('Waiting for the connection from the client(s). '
+ 'node: ' + node_s + ' '
+ 'port: ' + str(port_s))
try:
while True:
socket_s_r, client_address = socket_w.accept()
print('Connection from '
+ str(client_address)
+ " has been established.")
data_r = socket_s_r.recv(1024)
data_r_str = data_r.decode('utf-8')
print('I (the server) have just received the data __'
+ data_r_str + '__ from the client. '
+ str(client_address))
time.sleep(LOOP_WAIT)
print("Now, closing the data socket.")
socket_s_r.close()
except KeyboardInterrupt:
print("Ctrl-C is hit!")
print("Now, closing the data socket.")
socket_s_r.close()
print("Now, closing the waiting socket.")
socket_w.close()
クライアント側
こちらのコードで行いたいことはサーバ側よりもシンプルです。
クライアント側では通信先のサーバIP
とポート番号
を指定します。また、今回のプログラムでは送信内容もグローバルで宣言してしまいます。
import sys
SERVER = 'localhost'
WAITING_PORT = 8765
MESSAGE_FROM_CLIENT = "Hello, I am a client."
サーバ側のコードを乗り越えた方々なら容易に突破できたことでしょう。
続いてはこちらもソケット通信の関数を作成していきます。
def socket_client_single(hostname_v1 = SERVER, waiting_port_v1 = WAITING_PORT, message1 = MESSAGE_FROM_CLIENT):
import socket
import time
node_s = hostname_v1
port_s = waiting_port_v1
# socoket for receiving and sending data
# AF_INET : IPv4
# SOCK_STREAM : TCP
socket_r_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_r_s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
print("node_s:", node_s, " port_s:", str(port_s))
socket_r_s.connect((node_s, port_s))
print('Connecting to the server. '
+ 'node: ' + node_s + ' '
+ 'port: ' + str(port_s))
data_s_str = message1
#data_s = bytes(data_s_str, encoding = 'utf-8')
data_s = data_s_str.encode('utf-8')
socket_r_s.send(data_s)
print('I (a client) have just sent data __'
+ data_s_str
+ '__ to the server ' + node_s + ' .')
socket_r_s.close()
こちらでもサーバ側同様に
- ソケット通信の設定
- サーバへの接続リクエスト
- データの送信
- ソケット通信の終了
というプロセスを踏んでいます。
そして最後にこちらもソケット通信関数を扱うメインプログラムです。
if __name__ == '__main__':
print("Start if __name__ == '__main__'")
sys_argc = len(sys.argv)
count = 1
hostname_v = SERVER
waiting_port_v = WAITING_PORT
message_v = MESSAGE_FROM_CLIENT
while True:
#print(count, "/", sys_argc)
if(count >= sys_argc):
break
option_key = sys.argv[count]
#print(option_key)
if ("-h" == option_key):
count = count + 1
hostname_v = sys.argv[count]
#print(option_key, hostname_v)
if ("-p" == option_key):
count = count + 1
waiting_port_v = int(sys.argv[count])
#print(option_key, port_v)
if ("-m" == option_key):
count = count + 1
message_v = sys.argv[count]
#print(option_key, message_v)
count = count + 1
print(hostname_v)
print(waiting_port_v)
print(message_v)
socket_client_single(hostname_v, waiting_port_v, message_v)
こちらのプログラムもコマンドラインからのオプションをオミットすれば
if __name__ == '__main__':
print("Start if __name__ == '__main__'")
sys_argc = len(sys.argv)
count = 1
hostname_v = SERVER
waiting_port_v = WAITING_PORT
message_v = MESSAGE_FROM_CLIENT
print(hostname_v)
print(waiting_port_v)
print(message_v)
socket_client_single(hostname_v, waiting_port_v, message_v)
こんなもんですね。(以下クライアント側全コード)
import sys
SERVER = 'localhost'
WAITING_PORT = 8765
MESSAGE_FROM_CLIENT = "Hello, I am a client."
def socket_client_single(hostname_v1 = SERVER, waiting_port_v1 = WAITING_PORT, message1 = MESSAGE_FROM_CLIENT):
import socket
import time
node_s = hostname_v1
port_s = waiting_port_v1
# socoket for receiving and sending data
# AF_INET : IPv4
# SOCK_STREAM : TCP
socket_r_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_r_s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
print("node_s:", node_s, " port_s:", str(port_s))
socket_r_s.connect((node_s, port_s))
print('Connecting to the server. '
+ 'node: ' + node_s + ' '
+ 'port: ' + str(port_s))
data_s_str = message1
#data_s = bytes(data_s_str, encoding = 'utf-8')
data_s = data_s_str.encode('utf-8')
socket_r_s.send(data_s)
print('I (a client) have just sent data __'
+ data_s_str
+ '__ to the server ' + node_s + ' .')
socket_r_s.close()
if __name__ == '__main__':
print("Start if __name__ == '__main__'")
sys_argc = len(sys.argv)
count = 1
hostname_v = SERVER
waiting_port_v = WAITING_PORT
message_v = MESSAGE_FROM_CLIENT
while True:
#print(count, "/", sys_argc)
if(count >= sys_argc):
break
option_key = sys.argv[count]
#print(option_key)
if ("-h" == option_key):
count = count + 1
hostname_v = sys.argv[count]
#print(option_key, hostname_v)
if ("-p" == option_key):
count = count + 1
waiting_port_v = int(sys.argv[count])
#print(option_key, port_v)
if ("-m" == option_key):
count = count + 1
message_v = sys.argv[count]
#print(option_key, message_v)
count = count + 1
print(hostname_v)
print(waiting_port_v)
print(message_v)
socket_client_single(hostname_v, waiting_port_v, message_v)
ソケット通信をしてみる
それではプログラムを起動してみましょう。
お分かりかとは思いますが、サーバ側
を起動してからクライアント側
を起動しましょう。
(いやぁ隠すところが多すぎる…(だったら表示させなければいいのにとは思います。))
と、いうことで計3回クライアントからのアクセスおよびデータの受信ができたことがわかりますね。
これにてクローズドネットワーク内でのソケット通信は完了になります。
お疲れ様でした。