• TCP通信
  • UDP通信
  • TCP通信

    サーバ側

    サーバ側のプログラムは基本的に、

    1. socketでソケットを作成
    2. bindでアドレスとポート番号を指定
    3. listenでクライアントの接続を待つ
    4. acceptでクライアントの接続を受け付ける
    5. sendやrecvを使ってクライアントのデータの送受信を行う
    6. closeでソケットを閉じる

    の流れで行う。

    • tcp_server1.py
    from __future__ import print_function
    import socket
    from contextlib import closing
    
    def main():
      host = '127.0.0.1'
      port = 4000
      backlog = 10
      bufsize = 4096
    
      sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      with closing(sock):
        sock.bind((host, port))
        sock.listen(backlog)
        while True:
          conn, address = sock.accept()
          with closing(conn):
            msg = conn.recv(bufsize)
            print(msg)
            conn.send(msg)
      return
    
    if __name__ == '__main__':
      main()
    

    クライアント側

    クライアント側では、ソケットを作成した後にconnectを使ってサーバに接続し、通信完了後にcloseを実行する。

    • tcp_client1.py
    from __future__ import print_function
    import socket
    from contextlib import closing
    
    def main():
      host = '127.0.0.1'
      port = 4000
      bufsize = 4096
    
      sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      with closing(sock):
        sock.connect((host, port))
        sock.send(b'Hello world')
        print(sock.recv(bufsize))
      return
    
    if __name__ == '__main__':
      main()
    

    selectを使用する

    上のサーバ側のプログラムでは、acceptでクライアントの接続を待っている間にプログラムがブロックするため、同時に複数のクライアントと通信を行うことができない。 selectを使用すると複数のソケットを同時に監視することができるので、複数のクライアントと同時に通信を行うことができるようになる。

    • tcp_server2.py
    from __future__ import print_function
    import socket
    import select
    
    def main():
      host = '127.0.0.1'
      port = 4000
      backlog = 10
      bufsize = 4096
    
      server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      readfds = set([server_sock])
      try:
        server_sock.bind((host, port))
        server_sock.listen(backlog)
    
        while True:
          rready, wready, xready = select.select(readfds, [], [])
          for sock in rready:
            if sock is server_sock:
              conn, address = server_sock.accept()
              readfds.add(conn)
            else:
              msg = sock.recv(bufsize)
              if len(msg) == 0:
                sock.close()
                readfds.remove(sock)
              else:
                print(msg)
                sock.send(msg)
      finally:
        for sock in readfds:
          sock.close()
      return
    
    if __name__ == '__main__':
      main()
    
    • tcp_client2.py
    from __future__ import print_function
    import sys
    import socket
    from contextlib import closing
    
    def main():
      host = '127.0.0.1'
      port = 4000
      bufsize = 4096
    
      sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      with closing(sock):
        sock.connect((host, port))
        while True:
          line = sys.stdin.readline().rstrip()
          if len(line) == 0:
            break
          sock.send(line.encode('utf-8'))
          print(sock.recv(bufsize))
      return
    
    if __name__ == '__main__':
      main()
    

    UDP通信

    送信側

    UDPソケットを作成する場合、socket関数の引数にSOCK_DGRAMを指定する。 送信側はsendの代わりにsendtoで送信先を指定する必要がある。

    • send_udp.py
    from __future__ import print_function
    import socket
    import time
    from contextlib import closing
    
    def main():
      host = '127.0.0.1'
      port = 4000
      count = 0
      sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      with closing(sock):
        while True:
          message = 'Hello world : {0}'.format(count).encode('utf-8')
          print(message)
          sock.sendto(message, (host, port))
          count += 1
          time.sleep(1)
      return
    
    if __name__ == '__main__':
      main()
    

    受信側

    受信側はbindで受信先を指定してからrecvでデータを受信する。

    • recv_udp.py
    from __future__ import print_function
    import socket
    from contextlib import closing
    
    def main():
      host = '127.0.0.1'
      port = 4000
      bufsize = 4096
    
      sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      with closing(sock):
        sock.bind((host, port))
        while True:
          print(sock.recv(bufsize))
      return
    
    if __name__ == '__main__':
      main()
    

    マルチキャスト通信

    マルチキャスト通信の送信側のプログラムでは、setsockoptを使ってIP_MULTICAST_IFソケットオプションを設定する。 また、IP_MULTICAST_LOOPを0(false)に設定すると、マルチキャストパケットがローカルなソケットにループバックされなくなるので、自分が送信したパケットを受け取る際のオーバーヘッドを軽減させることができる。

    • send_udp_multicast.py
    from __future__ import print_function
    import socket
    import time
    from contextlib import closing
    
    def main():
      local_address   = '192.168.0.1' # 送信側のPCのIPアドレス
      multicast_group = '239.255.0.1' # マルチキャストアドレス
      port = 4000
    
      with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as sock:
    
        # sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 0)
        sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(local_address))
    
        count = 0
        while True:
          message = 'Hello world : {0}'.format(count).encode('utf-8')
          print(message)
          sock.sendto(message, (multicast_group, port))
          count += 1
          time.sleep(0.5)
      return
    
    if __name__ == '__main__':
      main()
    

    受信側のプログラムでは、IP_ADD_MEMBERSHIPソケットオプションを設定し、マルチキャストグループに参加する必要がある。 また、bindの前にSO_REUSEADDRを1(true)に設定しておくことで、複数のプロセスからマルチキャストを受信できるようになる。

    なお、ファイアウォールが有効になっていると通信できないことがあるので注意。

    • recv_udp_multicast.py
    from __future__ import print_function
    import socket
    from contextlib import closing
    
    def main():
      local_address   = '192.168.0.2' # 受信側のPCのIPアドレス
      multicast_group = '239.255.0.1' # マルチキャストアドレス
      port = 4000
      bufsize = 4096
    
      with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as sock:
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind(('', port))
        sock.setsockopt(socket.IPPROTO_IP,
                        socket.IP_ADD_MEMBERSHIP,
                        socket.inet_aton(multicast_group) + socket.inet_aton(local_address))
    
        while True:
          print(sock.recv(bufsize))
      return
    
    if __name__ == '__main__':
      main()