ホーム >

Pythonでネットワークプログラミング

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()