PHP でランダムな空きポート番号(未使用のポート番号)を取得するスクリプトです。PHP のビルトイン・ウェブサーバのポート指定時の空きポート探しなどにご利用ください。
なお「タイプ2」のスクリプトは PurePHP なので Windowsでも動作すると思います。
また、Mac 用ですが $ dev chrome index.php
とすると、ランダムな空きポートでビルトイン Web サーバーが立ち上がり、指定したブラウザで動作検証できるスクリプトを用意しました。
- OS: macOS HighSierra(OSX 10.13.5, 10.13.6)
- PHP: v7.2.6(CLI)
- 関連記事「macOS の bash で空きポートを取得する(ランダムな未使用ポート検索)」 @ Qiita
TL;DR(PHP で使用中のポートを元に空きポートを探す)
タイプ1(外部コマンド利用タイプ)
シェルの lsof
コマンドで使用中のポートを取得し、そこからランダムな未使用ポートを返します。
netstat
コマンドでもよかったのですが、他のプロセスで使っていたビルトインサーバのポートなど、表示されないポートがあったので lsof
にしました。
function getPortRandom($port_min = 50000, $port_max = 65535)
{
$cmd = "lsof -i -P | grep -i 'tcp' | sed 's/\[.*\]/IP/' \
| sed 's/:/ /' | sed 's/->/ /'| awk -F' ' '{print $10}' \
| awk '!a[$0]++'";
$ports = array_filter(explode(PHP_EOL, `$cmd`));
while (true) {
$port = rand($port_min, $port_max);
if (false === array_search($port, $ports)) {
break;
}
}
return $port;
}
2018/08/23 追記:`$cmd` 行をより正確に表示するように変更しました。
lsof -i -P | grep -i 'listen' 2>&1
lsof -i -P | grep -i 'tcp' | sed 's/\[.*\]/IP/' \
| sed 's/:/ /' | sed 's/->/ /'| awk -F' ' '{print $10}' \
| awk '!a[$0]++'
以前は IPv4 の listen
行(監視対象のポート)だけを取得していたのですが、すでに接続済(establish
)のポートと IPv6 も含めて、一意のポート一覧を取得するようにしました。
タイプ2(ソケット利用タイプ)
こちらは PHP の標準機能を使ったタイプです。ソケットを作成(socket_create_listen()
)する際に引数に「0
」を渡すと空きポートをランダムに自動で割り当てられたポートを使うタイプです。ビルトイン(標準)関数であるため、おそらく Windows でも利用可能かと思います。
前述の「外部コマンド利用のタイプ1」と比べ圧倒的に速いのですが「ネットワークの受信接続の許可」ポップアップが一瞬表示されるため、連続して何度か使うにはウザいタイプです。
function getPortRandom()
{
$socket = socket_create_listen(0); //ランダムな空きポートの割り当て
socket_getsockname($socket, $addr, $port); //ポート情報の取得
socket_close($socket); //ソケットを解放
return (int) $port; //解放されたソケットのポート番号を横取り
}
TS;DR
だって夏だから
Mac で PHP スクリプトの動作確認をするために Web サーバを建てるのも面倒なので、ビルトインサーバで検証していました。
しかし、そのたびに「空いているポート何かな」と悩むのが面倒になったのです。ウェルノウン・ポートなんかも、いちいち確認してられません。すべてが面倒なのですよ。暑いし。エアコン制限されてるし。_(:⁍」 )_
$ cd /path/to/my/php/script
$ ls
index.php
$
$ # ポート指定(このポートを決めるのが面倒)
$ PORT_TEMP=8888
$
$ # ブラウザ(Safari)とWebサーバ起動
$ open -a "Safari" "http://localhost:$PORT_TEMP" && php -S localhost:$PORT_TEMP
$
$ # ブラウザ(Chrome)とWebサーバ起動
$ open -a "Google Chrome" "http://localhost:$PORT_TEMP" && php -S localhost:$PORT_TEMP
そうだ PHP で行こう
そこで、以前作成した CentOS7 の SSH 接続用に Bash で空きポートをランダムに取得するシェル・スクリプトを使おうと思ったのですが、macOS の Bash では標準で shuf
コマンドに対応していないなど、ストレートに使えそうにありませんでした。
brew
でインストールするのも環境依存するのでちょっと違うし、それならば OS に標準で入っている PHP で取得すればいいじゃない、と思い立ちました。
まずは PHP の標準コマンドで「使用中のポートを確認するコマンドはないか」と確認してみたのですが、該当するものはありませんでした。
しかし、マニュアルの記述には無いのですが、マニュアルのコメント欄を見ると「 socket_create_listen()
標準関数の引数を空欄にするか 0
を渡すとランダムに空いているポートを割り当ててくれるよ」という記述が。
マニュアルだと第1引数のポート番号指定は必須になっているのですが、確かにランダムなポートが割り当てられたのです。
これでいける。
ん?いま一瞬何か見えなかった・・・か?
socket_create_listen()
関数でランダムに空きポートが割り当てられることが確認できたので、「PHP にランダムに空きポートを割り当てさせて、直後に解放させたら、そのポートが使えるんじゃね?」と思いつきました。
$ php -a
Interactive shell
php > $sock = socket_create_listen(0);
php > socket_getsockname($sock, $addr, $port);
php > socket_close($sock);
php > echo $port;
58487
「お、いけるかも」と関数化したのも束の間、あることに気づきました。なんか一瞬ピロピロっと表示されるのです。
あまりにも、一瞬すぎて何のメッセージが出ているのか分からなかったのですが、スクリプトを何度も叩くと確かに表示されているのです。
sleep()
を入れてみたところ、ソケットを作成するタイミングで以下の確認メッセージが出ていました。ソケットを解放(socket_close()
)すると消えるのですが、どうやらポートを変えまくってるからか、Mac のファイヤーウォールに怪しまれたようです。
まぁ、一回「許可」しておけば良いだけなのですが、他の環境で他の人が使う場合に面倒です。
「ポップアップが出たら許可する」ように案内するのも面倒ですし、マニュアルに書いても「よく分からないのでお断り申し上げる」と、ポチッと「拒否」されることは、まれによくあります。
それにより PHP 自体がロックされて「マニュアル通り作業しても動かないし、他のプログラムも動かなくなった!」というストーリーが走馬灯のように見えました。
そこで素直に外部コマンドでシェルを叩いて取得したプロセスを料理する方法が確実ということで、シェルと PHP の美味しいところどりをしてみました。
最終的なスクリプト
ターミナルで index.php
のあるディレクトリに移動後、$ dev chorme
と叩けば、そのディレクトリをルートとした WEB サーバと引数で指定したブラウザが起動するスクリプトにしました。(インストール済みのブラウザに限る)
$ cd /path/to/your/dev/dir
$ ls
index.php
$ dev opera
具体的には、以下のスクリプトのように shebang
付きの PHP スクリプトを、パスの通ったディレクトリに dev
というファイル名(拡張子なし)で設置して、chmod 0744 dev
で実行権限を与えて、「俺様簡易検証サーバ・コマンド」の完了です。
#!/usr/bin/env php
<?php
$browser_default = 'safari';
$browser = isset($argv[1]) ? strtolower($argv[1]) : $browser_default;
switch($browser){
case 'chrome':
$browser = 'Google Chrome';
break;
case 'firefox':
$browser = 'Firefox';
break;
case 'opera':
$browser = 'Opera';
break;
default:
$browser = 'Safari';
break;
}
$port = getPortRandom();
$cmd = "open -a \"{$browser}\" \"http://localhost:{$port}\" ";
$cmd .= "&& php -S localhost:{$port}";
`$cmd`;
/* [Functions] -------------------------------------------------------------- */
function getPortRandom($port_min = 50000, $port_max = 65535)
{
$cmd = 'lsof -i -P | grep -i "listen" 2>&1';
$rows = array_filter(explode(PHP_EOL, `$cmd`));
$ports = array_map(
function ($row) {
$row = str_replace(':', ' ', $row);
$cols = explode(' ', $row);
$cols = array_values(array_filter($cols));
return (int) $cols[9];
},
$rows
);
while (true) {
$port = rand($port_min, $port_max);
if (false === array_search($port, $ports)) {
break;
}
}
return $port;
}
2018/08/24 追記:macOS 標準のシェル・コマンドで空きポートを取得するスクリプトを書いたので、それを利用してよりコードが短くなりました。
function getPortRandom($port_min = 50000, $port_max = 65535)
{
$cmd = "lsof -i -P | grep -i 'tcp' | sed 's/\[.*\]/IP/' \
| sed 's/:/ /' | sed 's/->/ /'| awk -F' ' '{print $10}' \
| awk '!a[$0]++'";
$ports = array_filter(explode(PHP_EOL, `$cmd`));
while (true) {
$port = rand($port_min, $port_max);
if (false === array_search($port, $ports)) {
break;
}
}
return $port;
}
参考文献
- PHP:socket_create_listen - Manual @ PHP.net
- How can I find a list of used and unused ports with a MacBook? @ Quora.com
- Install shuf on OS X? @ StackExchange
-
lsof
コマンドの検索結果一覧 @ Qiita via Google