PythonでUDPマルチキャストをどのように行いますか?


88

PythonでUDPマルチキャストをどのように送受信しますか?そうするための標準ライブラリはありますか?

回答:


101

これは私のために働きます:

受け取る

import socket
import struct

MCAST_GRP = '224.1.1.1'
MCAST_PORT = 5007
IS_ALL_GROUPS = True

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if IS_ALL_GROUPS:
    # on this port, receives ALL multicast groups
    sock.bind(('', MCAST_PORT))
else:
    # on this port, listen ONLY to MCAST_GRP
    sock.bind((MCAST_GRP, MCAST_PORT))
mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)

sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

while True:
  # For Python 3, change next line to "print(sock.recv(10240))"
  print sock.recv(10240)

送信

import socket

MCAST_GRP = '224.1.1.1'
MCAST_PORT = 5007
# regarding socket.IP_MULTICAST_TTL
# ---------------------------------
# for all packets sent, after two hops on the network the packet will not 
# be re-sent/broadcast (see https://www.tldp.org/HOWTO/Multicast-HOWTO-6.html)
MULTICAST_TTL = 2

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, MULTICAST_TTL)

# For Python 3, change next line to 'sock.sendto(b"robot", ...' to avoid the
# "bytes-like object is required" msg (https://stackoverflow.com/a/42612820)
sock.sendto("robot", (MCAST_GRP, MCAST_PORT))

これは、機能しなかったhttp://wiki.python.org/moin/UdpCommunicationの例に基づいています

私のシステムは... Linux 2.6.31-15-generic#50-Ubuntu SMP Tue Nov 10 14:54:29 UTC 2009 i686 GNU / Linux Python 2.6.4


6
mac os xの場合、同じマルチキャストポートアドレスの組み合わせで複数のリスナーを許可するには、上記の例のsocket.SO_REUSEADDRの代わりにsocket.SO_REUSEPORTオプションを使用する必要があります。
atikat 2011

送信には、マルチキャストリスナーが特定のアダプターにバインドされているため、「sock.bind((<local ip>、0))」も必要でした。
マークフォアマン

2
udpマルチキャストの場合、ローカルグループポートではなくマルチキャストグループ/ポートにバインドする必要がありsock.bind((MCAST_GRP, MCAST_PORT))ます。コードが機能する場合と機能しない場合があります。複数のNICがある場合は機能しない場合があります
stefanB

@atikat:ありがとう!! なぜこれがMACで必要なのに、Ubuntuでは必要ないのですか?
九尾2013年

2
@RandallCook: ''をMCAST_GRPに置き換えると、socket.errorが発生します:[Errno 10049]要求されたアドレスはそのコンテキストでは無効です
stewbasic 2016年

17

マルチキャストグループにブロードキャストするマルチキャスト送信者:

#!/usr/bin/env python

import socket
import struct

def main():
  MCAST_GRP = '224.1.1.1'
  MCAST_PORT = 5007
  sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
  sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 32)
  sock.sendto('Hello World!', (MCAST_GRP, MCAST_PORT))

if __name__ == '__main__':
  main()

マルチキャストグループから読み取り、16進データをコンソールに出力するマルチキャストレシーバー:

#!/usr/bin/env python

import socket
import binascii

def main():
  MCAST_GRP = '224.1.1.1' 
  MCAST_PORT = 5007
  sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
  try:
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  except AttributeError:
    pass
  sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 32) 
  sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1)

  sock.bind((MCAST_GRP, MCAST_PORT))
  host = socket.gethostbyname(socket.gethostname())
  sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(host))
  sock.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, 
                   socket.inet_aton(MCAST_GRP) + socket.inet_aton(host))

  while 1:
    try:
      data, addr = sock.recvfrom(1024)
    except socket.error, e:
      print 'Expection'
      hexdata = binascii.hexlify(data)
      print 'Data = %s' % hexdata

if __name__ == '__main__':
  main()

私はこれを試しましたが、うまくいきませんでした。Wiresharkでは送信を確認できますが、IGMP参加の内容が表示されず、何も受信しません。
ゴードンリグレー

1
マルチキャストアドレスのローカルポートではなく、マルチキャストグループ/ポートにバインドする必要がありますsock.bind((MCAST_GRP, MCAST_PORT))
stefanB 2013年

1
この例は、あいまいな理由で私には機能しません。socket.gethostbyname(socket.gethostname())を使用してインターフェースを選択すると、必ずしも外部インターフェースが選択されるとは限りません。実際、Debianシステムでは、ループバックアドレスを選択する傾向があります。Debianは、ホスト名のホストテーブルに127.0.1.1のエントリを追加します。代わりに、socket.INADDR_ANYを使用する方が効果的です。これは、上位の回答が「pack」ステートメントを介して使用します(「+」よりも正確です)。また、上位の回答が正しく述べているため、IP_MULTICAST_IFを使用する必要はありません。
Brian Bulkowski 2016

1
@BrianBulkowski特定のインターフェイスにマルチキャストデータを送信する必要がある複数のインターフェイスを使用している私たちにとって、socket.INADDR_ANYを使用するプログラマーはたくさんいます。解決策はsocket.INADDR_ANYではありません。IPアドレスで適切なインターフェースを選択することですが、最善だと思います(構成ファイル、エンドユーザーに尋ねますが、アプリケーションのニーズに応じて選択します)。socket.INADDR_ANYは、マルチキャストデータを取得します。これは、シングルホームホストを想定している場合に最も簡単ですが、正しくないと思います。
マイクS

@MikeSいくつかの原則に同意しますが、IPアドレスを使用してインターフェースを選択するという考えはひどくひどく失敗しています。私は問題をよく知っていますが、ダイナミックな世界では、IPアドレスは答えではありません。したがって、すべてを繰り返し、インターフェイス名で選択し、インターフェイス名を調べて、現在のIPアドレスを選択し、それを使用するコードを作成する必要があります。うまくいけば、IPアドレスはその間に変更されていません。Linux / Unixがどこでもインターフェース名を使用するように標準化されていて、プログラミング言語が標準化されていれば、構成ファイルがより賢明になることを願っています。
Brian Bulkowski 2016年

13

より良い使用法:

sock.bind((MCAST_GRP, MCAST_PORT))

の代わりに:

sock.bind(('', MCAST_PORT))

同じポートで複数のマルチキャストグループをリッスンする場合は、すべてのリスナーですべてのメッセージを受信するためです。


6

マルチキャストグループに参加するために、PythonはネイティブOSソケットインターフェイスを使用します。Python環境の移植性と安定性により、ソケットオプションの多くはネイティブソケットsetsockopt呼び出しに直接転送されます。グループメンバーシップへの参加や削除などのマルチキャストモードの操作は、によってsetsockoptのみ実行できます。

マルチキャストIPパケットを受信するための基本的なプログラムは次のようになります。

from socket import *

multicast_port  = 55555
multicast_group = "224.1.1.1"
interface_ip    = "10.11.1.43"

s = socket(AF_INET, SOCK_DGRAM )
s.bind(("", multicast_port ))
mreq = inet_aton(multicast_group) + inet_aton(interface_ip)
s.setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, str(mreq))

while 1:
    print s.recv(1500)

まず、ソケットを作成してバインドし、トリガーを発行してマルチキャストグループへの参加をトリガーしますsetsockopt。最後に、パケットを永久に受信します。

マルチキャストIPフレームの送信は簡単です。システムに単一のNICがある場合、そのようなパケットを送信することは、通常のUDPフレーム送信と同じです。あなたが世話をする必要があるのは、sendto()メソッドで正しい宛先IPアドレスを設定することだけです。

実際、インターネットに関する多くの例が偶然に機能していることに気づきました。公式のPythonドキュメントでも。それらすべての問題は、struct.packを誤って使用しています。典型的な例は4slフォーマットとして使用されており、実際のOSソケットインターフェイス構造と一致していないことに注意してください。

Pythonソケットオブジェクトに対してsetsockopt呼び出しを実行すると、内部で何が起こるかを説明しようと思います。

Pythonは、setsockoptメソッド呼び出しをネイティブCソケットインターフェイスに転送します。Linuxソケットのドキュメント(を参照man 7 ip)ではip_mreqn、IP_ADD_MEMBERSHIPオプションの2つの形式の構造が紹介されています。最短の形式は8バイトの長さで、長い形式は12バイトの長さです。上記の例ではsetsockopt、最初の4バイトが定義しmulticast_group、次の4バイトが定義する8バイトの呼び出しを生成しますinterface_ip


2

py-multicastを見てください。ネットワークモジュールは、インターフェイスがマルチキャストをサポートしているかどうかを確認できます(少なくともLinuxでは)。

import multicast
from multicast import network

receiver = multicast.MulticastUDPReceiver ("eth0", "238.0.0.1", 1234 )
data = receiver.read()
receiver.close()

config = network.ifconfig()
print config['eth0'].addresses
# ['10.0.0.1']
print config['eth0'].multicast
#True - eth0 supports multicast
print config['eth0'].up
#True - eth0 is up

おそらく、IGMPが表示されないという問題は、マルチキャストをサポートしていないインターフェイスが原因でしたか?


2

他の回答のコードのいくつかの微妙な点を説明するためのちょうど別の回答:

  • socket.INADDR_ANY-(編集済み)のコンテキストではIP_ADD_MEMBERSHIP、これは実際にはソケットをすべてのインターフェイスにバインドするのではなく、マルチキャストがアップしているデフォルトのインターフェイスを選択するだけです(ルーティングテーブルによる)
  • マルチキャストグループに参加することは、ソケットをローカルインターフェイスアドレスにバインドすることと同じではありません

マルチキャスト(UDP)ソケットをバインドするとはどういう意味ですか?を参照してくださいマルチキャストの仕組みの詳細については

マルチキャストレシーバー:

import socket
import struct
import argparse


def run(groups, port, iface=None, bind_group=None):
    # generally speaking you want to bind to one of the groups you joined in
    # this script,
    # but it is also possible to bind to group which is added by some other
    # programs (like another python program instance of this)

    # assert bind_group in groups + [None], \
    #     'bind group not in groups to join'
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)

    # allow reuse of socket (to allow another instance of python running this
    # script binding to the same ip/port)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    sock.bind(('' if bind_group is None else bind_group, port))
    for group in groups:
        mreq = struct.pack(
            '4sl' if iface is None else '4s4s',
            socket.inet_aton(group),
            socket.INADDR_ANY if iface is None else socket.inet_aton(iface))

        sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

    while True:
        print(sock.recv(10240))


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--port', type=int, default=19900)
    parser.add_argument('--join-mcast-groups', default=[], nargs='*',
                        help='multicast groups (ip addrs) to listen to join')
    parser.add_argument(
        '--iface', default=None,
        help='local interface to use for listening to multicast data; '
        'if unspecified, any interface would be chosen')
    parser.add_argument(
        '--bind-group', default=None,
        help='multicast groups (ip addrs) to bind to for the udp socket; '
        'should be one of the multicast groups joined globally '
        '(not necessarily joined in this python program) '
        'in the interface specified by --iface. '
        'If unspecified, bind to 0.0.0.0 '
        '(all addresses (all multicast addresses) of that interface)')
    args = parser.parse_args()
    run(args.join_mcast_groups, args.port, args.iface, args.bind_group)

使用例:(以下を2つのコンソールで実行し、独自の--ifaceを選択します(マルチキャストデータを受信するインターフェイスと同じである必要があります))

python3 multicast_recv.py --iface='192.168.56.102' --join-mcast-groups '224.1.1.1' '224.1.1.2' '224.1.1.3' --bind-group '224.1.1.2'

python3 multicast_recv.py --iface='192.168.56.102' --join-mcast-groups '224.1.1.4'

マルチキャスト送信者:

import socket
import argparse


def run(group, port):
    MULTICAST_TTL = 20
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, MULTICAST_TTL)
    sock.sendto(b'from multicast_send.py: ' +
                f'group: {group}, port: {port}'.encode(), (group, port))


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--mcast-group', default='224.1.1.1')
    parser.add_argument('--port', default=19900)
    args = parser.parse_args()
    run(args.mcast_group, args.port)

使用例:#受信者が以下のマルチキャストグループアドレスにバインドし、一部のプログラムがそのグループへの参加を要求すると仮定します。また、ケースを単純化するために、受信者と送信者が同じサブネットの下にあると想定します

python3 multicast_send.py --mcast-group '224.1.1.2'

python3 multicast_send.py --mcast-group '224.1.1.4'


INADDR_ANYは'ローカルインターフェイスの1つを選択しませ]'。
user2074 2119

0

(tolomeaからの)クライアントコードをSolarisで機能させるには、IP_MULTICAST_TTLsocketオプションのttl値をunsignedcharとして渡す必要があります。そうしないと、エラーが発生します。これは、Solaris10および11で機能しました。

import socket
import struct

MCAST_GRP = '224.1.1.1'
MCAST_PORT = 5007
ttl = struct.pack('B', 2)

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
sock.sendto("robot", (MCAST_GRP, MCAST_PORT))

0

この例は、あいまいな理由で私には機能しません。

あいまいではありません、それは単純なルーティングです。

OpenBSDの場合

route add -inet 224.0.0.0/4 224.0.0.1

Linuxの開発者へのルートを設定できます

route add -net 224.0.0.0 netmask 240.0.0.0 dev wlp2s0

Linux上の1つのインターフェースにすべてのマルチキャストトラフィックを強制する

   ifconfig wlp2s0 allmulti

tcpdumpは非常にシンプルです

tcpdump -n multicast

あなたのコードには次のものがあります:

while True:
  # For Python 3, change next line to "print(sock.recv(10240))"

なぜ10240

マルチキャストパケットサイズは1316バイトである必要があります


-1

トロメアの答えは私のために働いた。私もそれをsocketserver.UDPServerにハックしました:

class ThreadedMulticastServer(socketserver.ThreadingMixIn, socketserver.UDPServer):
    def __init__(self, *args):
        super().__init__(*args)
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind((MCAST_GRP, MCAST_PORT))
        mreq = struct.pack('4sl', socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
        self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.