Pythonでのソケット通信(ポート番号・プロトコル・サーバー / クライアント)

このページにはプロモーションが含まれています

このページでは、Python でのソケット通信について解説していきます。

ソケット通信を使いこなすことができれば、複数のプログラム間でデータのやりとりを行うシステムや、簡単なネット対戦機能を搭載したゲームも開発できるようになります。

このページでは、まずソケット通信自体について説明し、続いてソケット通信を使いこなす上で必要になる前提知識について説明していきます。その後、Python でソケット通信プログラムを開発するために利用する socket モジュールと socket クラスおよび、その socket クラスを利用した簡単なソケット通信のプログラム例を示していきます。最後に、socket モジュールと他の通信ライブラリ・通信モジュールとの違いについて説明していきます。

最後までページを読んでいただければ、ソケット通信を使いこなす上で必要になる前提知識も身に付き、Python でのソケット通信の使い方についても理解できると思いますので、是非ページを読み進めていただければと思います!

ソケット通信

まず、ソケット通信について説明していきます。

ソケット通信とは、その名のとおりソケットを利用した通信のことを言います。

ソケット

ソケットとは、PC 等の端末上で動作するプログラム・アプリの仮想的な通信インターフェースのことを言います。要は、各プログラムの持つ “仮想的な通信の出入り口” です。このソケットをプログラムが操作・制御することで、他のソケットに対してデータを送信したり、他のソケットからデータを受信することができるようになります。

そして、このソケットを介して通信を行うことをソケット通信と言います。

ソケット通信のイメージ図

このソケット通信を利用すれば、複数の端末(PC やスマホ等)で動作するプログラムの間でデータの送受信を行なったり、一方のプログラムから他方のプログラムに対して通信で指示を出すようなことが可能となります。

スポンサーリンク

Python でのソケット通信

Python の場合は、Python の標準モジュールである socket モジュールの socket クラスによって、このソケットの作成や、ソケットを介した通信を行うことになります。

前述のとおり、ソケットは通信の入り口と出口となるため、ソケット通信を行うためには事前にソケットを用意しておく必要があります。このソケットは、socket クラスのコンストラクタを実行することで生成することができます。プログラム的にはコンストラクタを実行することでインスタンスが生成され、そのインスタンスによってソケットが管理されることになります。そして、この socket クラスのインスタンスからメソッドを実行させることで、その生成されたソケットをプログラムから操作・制御することができ、これによってソケットを介したデータの送信やデータの受信などの通信が行われることになります。

なので、ソケット通信を行う際には、まず socket クラスのコンストラクタを実行してインスタンスを生成する必要があります。さらに、そのインスタンスにメソッドを実行させることでソケットの操作・制御を行なってソケット通信を実現していくことになります。そして、ソケット通信が終了したら、生成したインスタンスを閉じる処理を行います(ソケットを閉じる)。

socketクラスのインスタンスにメソッドを実行させて通信を行う様子

なので、イメージとしてはソケット通信は下記のような流れで実現されることになります。socket.socket()socket クラスのコンストラクタで、sock がコンストラクタによって生成された socket クラスのインスタンスです。 

ソケット通信の流れ
import socket

# ソケットを生成する
sock = socket.socket()

sock.データを送信するメソッド(引数)
data = sock.データを受信するメソッド(引数)
sock.ソケットを閉じるメソッド()

ということで、ソケット通信においても基本的な処理の実行方法は他のモジュールや他のクラスと同様になります。ですが、ソケット通信プログラムの開発を行う上では、”通信” に関する前提知識を踏まえたうえで開発をしていくことが重要となります。

そのため、次はソケット通信プログラムを開発していくうえで事前に知っておいたほうが良い基本的な知識について解説していきます。

IP アドレスとポート番号

ソケット通信プログラムを開発していくうえでは、まず下記の3つに理解しておいた方が良いと思います。これらを理解しておくことで、より実践的なソケット通信プログラムを開発していくことができるようになりますし、ソケット通信プログラムを開発していく中で、どういったことを考慮しながら開発をしていく必要があるのかを自身で自然と思いつくようになると思います。

  • IP アドレスとポート番号
  • プロトコル
  • サーバーとクライアント

そのため、まずは上記3つについて説明したのちに、実際の Python でのソケット通信の実現方法について解説していきたいと思います。

最初に「IP アドレスとポート番号」について解説していきます。ソケット通信を理解するためには「IP アドレスとポート番号」の知識が必須となります。

まず前提として、通信は1つのプログラムで成立するものではなく、通信相手となるプログラムが存在して初めて成立するものになります。そして、通信プログラムを開発する上では、目的の通信相手と適切に通信を行うことが必要となります。このために必要となる要素の1つが、ここで説明する「IP アドレスとポート番号」となります。

IP アドレス

まず IP アドレスについて説明していきます。

IP アドレス:通信先を指定するための識別子

IP アドレスとは、PC やスマホなどの通信端末を特定するための識別子になります。より正確に言えば、ネットワークインターフェースを特定するための識別子になるのですが、これについては後述するものとし、まずは通信端末を特定するための識別子であるとシンプルに考えてください。

世の中には無数の通信端末が存在します。これらの無数の通信端末の中から目当てとなる端末に対して通信を行う必要があります。

世の中に無数の通信端末が存在することを示す図

ソケット通信において、その目当ての端末を指定するための識別子が IP アドレスになります。

MEMO

端末の識別子としてホストが利用されることもあります

これは、IP アドレスのような数値ではなく、端末に名付けられた名前の文字列となります

文字列なので人間にも直感的に理解することが出来ます

が、結局はホストも IP アドレスに変換したうえで通信が行われることになりますので、まずは IP アドレスが識別子であると考えてください

例えばデータを送信するときには、この IP アドレスで目当ての端末を指定する必要があります。”アドレス” という単語が含まれている通り、IP アドレスは住所みたいなものです。

IPアドレスを指定して目当ての端末にデータを送信する様子

こんな感じで、目当てとなる通信端末に対するデータの送信を実現するためには IP アドレスの指定が必要となります。

IP アドレスとネットワークインターフェース

また、1つの通信端末が複数の IP アドレスを持つ場合もあります。PC は Wi-Fi に接続して無線で通信を行うこともできますし、イーサネットケーブルのような有線で接続して通信を行うこともできます。このように複数の経路で通信を行うことが可能なのは PC が複数の通信機器(ネットワークカードなど)を備えているからになります。このような通信機器をネットワークインターフェースと呼びます。前述の例の場合は、無線接続用のネットワークインターフェースと有線接続用のネットワークインターフェースを備えていることになります。

1つの通信端末が複数のネットワークインターフェースを備えている様子

そして、IP アドレスは、これらの各ネットワークインターフェースに割り当てられることになります。したがって、1つの通信端末は複数の IP アドレスを持つことがあります。例えば、下の図で考えれば、PC1は2つのネットワークインターフェース(無線接続用・有線接続用)を持っています。そして、それぞれのネットワークインターフェースに IP アドレス 192.168.10.5192.168.11.5 とが割り当てられていることになります。

それぞれのネットワークインターフェースにIPアドレスが割り当てられている様子

この時、PC1はスマホ1からデータを受信する際は、IP アドレス 192.168.10.5 が割り当てられたネットワークインターフェースからデータを受信する必要がありますし、PC2からデータを受信する際は IP アドレス 192.168.11.5 が割り当てられたネットワークインターフェースからデータを受信する必要があります。

このように、世の中には無数の通信端末が存在し、さらに通信端末には複数のネットワークインターフェースが存在します。そして、それぞれのネットワークインターフェースに IP アドレスが割り当てられています。ソケット通信では、データを送信するときにも送信先のネットワークインターフェース、さらにデータを受信するときにも受信先のネットワークインターフェースを特定するために IP アドレスを指定することが必要となります。

論理的なネットワークインターフェース

また、ネットワークインターフェースには物理的なものだけではなく論理的なネットワークインターフェースも存在します。その1つに、自分自身と通信を行うためのネットワークインターフェースが存在します。

そして、このネットワークインターフェースには IP アドレスとして 127.0.0.1 が割り振られます。つまり、IP アドレス 127.0.0.1 にデータを送信すると自分自身がそのデータを受信することになります。この IP アドレス 127.0.0.1 はループバックアドレスと呼ばれる特別な IP アドレスです。通信プログラムは、複数の通信端末(PC 等)の間で通信を行うことになるため、通信プログラムの動作確認には普通に考えれば2つ以上の PC 等が必要になります。ですが、このループバックアドレスが存在するため自分自身の PC と通信を行うことが可能で、1つの PC のみで通信プログラムの動作確認を行うことが可能です。

ループバックアドレスの説明図

もし、複数の通信端末を使って通信プログラムの動作確認を行いたいけど PC を1つしか持っていないような場合は、ラズパイなどを購入して動作確認を行うのも良いと思います。PC を買おうと思えば 10 万円くらいかかりますが、ラズパイの場合は本体だけで 1 万円くらい、スターターキット付きの場合は 2 万円くらいで買えるのでオススメです。もちろん、通信プログラムの動作確認だけでなく、お試しで Linux を利用してみたり IoT に関する実験を行なってみるような用途でも利用可能です。

スポンサーリンク

ポート番号

次に、ポート番号について説明していきます。

PC 等の通信端末上では複数のアプリ・プログラムが動作している可能性があります。そして、それらのプログラムが通信を行うためにソケットを持っている可能性があります。つまり、1つの通信端末上には複数のソケットが存在しています。これらの複数のソケットのうち、どのソケットに対して通信を行いたいのかを指定するための識別子がポート番号になります。

前述の通り、IP アドレスを指定してデータの送信を行えば、その IP アドレスが割り当てられたネットワークインターフェースに対してデータが届くことになります。さらに、データの送信時にポート番号を指定しておくことで、そのデータは指定されたポート番号で受信待ちしているソケットに届けられることになります。そして、データがソケットに届けられれば、そのデータをプログラムが受信することができます。

IPアドレスとポート番号によって特定の端末の特定のプログラムにデータが届けられる様子

つまり、特定の端末(ネットワークインターフェース)にデータを届けるためには IP アドレスの指定が必要となり、その端末上で動作する特定のソケットにデータを届けるためにはポート番号の指定が必要となります。IP アドレスをマンションの住所と考えれば、そのマンションの部屋番号を示すのがポート番号となります。

IPアドレスとポート番号のイメージ図

ソケット通信と IP アドレス / ポート番号

ここまでの説明のように、ソケット通信で適切に相手にデータを送信したり、データを受信したりするためには IP アドレスやポート番号の指定が必要になります。

そのため、詳細は socket クラスのメソッド で説明しますが、ソケット通信時に使用するメソッドには IP アドレスとポート番号を引数で指定する必要があるものが多いです。

例えば、ソケット通信でデータを送信するメソッドには sendto が存在し、このメソッドでは引数で IP アドレスとポート番号を指定することができるようになっています。そして、この sendto メソッドを実行すると、指定された IP アドレスを割り振られたネットワークインターフェースにデータが送信され、さらに指定されたポート番号で待っているソケットにデータが割り振られることになります。

sendtoメソッド実行時にIPアドレスとポート番号の指定が必要であることを示す図

また、bind メソッドを実行すると、そのメソッドを実行したインスタンスのソケットが、引数で指定された IP アドレス&ポート番号に関連付けられることになります。そして、その状態のソケットで受信待ちを行うと(受信待ちは recvrecvfrom メソッドで実行する)、指定された IP アドレスのネットワークインターフェースとポート番号に対してデータの受信待ちが行われることになり、そこに送信されてきたデータを受信することができるようになります(データ受信時には、どのネットワークインターフェースからデータを受信し、どのポート番号宛のデータを受け取るのかを bind で事前に設定 [関連付け] しておく必要があります)。

bindメソッド実行時にIPアドレスとポート番号の指定が必要であることを示す図

こんな感じで、ソケット通信を行う上ではデータの送信やデータの受信に IP アドレスやポート番号の指定が必要となります。データを相手に届けるためには、通信相手が受信待ちしている IP アドレス・ポート番号に対してデータを送信する必要がありますし、データを相手から受け取るためには、通信相手が送信してくる IP アドレス・ポート番号に対してデータの受信待ちを行う必要があります。

この辺りの、通信プログラムの作り方については後述で詳細を解説していきます。まずは、ソケット通信を行ううえでは IP アドレスとポート番号が非常に重要な存在であることは覚えておいてください。

プロトコル

続いてプロトコルについて解説していきます。

スポンサーリンク

プロトコルの重要性

プロトコルとは、簡単に言えば通信を行う上でのルールになります。

通信は、自分だけでなく相手がいて成立するものです。そして、通信は相手が期待するデータや順序でデータの送受信を行うことで成立します。勝手に好きなようにデータを送信しても相手が混乱して通信が破綻することになります。通信プログラムを初めて開発する人にとっては、この点が難しい点であり戸惑う点になると思います。

例えば何も考えずに好き勝手プログラムを開発すると、お互いのプログラムが同時にデータの受信待ちを行い、延々と待ち続けるようなプログラムが出来上がってしまうこともあります。

複数のプログラムが同時に受信待ちを行なっている様子

なので、通信を行う複数のプログラムを開発する際には、通信時のルールを定め、そのルールに従って通信が行われるように開発することが重要になります。このルールには、データの送り方や送受信するデータのフォーマット、データの送受信の順序やタイミングが含まれます。そして、このルールがプロトコルと呼ばれます。ルールが無ければ互いのプログラムが好き勝手動作して通信が破綻することになりかねないですが、同一のルールに従ってプログラムを開発するようにすれば、それらのプログラム間でうまく通信が行えるようになります。

つまり、プロトコルと聞くと難しそうに感じるかもしれませんが、通信プログラムを開発する上での手順書のようなものでもあり、通信プログラムの開発を手助けしてくれるものになります。

プロトコルに従って通信プログラム開発を行う様子

トランスポート層のプロトコル

このプロトコルには数多くのものが存在します。また、独自でプロトコルを決めて通信プログラムを開発することもあります。

これらのプロトコルのうち、ソケット通信を行う上で、まず意識すべきプロトコルが TCP と UDP になります。

TCP と UDP は共に、TCP / IP モデルのトランスポート層のプロトコルになります。TCP / IP モデルは複数の端末間で通信を行う際のルールを階層的に定めたものになります。このモデルは「ネットワークインターフェース層」「インターネット層」「トランスポート層」「アプリケーション層」の4階層で表現されているのですが、特にソケット通信プログラムを開発する上で意識すべきなのは、後者の2つの「トランスポート層」と「アプリケーション層」になります。アプリケーション層については後述で解説します。

TCP/IPモデルの説明図

ソケット通信を行う上ではソケットが必要で、このソケットを作成してからソケットを利用して通信を行うことになるのですが、ソケット作成時には TCP or UDP のどちらの通信を行うのかを選択する必要があります。なので、ソケット通信プログラムを開発するためには、特徴を踏まえたうえで TCP or UDP のどちらで通信するのかを決定する必要があり、TCP と UDP についての最低限の理解が必要となります。

MEMO

厳密に言えばもっと他のプロトコルを選択することもできるのですが、このページでは TCP or UDP のどちらか一方でソケット通信を行うことを前提に解説していきます

TCP と UDP の共通点

これらの TCP と UDP はトランスポート層のプロトコルであり、この層のプロトコルではポート番号に応じたデータの振り分けや信頼性に関する制御が規定されています。このページでは、簡単にこれらを “データの届け方” と呼ばせていただきます。

IP アドレスとポート番号 の節で、ソケット間でデータの送受信を行うためには IP アドレスとポート番号が必要になると説明しましたが、このポート番号によってソケットにデータを振り分けることがトランスポート層の役割の1つになります。このポート番号によるソケットへのデータの振り分けは TCP でも UDP でも共通的に行われることになります。また、IP アドレスによってネットワークインターフェースにデータを届けるのは、トランスポート層よりも下位の層(インターネット層とネットワークインターフェース層)の役割になります。

ネットワークインターフェース間の通信がインターネット層以下の役割で実現されることを示す図

TCP と UDP の違い

また、TCP の場合は信頼性の高い通信を実現するため、例えばデータの送信時には再送制御と呼ばれる制御を行うことがルール化されています。そのため、TCP の場合、データを送信した後に相手からデータを受信したことを示す応答を受け取るような制御が必要となります。そして、応答がなければ相手がデータを受信できなかったとみなして再度データを送信するような制御が必要となります。このような制御を行うことで TCP では信頼性の高いデータの送信が実現されています。それに対して UDP の場合は、この再送制御が行われません。UDP の場合はデータを単に送信するだけで、相手がデータを受信したかどうかの確認は行われません。

また、再送制御のような制御を行うために、TCP ではデータの送受信を行う前にソケット間で接続の確立(コネクションの確率)を行う必要があります。それに対し、UDP の場合は接続の確立は不要で、いきなりデータの送受信を行うことができます。

このように、ポート番号に応じてデータをソケットに振り分けるという点では共通なのですが、データの送受信時の制御やデータの送受信するための前準備が TCP と UDP とで異なります。そして、これが異なるため TCP と UDP とでは通信の信頼性が異なります。TCP の方が通信の信頼性が高いですし、UDP の場合は通信の信頼性が低いですが、通信が高速であるという特徴があります。

なので、ソケット作成時には、上記のような TCP と UDP の違いを理解した上で、TCP と UDP のどちらで通信するのかを選択する必要があります。開発するプログラムがリアルタイム性を重視するものであれば UDP を選択し、信頼性を重視するものである場合は TCP を選択する、といった感じですね。

TCPとUDPの違いの説明図

ソケット通信とトランスポート層のプロトコルの関係

ということで、ソケット通信を利用するためには TCP や UDP についての最低限の理解が必要となります。なんですが、実はソケット通信プログラムを開発する際には、前述で紹介したような再送制御を行なったり、ポート番号によるデータの振り分けを行なったりするような制御は不要となります。

なぜなら、ソケット通信用のライブラリやモジュールがトランスポート層以下の制御を勝手に行なってくれるからです。Python の場合は、socket モジュールの socket クラスのメソッドを実行すれば、トランスポート層以下のプロトコルに応じた制御が勝手に行われることになります。例えばソケット生成時に TCP を選択すればデータ送信時に勝手に再送制御が行われます。UDP を選択すれば再送制御なしに高速な通信が行われます。

ソケット通信ライブラリでトランスポート層の制御が行われる様子

なので、ソケット通信を行う上では、TCP と UDP を選択する必要があるものの、TCP や UDP に従った通信制御は不要となります。基本的には socket クラスが提供するメソッドを実行すれば良いだけです。ただ、後述で解説するように、TCP と UDP とでは使用するメソッドが異なりますし、前述のように TCP ではデータの送受信を行う前に接続の確立が必要であり、そのためのメソッドの実行も必要となります。また、ソケットを生成する際には TCP or UDP のどちらを利用するかを指定する必要があります。

逆に言えば、これらさえ行えば、ソケット通信プログラム開発時はトランスポート層以下のプロトコルに関しては意識する必要はありません。ということで、ソケット通信プログラムの開発を行うのであれば、最低限でも良いので TCP と UDP の違いであったり、TCP 通信の場合に実行する必要のあるメソッド& UDP 通信の場合に実行する必要のあるメソッドくらいは理解しておくと良いでしょう。

アプリケーション層のプロトコル

続いて、トランスポート層の上位の層であるアプリケーション層のプロトコルについて解説していきます。

ソケット通信とアプリケーション層のプロトコル

前述の通り、トランスポート層以下に関してはソケット通信ライブラリ・モジュールが勝手にプロトコルに応じた制御・通信を実行してくれます。ですが、トランスポート層の上位層である “アプリケーション層” のプロトコルに関してはソケット通信モジュールは関与しません。なので、アプリケーション層のプロトコルに関しては、開発者がプロトコルに従うようにプログラムを開発する必要があります。

トランスポート層のプロトコルでデータの届け方のルールが規定されているのに対し、このアプリケーション層の各種プロトコルでは、送受信するデータのフォーマットや送受信する順序・タイミングが規定されています。HTTP や SMTP、FTP などといったプロトコルはアプリケーション層のプロトコルになります。

トランスポート層とアプリケーション層の違い

つまり、ソケット通信プログラムの開発者は、アプリケーション層のプロトコルで定められたフォーマットのデータを用意し、さらにプロトコルで定められた順序・タイミングに従ってデータの送受信が行われるようにプログラムを開発する必要があります。そして、前述の通り、socket クラスのメソッドさえ実行すれば、後はメソッド内でトランスポート層以下の制御が行われて相手にデータが届くようになっています。

ソケット通信プログラムの開発の仕方

ただし、TCP or UDP でデータ送信時やデータ送信前に実行すべきメソッドが異なるため、これらの使い分け等も考慮しながら開発していく必要があります。

さらに、実際に通信プログラムを開発する上では、どのアプリケーション層のプロトコルに従い、どのトランスポート層のプロトコルに従ったものを開発していくのかを事前に決めておく必要もあります。そして、これらのプロトコルに従うようにプログラムを開発していく必要があります。

アプリケーション層とトランスポート層のプロトコルを決め、それに従うようにプログラムを開発する必要があることを示す図

独自のプロトコルでの通信プログラムの開発

ただし、通信プログラムは、特に通信相手と同じプロトコルに従ってデータの送受信を行うことこそが重要であり、必ずしも HTTP や SMTP などの一般的なプロトコルに従わなければならないというわけではありません。例えば、自分で決めた独自のプロトコルに従って通信プログラムを開発することも可能です。自分で送受信するデータのフォーマットやデータの送受信を行う順序・タイミングを決め、それに合わせて開発を行なっても、そのプロトコルが破綻していない&複数のプログラムが同じプロトコルに従ってデータの送受信を行うように開発されていれば、データの送受信は上手く行うことができます。

独自のプロトコルを定める様子

例えば、何かしらの組み込み製品を開発するような場合、その製品内で動作する複数のプログラム間でのみ通信が行われるのであれば、開発者やチームで決めたプロトコルで通信させることも多いです。要は、自分達が開発するプログラム間でのみ通信を行うのであれば、それらのプログラムが同じプロトコルに従ってデータの送受信を行えば良いのですから、それが独自に決めたプロトコルでも問題ありません。

自身で開発するプログラム間での通信を独自に決めたプロトコルで行う様子

一般的なプロトコルでの通信プログラムの開発

ですが、HTTP 等の一般的なプロトコルで動作している “既存の通信プログラム” とデータの送受信を行いたいのであれば、その既存の通信プログラムが従っているプロトコルに合わせてプログラムを開発する必要があります。前述のとおり、通信プログラムは通信相手と同じプロトコルに従ってデータの送受信を行う必要があります。なので、既に何かしらのアプリケーション層のプロトコルに従って動作している通信プログラムと通信を行うためには、そのプログラムと同じプロトコルに従うようにプログラムを開発する必要があります(もしくは既存の通信プログラムの方を変更する必要があります)。

例えば、ウェブページを表示するアプリを開発したい場合、ウェブページを表示するためのデータ(例えば HTML)をサーバー装置から受信する必要があります。このサーバー装置上では HTTP サーバーというソフトが動作しており、この HTTP サーバーは HTTP というプロトコルに従って動作しています。そのため、ウェブページを表示するアプリを開発するのであれば、HTTP というプロトコルに従ってデータの送受信を行うように開発する必要があります。これに従わずにプログラムを開発すると、HTTP サーバー側が期待するフォーマットのデータを受信することができなくなるため、エラーが返却されたり応答が無かったりすることになります。

HTTPに従って動作するプログラムと独自のプロトコルに従って動作するプログラムとでは通信不可であることを示す図

で、こういった場合は、特定のアプリケーション層のプロトコルに従ったプログラムを開発する必要があるため、アプリケーション層のプロトコルを熟知した上でプログラムを開発する必要があります。このページでは、一般的なアプリケーション層のプロトコルに関しては深く説明しませんが、また機会があれば HTTP 等のプロトコルに従ったソケット通信プログラムの作り方についても別途説明していきたいと思います。

また、既存のプログラムと通信するためには、アプリケーション層だけでなく、トランスポート層のプロトコルも既存のプログラムと合わせる形で開発を行う必要があります。例えば、上記でも登場した HTTP サーバーに関して言えばトランスポート層のプロトコルとしては TCP が採用されていることがほとんどだと思いますので、HTTP サーバーと通信するプログラムを開発するのであれば、そのプログラムが利用するトランスポート層のプロトコルも TCP にしてやる必要があります。なので、こういった既存のプログラムと通信を行うのであれば、採用するアプリケーション層のプロトコル、さらにはトランスポート層のプロトコルが自然と一意に定まることになります。

既存のプログラムと通信をするためには、同じプロトコルで動作するプログラムを開発する必要があることを示す図

スポンサーリンク

プロトコルとソケット通信プログラムの開発

プロトコルに関してまとめると、まず通信プログラムは、通信相手と同じプロトコルに従って通信・データの送受信を行うことが必要です。

このプロトコルのうち、トランスポート層のプロトコルに関しては socket クラスのメソッド側でプロトコルに従った通信・制御が行われるようになっています。なので、socket クラスのメソッドを適切に実行してやれば、トランスポート層以下の通信は socket クラスが勝手に行なってくれることになります。ただし、TCP or UDP をソケット生成時に選択したり、TCP or UDP に応じて実行するメソッドを変更したりする必要はあります。

それに対し、アプリケーション層のプロトコルに関しては socket クラスは関与しないため、データ送信時はアプリケーション層のプロトコルで決められたフォーマットに従ったデータを用意してやる必要がありますし、データの送受信の順序やタイミングもアプリケーション層のプロトコルに従って行われるようにメソッドを実行するようにする必要があります。ただ、このプロトコルは独自で決めたものでも良いです。とにかく、通信相手と同じプロトコルに従ってデータの送受信を行うことが重要です。

後は、開発したいプログラムに応じて、どのプロトコルを採用するのかを予め決めておくことも重要です。通信相手と採用するプロトコルが異なれば通信が上手く行えないため、採用するアプリケーション層・トランスポート層のプロトコルを決めておき、それに従ってプログラムを開発していくことが必要となります(ソケット通信プログラムでは、アプリケーション層のプロトコルに関しては独自に決めたものでも良いです。また、通信相手が既に何らかのプロトコルに従って通信しているのであれば、それと同じプロトコルに従って通信するようにプログラムを開発する必要があります。

ということで、ソケット通信プログラムは主に下の図の破線で囲った部分のプロトコルを意識しながら開発していくことになります。トランスポート層も多少は意識する必要がありますが、プロトコルに従った制御・通信は socket クラスが実施してくれます。

TCP/IPモデルにおけるソケット通信プログラムのコーディング対象を示す図

サーバーとクライアント

ここまで通信プログラムは通信相手と同じプロトコルに従うように開発することが重要であると説明してきました。この説明を聞いて通信を行う2つのプログラムが全く同じ処理をすることをイメージされた方もおられるかもしれません。そのようにプログラムを開発することも可能なのですが、実際には2つのプログラムが異なる処理を行うように開発することの方が多いと思います。

具体的には、2つのプログラムをサーバーとクライアントという2つの異なる立場のプログラムとして開発することが多いです。例えば HTTP や SMTP 等のアプリケーション層のプロトコルでは、2つのプログラムをサーバーとクライアントの役割に分類し、それらが送受信するデータのフォーマットや送受信する順序やタイミングが定められています。つまり、サーバーとクライアントの2種類の異なるプログラム間で通信を行うことを前提としてプロトコルが定められています。そういったプロトコルは多いですし、何より対等のプログラムを開発するよりも異なる役割のプログラムを開発した方が通信プログラムの開発が楽になります(理由は後述します)。

サーバーとクライアントの違い

ここからは、上記で登場した “サーバー” と “クライアント” について解説していきます。

サーバーとはサービスを提供するプログラム・ソフトウェア(もしくはハードウェア)であり、クライアントとはサーバーが提供するサービスを利用するプログラム・ソフトウェア(もしくはハードウェア)となります。

これらのサーバーとクライアントが通信を行なって1つのシステムを形成するようなソフトウェアの設計モデルをクライアントサーバーモデルと呼びます。

プロトコル の節で説明したように、通信プログラムは通信相手と同じプロトコルに従って通信を行う必要があるのですから、クライアントサーバーモデルでは、サーバーとクライアントが同じプロトコルに従って通信を行う必要があります。

クライアントサーバーモデルの説明図

ただし、これらのサーバーとクライアントとでは処理の流れが異なります。

サーバーは受身のプログラムで、基本的にはクライアントからデータが送信されてくるのを待ち続けるプログラムとなります。そして、クライアントからデータが送信されてきた際に動き出し、そのデータに応じた処理を実行するようになっています。その後は、またクライアントからデータが送信されてくるのを待ち続け、同じことを繰り返します。

サーバーの役割を示す図

それに対し、クライアントは能動的なプログラムで、サービスの利用が必要になったタイミングでサーバーに対してデータの送信を行うプログラムとなります。このデータの送信によって、サーバーにサービスを利用させて欲しいと要求します(この要求は、リクエストと呼ばれることがあります)。例えば、GUI アプリであれば、ユーザーが何かしらのボタンをクリックしたときにデータを送信するようなイメージでデータの送信が行われます。

クライアントの役割を示す図

で、この時にクライアントがサーバーに送信するデータはアプリケーション層のプロトコルで規定されたフォーマットのものである必要があります。さらに、プロトコルによっては、サーバーがデータを受け取った後にデータ(レスポンス)を返却するような場合もあり、このデータのフォーマットもプロトコルで規定されたものである必要があります。要は、このクライアントとサーバー間のデータの送受信のルールがアプリケーション層のプロトコルで決められており、このプロトコルに従って双方のプログラムがデータを送受信する必要があります。

スポンサーリンク

クライアントサーバーモデルのメリット

このクライアントサーバーモデルの一番の特徴は、サーバーとクライアントが対等ではないという点になります。どちらが偉いというわけでもないのですが、前述の通りサーバーとクライアントとでは役割が異なります。そして、役割が違うため通信プログラムが開発しやすいです。

前述の通り、サーバーは基本的に常にクライアントからのデータの送信を待っているのですから、サーバーがまず行う処理はデータの受信待ちになります。そして、データの受信を行なったのちに、その受信したデータに応じた処理を実行すれば良いことになります。処理結果を返却する必要があるのであれば、処理を実行したのちにデータの送信をクライアントに対して行えば良いだけです。もちろん、プロトコルによってはもっと複雑なデータの送受信を行う必要がある場合もありますが、最初にデータの受信待ちを行い、データ受信後にプロトコルに応じた処理を行えば良いだけになります。

それに対し、クライアントは、サーバーは常にデータの受信待ちを行なっているのですから、好きなタイミングでデータの送信を行えば良いことになります。もちろんプロトコルに従ったフォーマットのデータを送信する必要あはります。そして、その後はプロトコルに応じてサーバー側からのデータの受信待ち等を行えば良いことになります。

クライアントとサーバーが最初に行う処理

もちろん、TCP と UDP の場合とでデータの送受信前の接続の確立の必要性の有無が異なるのですが、データの送受信に関して言えば、上記のようにサーバーとクライアントとでは基本的に最初の一手が決まっていることになります。なので、例えばクライアントは自身がデータを送信する前に相手からデータが送信されてくるようなことは心配する必要がありません。サーバーに関しても同様のことが言えます。そのため、基本的にサーバーとクライアントでタイミングによって実行する処理・メソッドが一意に決まることになります。なので、サーバーとクライアントそれぞれの処理の流れはある程度パターン化されることになり、プログラム開発が容易となります。

ただし、プロトコル の節でも解説したように、TCP と UDP の場合とで利用するメソッドや接続の確立の必要性の有無が異なることになりますので、基本的な処理の流れは、サーバーとクライアントだけでなく、TCP と UDP も掛け合わせた下記の4パターンに分けられることになります。

  • TCP 通信を行うサーバー
  • TCP 通信を行うクライアント
  • UDP 通信を行うサーバー
  • UDP 通信を行うクライアント

もちろん、これらでは最初のデータの送受信までの処理の流れがパターン化されているだけで、それ以降の処理や送受信するデータの用意はアプリケーション層のプロトコルに従って実装する必要があります。ですが、最初のデータの送受信までの処理の流れだけでもパターン化されているとかなり通信プログラムが開発しやすくなるため、これらの4つのパターンは頭に入れておいた方が良いと思います。

これら4つのパターンのスクリプトの実例に関しては、ソケットの生成方法や socket クラスのメソッドの説明を行なった後に、ソケット通信プログラムの例 の節で紹介したいと思います。

少し前置きが長くなりましたが、ここまでがソケット通信プログラムを開発する上で知っておいた方が良い知識の解説になります。ここからは、結局はここまで解説してきた内容を実現するためのメソッドの利用方法やスクリプトの例の解説になりますので、ここまで解説してきた内容を頭に入れておいていただければ、ここからの説明もすんなり理解できると思います。

ソケットの生成

では、ここからは socket モジュールの  socket クラスを利用した Python でのソケット通信の作り方を説明していきたいと思います。まずは、ソケットの生成方法について説明し、その後、データの送受信や通信の前準備を行う socket クラスの各種メソッドの紹介を行っていきたいと思います。 

socket のインスタンスの生成

ここまで説明してきたとおり、ソケット通信はソケットを利用した通信であり、ソケット通信を行うためにはソケットを生成しておく必要があります。また、これも前述で少し説明しましたが、ソケットの生成は socket モジュールの socket クラスのコンストラクタで生成することができます。

このコンストラクタを実行することでソケットが生成され、そのソケットを管理するためのインスタンスが返却されます。そして、その生成されたソケットでのソケット通信は、その返却されたインスタンスにメソッドを実行させることで行うことが出来ます。

socketクラスのインスタンスにメソッドを実行させて通信を行う様子

socket クラスのコンストラクタ

ということで、ソケット通信を行うためには、まず socket クラスのコンストラクタを実行してインスタンスを生成し、その後、そのインスタンスから各種メソッドを実行するような処理を記述していく必要があります。

ここでは、その socket クラスのコンストラクタについて説明していきます。

まず、socket クラスのコンストラクタは、socket モジュールを import しておけば socket.socket() から実行することができます。そして、この socket.socket() の返却値が socket クラスのインスタンスとなります。

socket クラスのコンストラクタ、すなわち socket.socket() の引数には下記が用意されています。これらの引数の指定によって生成するソケットのオプションを指定することができます。

  • family
  • type
  • proto
  • fileno

これらを指定しなければ適当な初期値がオプションに設定されてソケットが生成されることになりますが、少なくとも familytype に関しては引数を指定することが多いです。protofileno は指定しないことも多いため、このページでは解説を省略し、familytype に対してのみ解説を行います。

引数 family

まず引数 family (第1引数) にはアドレスファミリーの種類を指定します。

socket モジュールでは、通信相手を決めるために様々なメソッドで引数に address が指定できるようになっています。例えば、後述で解説する sendto メソッドでは、引数 address に対してデータの送信が行われます。この address のフォーマットが、family によって異なります。

で、この引数 familiy には様々な値を指定することが出来るのですが、まずは引数 family には socket.AF_INET を指定するのでよいと思います。これにより、IPv4 で通信を行うソケットを生成することができ、この場合の address のフォーマットは (IPv4アドレス, ポート番号) となります。すなわち、第1要素を IPv4のアドレスを表す文字列、第2要素をポート番号を表す整数とするタプルとなります。そして、これらを指定することで、IP アドレスとポート番号 の節で説明したように任意のソケットに対してデータの送信等を行うことが出来るようになります。

また、実はここまでの解説に関しても、引数 family には socket.AF_INET を指定することを前提とした説明を行ってきていますし、ここからの解説も、引数 familysocket.AF_INET を指定することを前提に説明していきたいと思います。

引数 type

引数 type (第2引数) にはソケットのタイプを指定するのですが、この引数には基本的には下記の2つのいずれかを指定することが多いです。

  • socket.SOCK_STREAM:TCP 通信を行う
  • socket.SOCK_DGRAM:UDP 通信を行う

トランスポート層のプロトコル で解説したように、ソケット通信では基本的に TCP or UDP の通信を行うことになります。TCP or UDP のどちらのプロトコルで通信を行うのかを引数 type で指定することになります。

具体的には、TCP 通信を行うプログラムを開発するのであれば、socket クラスのコンストラクタの引数 typesocket.SOCK_STREAM を指定してソケットを生成し、UDP 通信を行うプログラムを開発するのであれば socket.SOCK_DGRAM を指定します。

通信を行うプログラム同士は同じプロトコルに従って通信を行うことが必要となります。従うプロトコルが異なっていると基本的に通信プログラムは意図通りに動作しません。そのため、通信を行うプログラム同士はソケットを生成するときに同じ type 引数を指定する必要があります(引数 family も同様です)。

2つのソケットの利用するプロトコルが異なっていて通信が行えない様子

また、トランスポート層のプロトコル でも少し触れましたが、TCP or UDP のどちらで通信を行うのかで使用するメソッドや接続確立の必要性の有無が異なります。なので、この引数 type はソケット通信プログラムを開発していく上で非常に重要な引数となります。

socket クラスのコンストラクタの実行例

下記に socket クラスのコンストラクタの実行例を示します。

socketのコンストラクタの実行例
import socket

# ソケットを生成する
sock = socket.socket(socket.AF_INET, socket.SOCKET_DGRAM)

上記では、第1引数 (引数 family) に socket.AF_INET を指定し、さらに第2引数 (引数 type) に SOCKET_DGRAM を指定しているため、このコンストラクタの実行によって生成されるソケットは IPv4 で UDP 通信を行うためのソケットとなります。そして、そのソケットの操作を行うインスタンスが変数 sock から参照されることになります。 

なので、この sock からメソッドを実行させて、様々なソケットの制御やソケットでの通信を行うことができることになります。これらのメソッドに関しては、socket クラスのメソッド で紹介していきます。

スポンサーリンク

ソケットを閉じる (close)

コンストラクタと一緒に覚えておきたいのが close メソッドになります。一般的なファイル等と同様に、ソケットは不要になったら閉じる必要があります。

このソケットを閉じるためには、そのソケットに対応する socketクラスのインスタンスから close メソッドを実行する必要があります。このソケットを閉じる処理が行われないと、生成したソケットがずっとプログラム内に残ってしまうことになります。そして、close せずにソケットの生成を何回も行うと、いずれはソケットの生成可能数の上限に達し、ソケットの生成に失敗することになります。

なので、ソケットは不要になったら close メソッドで閉じる必要があります。

Python の場合はガベージコレクション機能がありますので、close メソッドの実行を忘れても socket クラスのインスタンスへの参照が無くなればガベージコレクションによって socket クラスのインスタンスが破棄され、それによって自動的にソケットが閉じられるようになっています。ですが、基本的には不要になったソケットは close メソッドで明示的に閉じるようにするのが無難だと思います。

close メソッドの使用例

close メソッドの使用例を下記に示します。

closeメソッドの使用例
import socket

# ソケットを生成する
sock = socket.socket()

sock.データを送信するメソッド(引数)
data = sock.データを受信するメソッド(引数)
sock.close()

with を利用したソケットのクローズ

また、with 文を利用し、with 文のブロックを抜けたときに自動的にソケットを閉じるようにするのでも問題ありません。要は、不要になったソケットが確実に閉じられるようにすることが重要です。

withメソッドの使用例
import socket

# ソケットを生成する
with socket.socket() as sock:
    sock.データを送信するメソッド(引数)
    data = sock.データを受信するメソッド(引数)

# with文のブロックを抜けるとソケットが閉じられる

また、このページでの説明は省略しますが、socket クラスには shutdown メソッドが存在します。shutdown メソッドは、そのソケットでの通信の遮断のみ行うメソッドであり、メソッド名としては意味合いが似ていそうなメソッドではありますが、shutdown メソッドは “ソケット自体を閉じるメソッドではない” ことに注意してください。

socket クラスのメソッド

ここまで、ソケットを生成するコンストラクタおよび、ソケットを閉じる close メソッドの紹介を行ってきました。これらは、ソケット通信を行うために最低限実行が必要な前処理&後処理になります。

ここからは、実際にソケット通信を行うための socket クラスのメソッドの紹介を行っていきます。ここで紹介するメソッドが socket クラスのメソッドの全てというわけではありませんが、ここで紹介するメソッドはソケット通信を行う上で非常によく利用するメソッドであり、まずこれらのメソッドを使いこなせば、基本的なソケット通信は行えるようになります。

また、詳細に関しては ソケット通信プログラムの例 で説明しますが、UDP の場合はクライアントとサーバーで下図のような流れでメソッドを実行していくことになります。

UDP通信におけるサーバー・クライアント間の処理の実行シーケンス

さらに、TCP の場合はクライアントとサーバーで下図のような流れでメソッドを実行していくことになります。

TCP通信におけるサーバー・クライアント間の処理の実行シーケンス

このように UDP と TCP とで、さらにクライアントとサーバーとで利用するメソッドが異なります。実行するメソッドが異なる理由や、各種メソッド1つ1つの使い方の詳細に関してはここから説明していきますが、まずは上記のような流れを簡単に理解しておいていただくと、以降の説明がより理解しやすくなると思います。

では、各種メソッドの説明を行っていきます。

sendto

まず最初に紹介するのが sendto メソッドになります。

sendto メソッド

sendto メソッドは、引数 data (第1引数) に指定したデータを引数 address (第2引数) に指定した IP アドレス&ポート番号に送信するメソッドになります。sendto メソッドは、送信に成功すると送信したデータのサイズを返却します。

data には byte-like なオブジェクト(例えばバイト型のデータ)を指定する必要があります。また、address にはタプルを指定し、このタプルの第1要素には IP アドレスを示す文字列、第2要素にはポート番号を指定する必要があります。

コンストラクタ実行時に引数 familysocket.AF_INET を指定して生成したソケットの場合、IP アドレスを示す文字列には IPv4 形式のものを文字列で指定する必要があります。例えば、'192.168.10.100' といったように、4つの数字をピリオド (.) 区切りで並べた文字列を指定します。

上記のように引数を指定して sendto メソッドを実行すれば、引数 data に指定したデータが、指定した IP アドレスが割り当てられたネットワークインターフェースに送信されます。さらに通信端末内の指定したポート番号でデータの受信待ちを行なっているソケットに割り振られることになります。

通信で主に行うことはデータの送信とデータの受信であり、前者を行うメソッドの1つが、この sendto になります。この sendto は UDP 通信で利用し、TCP 通信では、後述で紹介する send メソッドを利用することになります。この理由については、次の send で説明します。

sendto メソッドの使用例

sendto メソッドの使用例を下記に示します。

sendtoメソッドの使用例
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

data = b'Hello World'
address = ('192.168.10.100', 40001)

# dataをaddressに送信する
sock.sendto(data, address)

sock.close()

上記を実行することで、data が IP アドレス 192.168.10.100 の PC (スマホなどの可能性もあり) のポート番号 40001 で受信待ちしているソケットにデータが送信されることになります。また、b'Hello World' のように文字列の前に b を指定しているのは、文字列をバイト型に変換するためになります。ソケット通信では直接文字列を送受信することはできないため、バイト型に変換してから sendto メソッドの引数に指定するようにしています。

 sendtoメソッドの説明図

スポンサーリンク

send

データを送信するメソッドには、sendto だけでなく send も存在します。

send メソッド

send は TCP 通信時にデータを送信する際に利用するメソッドとなります。

send メソッドを実行するときに必須となる引数は data (第1引数) のみとなります。sendto 同様に、引数 data には byte-like なオブジェクトを指定する必要があり、引数 data を適切に指定して send メソッドを実行することで、data に指定したデータを送信することができます。ですが、sendto メソッドとは異なり、引数 address の指定は不要です。というか指定すると例外が発生します。

つまり、send メソッド実行時には送信先を指定することができません。では、send メソッドを実行すると、どこに data が送信されることになるのでしょうか?

この送信先は、既に接続を確立しているソケットとなります。TCP の場合は通信相手と接続を確立してからデータの送受信を行う必要があり(コネクション型通信)、接続を確立した後に send メソッドでデータを送信すれば、その接続を確立したソケットにデータが送信されることになります。そのため、send メソッドの場合は送信先を示すパラメータを引数で指定する必要はありません。それに対し、UDP の場合は接続を確立しませんので、データの送信時に送信先となる IP アドレスとポート番号を明示的に指定する必要があります。そのため、UDP でデータを送信する際には、引数で IP アドレスとポート番号を指定可能な sendto メソッドを利用することになります。

また、send メソッドは、sendto メソッド同様に、送信に成功すると送信したデータのサイズを返却します。

send メソッドの使用例

下記に send メソッドの使用例を示します。詳細は後述で解説しますが、下記における connect が接続を確立するメソッドになります。connect メソッドについては connect の節で説明します。

sendメソッドの使用例
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

address = ('192.168.10.100', 40001)
sock.connect(address)

data = b'Hello World'

# connectした相手にdataを送信する
sock.send(data)

sock.close()

上記を実行することで、まず IP アドレス 192.168.10.100 の PC (スマホなどの可能性もあり) のポート番号 40001 で受信待ちしているソケットと接続が確立されます。そして、その後、接続を確立した相手に data が送信されることになります。

sendメソッドの説明図

recv

ここまで sendtosend の紹介を行いましたが、これらはデータを送信するメソッドになります。通信では互いにデータのやり取りを行うため、データの送信だけではなくデータの受信も必要となります。

recv メソッド

そのデータの受信を行うメソッドの1つが recv になります。recv メソッドは、引数 bufsize (第1引数) に一度に受信するデータの最大サイズを整数として指定して実行します。また、recv メソッドは TCP でも UDP でも利用可能なメソッドとなります。

recv メソッドを実行すると、ソケットは、そのソケットに関連付けされたネットワークインターフェース・ポート番号に対しての受信待ちを行います。そして、その設定されたネットワークインターフェース・ポート番号に対してデータが送信されてくるとデータの受信(読み込み)が行われ、recv メソッドがその受信したデータを返却します。返却されるデータはバイト型となります。

上記の説明からも分かるように、recv メソッドを実行するためには、その recv メソッドを実行するソケットに対してネットワークインターフェース・ポート番号を事前に “関連付け” しておく必要があります。この設定を行ってから recv メソッドを実行すれば、複数存在するネットワークインターフェースのうち、その設定されたネットワークインターフェースに対しての受信待ちが行われるようになります。そして、そのソケットに関連付けされたポート番号宛に送信されてきたデータが送信されてくると、ソケットがそのデータを受信することになります。

この関連付けに関しては socket クラスのインスタンスに bind メソッドを実行させることで行うことができます。bind メソッドに関しては bind の節で解説します。

recv メソッドの使用例

下記に recv メソッドの使用例を示します。

recvメソッドの使用例
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

sock.bind(('192.168.10.5', 40001))

# bindしたaddressからデータを受信
data = sock.recv(1024)
print(data)

sock.close()

上記を実行することで、192.168.10.100 が割り当てられたネットワークインターフェース&ポート番号 40001 に対して受信待ちが行われます。そして、これらのネットワークインターフェース・ポート番号宛にデータが送信されてくるとデータを受信し、受信したデータは recv メソッドの返却値として得ることができます。

recvメソッドの説明図

recvfrom

recv 同様に、recvfrom もデータを受信するメソッドとなります。

recvfrom メソッド

recvfrom の使い方は recv メソッドとほぼ同じです。

ですが、返却値が2つであるという点に注意してください。1つ目の返却値は recv 同様に受信したデータになります。2つ目の返却値は、データを送信してきたソケットの情報になります。より具体的には、データを送信してきたソケットに応答するためのネットワークインターフェースとポート番頭を要素とするタプルになります。

したがって、UDP 通信の場合、recvfrom の返却値の2つ目をそのまま sendto の第2引数(引数 addr)に指定して sendto メソッドを実行すれば、データを送信してきた相手に応答となるデータを送り返すことができることになります。recvfrom も TCP と UDP の両方で利用可能なメソッドとなりますが、この “データを送信してきた相手へのデータの返却” を実現するために、特に UDP でのデータの受信では recvfrom を利用する機会が多いです。

recvfromの2つ目の返却値に対してsendtoすれば、データの送信元にデータを返却することができることを示す図

TCP の場合は、send メソッドを実行すれば既に接続を確立しているソケットにデータが送信されるようになっているため、わざわざ recvfrom メソッド利用して相手のソケットの情報を取得する機会は少ないです。

recvfrom メソッドの使用例

下記に recvfrom メソッドの使用例を示します。

recvfromメソッドの使用例
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

sock.bind(('192.168.10.5', 40001))

# bindしたaddressからデータを受信
data, addr = sock.recvfrom(1024)

# データを送信してきた相手に返事を送信
sock.sendto(b'Thank you!', addr)

sock.close()

上記を実行することで、まず 192.168.10.100 が割り当てられたネットワークインターフェース&ポート番号 40001 に対して受信待ちが行われます。そして、これらのネットワークインターフェース・ポート番号宛にデータが送信されてくるとデータが受信されることになります。さらに、上記の場合は、データを送信してきたソケットの情報が recvfrom の2つ目の返却値 addr として得られます。なので、sock.sendto(b'Thank you!', addr) を実行することで、そのデータを送信してきたソケットに対して b'Thank you!' が返却されることになります。

recvfromメソッドの説明図

スポンサーリンク

bind

ここまで、データの送受信を行うメソッドの紹介を行ってきました。通信で主に行うことは「データを相手に届けること」「データを相手から受け取ること」となり、これらを行うのがここまで紹介してきたメソッドとなります。

ただ、これらを行うためには、その前準備や後処理が必要となり、それらを行うためのメソッドも適切に使いこなす必要があります。ここからは、データの送受信を行うために実行が必要となるメソッドを紹介していきます。

bind メソッド

最初に紹介するメソッドは bind になります。

IP アドレスとポート番号 等でも説明したように、特定の端末内の特定のソケットにデータを届けるためには、データ送信時に IP アドレスとポート番号を指定する必要があります。これにより、指定された IP アドレスが割り当てられたネットワークインターフェースにデータが届けられ、さらに、指定されたポート番号で受信待ちしているソケットにデータが割り振られることになります(プロトコル の節で説明したように、このポート番号へのソケットの割り振りまでは socket クラスのメソッド内で行われることになります)。

逆に言えば、データを受信する側のソケットは、相手からデータを受け取るために特定のポート番号で受信待ちをしておく必要があります。さらに、端末内に複数のネットワークインターフェースが存在する場合もあるため、どのネットワークインターフェースからデータを受信するのかも設定しておく必要があります。

これらの設定をソケットに対して実施するのが bind メソッドになります。そして、この設定は “バインド” や “関連付け” と呼ばれます。

bind メソッドは、引数 address (第1引数) にネットワークインターフェースの IP アドレスおよびポート番号を要素とする下記のような形式のタプルを指定して実行します。

(IPアドレス, ポート番号)

これにより、bind メソッドを実行したソケットに、引数に指定した IP アドレスのネットワークインターフェースおよびポート番号が関連付けされることになります。そして、recv で説明したように、bind 実行後に recv メソッドや recvfrom メソッドを実行すれば、それらの関連付けされたネットワークインターフェースおよびポート番号に対して受信待ちが行われます。

また、後述で紹介する listen に関しても、受信待ちするのが接続要求と呼ばれる特殊なデータではあるものの、結局 recv 同様にデータの受信待ちを行うためのメソッドですので、listen を実行する前にも bind メソッドによるネットワークインターフェース&ポート番号の関連付けが必要となります。

少しややこしい話ではあるのですが、まずは、何かしらの受信待ちを行うためには「待ちの対象となるネットワークインターフェースやポート番号を事前にソケットに関連付けしておく必要がある」とシンプルに考えていただくのが良いと思います。そして、その関連付けを行うための方法の1つが bind メソッドの実行になります。 

ただ、ソケットに対して関連付けられる IP アドレスは1つのみではありません。bind 実行時に IP アドレスに '0.0.0.0' を指定すれば、スクリプトを実行した PC の全ネットワークインターフェースをソケットに設定することもできます。それに対し、関連付けられるポート番号は1つのみとなります。

bindの第1引数に'0.0.0.0'を指定する様子

bind メソッドの使用例

下記に bind メソッドの使用例を示します。

bindメソッドの使用例
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

sock.bind(('0.0.0.0', 40001))

# bindしたaddressからデータを受信
data = sock.recv(1024)

sock.close()

上記を実行すれば、まず bind メソッドによって sock に対して PC 内の全ネットワークインターフェース&ポート番号 40001 が関連付けられ、さらに recv メソッドによって、これらのネットワークインターフェース・ポート番号宛にデータが送信されてくるとデータが受信されることになります。

bindメソッドの説明図

bind メソッドを利用しない関連付け

このソケットへのネットワークインターフェースとポート番号の関連付けは、bind メソッドの実行なしに自動的に行われることもあります。

例えば、bind メソッドを実行する前に sendto で紹介した sendto メソッドや、後述の connect で紹介する connect メソッドを実行すれば、それらを実行したソケットに対してネットワークインターフェースとポート番号が自動的に関連付けされます。そのため、これらを実行した後に recv メソッド等でデータの受信待ちを行うのであれば bind メソッドを実行する必要がなく、その自動的に関連付けされたネットワークインターフェースとポート番号に対して受信待ちが行われることになります。

上記のような仕組みが存在するため、最初に sendto メソッドや connect メソッドを実行するクライアント側のプログラムに関しては bind メソッドの実行なしに recv メソッド等でのデータの受信を行うことが可能です(クライアントのプログラムの処理の流れに関しては ソケット通信プログラムの例 で詳細を説明します)。

クライアントのsendto実行時にクライアント側のソケットに自動的にネットワークインターフェースとポート番号が関連付けられる様子

逆に、サーバープログラムに関しては、データの受信を行う recv メソッドや listen メソッドを最初に実行する必要があるため、bind メソッドの実行が必須となります。

また、明示的にポート番号を指定したいような場合や、自動的なポート番号等の関連付を避けたいような場合は、クライアント側でも bind メソッドを実行してソケットに関連付けするポート番号等を明示的に指定するようなことも可能です。

connect

connect は、相手に接続要求を送信するメソッドになります。

connect メソッド

TCP 通信においては、データの送受信を行う前に通信相手と接続(コネクション)を確立しておく必要があります。この接続を確立するために、相手に対して接続の要求を送信するメソッドが connect になります。

この connect を実行するのはクライアント側のプログラムとなります。そして、後述で説明する accept メソッドによってサーバーが接続受付を行うことで、クライアントとサーバー間で接続が確立されることになります。

そして、接続を確立した後は基本的には接続相手とデータの送受信を行うことになります。そのため、send の節で説明したように、接続を確立した後に send メソッドを実行すれば、メソッド実行時に宛先を指定しなくても接続相手に対してデータが送信されることになります。

connect後の各種ソケットの様子

また、上の図からも分かるように、サーバー側ではデータの送受信と connect の受信待ちとでは異なるソケットを利用して通信を行うことになります。この辺りは後述で解説していきます。

connect メソッドの使用例

下記に connect メソッドの使用例を示します。

connectメソッドの使用例
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

address = ('192.168.10.100', 40001)
sock.connect(address)

data = b'Hello World'

# connectした相手にdataを送信する
sock.send(data)

sock.close()

上記を実行すれば、まず IP アドレス 192.168.10.100 が割り当てられたネットワークインターフェースに接続要求が送信され、さらにその通信端末内でポート番号 40001 に対して接続要求が割り振られます。接続要求を受け取った相手によって接続が受け付けられれば接続が確立され、その相手に send メソッドによって data が送信されることになります。

connectメソッドの説明図

listen

先ほどの connect の説明の中で、connect はクライアントからサーバーに対して接続要求を送信するメソッドであることを説明しました。この接続要求を受け取って接続を確立するためには、サーバー側では2つのメソッドを実行する必要があります。

listen メソッド

その1つ目のメソッドが、ここで説明する listen になります。listen はソケットをリッスン状態に移行するためのメソッドとなります。リッスン状態とは、特定のポートを空けて相手からのデータの受信が可能となっている状態となります。TCP 通信の場合、サーバーは listen メソッドを実行してクライアントでの  connect 実行による接続要求を受信可能な状態にしておく必要があります。

サーバーが listen メソッドを実行していない場合、そのサーバーは接続要求の受信が不可の状態となるため、この状態でクライアントが connect で接続要求を送信するとクライアント側で ConnectionRefuesedError の例外が発生することになります。

なので、クライアントが connect を実行するのであれば、基本的にサーバーは connect が実行される前に listen メソッドを実行しておく必要があります。

connect実行前にlistenを実行しておく必要があることを説明する図

また、ソケットをリッスン状態にするためには、あらかじめ接続要求の受信を行うネットワークインターフェース・ポート番号をソケットに関連付けしておく必要があります。つまり、listen メソッドを実行するためには、事前に bind メソッドを実行してソケットに対してネットワークインターフェース・ポート番号の関連付けを行っておく必要があります。

接続要求のキュー

もう少し listen メソッドの詳細を説明しておくと、listen メソッドを実行することで接続要求を一時保存するためのキュー(バッファー)が用意されることになります。接続要求は複数のクライアントから送信されてくる可能性もあるため、複数のクライアントから同じタイミングで接続要求が送信されてくる可能性もあります。また、特定のクライアントと接続を確立してデータの送受信を行っている間に他のクライアントから接続要求が送信されてくる可能性もあります。そんな時でも、一旦接続要求を保存しておけるように保存先となるキューが必要となります。イメージとしては、このキューを用意するのが isten メソッドで、さらに、このキューから接続要求を取得するのが、次に説明する accept メソッドとなります。

接続要求がキューに保存される様子

そして、このキューのサイズは変更可能で、このサイズは listen メソッドの第1引数(引数 backlog)を指定することで変更することができます。指定しなければ、適当なデフォルトのサイズでキューが用意されることになります。

listen メソッドの使用例

listen メソッドの使用例に関しては、次の accept メソッドの使用例と一緒に紹介したいと思います。

スポンサーリンク

accept

先ほど紹介した listen メソッドの次に実行する必要のあるメソッドが accept となります。

accept メソッド

accept は、クライアントからの接続要求に対して接続の受付(接続の許可)を行い、さらに接続の確立を行うメソッドになります。accept メソッドは引数なしで実行可能です。

また、accept を実行する前には、listen メソッドを実行してソケットを接続要求を受信可能な状態にしておく必要があります。listen メソッドを実行せずに accept メソッドを実行すると OSError の例外が発生するので注意してください。

もう少し詳細を説明すると、listen で説明したように、listen メソッドを実行したソケットは接続要求が受け付け可能となり、送信されてきた接続要求はキューに保存されます。そして、accept メソッドが実行された際に、accept メソッドがキューから接続要求を取得し、その接続要求の受付&接続の確立を行います。キューが空の場合、すなわち接続要求が送信されてきていない時は、accept メソッドは接続要求がキューに保存されるまで待ちの状態となります。

acceptが接続要求を取得するまでの流れ

また、接続が確立できれば、accept メソッドは socket クラスの新たなインスタンスと接続の要求を送信してきた相手の情報の2つのオブジェクトを返却して終了します。後者は recvfrom で説明した2つ目の返却値と同様なので説明は省略します。

ポイントになるのが1つ目の返却値となる socket クラスの新たなインスタンスで、このインスタンスは接続を確立した状態のソケットとなります。そして、このソケットは accept メソッドを実行したインスタンスとは異なるものとなります。

accept メソッドを実行したインスタンスのソケットは accept メソッドを実行してもリッスン状態のままとなっています。なので、サーバーが accept メソッド実行後もクライアントは接続要求を送信することが可能です(接続要求を一時保存するキューが空いていれば)。逆に言えば、リッスン状態なので、このソケットを利用したデータの送受信は不可です。

それに対し、accept メソッドが接続確立後に返却する socket クラスのインスタンスは、それとは異なるソケットに対応するインスタンスであり、このソケットは接続確立状態(エスタブリッシュ)となっています。そして、この状態のソケットであれば、データの送受信を行うことが可能です。

なので、accept メソッドが接続確立をしてメソッドが完了した後は、accept メソッドが返却したインスタンスを利用してデータの送受信を行う必要があります。

acceptを実行するソケットとaccept後にデータの送受信を実行するソケットとが異なることを示す図

また、accept メソッドから返却されたインスタンスに関しても、不要になったら close する必要があります。

accept メソッドの使用例

下記に accept メソッドの使用例を示します。listen メソッドの使用例に関しても、下記の例で理解していただけるのではないかと思います。

accepttメソッドの使用例
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.bind(('0.0.0.0', 40001))

sock.listen(5)

e_sock, addr = sock.accept()
data = e_sock.recv(1024)
e_sock.close()

sock.close()

上記を実行すれば、bind メソッドと listen メソッドの実行により、ソケットは本スクリプトを実行した端末に存在する全ネットワークインターフェース&ポート番号 40001 に対する接続要求を受信可能となります。そして、accept メソッドの実行により、接続要求の受信待ち状態となります。さらに、クライアントから接続要求が送信されてきた際に、待ち状態だった accept メソッドが動作し、その接続要求の受付と接続の確立が行われます。そして、接続確立後のソケットが、1つ目の返却値として返却されることになります。

このソケットは接続確立状態となっていますので、後は、このソケット e_sock から recv メソッドや send メソッドを実行してデータの送受信を行うことになります。

listenメソッドとacceptメソッドの説明図

基本的なメソッドの紹介は以上となります。次は、ソケット通信プログラムの例を確認していきましょう!

ソケット通信プログラムの例

では、次はソケット通信プログラムの例を示していきたいと思います。

プロトコル の節で説明したように、ここまで紹介してきたメソッドを適切に実行することで TCP or UDP のプロトコルに従った通信が行われるようになります。ただし、アプリケーション層のプロトコルに関してはソケット通信のライブラリは関与しません。そのため、ソケット通信プログラムを開発していく際には、まずは従うべきアプリケーション層のプロトコルを決め、それに従うようデータの送受信を行うサーバープログラムとクライアントプログラムを開発していく必要があります。

今回は下記のプロトコルに従うプログラムを開発していきたいと思います。サーバー目線で記述したプロトコル、というかルールになります。これは私が独自に決めたものですが、これらを他のプロトコルに当てはめれば、そのプロトコルに従った通信プログラムを開発していけることになります。

  • サーバーは半角英数字の文字列をクライアントから受信する
  • サーバーは受信した文字列を大文字に変換した結果をクライアントに送信する

非常に簡単なプロトコルになりますが、おそらくこのページをここまで読んでくださっている方はソケット通信初心者の方が多いと思いますので、ソケット通信の最初の一歩となるプログラム開発には丁度良い難易度になると思います。

ここからは、このプロトコルに従ったサーバーとクライアントのスクリプトの例を示していきます。さらに、TCP と UDP とで実行するメソッド等が異なるため、TCP 通信を行うサーバーとクライアント、さらに UDP 通信を行うサーバーとクライアントの計4つのスクリプト例を示していきます。

さて、上記のプロトコルを読んで、どんなプログラムを開発すればよいかが大体イメージできたでしょうか?

データの送受信のみに焦点を当てれば、まずサーバーは最初にクライアントからのデータの受信待ちを行う必要があります。そして、サーバーはクライアントからデータを受信したら受信したデータを大文字に変換し、それをクライアントに送信することになります。これだけです。サーバーとクライアント で解説したように、サーバーが最初に行う処理は、ソケット利用のための準備や接続の確立等を除けば基本的には受信待ちになります。

それに対し、クライアントは最初にデータの送信を行う必要があります。そして、データ送信後にサーバーがデータを送信してくるため、それを受信するためにデータの受信待ちを行う必要があります。

また、クライアントからサーバーへ送信するデータおよびサーバーからクライアントへ送信するデータのフォーマットは文字列となります。もっと細かく言えば半角英数字の文字列ですね。ただし、ソケット通信で送信可能なデータはバイト型であるため、データを送信する際には文字列をバイト型に変換してから送信する必要がありますし、データを受信した際にはバイト型のデータを文字列に変換する必要があります。

さらに、IP アドレスとポート番号socket クラスのメソッド で説明したように、sendto メソッドによるデータの送信時や connect メソッドによる接続要求の送信時には、送信先となる IP アドレスとポート番号を引数で指定する必要があります。さらに、サーバー側が recv or recvfrom メソッドによってデータを受信したり、listen メソッドによって接続要求を受信可能したりする際には、bind メソッドによってソケットに対してネットワークインターフェースとポート番号を関連付け、どのネットワークインターフェース&ポート番号に対して受信待ちするのかを設定しておく必要があります。今回は、サーバーは全ネットワークインターフェースに対してポート番号 40001 で受信待ちを行うように設定したいと思います。また、今回はサーバーとクライアントが同じ PC 上で動作することを前提に開発を行っていきたいと思います。したがってクライアントは 127.0.0.1 のポート番号 40001 にデータを送信すればよいことになります。

さらにさらに、プロトコルsocket クラスのメソッド で説明したように、TCP と UDP によって実行するメソッドを使い分ける必要がありますし、ソケットの生成 で説明したように、そもそも通信を TCP or UDP に従って行うのかをソケット生成時に引数で指定する必要があります。

ということで、上記のような簡単なプロトコルで通信を行うだけでも、このページでここまで解説してきた内容全てを考慮してプログラムを開発していく必要があります。是非、各スクリプトで実行するメソッドの意味合いや、なぜそのメソッドの実行が必要なのかを考えながら紹介するスクリプトを読んでみていただければと思います。とはいえ、各プログラムのスクリプトはそこまで難しいものではなく、思ったよりもシンプルだと思います。なので、気軽にこの先も読み進めていただければと思います!

UDP 通信を行うプログラム

前置きが長くなりましたが、ここからは実際にスクリプトを作成する上でのポイントと、実際のスクリプトのソースコードを示していきたいと思います。

まずは UDP 通信を行うプログラムのスクリプトの作り方について説明していきます。

プロトコル で解説したように、UDP の場合は接続の確立が不要なので、処理の流れは TCP の場合に比べてシンプルになります。

まずサーバー側はクライアントからのデータの受信を行うため、ソケットの生成および bind メソッドの実行を行った後、recvfrom でデータの受信待ちを行います。ここまでが UDP 通信におけるサーバーの処理の基本パターンとなります。また、前述で示したプロトコルに従うため、データの受信後には文字列を大文字に変換した結果をクライアントに返却する必要があります。この返却には sendto メソッドを利用し、recvfrom の2つ目の返却値として得られたデータを第2引数に指定して sendto メソッドを実行してやればクライアントにデータを送り返すことができます。

 socket クラスのメソッド で説明したように UDP ではデータを送信するときに sendto メソッドを利用します。また、データを受信した後に、データの送信元に処理結果を応答や返却したい場合は、データ受信時に recvfrom を利用するのが便利です。

クライアント側に関しては、ソケット生成後に sendto メソッドでサーバーが受信待ちをしているネットワークインターフェース・ポート番号に対してデータを送信します。今回は、前述のとおり 127.0.0.1 のポート番号 40001 に対してデータを送信します。さらに、今回従うプロトコルでは、クライアントからデータを受け取ったサーバーがデータを返却してくるようになっているため、データの送信後には recv or recvfrom でデータの受信を行う必要があります。bind で説明したように、クライアント側のソケットに関しては、ネットワークインターフェース・ポート番号が自動的に関連付けされるため bind は不要になります。

ということで、UDP 通信を行うサーバー・クライアント間のメソッドの実行の流れは下図のようなシーケンスとなります。あとは、このシーケンスに合わせてサーバーとクライアントのスクリプトをコーディングしてやれば良いことになります。

UDP通信におけるサーバー・クライアント間の処理の実行シーケンス

UDP 通信のサーバー

続いて、具体的なスクリプト例を示していきます。UDP 通信のサーバープログラムのスクリプトの例は下記のようになります。

udp_server.py
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', 40001))

# 無限ループで常駐させる
while True:

    # データの受信待ち
    data, addr = sock.recvfrom(1024)

    # サービス・受信データに応じた処理
    str_data = data.decode()
    upper_str_data = str_data.upper()

    # 処理結果の送信
    send_data = upper_str_data.encode()
    sock.sendto(send_data, addr)

sock.close()

上記スクリプトのポイントを簡単に説明しておきます。

上記スクリプトでは、まず socket.socket によってソケットの生成を行なっています。第2引数(引数 type)に socket.SOCK_DGRAM を指定しているため、このソケットでは UDP で通信が行われることになります。

さらに、次に bind を実行して、このプログラムを実行する端末内に存在する全ネットワークインターフェース&ポート番号 40001 とを関連付けています。bind で説明したように、bind メソッドの引数に指定するタプルの第1要素を '0.0.0.0' とすることで、全ネットワークインターフェースとソケットを関連付けることができます。

あとは、クライアントからデータが送信されてくるのを recvfrom で待ち、データが受信できれば sendto で応答を返却するだけです。recvfrom の返却値の2つ目はデータの送信元のネットワークインターフェース・ポート番号を要素とするタプルとなるため、これを sendto の第2引数(引数 address)に指定してやれば、データの送信元のクライアントに応答をデータを送信することが可能です。

サーバーは、クライアントが好きなタイミングでサービスの利用を要求できるように、基本的に常駐して常にクライアントからのデータの受信待ちを行なっておく必要があります。そのため、上記のような無限ループの中で受信待ちしておく、というのがサーバーの基本的な動作パターンとなります。これは、後述で紹介する TCP の場合も同様です。

また、recvfromrecv で受信するデータはバイト型、さらに sendtosend で送信するデータもバイト型となり、受信したデータはそのままでは文字列として扱えませんし、文字列はそのままでは送信することができません。そのため、受信したバイト型のデータを文字列に変換するために decode を、文字列をバイト型に変換するために encode を実行するようにしています。これに関しては、説明は省略しますが、後述で紹介するスクリプトでも同様となります。

UDP 通信のクライアント

次に、クライアントプログラムのスクリプトを紹介します。

先ほど示したサーバープログラムの相手となるクライアントプログラムのスクリプトの例は下記のようになります。

udp_client.py
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

input_str_data = input('文字列を入力してください : ')

# データの送信
data = input_str_data.encode()
address = ('127.0.0.1', 40001)
sock.sendto(data, address)

# データの受信
data = sock.recv(1024)
str_data = data.decode()

print(str_data)

sock.close()

サーバーは基本的に常にデータの受信待ちを行っているのですから、クライアントは好きなタイミングでデータの送信を行ってサービスの提供を要求すればよいことになります。なので、サーバープログラムを起動した後であれば、基本的にいつでも sendto でデータを送信しても良いことになります。上記では、ユーザーが文字列の入力を完了したタイミングで sendto メソッドを実行するようにしています。

そして、UDP 通信のサーバー で示したスクリプトでは、サーバーはデータ受信後に応答となるデータをクライアントに返却してくるようになっているため、クライアントはデータ送信後にデータの受信待ちを行う必要があります。この受信待ちを、上記では recv メソッドで行っています。bind で説明したように、クライアント側のソケットは自動的な関連付けが行われるため、受信待ちを行う場合でも bind の実行は必須ではありません。

こんな感じで、特に UDP 通信の場合、クライアントは最初にデータを sendto メソッドで送信し、その結果を recvrecvfrom メソッド等で受け取るという流れが基本的な動作になると思います。

ただ、1つ注意点があって、それはクライアントからのデータの送信前にサーバー側に受信待ちを行わせておく必要があるという点になります。そのため、基本的にはサーバーを先に起動し、その後にクライアントからデータの送信を行うような流れで動作させる必要があります。そういった背景もあり、サーバープログラムに関しては、PC 等の通信端末が起動するタイミングで一緒に起動し、その後ずっと受信待ちを続けるような動作になっていることが多いです。

UDP 通信を行うプログラムの実行方法

UDP 通信のサーバーUDP 通信のクライアント とで示したスクリプトの実行方法を紹介しておきます。

まず、UDP 通信のサーバー で示したスクリプトを udp_server.py という名前、さらに UDP 通信のクライアント で示したスクリプトを udp_client.py という名前で適当な同じフォルダに保存してください。そして、ターミナルアプリ(Windows の場合はコマンドプロンプトや PowerShell 等)を2つ起動し、cd コマンドを実行して両方のターミナルでスクリプトを保存したフォルダーに移動してください。

続いて、一方側のターミナルで下記のように udp_server.py を python コマンドで実行してください。このターミナルでの操作は以上で終了です。これによりサーバーが立ち上がり、クライアントからのデータの送信を待ち続けるようになります。

python udp_server.py

次に、他方側のターミナルで下記のように udp_client.pypython コマンドで実行してください。

python udp_client.py

すると、文字列の入力が促されますので、適当に小文字で文字列を入力してエンターキーを押してください。

文字列を入力してください :

これにより、入力した文字列を大文字に変換した結果が表示されるはずです。

文字列を入力してください : hello world
HELLO WORLD

これは、サーバーがクライアントから受け取った文字列を大文字に変換した結果となります。この結果をクライアントが受け取り、それを表示しています。

簡単ですが、これによりサーバーとクライアント間でデータの送受信を行う様子が確認できたことになります。また、サーバーは起動したままになっていますので、再度 udp_client.py を実行して同様の操作を行えば、また文字列を大文字に変換した結果が表示されることを確認できるはずです。こんな感じで、サーバーは常に起動してクライアントからのデータの受信を待ち続けるようにしておくことが多いです。サーバーを終了させたい場合は、udp_server.py を実行した方のターミナルで control + c を入力してプログラムを強制終了させてください。

スポンサーリンク

TCP 通信を行うプログラム

次は、TCP 通信を行うプログラムを紹介していきます。

プロトコル の節で解説したように、TCP 通信ではデータの送受信を行う前に、通信を行う2つのソケット間で接続を確立しておく必要があります。この接続を確立する必要がある点が UDP 通信の場合との決定的な違いで、この違いがあるために若干 UDP 通信に比べて TCP 通信の方が処理が複雑になります。ですが、接続を確立してしまえば、データの送受信は接続を確立した相手と勝手に行われるようになるため、わざわざ送信時に IP アドレスやポート番号を指定する必要がなく、データの送受信自体の処理はシンプルになります。

また、TCP と UDP とでデータの送り方の違いがあるものの、アプリケーション層としては送受信するデータのフォーマットやデータの送受信の順序・タイミングは TCP と UDP とで同じとなります。したがって、ソケット通信プログラムとしては、接続を確立した後に送受信するデータのフォーマットや送受信の順序・タイミング等は UDP の場合と同じと考えて問題ありません。実際には、TCP と UDP とでは送受信されるデータのヘッダーなどが異なりますし、送受信の順序も異なるのですが、このあたりの違いは socket クラス側で吸収してくれます。なので、ソケット通信プログラム開発者は、このあたりを考慮して開発する必要はありません。

具体的なプログラムの処理の流れを解説すると、まずサーバーはクライアントからの接続要求を受信するために、ソケット生成および bind 実行後に listen メソッドと accept メソッドを実行する必要があります。クライアントから接続要求が送信されてくれば、accept メソッドにより接続が確立されますので、後はクライアントからのデータの受信待ちを行えば良いです。ここまでが TCP 通信を行う場合のサーバーの典型的な処理の流れとなります。その後は、アプリケーション層のプロトコルに合わせてデータの送受信や提供するサービスに応じた処理を行うことになります。この時に利用するソケットは接続確立済みのもの、すなわち accept メソッドが返却したソケットである必要があります。

TCPのサーバーが2つのソケットを利用することを説明する図

それに対し、クライアントはサーバーとデータの送受信を行うために、まずは connect メソッドを実行して接続要求をサーバーに対して送信します。これにより、サーバーが接続要求の受信待ち状態、すなわち accept 実行中であれば、サーバーによって接続要求が受け入れられて接続の確立が行われます。接続が確立されれば、クライアントはサーバーに対してデータの送信を行います。今回従うプロトコルにおいては、サーバーがクライアントに応答を返却するようになっているため、クライアントはその応答を受信するために受信待ちを行います。

TCP 通信の場合は、接続が確立されたソケット、すなわちサーバー側における accept メソッドから返却されるソケット、および、クライアント側における connect メソッドを実行したソケットでデータの送受信を行えば、自動的に接続を確立しているソケットの間でデータの送受信が行われることになるため、データの送信時には sendto で IP アドレスやポート番号を指定する必要はありません。その代わりにデータの送信時には send を利用することになります。

したがって、TCP 通信におけるクライアントとサーバーのメソッド実行の流れは下記のようなシーケンスとなります。あとは、このシーケンスに合わせてサーバーとクライアントのスクリプトをコーディングしてやれば良いことになります。

TCP通信におけるサーバー・クライアント間の処理の実行シーケンス

TCP 通信のサーバー

続いて、具体的なスクリプト例を示していきます。TCP 通信のサーバープログラムのスクリプトの例は下記のようになります。

tcp_server.py
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.bind(('0.0.0.0', 40001))

sock.listen(5)

# 無限ループで常駐させる
while True:

    # 接続要求の受付 / 接続の確立
    e_sock, addr = sock.accept()

    # データの受信待ち
    data = e_sock.recv(1024)
    
    # サービス・受信データに応じた処理
    str_data = data.decode()
    upper_str_data = str_data.upper()

    # 処理結果の送信
    send_data = upper_str_data.encode()
    e_sock.send(send_data)
   
    e_sock.close()

sock.close()

UDP 通信のサーバー で示したスクリプトとの差を比較していただければ、TCP 通信と UDP 通信とでサーバー側のスクリプトがどのように異なるのかを理解していただけるかと思います。

まず、TCP 通信を行うのですから、ソケット生成時に実行する socket.socket の第2引数には socket.SOCK_STREAM を指定する必要があります。bind に関しては UDP 通信の時と同様に実行してやれば良いのですが、その次に接続の確立を行うために listenaccept を実行する必要があります。accept を実行すれば、クライアントから接続要求が送信してくるまで accept は終了せずに待機することになります。なので、上記のように while ループの中で accept を実行すれば、基本的にサーバーは常駐してクライアントからの接続要求待ち続ける動作を実現することができます。

クライアントから接続要求を受け取れば、accept は終了して新たなソケットを返却します。このソケットがクライアントと接続確立済みのものとなりますので、あとはこのソケットを利用してデータの送受信を行えば良いだけです。ただし、データの送信先は接続確立済みのソケットとなりますので、sendto ではなく send メソッドで行う必要があります。

クライアントとの一連のデータの送受信が完了すれば、その接続確立済みのソケットは不要になるため close メソッドでソケットを閉じ、ループの最初に戻って accept を実行することになります。これにより、クライアントからの新たな接続要求を受け取ることができるようになります。

TCP 通信のクライアント

また、先ほど示したサーバープログラムの相手となるクライアントプログラムの例は下記のようになります。

tcp_client.py
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

input_str_data = input('文字列を入力してください : ')


# 接続要求の送信
address = ('127.0.0.1', 40001)
sock.connect(address)

# データの送信
data = input_str_data.encode()
sock.send(data)

# データの受信
data = sock.recv(1024)
str_data = data.decode()

print(str_data)

sock.close()

こちらも、UDP 通信のクライアント で示したスクリプトとの差を比較していただくと、TCP 通信と UDP 通信とのクライアントで実行すべきメソッドが理解しやすいと思います。

やはり UDP 通信のクライアントと比べて一番大きな差があるのは connect メソッドを実行する必要がある点になると思います。この connect メソッドの実行により、引数で指定した IP アドレス・ポート番号に接続要求が送信されることになります。この引数には、接続要求を受け付け可能、すなわち listen メソッドを実行したソケットに関連付けられている IP アドレスとポート番号を指定する必要があります。

さらにサーバー側の accept メソッドにより接続要求が受け付けられれば、connect を実行したソケットが接続確立済みの状態となります。あとは、このソケットを使ってデータの送受信を行えば良いだけです。このソケットで send メソッドを実行すれば、接続確立済みの相手側のソケットに対してデータが送信されることになるため、UDP 通信の時のように sendto メソッドを使ってわざわざ送信先を引数で指定することは不要となります。

TCP 通信を行うプログラムの実行方法

スクリプトの実行の仕方に関しては UDP 通信を行うプログラムの実行方法 で説明した実行の仕方と同様なので説明は省略させていただきます。また、得られる結果も TCP と UDP とで同じになるはずです。このように、スクリプトの作り方は TCP / UDP で異なるものの、どちらでも同様のシステムが作成可能であることが確認できると思います。ただし、プロトコル で説明したようにデータ送受信の仕方が TCP と UDP とで異なるため、実際には通信の信頼性や通信速度が異なることになります。

その他の通信ライブラリ・通信モジュールとの関係性

ここまでの説明を理解していただければ、ソケットを利用した TCP 通信 or  UDP 通信を行うサーバー・クライアントを開発し、お互いに通信し合うシステムやプログラムを開発できるようになると思います。

ソケット通信を利用することができるようになれば、開発可能なプログラムの幅が一気に広がるので、是非ソケット通信を利用した様々なプログラムの開発に挑戦してみてください。

ただし、全ての通信プログラムを socket モジュールを使って実現する必要はありません。他にも通信に利用できるライブラリやモジュールが存在します。このページの最後に、これらのライブラリ・モジュールと、ソケット通信との関係性について解説しておきます。

ここまで、ソケット通信のライブラリ・モジュールではトランスポート層以下の通信・制御を行ってくれることを説明しました。ですが、アプリケーション層のプロトコルに従った処理に関しては開発者自身が実装する必要があります。

なんですが、実は Python にはアプリケーション層のプロトコルに関する通信・制御を行ってくれるライブラリ・モジュールも数多く存在しています。

HTTP であれば、requests モジュール等がそれにあたります。この requests は  HTTP (のクライアント) に特化したモジュールであり、例えば特定のメソッドに送信したいデータの本文を指定さえしてやれば、それがメソッド内で HTTP に従ったフォーマットのデータに変換され、そのデータが通信相手に送信されるようになっています。そして、そのメソッドの返却値として、通信相手から受信したデータが得られるようになっています。

requestsモジュールの説明図

つまり、socket モジュールではトランスポート層以下のプロトコルに従った制御・通信しか行われませんが、アプリケーション層のプロトコルまで含めた制御・通信を行ってくれるモジュールが存在します。この場合は、メソッドの実行等を行うだけで、アプリケーション層のプロトコルに従ったフォーマットのデータの送信及びプロトコルに従った順序・タイミングでのデータの送受信まで行われることになります。おそらく、メソッド内部でソケット通信が行われているのだとは思うのですが、これらを利用することで開発者はソケット通信を意識することなく通信を行うことが可能となります。

なので、アプリケーション層のプロトコル用のモジュールが存在するのであれば、実はわざわざ socket モジュールを利用してソケット通信を行わなくても通信を行うことは可能です。なんですが、例えば requests モジュールが HTTP のみに対応しているように、これらは特定のプロトコルにのみ対応したモジュールとなりますので、そのプロトコルに対応したモジュールが存在しなければ socket モジュールを利用し、アプリケーション層のプロトコル部分に関しては開発者が実装しなければならないことになります。また、独自のプロトコルを採用するような場合も、socket モジュールを利用し、その独自のプロトコルに従うように開発者が実装する必要があります。

socket モジュールはアプリケーション層のプロトコルに関与しないため、逆に言えば、開発者が頑張って開発を行えば、どんなアプリケーション層のプロトコルにも対応可能です。非常に汎用性の高いモジュールであると言えると思います。それに対し、特定のアプリケーション層のプロトコルに対応したモジュールに関しては、そのプロトコルにしか対応はできませんが、より簡単な実装や使い方でそのプロトコルを実現することができます。

socketとその他の通信モジュールとの違いをまとめた図

こういった違いも理解しながら、使用するモジュールの選択も行っていただければと思います!

また、たくさんの便利な通信モジュールが存在するので、もしかしたらソケット通信を利用する機会は少ないかもしれませんが、少なくとも通信プログラムを開発するのであればソケット通信について学んでおくことは無駄ではないと思います。是非、このページで解説した内容に関しては頭の片隅にでも置いておいていただければと思います。

まとめ

このページでは、Python でのソケット通信の使い方、および、ソケット通信プログラムを開発する上で身につけておきたい知識について解説しました!

ソケット通信プログラムを開発する上では、IP アドレスとポート番号・プロトコル・サーバーとクライアントに関しては理解しておいた方が良いと思いますし、これらを理解しておくことで、ソケット通信プログラムを開発していく中で、実行が必要になる処理やメソッドも自然と頭の中で思いつくようになると思います。

プロトコルに関しては難しそうな言葉なので苦手意識を持っている方も多いと思いますが、特にトランスポート層のプロトコルに関しては、まずは TCP と UDP の特徴および、TCP と UDP とで利用するメソッドの違いに関して最低限理解しておけば良いと思います。

独立して単独で動作するプログラムに関してはメソッドやクラスを使いこなすだけでやりたいことが実現できますが、通信プログラムに関しては通信相手と息を合わせて処理が行われるように開発する必要があり、この点が通信プログラム開発での一番難しいところになるのではないかと思います。クライアントサーバーモデルを採用したり、アプリケーション層のプロトコルを理解することで、この難しい点も解消できると思いますので、この辺りもしっかり理解しておくと良いと思います!

ぜひ、このページで学んだことを活かし、様々な通信プログラムの開発に挑戦してみてください!

同じカテゴリのページ一覧を表示