Engineering Note

プログラミングなどの技術的なメモ

サーバソケットの多重化(Thread)(Pythonによるネットワークプログラミング)

client

本記事は、Pythonによるネットワークプログラミングについての学習メモとなります。

参考書籍としてLinuxネットワークプログラミングバイブルを用い、同書の内容に沿ったかたちで、Pythonに書き直しをしていきます。

今回は、サーバソケットのマルチクライアント化(多重化)の方法として、threadingモジュール(pthread)を利用した方法について学んでいきます。

 

 

ネットワークプログラミングについて

ネットワークプログラミングの目的はデータの送受信をすることで、そのためにソケットというインターフェースを利用します。

このソケットは、TCP/IPの誕生時にBSD Uinux上に実装されたものですが、非常に使い勝手が良かったため、WindowsなどのUnix系以外のOSでも利用されています。

Pythonで実装する際の手順やオプションの設定なども、概ねそのままプログラミングすることができます。

 

多重化とは

通常のサーバソケットでは、accept()(もしくはrecv())を呼び出した後、それ以降の処理をブロックしてしまいます(ブロッキングIO)。

そのため、他のコネクションが確立したソケットは、その処理が完了するまで待機させられ、クライアントごとの同時接続ができない使い勝手の悪いアプリケーションになってしまいます。

これらの問題を解決するためには、多重化と呼ばれる技術を使い、マルチクライアント化に対応する必要があります。

 

Threadによるマルチクライアント化

前回はfork()を用いた多重化について学びました。

 


今回のマルチスレッドでは、1つのプロセス内に複数のスレッドを並列処理していくものになり、UnixLinuxではpthread(Posix Thread)というものを利用します。

そのため、複数のスレッドが同一のスタティック領域を参照し、またスレッドに異常が発生した場合はプロセス全体にも影響するため、スレッドセーフなコーディングが必要とされます。

 

 

サーバプログラムの作成 

それでは、threadingモジュールを利用したマルチプロセスによるサーバプログラムを作成します。

以下がソースコードになります。

 

# server_multi_thread.py
import socket
import sys
import errno
import threading

def server_socket(portnum):
    try:
        for res in socket.getaddrinfo(None, portnum, socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_PASSIVE):
            af, socktype, proto, canonname, sa = res
            break
    except socket.gaieor as e:
        print("getaddrinfo():{}".format(e))
        sys.exit(1)

    try:
        nbuf, sbuf = socket.getnameinfo(sa, socket.AI_PASSIVE)
    except socket.gaieor as e:
        print("getnameinfo():{}".format(e))
        sys.exit(1)

    print("port={}".format(sbuf))

    try:
        soc = socket.socket(af, socktype, proto)
    except OSError as e:
        print("socket:{}".format(e))
        sys.exit(1)

    try:
        soc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    except OSError as e:
        print("setsockopt:{}".format(e))
        sys.exit(1)

    try:
        soc.bind(sa)
    except OSError as e:
        print("bind:{}".format(e))
        soc.close()
        sys.exit(1)

    try:
        soc.listen(socket.SOMAXCONN)
    except OSError as e:
        print("listen:{}".format(e))
        soc.close()
        sys.exit(1)

    return soc

def accept_loop(soc):
    while True:
        try:
            acc, addr = soc.accept()
            hbuf, sbuf = socket.getnameinfo(addr, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV)
            print("accept:{}:{}".format(hbuf, sbuf))
            t = threading.Thread(target=send_recv, args=[acc])
            t.start()
                                
        except InterruptedError as e:
            if e.errno != errno.EINTR:
                print("accept:{}".format(e))
        except RuntimeError as e:
            print("thread:{}".format(e))

def send_recv(acc):
    buf_size = 512
    id = threading.get_ident()
    while True:
        try:
            data = acc.recv(buf_size)
        except InterruptedError as e:
            print("recv:{}".format(e))
            break 

        if (len(data) == 0):
            # EOF
            print("[{}]recv:EOF".format(id))
            break

        data = data.rstrip()
        try:
            print("[{}]{}".format(id, data.decode('utf-8')))
        except UnicodeDecodeError:
            pass
        try:
            acc.send(data + b':OK\r\n')
        except InterruptedError as e:
            print("send:{}".format(e))
            break
    acc.close()

if __name__ == '__main__':
    if (len(sys.argv) != 2):
        print("Usage: {} <server port>".format(sys.argv[0]))
        sys.exit(1)

    soc = server_socket(sys.argv[1])

    print("ready for accept")

    try:
        accept_loop(soc)
        soc.close()
    except KeyboardInterrupt:
        soc.close()
        sys.exit(1)

 

動作確認

それでは、上記プログラムを実行してみます。

 

 > python3 server_multi_thread.py 8888
 port=8888
 ready for accept
 accept:127.0.0.1:34870
 accept:127.0.0.1:34871
 [-1223947456]ok
 [-1234175168]ok
 [-1223947456]recv:EOF
 [-1234175168]recv:EOF

 

 

最後に

今回はサーバのマルチクライアント化として、threadingモジュールを利用した方法について学びました。

これで一通りのマルチクライアント化の方法を学ぶことができました。

 

参考書籍

Linuxネットワークプログラミングバイブル

TCP/IPソケットプログラミング C言語編