Python에서 UDP 멀티 캐스트는 어떻게합니까?


86

Python에서 UDP 멀티 캐스트를 어떻게 보내고 받습니까? 그렇게 할 수있는 표준 라이브러리가 있습니까?

답변:


98

이것은 나를 위해 작동합니다.

받다

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))"도 필요했습니다.
Mark Foreman 2012

2
udp 멀티 캐스트의 경우 로컬 그룹 포트가 아닌 멀티 캐스트 그룹 / 포트에 바인딩해야 sock.bind((MCAST_GRP, MCAST_PORT))합니다. 코드가 작동 할 수도 있고 작동하지 않을 수도 있습니다. 여러 NIC가있는 경우 작동하지 않을 수 있습니다
stefanB

@atikat : 감사합니다 !! MAC에서는 왜 필요하지만 Ubuntu에서는 필요하지 않습니까?
Kyuubi

2
@RandallCook는 : [errno를 10049] 요청한 주소가 해당 컨텍스트에서 유효하지 않은 : 나는 MCAST_GRP I에 의해 '교체 할 때 socket.error 얻을
stewbasic

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 조인 항목이 표시되지 않고 아무것도 수신하지 않습니다.
Gordon Wrigley

1
멀티 캐스트 주소의 로컬 포트가 아닌 멀티 캐스트 그룹 / 포트에 바인딩해야합니다.sock.bind((MCAST_GRP, MCAST_PORT))
stefanB

1
이 예는 모호한 이유로 저에게 적합하지 않습니다. socket.gethostbyname (socket.gethostname ())을 사용하여 인터페이스를 선택한다고해서 항상 외부 인터페이스가 선택되는 것은 아닙니다. 사실 데비안 시스템에서는 루프백 주소를 선택하는 경향이 있습니다. Debian은 호스트 이름에 대한 호스트 테이블에 127.0.1.1 항목을 추가합니다. 대신 더 높은 순위의 답변이 'pack'문 ( '+'보다 더 정확함)을 통해 사용하는 socket.INADDR_ANY를 사용하는 것이 더 효과적입니다. 또한 더 높은 순위의 답변이 올바르게 설명하므로 IP_MULTICAST_IF를 사용할 필요가 없습니다.
Brian Bulkowski 2016

1
@BrianBulkowski에는 socket.INADDR_ANY를 사용하는 많은 프로그래머가 있습니다. 여러 인터페이스를 사용하는 우리에게는 큰 슬픔과 당황스러워서 특정 인터페이스에 멀티 캐스트 데이터가 있어야합니다. 해결책은 socket.INADDR_ANY가 아닙니다. IP 주소로 적절한 인터페이스를 선택하는 것이지만 최선이라고 생각합니다 (최종 사용자에게 요청하지만 애플리케이션의 요구 사항에 따라 선택하는 구성 파일). socket.INADDR_ANY는 멀티 캐스트 데이터를 true로 얻을 수 있으며 단일 홈 호스트를 가정하는 경우 가장 쉽지만 덜 정확하다고 생각합니다.
Mike S

@MikeS는 원칙적으로 동의하지만 IP 주소를 사용하여 인터페이스를 선택하는 아이디어는 엄청나게 끔찍합니다. 나는 문제를 잘 알고 있지만 역동적 인 세계에서는 IP 주소가 답이 아닙니다. 따라서 모든 것을 반복하고 인터페이스 이름으로 선택하고, 인터페이스 이름을 확인하고, 현재 IP 주소를 선택하고,이를 사용하는 코드를 작성해야합니다. 그동안 IP 주소가 변경되지 않았기를 바랍니다. Linux / Unix가 모든 곳에서 인터페이스 이름을 사용하도록 표준화하고 프로그래밍 언어가 구성 파일을 더 합리적으로 만들었 으면합니다.
Brian Bulkowski 2011

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 주소를 설정하는 것뿐입니다 .

사실 인터넷에 대한 많은 예가 우연히 작동한다는 것을 알았습니다. 공식 파이썬 문서에서도. 그들 모두에 대한 문제는 struct.pack을 잘못 사용하고 있습니다. 일반적인 예제는 4sl형식으로 사용 되며 실제 OS 소켓 인터페이스 구조와 일치하지 않습니다.

파이썬 소켓 객체에 대한 setsockopt 호출을 실행할 때 후드 아래에서 일어나는 일을 설명하려고 노력할 것입니다.

Python은 setsockopt 메서드 호출을 네이티브 C 소켓 인터페이스로 전달합니다. Linux 소켓 설명서 (참조 man 7 ip)에서는 ip_mreqnIP_ADD_MEMBERSHIP 옵션에 대한 두 가지 형태의 구조를 소개 합니다. 가장 짧은 형식은 길이가 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)

샘플 사용 : (아래 두 개의 콘솔에서 실행하고 자신의 --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는 '로컬 인터페이스 중 하나를 선택]' 하지 않습니다 .
론의 후작

0

클라이언트 코드 (tolomea에서)가 Solaris에서 작동하도록하려면 IP_MULTICAST_TTL소켓 옵션에 대한 ttl 값을 부호없는 문자로 전달해야합니다 . 그렇지 않으면 오류가 발생합니다. 이것은 Solaris 10 및 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))

-1

tolomea의 대답은 나를 위해 일했습니다. 나는 그것을 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.