なぜIPを探しているのですか?
先日、データベースの更新を特定の端末に送信するタスクに直面しました。 しかし、送信する前に、どこに送信するか、どこで受け取るかを把握する必要がありました。 一見したところ、サーバーのIPアドレスを端末に伝えてデータを収集する方が論理的ですが、次のような微妙な点がこのような実装を妨げていました。
- これらの端末は公開されており、 キオスクモードで動作します 。 したがって、任意の種類の管理パネルを追加するという考えはすぐになくなりました。なぜなら、ランダムなユーザーは、好きなIPアドレスの設定を「急ぐ」ことができるからです。
- 更新サーバーのIPアドレスを端末に縫うことは可能ですが、サーバーは、私の場合、ユーザーがサブネット上の任意のコンピューターで実行できるデスクトップアプリケーションであるため、このソリューションも機能しませんでした。
- 前の2つの点を考慮すると、パスワードを使用して管理パネルを実装することは可能ですが、それにもかかわらず、更新サーバーの新しいIPアドレスを常に駆動します。これはメンテナンススタッフにとっては頭痛の種です。
したがって、「ピックアップ」のアイデアから「送信」のアイデアに移り、Python 3でIPアドレスの自動検索の実装をいじり始めました。
最初に思いついたのは、データベースの定期的な配布とudp broadcastによるハッシュの合計でしたが、残念ながら、UDPプロトコルは配信された情報の整合性を保証しません 。 ただし、ブロードキャストアドレスを使用するという考え方が最終的な実装方法でした。
そのため、最終的に、UDPサーバーからブロードキャストアドレス255.255.255.255にブロードキャストを送信し、ターミナルにUDPサーバーをインストールすることにしました。UDPサーバーは、このブロードキャストのコマンドを受信すると、中央サーバーへのTCP接続を開きます。
最初のステップ:UDPクライアントを作成する
公式のPythonサイトにはソケットの実装例がいくつかありますが、すぐに問題に遭遇しました。 ブロードキャストアドレスに送信するとき、インタープリターは以下を発行しました: PermissionError:[Errno 13] Permission denied 。 「stackoverflow」で、問題の解決策を見つけました-ソケットへのそのような配布のために、特別なフラグSO_BROADCASTを設定する必要があります。 この事実を考えると、UDPクライアントを作成する機能は次の形式を取りました。
def create_broadcast_socket(): udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) udp_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) udp_sock.settimeout(2) return udp_sock
また、次の関数は、このソケットを介してさまざまなメッセージを送信します。
def ask_addresses(): with create_broadcast_socket() as sock: sock.sendto('show yourself'.encode('utf-8'), ('255.255.255.255', PORT)) def update_many(): with create_broadcast_socket() as sock: sock.sendto('get updates'.encode('utf-8'), ('255.255.255.255', PORT)) def update_one(ip): with create_broadcast_socket() as sock: sock.sendto('get updates'.encode('utf-8'), (ip, PORT))
名前から、彼らが何をしているのかが明確であり、ここで特別な説明は必要ないと思います。
2番目のステップ:UDPサーバーとTCPクライアントを作成する
さいわい、 socketserverモジュールはすでに標準言語ライブラリにあります。 本格的なUDPサーバーを作成するには、 DatagramRequestHandlerクラスから継承し、 handle()メソッドにロジックを実装するだけで十分です。
class EchoServer(socketserver.DatagramRequestHandler): def handle(self): data = self.request[0].strip().decode('utf-8') client_ip = self.client_address[0] if data.startswith('show yourself'): print('show myself') with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as tcp_sock: tcp_sock.connect((client_ip, PORT)) tcp_sock.send('show\n'.encode('UTF-8')) elif data.startswith('get updates'): print('get updates FROM ') with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as tcp_sock: tcp_sock.connect((client_ip, PORT)) tcp_sock.send('get\n'.encode('UTF-8')) part = tcp_sock.recv(1024) file = open('internal.db', 'wb') while part: file.write(part) print(part) part = tcp_sock.recv(1024) file.close() print(self.request)
このサーバーは、特定のポートでUDP接続をリッスンします(ポート番号はグローバル変数PORTに保存されます)。 パケットを受信した後、その内容をチェックし、 「show your」というメッセージがパケット内にある場合、TCP接続を開き、 「show \ n」というメッセージを送信します。その後、更新サーバーのTCPサーバーはこのIPアドレスをアドレスのセットに追加します。 パケットで「get updates」というメッセージを受信した場合、ターミナルはTCP接続を開き、 「get \ n」というメッセージを送信します。その後、SQLiteデータベースファイルのダウンロードが開始されます。 TCPサーバー上のソケットでreadline()メソッドを呼び出すことができるように、便宜上、メッセージの最後に「\ n」文字を使用しました
これらはすべて次のように始まります。
def run_echo_server(): server = socketserver.UDPServer(('', PORT), EchoServer) server.serve_forever()
アドレスの代わりに空の行は、利用可能なすべてのネットワークインターフェイスで接続をリッスンするようサーバーに指示します。
3番目のステップ:TCPクライアントの作成
この通信チェーンの最後のリンクは、更新サーバー上のTCPサーバーになります。 同じsocketserverモジュールのStreamRequestHandlerクラスに基づいて実装されます。 最初の場合と同様に、 handle()メソッドを実装するためだけに残りました。
class NetworkController(socketserver.StreamRequestHandler): def handle(self): request_type = self.rfile.readline() print("{} wrote: {}".format(self.client_address[0], request_type)) if request_type.decode('UTF-8') == 'show\n': scales_catalogue.add(self.client_address[0]) # print(scales_catalogue) elif request_type.decode('UTF-8') == 'get\n': file = open('internal.db', 'rb') part = file.read(1024) while part: self.wfile.write(part) part = file.read(1024) file.close()
上記のように、 「show \ n」というメッセージを受信すると、サーバーはアドレスの重複を避けるために、メッセージが到着したIPアドレスを内部アレイに追加します。 サーバーが「get \ n」というメッセージを受信すると、1024バイトの部分でデータベースファイルの送信を開始します。
このサーバーは、次の機能で開始します。
def create_server(): return socketserver.TCPServer(('', PORT), NetworkController) def run_pong_server(): server = create_server() server.serve_forever()
UDPサーバーと同様に、空の行により、サーバーは使用可能なすべてのネットワークインターフェイスで接続をリッスンします。
まとめ
したがって、システムは、UDPメーリングを介して端末のアドレスを検出し、IPアドレスのリストから選択して端末を強制的に更新されたデータベースファイルを選択するか、UDPメーリングを介してネットワーク上のすべての端末が更新を選択できるようにすることが判明しました。
ニュアンスが理解されない場合、完全なソースコードはGitHubのリポジトリのパブリックドメインにあります 。