One IT Thing

IT業界で飯を食う為の学習系雑記

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

執筆者:

関連記事

Javascript(暗号化JSライブラリ「Forge」)とp12ファイルで署名値を作成、Javaで検証する

前回、送信データの改ざんを検知する為、簡易的なセキュリティトークンであるPKCS#12形式のファイルを作成しました。  One IT Thing開発用のPKCS#12ファイルをOpenSSL …

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

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

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

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

ブラウザでRSA暗号化したデータをサーバで復号する(Angular + JSEncrypt、Spring MVC)【前編】

セキュリティ的にクリティカルなデータをクライアントブラウザで暗号化保存するようにしてみます。 通信経路はHTTPSで暗号化されていてもスマホに重要なデータが平文で残っていたら珠に傷です。 目次1 環境 …

Tomcat9をCentOS7&OpenJDK11で起動

CentOS 7.6にOpenJDK 11を以下の記事で入れている状態です。 One IT ThingCentOS7にOpenJDK11をインストール、alternatives後の再ログイン …

 

shingo.7k24
 

東京在勤、職歴20年越え中年ITエンジニアです。まだ開発現場で頑張っています。

19歳(1996年)から書き始めた個人日記が5,000日を超え、残りの人生は発信をして行きたいと思い、令和元日からこのサイトを開始しました。勉強と試行錯誤をしながら、自分が経験したIT関連情報を投稿しています。

私と同じく、今後IT業界で生計を立てて行きたいと考えている方や、技術共有したいけどフリーランスで孤独、といった方と一緒に成長、知識共有して行けたら楽しいな、と思っています。