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

執筆者:

関連記事

特定ドメインのサブドメインを探る

注:dnsmapは第三者サーバに対しては使用せず、自分の管理ドメインに対してセキュリティ確認用途でご使用ください。 昨今のDNSサーバはゾーン転送が抑制されており、サブドメイン情報を引っ張ってくること …

インターネット速度テストサービスの結果を比較してみる

「インターネット疎通の速度が帯域通りになっているか確かめたい」 仕事でもプライベートでもそんなニーズがあります。 数多あるインターネット速度計測サービスを試してみて「日本国内で使う場合、最も正確な速度 …

hidden.inとSoftEtherで無料のビデオ会議環境を構築する

自粛が続いて会えない人も多くなり、人との交流が恋しくなりますね。 仕事ではミーティングをオンライン化するニーズが増え、以下メジャーなパブリックサービスを使っている人も増えていると思います。 Skype …

LANに接続しているPCをWindowsから見つける

家庭内LANでNASのIPアドレスを忘れたので調べたい職場LANに接続中のホストを調べたい NetEnumを入れられれば取れる情報も多く、ポートスキャンやパケットキャプチャも出来て幸せになれます。 単 …

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

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

 

shingo.nakanishi
 

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