こちらの記事では、ソケット通信についての仕組みを図解でわかりやすく解説します。
ソケットとは
まずソケット(socket)とは、受け口、軸受け、電球受けという意味を持ちますが、ざっくり言えば「電球に電気を通すための境界」と言えます。
ソフトウェアの領域においては、アプリケーション同士がデータを通信するためにソケットにデータを流し込むという仕組みがあります。
たとえば、このようにして Web ページで記事を見ている今でも、その裏側でソケット通信が行われています。
ソケット通信とは
ソケット通信を理解するにあたって、必要となる前提知識があるので、次の項目をかならず押さえておきましょう。
- TCP/IP モデルポート番号 IP アドレス
- クライアントサーバモデルクライアントサーバ
- API インターフェース
これらの前提知識をもとにして「ソケット・ソケット通信とは」というまとめを行います。
TCP/IP モデルについて
TCP/IP モデルとは、インターネットにおける、標準的な プロトコルスタックです。プロトコルスタックとは、コンピュータが通信するために必要なプロトコルの階層のことです。
プロトコルとは、データを通信をするうえでの約束事・規約のことです。TCP/IPモデルを理解する上では、OSI参照モデルについても理解しておいた方が良いでしょう。OSI参照モデルについては、別の記事にて解説しています。
https://envader.plus/article/38
コンピュータは、データ通信を実現するにあたり、厳格にデータを取り扱う必要があります。たとえば、今回関係する部分で具体例を挙げます。
- ポート番号は 16bit
- IP アドレス(IPv4)は 32bit
このような事細かな取り決めが、IETFという組織が策定するRFCという文書によって、何千もの仕様として定められています。もう少し具体的な実例で、TCP/IP モデルについて考えてみましょう。
たとえば、パソコンで Web サーフィンをするとき、Chrome などの Web ブラウザで Youtube を見たり、Twitter を眺めたりします。このとき、上位からHTTP(HTTPS)、TCP、IP、Ethernetといった階層化されたプロトコルを利用しており、次の図のような形でまとめることができます。
TCP/IP モデルは、図の通り 4 階層で構成されていて、上位から次のようにレイヤが分かれます。
階層 | TCP/IP プロトコルスタック | プロトコル |
---|---|---|
4 | アプリケーション層 | HTTP |
3 | トランスポート層 | TCP |
2 | インターネット層 | IP |
1 | ネットワークインターフェース層 | Ethernet |
その他にも、envader.plus のようなドメイン名から、34.111.64.3 という IP アドレスを求めるためには、次のようなプロトコルを利用します。
階層 | TCP/IP プロトコルスタック | プロトコル |
---|---|---|
4 | アプリケーション層 | DNS |
3 | トランスポート層 | UDP |
2 | インターネット層 | IP |
1 | ネットワークインターフェース層 | Ethernet |
このようにして、日常生活においてコンピュータで何らかのソフトウェアを利用してデータをやり取りするとき、必ず TCP/IP モデルを利用していると言えます。なかでも特に注目したいのは、TCP/IP モデルのなかでも、トランスポート層、インターネット層の 2 つです。それぞれの階層で指定することがあります。
プロトコルの階層 | 必要となる情報 |
---|---|
トランスポート層 | OS 上で動いているプログラムにデータを届けるためにポート番号を指定します |
インターネット層 | ネットワーク上のコンピュータを識別するために IP アドレスを指定します |
この記事で、最終的に Python でサンプルプログラムを作るにあたって、特に重要なポート番号とIP アドレスについて説明します。ポート番号とは、コンピュータ通信においてOS 上で動作しているプログラムを識別するための番号です。この OS 上のプログラムをプロセスといいます。プロセスとはコンピュータのメモリ上にあるプログラムの実体のことです。実際にコンピュータ上で動いているプロセスを確認してみましょう
Windows を使っている方は、コマンドプロンプトを起動して、次のコマンドを打ちこんでみてください。
> tasklist
Linux を使っている方は、ターミナルを起動して、次のコマンドを打ちこんでみてください。
ps -aux
実行結果がずらっと並びますが、それがコマンド実行時に、OS が実行しているプロセスの一覧です。
PIDという項目がありますが、それはプロセス ID を意味しており、このPIDによって OS がプロセスを識別しています。
ご覧の通り、プロセスとはコンピュータ上では数えきれないほどたくさん動いています。
そのため、プログラム同士が通信するためには、「どのプロセスにどのデータを送ればいいか」をポート番号で明示する必要があります。
では、実際にコンピュータのプログラムが通信していることも確認しておきましょう。
Windows を使っている方は、コマンドプロンプトを起動して、次のコマンドを打ちこんでみてください。
netstat -a
Linux を使っている方は、ターミナルを起動して、次のコマンドを打ちこんでみてください。
sudo apt update
# ユーザのパスワードを入力してください
sudo apt install net-tools
netstat -a
今回このコマンドの実行結果において、重要な点はローカルアドレス(Local Address)と外部アドレス(Foreign Address)の 2 点です。
IPアドレスについては、別の記事で解説しています。
https://envader.plus/article/51
それぞれ送信元アドレスと宛先アドレスという意味で、IP アドレス:ポート番号という対応で書かれています。次の例では、ローカルアドレスは 192.168.0.1:32541、外部アドレスは 34.111.64.3:80 としています。
IP アドレスでコンピュータを識別できますが、それよりも内側の、コンピュータのどのプログラムのデータかまでは分かりません。そのため、プロセスにデータを届けるためには、ポート番号をもとにデータの宛先を識別することになります。
ちなみに、ゲートウェイとは一般的には、ルータを指します。
ルータではLAN(Local Area Network)で使用するプライベート IP アドレスと、インターネットで使用するグローバル IP アドレスが、PAT(Port Address Translation)によってポート番号とともに対応付けて変換されています。
したがって、サーバ側のゲートウェイのインターフェースでは 34.111.64.3:80 を受け取っていますが、サーバに送るために 10.10.10.67:60001 に置き換えられます。
LAN(Local Area Network)については、以下のリンクよりご覧ください。
https://envader.plus/article/49
クライアントサーバモデルについて
クライアントサーバモデルとは、ソフトウェアアーキテクチャのひとつです。
ここでいうクライアントとは、ソフトウェアを利用するコンピュータやソフトウェアを指します。
たとえば、HTTP クライアントというとき、それは Web ブラウザや、Web ブラウザを起動している PC・スマホなどの端末を指します。
そしてサーバとは、ソフトウェアを提供するコンピュータやソフトウェアを指します。たとえば、Web サーバというとき、それは nginx や Apache HTTP Server、またはそれらを搭載したコンピュータなどを指します。
上の図のように、クライアントとサーバが通信を行う流れは次のようになります。
- クライアントがサーバにリクエストを送る
- サーバが受け取ったリクエストをもとにレスポンスを返す
- クライアントがサーバからのレスポンスを受け取る
つまり、上記の例になぞらえると HTTP クライアントが Web サーバにリクエストを送り、Web サーバが HTTP クライアントにレスポンスを返すということです。これは先ほどの TCP/IP モデルで話した内容を織り交ぜると、次のようにまとめられます。
階層 | TCP/IP モデル | クライアント | サーバ |
---|---|---|---|
4 | アプリケーション層 | HTTP | HTTP |
3 | トランスポート層 | TCP | TCP |
2 | インターネット層 | IP | IP |
1 | ネットワークインターフェース層 | Ethernet | Ethernet |
上の図のように、HTTP クライアントと Web サーバの通信では、データは次の経路をたどります。
HTTP クライアントがリクエストを送るとき、それぞれの階層でヘッダを付加します。これをカプセル化といいます。
順番 | TCP/IP モデル | プロトコル例 |
---|---|---|
1 | アプリケーション層 | HTTP |
2 | トランスポート層 | TCP |
3 | インターネット層 | IP |
4 | ネットワークインターフェース層 | Ethernet |
Web サーバがリクエストを受けるとき、それぞれの階層でヘッダを取り外します。これを非カプセル化といいます。
順番 | TCP/IP モデル | プロトコル例 |
---|---|---|
1 | ネットワークインターフェース層 | Ethernet |
2 | インターネット層 | IP |
3 | トランスポート層 | TCP |
4 | アプリケーション層 | HTTP |
API について
APIとは、アプリケーション・プログラミング・インターフェース(Application Programming Interface)の略です。インターフェースとは、異なる 2 つのものをつなぐときの境界面、もしくは接点という意味です。抽象的に言えば、ライブラリやフレームワークの関数もインターフェースに含まれます。たとえば、次の Python プログラムを見てみましょう。
print("Hello world!")
ここでやっているのは「print 関数というインターフェースを、”Hello world!”という文字列を渡して呼び出すこと」です。
そうすれば、あとは print 関数の処理を委ねるだけです。ちなみに、print 関数は、引数に受け取った文字列を、標準出力に表示します。もし print 関数を利用しないでこの機能を実現する場合は、一から自分でそのプログラムを書かなければいけません。それはとても非効率的なので、実現できる機能があれば、必ずライブラリや組み込み関数などのインターフェースを利用します。
つまり、プログラミングでは、すでに API として使える処理は API に利用することで簡単に実装できます。そして、多くの場合、既存の API を自分で書きなおすよりも、API を利用する方がバグは少ないので、わざわざ実務において再実装をすることはありません。
「使用するメモリをもっと減らしたい」とか「実行時間をさらに短くしたい」など、既存の API よりも実行効率を良くしたいときに、はじめて API を再実装することになります。
ソケット・ソケット通信とは
では、「ソケットとは何か」というと、クライアントサーバモデルにおける通信のインターフェースです。そして、「ソケット通信」とは、このソケットを用いた通信のことです。TCP/IP モデルでいうと、トランスポート層であり、イメージとしては、次のようになります。
この記事のサンプルプログラムでは、実際に Python のソケット API を扱います。
プログラムからソケットにデータを流し込めば、自動的に通信相手のプログラムにデータが送られるという仕組みになります。すなわち、ソケット API を利用することで、1 からネットワークの通信部分のプログラムを書かなくていい、ということです。
ソケット通信の仕組みについて
クライアントサーバモデルでソケット通信を実現するときには、クライアントとサーバの両方にソケットを用意します。それぞれサーバとクライアントで、ソケットを利用する過程を頭に入れておきましょう。TCP 通信を想定しています。
ソケットの作成
サーバ、クライアントでそれぞれ、socket.socket()
を実行しソケットオブジェクトを作成します。
作成したソケットと IP・ポート番号の紐づけ
サーバでsocket.bind()
を実行し、サーバの IP アドレスと、利用するポート番号を、ソケットに結び付けます。
ソケット間でのデータの送受信
サーバでsocket.listen()
、socket.accept()
、socket.send()
を実行し、
- サーバで
socket.listen()
を実行し、リクエスト待ちの状態にする。 - クライアントで
socket.connect()
を実行し、サーバにリクエストを送る。 - サーバで
socket.accept()
を実行し、クライアントのリクエストを受信する。 - サーバで
socket.send()
を実行し、クライアントにレスポンスを送る。 - クライアントで
socket.recv()
を実行し、レスポンス受信する。
ソケットの削除
サーバ、クライアントでそれぞれ、socket.close()
を実行しソケットオブジェクトを作成します。
Python でサンプルプログラミングを書いてみましょう
実際に Python プログラムで、TCP と UDP のソケット通信を実装していきます。今回は、WSL2(Ubuntu22.04)の環境を前提とします。
Pythonの環境構築については、別の記事にて解説しています。
https://envader.plus/course/8/scenario/1071
クライアントサーバモデルを利用するにあたって、2 台のコンピュータが必要かと思われますが、じつはそれぞれ同じコンピュータ上にサーバ・クライアントのプログラムを実装することができます。
これはクライアントとサーバで、ポート番号とプロセスが異なるからこそ実現できるということを、改めて理解しておきましょう。
では、サーバ・クライアントのそれぞれのファイルを用意して、コードを編集・保存後にコマンドを実行して、サーバ、クライアントの順にプログラムを起動します。サーバでは、5 回リクエストを受け付けた場合、自動的にソケットを削除するようにプログラムしています。
クライアントのプログラムを実行することで、サーバ・クライアントはそれぞれリクエスト・レスポンスを送受信します。
Python で TCP 通信を実装する
server_tcp.py
import socket
# socket.socket(): ソケットオブジェクトの作成
# AF_INET: アドレスファミリ(使うアドレスの種類がまとめられたもの)のうち、IPv4を指定する
# SOCK_STREAM: TCPプロトコルを指定
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# socket.bind(): IPアドレスとポート番号を作成したソケットオブジェクトに紐づける
# 127.0.0.1: ローカルホストを指定する
# 60001: ポート番号のうち60001を指定する(待ち受けポート)
s.bind(('127.0.0.1', 60001))
# socket.listen(): クライアントからの入力待ち状態になる
# 1: 並列的に処理できるリクエスト数を1つに指定する
s.listen(1)
count = 0
while count < 5:
# socket.accept(): クライアントからの接続を受け付ける
# conn: 新しく作成したソケットオブジェクト
# addr: 受信したIPアドレス
conn, addr = s.accept()
# サーバの標準出力に文字列を出す
# addr: (クライアントのIPアドレス, クライアントのポート番号)
print(f'Source IP Address: {addr}')
# クライアントのソケットにデータを送信する
conn.send(b'Hello World!')
# クライアントのソケットオブジェクトを削除する
conn.close()
count += 1
# サーバのソケットを削除する
s.close()
WSL2
python3 server_tcp.py
client_tcp.py
import socket
# ホストのソケットオブジェクトの作成
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# サーバにリクエストを送る
# 127.0.0.1: サーバのIPアドレス
# 60001: サーバが待ち受けするポートを指定する
s.connect(('127.0.0.1', 60001))
# サーバからデータを受信する
data = s.recv(4096)
# クライアントの標準出力に受信したデータを表示する
print(data)
# クライアントのソケットを削除する
s.close()
WSL2
python3 client_tcp.py
TCP はコネクション指向のため、かりにサーバがソケットを削除したあとに、クライアントがリクエストすると、エラーが起きます。
Python で UDP 通信を実装する
server_udp.py
import socket
# SOCK_DGRAM: UDPプロトコルを指定する
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', 60002))
count = 0
while count < 5:
# クライアントからデータを受信する
data, addr = s.recvfrom(4096)
# クライアントから受信したデータと、クライアントのIPアドレス、ポート番号をそれぞれ表示する
print(data, addr)
count += 1
s.close()
WSL2
python3 server_udp.py
client_udp.py
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# サーバを指定して、"Hello Server!"という文字列を送る
s.sendto(b'Hello Server!', ('127.0.0.1', 60002))
WSL2
python3 client_udp.py
UDP はデータグラム指向のため、かりにサーバがソケットを削除したあとに、クライアントがリクエストしても、エラーは起きません。
【補足】TCP と UDP の違いについて
TCP と UDP の大きな違いには、信頼性・早さの 2 つがあり、それぞれ次のような特徴になります。
プロトコル | 信頼性 | 早さ | 特徴 | 利用例 |
---|---|---|---|---|
TCP | 〇 | △ | 送信路の確立、データの再送制御をする。信頼性が高く、完全なデータが求められるアプリケーションで利用する。 | HTTP、SMTP、FTP、SSH |
UDP | △ | 〇 | 送信路の確立、データの再送制御をしない。データ量の少なく、即時性が求められるアプリケーションで利用する。 | DNS、NTP、TFTP、SNMP |
まとめ
ソケット通信について学ぶなかで、ネットワーク、サーバ・OS、プログラミングというそれぞれの領域を横断しました。これらのつながりを一気貫通して理解することで、プログラミングだけでなくインフラについて学ぶ必要性を知ることができます。
この記事でソケット通信について解説した内容をまとめます。
- ソケットは TCP/IP モデルにおけるトランスポート層である
- ソケット通信の流れは、ソケットの作成、作成したソケットと IP・ポート番号の紐づけ、ソケット間でのデータの送受信、ソケットの削除となる
- ソケット通信は、プログラムから API を呼び出して実装する
【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。
「フリーランスエンジニア」
近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。
「成功する人とそうでない人の違いは何か?」
私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。
比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。
多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、
エンベーダー編集部
エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。
関連記事
2020.02.25
完全未経験からエンジニアを目指す爆速勉強法
USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
- キャリア・学習法
- エンジニア
2023.08.19
ノーコード・ローコード開発は簡単?稼げる?
そこで今回は、エンジニア初学者の方に向けて、「ノーコード・ローコードは稼げるのか」について解説していきます。
- プログラミング
2023.07.03
プログラミングスクールで学べることとは? 料金から学習内容の違いまで調査
今回はこれからエンジニアを目指す方向けにプログラミング技術を学べるスクールに関して、料金や学習出来る内容の違いなどの調査を行ないました。
- プログラミング
2022.12.14
CI(Continuous Integration)継続的インテグレーションを導入するメリット
Integrationは統合という意味で、継続的に統合・結合を行うことをCIと呼びます。
- プログラミング