この記事では、下記の内容についてまとめます。
- ソケット通信自体の説明
- ソケット通信のフロー
- ソケット通信を行うサンプルプログラム(サーバー側)
ソケットとは?
TCP/IPを利用して通信を行う際に必要な送受信処理をプログラム側で利用するためのインターフェース的なもの。
ソケットを用いた通信の裏側ではTCP/IP (or UDP) がベースとなっているので、勉強の第一歩として最適かと思いました。
下記のサイトでは、ソケット通信に対して下記のような記載をしています。
ソケットとは、アプリケーションがデータを送受信するための仕組みを抽象化したものです。 ソフトウェア開発者は、ソケットの仕組みに則ってプログラムを記述すれば、具体的な通信手段や手順の詳細を知らなくても、通信を行うことができます。
他のサイトなども見た上で、ソケット通信に対するポイントは下記の通り。
Pythonでも、ソケット通信を行うためのライブラリーが提供されているのでこちらを利用する。
ソケット通信のフロー
ソケット通信のフローは下記の通り。
【クライアント】
- ソケットの作成 (
socket()
) - ソケットに接続先のIPアドレスとポート番号を渡して接続要求を行う (
connect()
) - ソケットにデータを送信する (
send()
) - コネクションを終了する (
close()
)
【サーバー】
- ソケットの作成 (
socket()
) - ソケットに接続を待ち受けるIPアドレスとポート番号を渡す (
bind()
) - 接続の受け付けを開始する (
listen()
) - 新しい接続要求を受け付ける (
accept()
) - ソケットからデータを受信する (
recv()
) - コネクションを終了する (
close()
)
サンプルコード
今回はサーバー側のプログラムのみ準備しました。(コードはリンク先のサンプルを参考に少しだけ修正)
IPアドレス:127.0.0.1、ポート番号:7777で接続を待ち受けるプログラムを用意して、クライアント側はncコマンドを利用して先ほどのIPアドレスとポートを利用して接続要求するようにしました。
import socket def create_server_socket(host, port): # アドレスファミリ(AF_INET:Ipv4)とソケットタイプ(SOCK_STREAM:TCP)を指定 server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # ソケットオプションでソケットの再利用フラグをONに設定 # クライアントと通信途中で中断した場合同じIPアドレスとポートでバインドできるようにする server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) return server_sock def accept_loop(server_sock): while True: # 4.新しい接続要求を受け付ける # server_sock.accept()はブロッキング処理なので、接続が来るまでは次の処理に進まない print("start accepting...") conn, (client_host, client_port) = server_sock.accept() # ソケットのファイル記述子、新規に作成されたソケットにBindされたIPアドレス・ポートを表示する print('[FD:{}] Accept: {}: {}'.format(conn.fileno(), client_host, client_port)) # ソケットからデータを受信し、結果をbytesオブジェクトで返す(一度に512bytes受信する) data = conn.recv(512) print('[FD:{}] Recv: {}'.format(conn.fileno(), data)) # 送信されてきたデータを返す conn.send(data) # 5.接続を切断する conn.close() if __name__ == '__main__': host, port = "localhost", 7777 # 1.ソケットの作成 server_sock = create_server_socket(host, port) # 2.接続を待ち受けるIPアドレスとポートを指定する server_sock.bind((host, port)) # 3.接続要求の受け付けを開始する server_sock.listen(5) print('Server Run Port: {}'.format(port)) try: accept_loop(server_sock) except KeyboardInterrupt: server_sock.close()
検証
最初にサーバー側のプログラムを起動します。
❯ python simple_socket.py Server Run Port: 7777
netstatコマンドで確認するとポート:7777でLISTENしていることがわかります。 この段階でサーバー側では、ソケットの作成〜接続要求の受付開始まで完了している状態です。
❯ netstat -an | grep 7777 tcp4 0 0 127.0.0.1.7777 *.* LISTEN
次にncコマンドを実行して、先ほどのIPアドレス・ポートに接続要求を行います。
❯ nc localhost 7777
プログラムの標準出力・netstatコマンドの結果を見ると、接続要求を受け付けてTCPコネクションを確立した上でaccept()
によって新たなソケットの作成(接続要求の受付まで対応可能)が行われていることがわかります。
❯ python simple_socket.py Server Run Port: 7777 start accepting... [FD:6] Accept: 127.0.0.1: 63897
❯ netstat -an | grep 7777 tcp4 0 0 127.0.0.1.7777 127.0.0.1.63897 ESTABLISHED tcp4 0 0 127.0.0.1.63897 127.0.0.1.7777 ESTABLISHED tcp4 0 0 127.0.0.1.7777 *.* LISTEN
最後にncコマンド側で任意のメッセージを送信します。
手順としては、コンソール上でncコマンドの実行後に任意の文字列を入力してエンターを押すだけです。
❯ nc localhost 7777 aaa aaa
プログラム側の標準出力では、クライアント側から送信したメッセージaaa
が受信できていることがわかります。
❯ python simple_socket.py Server Run Port: 7777 start accepting... [FD:6] Accept: 127.0.0.1: 63897 [FD:6] Recv: b'aaa\n'
netstatコマンドで確認すると、先ほどのコネクションが存在しないことが確認できます。
これはプログラム上でメッセージのやり取りをした後で、コネクションを解放する処理 (close()
) を実行したためです。
❯ netstat -an | grep 7777 tcp4 0 0 127.0.0.1.7777 *.* LISTEN
3-way handshakeなど細かい部分はメソッドによって隠蔽されていますが、大まかな流れは掴めた気がします。 ncコマンドを利用すれば細かい状態遷移も再現できるみたいなので、別途検証してみようと思います。
わからない点
まだ、調べきれていない点を下記に列挙していきます。 (次回の記事で調べた内容もpostするかもしれません。)