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」です。 最近は各トップレ …

StripeとJavaで単発Web決済を一通り流してみる

会社仕事でも個人ビジネスでも、商品やサービスの対価として利用者に課金方法を提供したい時があるかと思います。 最もメジャーな決済方法であるクレジットカードで課金してもらう仕組みを導入したいところですが、 …

JavaでRSA暗号を使う際にCRYPTREC暗号リストに足元をすくわれる可能性を回避する

標準的な暗号しか使わないケースでもJavaでRSAを使う時はBouncyCastleを入れておいた方が無難、という話です。 “ECB”という文字列がプログラム中にあると監査に引 …

IPAmj明朝のttfファイルをJVMに読み込ませてSwingで表示(要Java11)

目次1 はじめに2 Java11でSwingがIVSを認識するようになった3 Java Swingソース4 動作確認4.1 Java 8(1.8.0_202)で実行4.2 Java 11(11.0.2 …

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

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

 

shingo.nakanishi
 

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