java network perl

JavaとPerlでTCPソケット通信

投稿日:2019年6月6日

はじめに

前回の続きです。

前回はPerl 5.8でしたが今回はPerl 5.26にアップデート済みです。TCPソケットサーバをPerlで立ち上げ、JavaのTCPソケットクライアントから電文送信。サーバではnetstat -anコマンドを実行してその結果をJavaクライアントに返却します。

対象読者

  • perlでスクリプトを書いたことが無い方。
  • perlの全盛期を知らないミレニアル世代の方。
  • perlと聞くと若かりし頃を思い出すアラフォー世代の方。

実装

TCPサーバ(Perl)

IO::Select、IO::Socketモジュールを使うことで、1スレッドでも後から接続しに来たクライアントソケットが待たされないノンブロッキング通信を行います。Selectを使うと他にも複数ポートを待ち受ける複数ソケットから対象のソケットをセレクトすることが出来たりします。

(use 5.12以上だとuse strictと同じ効果があります)

use 5.26.0;
use IO::Select;
use IO::Socket;

# コマンドライン引数からサーバソケットポート番号を設定
#(指定が無ければ5001番ポート使用)
my $port = shift || 5001;

my $listener = IO::Socket::INET->new(
    LocalPort => $port,
    Listen    => SOMAXCONN,
    Proto     => 'tcp',
    Reuse     => 1,
);

if ( !$listener ) {
    die "can't listen $!\n";
} else {
    print "listening $port\n";
}

my $selector = IO::Select->new( $listener );

while( my @ready = $selector->can_read() ) {
    foreach my $sock ( @ready ) {
        if( $sock == $listener ) {
            # Create a new socket
            my $new_sock = $listener->accept;
            $selector->add( $new_sock );
        }
        else {
            # クライアントソケットから行数分の電文読み込み
            while ( my $line = <$sock> ) {
                if ( $line ) {
                    say "message from client : ", $line;

                    # netstatを実行して結果をファイルハンドルに詰め込む
                    open( FH, "netstat -an |" ) or die "can't exec netstat $!";

                    # 結果行数分回してソケットにwrite
                    while ( <FH> ) {
                        print $sock $_;
                    }

                    $sock->flush();

                    # セレクタからソケット削除
                    $selector->remove( $sock );
                    $sock->close();
                }
            }
        }
    }
}

TCPクライアント(Java)

Eclipse + Java8です。

package com.sample.snippet;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class NetstatClient {

	public static void main(String[] args) throws Exception {
        String server = "localhost";
        int port = 5001;

        // perlのサーバソケットに接続
        Socket socket = new Socket(server, port);

        // ソケット入力
        BufferedReader in =
            new BufferedReader(new InputStreamReader(socket.getInputStream()));

        // ソケット出力
        PrintWriter out =
            new PrintWriter(socket.getOutputStream(), true);

        // 電文送信
        out.println("give me netstat results");

        // ソケットから返却されたnetstat結果を行数分読み込み
        String line = null;
        while((line = in.readLine()) != null) {
            System.out.println("受信:" + line);
        }

        in.close();
        socket.close();

        System.out.println("完了");

    }
}

実行

perlコマンドでTCP:5001サーバを起動します。

C:\src\perl>perl netstat-server.pl 5001
listening 5001

EclipseからJavaクライアントを実行。

受信:
受信:アクティブな接続
受信:
受信:  プロトコル  ローカル アドレス      外部アドレス           状態
受信:  TCP         0.0.0.0:80             0.0.0.0:0              LISTENING
受信:  TCP         0.0.0.0:135            0.0.0.0:0              LISTENING
受信:  TCP         0.0.0.0:445            0.0.0.0:0              LISTENING
受信:  TCP         0.0.0.0:623            0.0.0.0:0              LISTENING
受信:  TCP         0.0.0.0:1801           0.0.0.0:0              LISTENING
受信:  TCP         0.0.0.0:2103           0.0.0.0:0              LISTENING
受信:  TCP         0.0.0.0:2105           0.0.0.0:0              LISTENING

    (snip)

受信:  UDP         [::]:65086             *:*                    
受信:  UDP         [::1]:1900             *:*                    
受信:  UDP         [::1]:5353             *:*                    
受信:  UDP         [::1]:60416            *:*                    
受信:  UDP         [fe80::4881:bab0:17bf:1336%12]:1900  *:*                    
受信:  UDP         [fe80::4881:bab0:17bf:1336%12]:60414  *:*                    
受信:  UDP         [fe80::90fe:ec81:9d44:c68f%15]:1900  *:*                    
受信:  UDP         [fe80::90fe:ec81:9d44:c68f%15]:60415  *:*                    
完了

コンソールにサーバから返却されたnetstatの結果が表示されました。

まとめ

スレッドの排他制御手段がAPIレベルで豊富に用意されているJavaのような言語なら、スレッドを使ってノンブロッキングにした方が分かり易いプログラムになるかと思います。

しかしリクエストが来る度にスレッドを起動するのはマシンリソースを食ってしまう為、どの言語でもセレクタでソケットチャネルを切り替えるAPIが実装されていてシングルスレッドでも複数リクエストを捌ける仕組みを作れる手段が用意されています。perlでセレクタを覚えておけば他の言語でも学習転移出来ますね。

ただ最近のマシンリソースは昔とは比べ物にならない程性能が上がっている為、数千程度のスレッド生成なら大抵は耐えられます。


2. Why Use a Selector?


With a selector, we can use one thread instead of several to manage multiple channels. Context-switching between threads is expensive for the operating system, and additionally, each thread takes up memory.
Therefore, the fewer threads we use, the better. However, it’s important to remember that modern operating systems and CPU’s keep getting better at multitasking, so the overheads of multi-threading keep diminishing over time.

「 なぜセレクタを使うのですか?

セレクタを使用すると、複数のチャネルを管理するために複数のスレッドの代わりに1つのスレッドを使用できます。スレッド間のコンテキスト切り替えは、オペレーティングシステムにとってコストがかかり、さらに、各スレッドがメモリを占有します。 したがって、使用するスレッドが少ないほど、優れています。ただし、最近のオペレーティングシステムとCPUはマルチタスク処理で向上し続けているため、マルチスレッド処理のオーバーヘッドは時間の経過とともに減少し続けています。 」


https://www.baeldung.com/java-nio-selector

サーバ側の処理が適度に軽ければCore i7、16GbyteメモリのTomcat1台でも同時2000スレッドは普通に捌けますし、2台以上で負荷分散するのが普通ですから余程のアクセスを想定しない限りスレッドを使っていいと思います。オリンピックの予約チケットサイトクラスの同時リクエスト数が予想されるなら、セレクタを使って工夫したり、Nginxでスレッド起動オーバーヘッドが無いサーバの選択をしたりすれば良いかと思います。

-java, network, perl

執筆者:

関連記事

whoisコマンドで新興TLDのドメイン創設日を調べる

各ドメインの情報は属するトップレベルドメイン(.jpなど)が管理するDNSサーバによって管理されています。この情報を辿って指定したドメインの情報を参照する仕組みが「whois」です。 最近は各トップレ …

JUnitコードの自動生成も出来る古の神Eclipseプラグイン「CodeProAnalytix」は今でも使える

デスマーチPM「各社のPG進捗が遅れていてユニットテストを書く暇が全く無いんですよ、なにかいい手は有りませんかね・・・? 因みに本プロジェクトはカバレッジ100%で請け負っています!(キリッ)」 何故 …

SSHトンネルを使ってリモートデータベースをBI

クラウド上のDB分析環境構築を想定して、ローカルPCにSSHトンネル(ポートフォワード)環境を作ります。 リモートDBポートにクラウド外からセキュアに接続できれば、クラウドにBIツールを入れる手間が省 …

CentOS7にOpenJDK11をインストール、alternatives後の再ログインでJAVA_HOMEも自動変更

OpenJDKはCentOS-Baseリポジトリのupdatesに登録されているので新規yumリポジトリ追加は不要です。 [root@spock tmp]# yumdb search from_rep …

IVS対応フォント「IPAmj明朝」で使えるフォントをWeb上に一覧表示してみる(2)

前回の続きです。One IT ThingIVS対応フォント「IPAmj明朝」で使えるフォントをWeb上に一覧表示してみる(1)https://one-it-thing.com/2098IPAmj明朝フ …

 

shingo.nakanishi
 

東京在勤、1977年生まれ、IT職歴2n年、生涯技術者として楽しく生きることを目指しています。デスマに負けず健康第一。