テキストチャット

簡単な Chat プログラム

さっきは、一方向でデータ通信を1回するだけでしたが、今度は、双方向に通信を繰り返してみましょう。

作成するのは、簡単なチャットです。

  • chat_server.pyが指定ポートで待っている
  • chat_client.pyが、ホスト名とポート番号を指定して接続を行う
  • 接続完了後は、クライアント、サーバが交互に一行ずつメッセージを交換する。
  • どちらかが exit という入力を入れたら終了

動作イメージは、こんな感じ

    server 側                             client 側
     [起動]
                                        [起動]
                                  (Client) Input:
				  clientMsg0 [入力]
Client says: clientMsg0   [受信]
(Server) Input:
serverMsg0 [入力]
                                  Server says: serverMsg0 [受信]
                                  (Client) Input:
				      client3  [入力]
Client says: client3 [受信]
(Server) Input:
exit  [入力]
                                  Server msg: exit  [受信]
Program finished with exit code 0
                                  Program finished with exit code 0

textchat

まずは、動かしてみて、イメージを掴んでください。

実行の際に、ターミナルを二つ同時に見れないと困りますよね。

  • PyCharm の場合は、実行タブが二つあるので、一方を右か下の領域に drag すると、画面分割できるようになります。
  • vscode の場合は、複数のターミナルを実行している場合は、ターミナル画面右に虫アイコンが複数現れるかと思います。虫アイコンを drag してターミナル以外のところなどに持っていくと、画面が分かれたような気がします。

接続

connection を張るまでは、先ほどの例と差はありません。 違うのは、今回は双方向通信なので、Server, Client 両側で、Input/OutputStream を両方開いている点ですね。

サーバ側

def send_server():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as serverSock:
        serverSock.bind((HOST, PORT))
        serverSock.listen()
        sock, client = serverSock.accept()
        recv_and_send(sock)
  • 接続開始〜確立は同じ
  • sock を用いて recv_and_send(sock)を実行

クライアント側

def chat_client():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((SERVER, PORT))
        send_and_recv(sock)
  • 接続開始〜確立は同じ
  • sock を用いて send_and_recv(sock)を実行

交互に送受信

サーバ(受信・送信を交互に)

def recv_and_send(sock: socket):
    while True:
        data = sock.recv(BUFSIZE)
        line = data.decode('utf-8')
        print("Client says: ", line)
        if line == "exit":
            break
        print("(Server) Input: ")
        user_msg = input()
        sock.sendall(user_msg.encode('utf-8'))
        if user_msg == "exit":
            break

主なI/O

  • input(): ユーザ入力の取り込み, 文字列
  • sock.recv(): クライアントからの受信用
    • byte 列を文字列にするために decode('utf-8') する
  • sock.sendall(): クライアントへの送信用
    • 文字列をbyte 列にするために encode('utf-8') する

プログラム見てもらえば、交互に受信&送信しているだけですね。

クライアント(受信・送信を交互に)

受信と送信の順序が逆になっている以外は、処理の流れはまったく同じです。

def send_and_recv(sock: socket):
    while True:
        print("(Client) Input: ")
        user_msg = input()
        sock.sendall(user_msg.encode('utf-8'))
        if user_msg == "exit":
            break

        data = sock.recv(BUFSIZE)
        line = data.decode('utf-8')
        print(data, line)
        print("Server says: ", line)
        if line == "exit":
            break

まとめ

交互に通信

実は、今回のプログラム互い違いに通信してました。逆にいうと、双方のユーザが好きなタイミングでメッセージを送ることができていません。 それは、プログラムが、「標準入力を待つか」「ネット入力を待つか」のどちらかしかできないからです。一度待ち始めると、途中で止めづらい。 で、このあたりはマルチスレッドをつかうと簡単に解消できるのですが、それはまた後日。

TCP 接続

今回あつかったのは TCP 接続です。 2点間の通信を担当し、パケット落ちなどがあると再送手続きなどしてくれます。 一方で、ネットワークの勉強をした人なら、 UDP 接続というのも聞いたことがあるかと思います。 パケットを直接おくるためのもので、マルチキャストやブロードキャストも可能ですが、パケット落ちなどがあっても、利用者側で対応しなくてはいけません。

今回は、再送制御などは TCP にお任せしています。