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

執筆者:

関連記事

Spring&JSPの検証環境を作る

2019年現在、オワコン風潮の強いJSPですが使っているプロジェクトもまだまだあり、枯れた技術を好む官公庁系のプロジェクトでは根強いシェアを誇っています。実装検証をする為に環境を作る機会があったりする …

JavaコアAPI数の遷移をChart.jsでグラフ化してみる

chart.jsの試用を兼ねてJavaバージョンが上がるごとに標準搭載されているAPI数がどう変化しているか調べました。 目次1 調査結果2 グラフ化3 不要なAPIは外部化の傾向 調査結果 Orac …

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

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

宅内CentOS8にSoftEtherをインストールしてインターネット外部からLAN参加出来るVPN環境を無料で作る

2020/04現在、COVID-19の影響を受けて自宅から会社のVPNに繋いでリモートワークに移行している方も増えてきたんじゃないでしょうか。(残念ながら私はまだ出社必須ですが(-_-; そもそも、も …

Doxygenでspring-frameworkをドキュメント化

以前にspring-frameworkのソースリーディングが出来る環境をOpenGrokで作りました。One IT ThingOpenGrokをインストールしてソースリーディング環境を作るhttps: …

 

shingo.nakanishi
 

東京在勤、1977年生まれ、IT職歴2n年、生涯現役技術者を目指しています。健康第一。