第16章 ネットワーク

ソケット通信の基礎

「ソケット通信」は、ネットワーク上での通信を実現するためのプログラミングインタフェースです。Pythonの標準ライブラリであるsocketモジュールを使用することで、簡単にソケット通信を実現することができます。以下に、基本的なTCPのクライアントおよびサーバーのソケット通信の例を示します。

TCPサーバ

サーバーサイドのソケット通信では、まず待ち受けのためのソケットを作成し、クライアントからの接続を待機します。

import socket

# ホストとポート番号を指定
HOST = '127.0.0.1'
PORT = 65432

# ソケットオブジェクトの作成
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    # アドレスの再利用を設定
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
    # ソケットにホストとポートをバインド
    s.bind((HOST, PORT))
    
    # 接続の待機
    s.listen()
    print("クライアントからの接続を待機中...")
    
    # 接続が来たら新しいソケットとアドレスを取得
    conn, addr = s.accept()
    
    with conn:
        print(f'接続されました: {addr}')
        # データの受信と送信
        data = conn.recv(1024)
        conn.sendall(data)

TCPクライアント

クライアントサイドでは、サーバーのホストとポートに接続します。接続後、データを送受信することができます。

import socket

# サーバーのホストとポート番号を指定
HOST = '127.0.0.1'
PORT = 65432

# ソケットオブジェクトの作成と接続
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    
    # データの送信
    s.sendall(b'Hello, Server!')
    
    # データの受信
    data = s.recv(1024)
    
print(f'受信したデータ: {data.decode()}')

UDPサーバー

サーバーサイドのUDP通信では、データの受信待ちを行います。接続の確立は必要ありません。

import socket

# ホストとポート番号を指定
HOST = '127.0.0.1'
PORT = 65432

# UDPソケットの作成
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
    # ソケットにホストとポートをバインド
    s.bind((HOST, PORT))
    
    print("データの受信を待機中...")
    
    # データの受信 (データと送信元のアドレスを取得)
    data, addr = s.recvfrom(1024)
    print(f'受信データ: {data.decode()} 送信元アドレス: {addr}')

UDPクライアント

クライアントサイドでは、指定したサーバーのホストとポートにデータを送信します。

import socket

# サーバーのホストとポート番号を指定
HOST = '127.0.0.1'
PORT = 65432

# UDPソケットの作成
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
    # データの送信
    s.sendto(b'Hello, Server!', (HOST, PORT))


エコーサーバの実装例

エコーサーバは、クライアントからのメッセージをそのままクライアントに返すシンプルなサーバです。以下は、TCPを用いたエコーサーバの例を示しています。

import socket

def start_echo_server(host, port):
    # TCPソケットの作成
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((host, port))
        s.listen()

        print(f"エコーサーバが {host}:{port} で待機中...")

        # クライアントからの接続を待機
        conn, addr = s.accept()
        with conn:
            print(f"接続先: {addr}")
            while True:
                data = conn.recv(1024)
                if not data:
                    break
                print(f"受信データ: {data.decode()}")
                conn.sendall(data)

if __name__ == "__main__":
    HOST = '127.0.0.1'
    PORT = 65432
    start_echo_server(HOST, PORT)

エコーサーバは、クライアントからの接続を待機し、メッセージを受信するとそのメッセージをそのまま返します。


非同期ソケットプログラミング

asyncioを用いた非同期ソケットプログラミングを実現する際、通常はasyncioのストリーム (StreamReaderStreamWriter) を使用します。

以下は、非同期エコーサーバと非同期エコークライアントの例を示しています。


非同期エコーサーバ

import asyncio

async def echo_handler(reader, writer):
    data = await reader.read(100)
    message = data.decode()
    addr = writer.get_extra_info('peername')
    
    print(f"受信 {message} from {addr}")
    
    print("送信:", message)
    writer.write(data)
    await writer.drain()

    writer.close()
    await writer.wait_closed()

async def main():
    server = await asyncio.start_server(echo_handler, '127.0.0.1', 8888)

    addr = server.sockets[0].getsockname()
    print(f'エコーサーバーが {addr} で待機中...')

    async with server:
        await server.serve_forever()

asyncio.run(main())


非同期エコークライアント

import asyncio

async def echo_client(message):
    reader, writer = await asyncio.open_connection('127.0.0.1', 8888)

    print(f'送信: {message}')
    writer.write(message.encode())
    await writer.drain()

    data = await reader.read(100)
    print(f'受信: {data.decode()}')

    writer.close()
    await writer.wait_closed()

message = "Hello, Asyncio!"
asyncio.run(echo_client(message))


ネットワーク関連ツール

Pythonは、ネットワーク関連のツールやスクリプトを手軽に作成するための機能を多数提供しています。以下は、Pythonを使用して作成できる基本的なネットワークツールの一例です。


IPアドレスの確認

import socket

def get_ip_address():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # GoogleのDNSサーバをターゲットとする
    s.connect(("8.8.8.8", 80))
    ip_address = s.getsockname()[0]
    s.close()
    return ip_address

print(f"My IP Address: {get_ip_address()}")


ポートスキャナ

import socket

def scan_ports(target_ip, ports_to_scan):
    open_ports = []
    for port in ports_to_scan:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        socket.setdefaulttimeout(1)
        result = s.connect_ex((target_ip, port))
        if result == 0:
            open_ports.append(port)
        s.close()
    return open_ports

target = "127.0.0.1"
open_ports = scan_ports(target, range(1, 1025))
print(f"Open ports on {target}: {open_ports}")


WHOIS情報の取得

import whois

def get_whois(domain):
    return whois.whois(domain)

domain_info = get_whois("google.com")
print(domain_info)


DNSルックアップ

import socket

def get_domain_ip(domain):
    return socket.gethostbyname(domain)

def get_ip_domain(ip_address):
    return socket.gethostbyaddr(ip_address)

print(get_domain_ip("google.com"))
print(get_ip_domain("8.8.8.8"))


簡易HTTPサーバ

# Python 3.xでの記述
from http.server import SimpleHTTPRequestHandler, HTTPServer

def run_server(port=8080):
    handler = SimpleHTTPRequestHandler
    httpd = HTTPServer(("", port), handler)
    print(f"Serving on port {port}")
    httpd.serve_forever()

run_server()


ICMPを使用したPingツール

from ping3 import ping

def send_ping(host):
    response_time = ping(host)
    if response_time:
        print(f"{host} is reachable. Response time: {response_time} ms")
    else:
        print(f"{host} is unreachable.")

send_ping("google.com")


ネットワークインターフェースの情報取得

import netifaces

def list_interfaces():
    return netifaces.interfaces()

def get_interface_details(interface):
    return netifaces.ifaddresses(interface)

print(list_interfaces())
print(get_interface_details("eth0"))  # 例: "eth0" インターフェースの詳細情報を取得


練習問題1.

TCPソケットを使用して、簡易的なチャットサーバとクライアントを作成してください。

  1. サーバーは指定されたポートで待機し、複数のクライアントからの接続を受け入れることができます。
  2. クライアントはサーバーに接続し、テキストメッセージを送信します。
  3. サーバーは、受信したメッセージをすべての接続されたクライアントにブロードキャストします。


練習問題2.

指定されたドメイン名に関連する情報(IPアドレス、別名、使用されているメールサーバなど)を取得するツールを作成してください。

  1. socketおよびその他の関連モジュールを使用して、指定されたドメイン名のIPアドレスを取得します。
  2. 追加で、DNSのMXレコードを調査して、指定されたドメインのメールサーバを取得します。


練習問題3.

asyncioモジュールを使用して、非同期のエコーサーバを作成してください。

  1. サーバーは指定されたポートで待機し、複数のクライアントからの接続を同時に処理できるようにします。。
  2. クライアントはサーバーにメッセージを送信し、そのメッセージをそのまま返信として受け取ります。
  3. サーバーは受信したメッセージをそのままクライアントに返します。