超初心者がソケット通信を理解する
1. はじめに
最近は何をするにも便利なフレームワークやライブラリがあり、
本質をあまり理解しなくてもそれなりのものは作れてしまう時代です。
私はまだ未成年なのですが、初めてプログラミングを触った時にはすでに便利なライブラリに溢れ、それさえ覚えれば何でもできる時代になっていました。
その一方でエンジニア記事では便利なライブラリの解説ばかりで本質を説明していないものが多いです。
今回は初投稿なのでネットワークの基礎を理解してまとめてみようと思います。
まだまだ初心者なので違うところがあったらコメントください
2. ソケット通信について
OSI参照モデルをみてみましょう
階層 | 名称 | 役割 |
---|---|---|
7 | アプリケーション層 | ユーザーと直接やり取りする層。メール、Web、ファイル転送などのアプリケーションを実行する。 |
6 | プレゼンテーション層 | データの表現形式を変換する層。異なるコンピュータ間でデータのやり取りを可能にする。 |
5 | セッション層 | アプリケーション間の通信を管理する層。セッションの開始、終了、切断などを制御する。 |
4 | トランスポート層 | アプリケーション層とネットワーク層の間の層。データの転送を保証する。 |
3 | ネットワーク層 | データのルーティングを行う層。ネットワーク上の宛先にデータを送信する。 |
2 | データリンク層 | データのフレーム化を行う層。パケットの送受信を制御する。 |
1 | 物理層 | データのビット列で表現する層。ネットワーク上の機器間の接続を制御する。 |
階層で言うと7番目、つまり一番上にくるのがアプリケーション層です。
ソケット通信はこのアプリケーション層の通信を補います。
アプリケーション層の役割はユーザーと直接やりとりをすることなのでソケット通信でユーザーと直接やりとりをすることができます。
3. ソケット通信のフロー概要
サーバのフロー
- socket()はソケットを作成します。
- bind()はソケットをローカルのアドレス(ソケットファイルやIP+ポート)にバインドします。
- listen()はソケットに接続を待ち受けるように命令します。
- accept()は外部からの接続に対して新しいソケットを作成します。
- send()/receive()はデータの送受信を行います。
- close()ではソケットをクローズします。
クライアントのフロー
- socket()はソケットを作成します。
- connect()はリモートのソケットに接続します。
- send()/receive()はデータの送受信を行います。
- close()ではソケットをクローズします。
4. node.jsでの実装
TCP/IP通信を行うにはnet
を使います。
TCP/IPがトランスポート層なので、net
はアプリケーション層のプロトコルを作る?と言ってよろしいのでしょうか?(net
はアプリケーション層のプロトコルを作ると言えるのかわからないのでコメントで指摘お願いします🙏)
npmでnetをインストールします
npm install net
データを受信する側
const net = require("net");
const serverOptions = {
lookup: true,
};
const server = net.createServer(serverOptions, (socket) => {
// 接続されたら
console.log("新しい接続が到着しました: " + socket.remoteAddress);
// メッセージを送信
socket.write("hello, world!");
// メッセージを受信
socket.on("data", (data) => {
console.log("受信したメッセージ: " + data);
// メッセージをオウム返し
socket.write(data);
});
});
server.listen(8080);
サーバーとクライアントの両方で、net.SocketOptionsオブジェクトのlookup()プロパティをtrueに設定する設定により、サーバーはクライアントのIPアドレスを取得するために、DNSルックアップを行うようになります。
データを送信する側
const net = require("net");
// ローカルのIPアドレスとポート番号を取得
const localIP = require("os").networkInterfaces().find(
(interface) => interface.family === "IPv4"
).address;
const localPort = 1234;
// ルーターのグローバルIPアドレスとポート番号を取得
const routerIP = getRouterIP();
const routerPort = 8080;
// ローカルでTCPサーバーを開く
const server = net.createServer((socket) => {
// 接続されたクライアントからのメッセージを受信
socket.on("data", (data) => {
console.log(data);
// クライアントにメッセージを送信
socket.write("Hello, from the other side!");
});
});
server.listen(localPort, localIP);
// ルーターのグローバルIPアドレスとポート番号を使って、ワイドネットのサーバーに接続
const client = net.connect(routerPort, routerIP);
// ワイドネットのサーバーにメッセージを送信
client.write("Hello, from the local network!");
// ワイドネットのサーバからのメッセージを受信
client.on("data", (data) => {
console.log(data);
});
function getRouterIP() {
// SSDPでルータ機器を見つける
const ssdp = new SSDP();
const devices = ssdp.discover();
// 見つかったルータ機器の中から、グローバルIPアドレスを持つものを探す
for (const device of devices) {
if (device.location.match(/:80/)) {
return device.location.split(":")[0];
}
}
// ルータ機器が見つからなかった場合
return null;
}
getRouterIP()
この関数はローカルのIPアドレスとポート番号を取得します。次に、ルーターのグローバルIPアドレスとポート番号を取得します。
net.connect(routerPort, routerIP);
この関数はルーターのグローバルIPアドレスとポート番号を取得したら、ローカルでTCPサーバーをオープンします。サーバーがオープンしたら、クライアントからの接続を待ち受けます。
client.write("Hello, from the local network!");
サーバーに接続してメッセージを送信します。
4. 終わりに
OSI参照モデルの7層のアプリケーション層について理解しました。
ついでに6層のTCP/IPにも片足を突っ込みました。
全ての層を理解する必要はないかもしれませんが、低いレイヤーの知識も身につけるとよりWebについて理解できそうです。
初心者なので間違いなどがあったらぜひご指摘ください!
終わり
参考:https://qiita.com/t_katsumura/items/a83431671a41d9b6358f
:https://ansl-blog.hatenablog.com/entry/2019/05/22/OSI参照モデルとTCP/IPについて
:https://nodejs.org/docs/latest/api/net.html
Discussion