1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

wifiルーターを用いたクローズドネットワークでのソケット通信

Posted at

目的

 本記事ではデバイス間(PCとラズパイ)でのソケット通信を、wifiルーターを用いたクローズドネットワーク内で実装してみたいと思います。

 ゆくゆくはクローズドネットワーク内で複数のドローンと通信を確立して、制御を行いたいと思っています。

使用したハード・ソフト

  • MacStudio (2022) ←メイン
  • MacBookPro(2016) ←サブ
  • RaspberryPi 4B
  • VisualStudioCode
  • WiFiルーター(BUFFALO WSR-2533DHP)

アジェンダ

  • WiFiルーターのクローズドネットワーク設定
  • ソケット通信の実装

WiFiルーターのクローズドネットワーク設定

 今回はソケット通信を用いますが、ソケット通信というのはネットワークにおいて、ソケットという「出入り口」を作ることで、通信を行うものです。(超簡単な説明)
 
 これを世界中と繋がるオープンなネットワークで行うこともできますが、セキュリティの面や自分のやりたいことは手元のデバイスを接続できれば良いため、インターネットには接続していないネットワーク(クローズドネットワーク)を作成して使用していきたいと思います。
 当初私はルーターをデフォルトの状態で起動し、接続を試みましたが、設定されているIPアドレスが自動取得になっていたりすることでうまく接続が確立できない経験があるため以下の手順を踏む必要がありました。

 上記のサイトにある方法でWiFiルーターをクローズ状態にできました。
行ったことは

  • ルーター機能の使用
  • マニュアル動作に変更
  • IPアドレス、サブネットマスクの固定

になります。

ソケット通信の実装

ソケット通信の実装をするにあたって私はMacRaspberry Piを使用します。
Raspberry Piを使用するために私はMacからSSH接続によってRaspberryPiを扱いたいと思います。
SSH接続自体はターミナルから簡単に行うことができますが、本開発ではVSCodeを用いたいと思います。

MacからSSH接続をするためにMacRaspberryPiの両方を先ほど設定したクローズドネットワークに接続します。

※Macをお使いの方で、ターミナルからのSSH接続はできるのにVSCodeで接続しようとした際にうまくいかない(XHRエラーなど)場合には以下の記事を参考にしてみてください。(私も以前陥ってしまい、参考にさせていただきました。)

VSCodeでSSH接続を行なって開発を行う際に役立つのが「Remote SSH」という拡張機能です。

スクリーンショット 2023-08-08 13.59.29.png

 私はこちらをインストールして利用しています。以下の記事でわかりやすい説明をされていたので、導入を行う際は参考にしてみてください。

私は新規SSH接続を行う際は下の写真のようにリモートメニューからSSHの「」ボタンを選択し、

スクリーンショット 2023-08-08 14.05.08.png

ssh接続のコマンドを入力しましょう。
(ssh ユーザ名@IPアドレス        のようなものです。)

スクリーンショット 2023-08-08 14.05.20.png

その後接続先のOSパスワードの入力画面が出ることがありますが、提示された通りに入力します。

これにて左下の表示が接続先の名前に変更されれば接続が完了しています。

スクリーンショット 2023-08-08 14.15.36.png

 さて、やっとソケット通信による通信を行うところまで来ました。
本来ならばソケット通信とは?いつ使うの?などと言った解説を行うべきかとは思うのですが、他の知識を持った方が詳しく解説されている記事がたくさんあるのでそちらを参考にしてください…

↑こちらが個人的にはわかりやすかったです。

(やっと)コードを書いていきましょう。

今回は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の方はipconfigMacLinuxの方は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)
    

ソケット通信をしてみる

 それではプログラムを起動してみましょう。

お分かりかとは思いますが、サーバ側を起動してからクライアント側を起動しましょう。

スクリーンショット 2023-08-08 23.31.43.png

(いやぁ隠すところが多すぎる…(だったら表示させなければいいのにとは思います。))

と、いうことで計3回クライアントからのアクセスおよびデータの受信ができたことがわかりますね。
これにてクローズドネットワーク内でのソケット通信は完了になります。
お疲れ様でした。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?