Python Language
ソケット
サーチ…
前書き
多くのプログラミング言語は、ソケットを使用してプロセス間またはデバイス間で通信します。このトピックでは、Pythonのソケットモジュールを使用して、一般的なネットワークプロトコルを使ってデータを送受信できるようにする方法について説明します。
パラメーター
パラメータ | 説明 |
---|---|
socket.AF_UNIX | UNIXソケット |
socket.AF_INET | IPv4 |
socket.AF_INET6 | IPv6 |
socket.SOCK_STREAM | TCP |
socket.SOCK_DGRAM | UDP |
UDP経由のデータ送信
UDPはコネクションレスプロトコルです。他のプロセスまたはコンピュータへのメッセージは、何らかの接続を確立することなく送信されます。あなたのメッセージが受信された場合、自動確認はありません。 UDPは、通常、レイテンシに敏感なアプリケーションや、ネットワーク全体のブロードキャストを送信するアプリケーションで使用されます。
次のコードは、UDPを使用してローカルホストのポート6667で待機しているプロセスにメッセージを送信します
UDPはコネクションレスなので、送信後にソケットを「閉じる」必要はないことに注意してください。
from socket import socket, AF_INET, SOCK_DGRAM
s = socket(AF_INET, SOCK_DGRAM)
msg = ("Hello you there!").encode('utf-8') # socket.sendto() takes bytes as input, hence we must encode the string first.
s.sendto(msg, ('localhost', 6667))
UDPによるデータ受信
UDPはコネクションレスプロトコルです。つまり、メッセージを送信するピアは、メッセージを送信する前に接続を確立する必要はありません。 socket.recvfrom
はタプルを返す( msg
[ソケットが受け取ったメッセージ]、 addr
[送信者のアドレス])
socket
モジュールのみを使用するUDPサーバ:
from socket import socket, AF_INET, SOCK_DGRAM
sock = socket(AF_INET, SOCK_DGRAM)
sock.bind(('localhost', 6667))
while True:
msg, addr = sock.recvfrom(8192) # This is the amount of bytes to read at maximum
print("Got message from %s: %s" % (addr, msg))
以下は、 socketserver.UDPServer
を使用した別の実装socketserver.UDPServer
:
from socketserver import BaseRequestHandler, UDPServer
class MyHandler(BaseRequestHandler):
def handle(self):
print("Got connection from: %s" % self.client_address)
msg, sock = self.request
print("It said: %s" % msg)
sock.sendto("Got your message!".encode(), self.client_address) # Send reply
serv = UDPServer(('localhost', 6667), MyHandler)
serv.serve_forever()
デフォルトでは、 sockets
ブロックされます。つまり、スクリプトの実行は、ソケットがデータを受け取るまで待機します。
TCP経由のデータ送信
インターネットを介してデータを送信することは、複数のモジュールを使用して可能になります。ソケットモジュールは、他のコンピュータやプロセスからのデータの送受信を担う、基礎となるオペレーティングシステムオペレーションへの低レベルのアクセスを提供します。
次のコードは、ホストlocalhost上のポート6667で待機しているTCPサーバーにバイト文字列b'Hello'
送信し、終了時に接続を閉じます。
from socket import socket, AF_INET, SOCK_STREAM
s = socket(AF_INET, SOCK_STREAM)
s.connect(('localhost', 6667)) # The address of the TCP server listening
s.send(b'Hello')
s.close()
ソケット出力はデフォルトでブロックされています。つまり、プログラムは接続が完了するまで待機し、アクションが完了するまで呼び出しを待機します。接続の場合、サーバーが実際に接続を受け入れることを意味します。送信の場合、オペレーティングシステムにデータをキューイングして後で送信するのに十分なバッファスペースがあることを意味します。
ソケットは使用後は常に閉じてください。
マルチスレッドTCPソケットサーバー
引数なしで実行すると、このプログラムはポート5000
127.0.0.1
への接続をリスンするTCPソケットサーバーを開始します。サーバーは、別のスレッドで各接続を処理します。
-c
引き数を指定して実行すると、このプログラムはサーバーに接続し、クライアント・リストを読み取り、それを出力します。クライアントリストはJSON文字列として転送されます。クライアント名は、 -n
引数を渡すことで指定できます。異なる名前を渡すことにより、クライアントリストへの影響が観察されることがあります。
client_list.py
import argparse
import json
import socket
import threading
def handle_client(client_list, conn, address):
name = conn.recv(1024)
entry = dict(zip(['name', 'address', 'port'], [name, address[0], address[1]]))
client_list[name] = entry
conn.sendall(json.dumps(client_list))
conn.shutdown(socket.SHUT_RDWR)
conn.close()
def server(client_list):
print "Starting server..."
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('127.0.0.1', 5000))
s.listen(5)
while True:
(conn, address) = s.accept()
t = threading.Thread(target=handle_client, args=(client_list, conn, address))
t.daemon = True
t.start()
def client(name):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 5000))
s.send(name)
data = s.recv(1024)
result = json.loads(data)
print json.dumps(result, indent=4)
def parse_arguments():
parser = argparse.ArgumentParser()
parser.add_argument('-c', dest='client', action='store_true')
parser.add_argument('-n', dest='name', type=str, default='name')
result = parser.parse_args()
return result
def main():
client_list = dict()
args = parse_arguments()
if args.client:
client(args.name)
else:
try:
server(client_list)
except KeyboardInterrupt:
print "Keyboard interrupt"
if __name__ == '__main__':
main()
サーバー出力
$ python client_list.py
Starting server...
クライアント出力
$ python client_list.py -c -n name1
{
"name1": {
"address": "127.0.0.1",
"port": 62210,
"name": "name1"
}
}
受信バッファは1024バイトに制限されています。クライアントリストのJSON文字列表現がこのサイズを超えると、切り捨てられます。これにより、次の例外が発生します。
ValueError: Unterminated string starting at: line 1 column 1023 (char 1022)
Linux上のRawソケット
まず、ネットワークカードの自動チェックサム機能を無効にします。
sudo ethtool -K eth1 tx off
次に、SOCK_RAWソケットを使用してパケットを送信します。
#!/usr/bin/env python
from socket import socket, AF_PACKET, SOCK_RAW
s = socket(AF_PACKET, SOCK_RAW)
s.bind(("eth1", 0))
# We're putting together an ethernet frame here,
# but you could have anything you want instead
# Have a look at the 'struct' module for more
# flexible packing/unpacking of binary data
# and 'binascii' for 32 bit CRC
src_addr = "\x01\x02\x03\x04\x05\x06"
dst_addr = "\x01\x02\x03\x04\x05\x06"
payload = ("["*30)+"PAYLOAD"+("]"*30)
checksum = "\x1a\x2b\x3c\x4d"
ethertype = "\x08\x01"
s.send(dst_addr+src_addr+ethertype+payload+checksum)