忍者ブログ

VB.NET-TIPS などプログラミングについて

VB.NETのTIPS(小技集)を中心に、Javascript その他のプログラミングについて少し役に立つ情報を発信します。いわゆる個人的な忘備録ですが、みなさんのお役に立てれば幸いです。

名前付きパイプを使ったプロセス間通信について

名前付きパイプは、パイプサーバーと複数のパイプクライアントとの間でのプロセス間通信ができます。 名前付きパイプは、メッセージ単位で通信するモードと、バイトストリームとして読み書きするモードのいずれかが選べます。
どちらのモードでも、名前付きパイプを生成し、接続した後は、サーバー側・クライアント側とも通常のファイル入出力を使うことで通信が行われます。

名前付きパイプの例として以下の2通りの方法を示します。


■1回の通信毎に名前付きパイプを生成する方法

サーバー側では、名前付きパイプ処理を行うスレッドを立ち上げて、そのスレッドの中で通信処理を行います。
フォームに通信開始ボタンと、受信メッセージ等の表示用にリッチテキストボックスを設置します。
スレッド処理では NamedPipeServerStream によりサーバー用名前付きパイプクラスを生成します。
その後 WaitForConnection メソッドで接続を待ち StreamReader によりパイプからの受信文字列を取得します。 取得文字列をリッチテキストボックスにデリゲートで表示し、すぐに StreamReader とサーバー用名前付きパイプクラスを閉じます。

サーバー側プログラム
Imports System.Threading
Imports System.IO
Imports System.IO.Pipes

Public Class frmNameServer

    '別スレッドからメッセージを処理するためデリゲートを利用
    Delegate Sub SetRichTextBox1Delegate(ByVal Value As String)
    Private RichTextBox1Delegate As New SetRichTextBox1Delegate(AddressOf _
                                                                AppendTextRichTextBox1)
    'リッチテキストボックスにメッセージを表示する
    Private Sub AppendTextRichTextBox1(ByVal message As String)
        '文字列を最後尾に追加
        RichTextBox1.AppendText(message)
        'カレット位置を末尾に移動
        RichTextBox1.SelectionStart = RichTextBox1.TextLength
        'カレット位置までスクロール
        RichTextBox1.ScrollToCaret()
    End Sub

    '別スレッドからボタンの許可設定処理するためデリゲートを利用
    Delegate Sub SetButton1EnabledDelegate(ByVal bln As Boolean)
    Private SetButton1Delegate As New SetButton1EnabledDelegate(AddressOf _
                                                                SetButton1Enabled)
    '[Buttin1]のEnabled設定
    Private Sub SetButton1Enabled(ByVal blnEnabled As Boolean)
        Me.Button1.Enabled = blnEnabled
    End Sub

    '[Start NamePipe]ボタンクリックイベント
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                                                                        Handles Button1.Click
        '名前付きパイプによる通信スレッド開始
        Dim server As New Thread(AddressOf ServerThread)
        server.Start()
        AppendTextRichTextBox1("名前付きパイプ通信待機中..." & vbNewLine)

        '[Start NamePipe]ボタン不可設定
        Me.Button1.Enabled = False
    End Sub

    '名前付きパイプによる通信スレッド
    Private Sub ServerThread()
        'サーバー用のパイプを双方向でクライアント数は2個で生成
        Dim pipeServer As New NamedPipeServerStream("PIPE_TEST", PipeDirection.InOut, 2)
        Try
            'パイプスレッド取得
            Dim threadId As Integer = Thread.CurrentThread.ManagedThreadId
            Me.Invoke(RichTextBox1Delegate, _
                      New Object() {"パイプ生成完了(スレッドID:" & threadId & ")" & vbNewLine})

            'パイプの接続を待つ
            pipeServer.WaitForConnection()
            Me.Invoke(RichTextBox1Delegate, _
                      New Object() {"パイプ接続完了" & vbNewLine})

            'ストリーム読込を生成
            Dim pipeStreamReader As New StreamReader(pipeServer)
            Try
                'パイプからの文字列受信(入力)
                Dim strRead As String = pipeStreamReader.ReadLine()
                '受信文字列の表示
                Me.Invoke(RichTextBox1Delegate, _
                          New Object() {"受信文字列:" & strRead & vbNewLine})

            Catch ex As IOException
                MsgBox(ex.Message)
            Finally
                pipeStreamReader.Close()
            End Try

            Me.Invoke(RichTextBox1Delegate, _
                      New Object() {"パイプ通信の終了" & vbNewLine})

            '[Button1]の許可設定
            Me.Invoke(SetButton1Delegate, New Object() {True})

        Catch ex As Exception
            MsgBox(ex.Message)
        Finally
            pipeServer.Close()
        End Try
    End Sub

End Class


クライアント側プログラムは送信ボタンとメッセージ入力用にテキストボックスを設置します。
送信ボタンを押下した時に NamedPipeClientStream によりクライアント用名前付きパイプを生成します。
そのクラスの Connect メソッドで接続後ストリームライタで1回のみ文字列送信を行います。
送信後、ストリームライタとクライアント用名前付きパイプを閉じます。

クライアント側プログラム
Imports System.IO
Imports System.IO.Pipes
Imports System.Security.Principal

Public Class frmNameClient

    '[Start Named Pipe]ボタンクリック時イベント
    Private Sub ButtonSend_Click(sender As Object, e As EventArgs) _
                                                    Handles ButtonSend.Click
        Dim pipeStream As NamedPipeClientStream = Nothing
        Dim pipeSw As StreamWriter = Nothing
        Try
            '[Send Message]ボタンを不可設定
            Me.ButtonSend.Enabled = False
            '名前付きパイプ・クライアント生成
            pipeStream = New NamedPipeClientStream(".", "PIPE_TEST", _
                                                   PipeDirection.InOut, _
                                                   PipeOptions.None, _
                                                   TokenImpersonationLevel.Impersonation)
            '名前付きパイプ接続
            pipeStream.Connect(1000)
            'ストリームライタ
            pipeSw = New StreamWriter(pipeStream)
            pipeSw.AutoFlush = True

            '文字列送信
            pipeSw.WriteLine(Me.TextBox1.Text.Trim)

        Catch ex As Exception
            MsgBox(ex.Message)
        Finally
            'ストリームライタを閉じる
            If Not pipeSw Is Nothing Then
                pipeSw.Close()
            End If
            '名前付きパイプを閉じる
            If Not pipeStream Is Nothing Then
                pipeStream.Close()
            End If
            '[Send Message]ボタンを許可設定
            Me.ButtonSend.Enabled = True
        End Try
    End Sub

End Class

プログラムの実行は以下の様になります。


■名前付きパイプを生成後、複数回の通信を行う方法

1回の通信毎に名前付きパイプを生成するのでは効率が悪いですし、連続での通信ができませんので サーバー側のスレッド部分を複数回の通信が出来る様に変更します。
変更した部分は、スレッド内でストリーム読込を生成をした後で、ループを形成しパイプからの文字列受信を繰り返す様にしています。 受信した文字列がNULLの場合にストリームライタとクライアント用名前付きパイプを閉じます。

サーバー側プログラム
Imports System.Threading
Imports System.IO
Imports System.IO.Pipes

Public Class frmNameServer

    '別スレッドからメッセージを処理するためデリゲートを利用
    Delegate Sub SetRichTextBox1Delegate(ByVal Value As String)
    Private RichTextBox1Delegate As New SetRichTextBox1Delegate(AddressOf _
                                                                AppendTextRichTextBox1)
    'リッチテキストボックスにメッセージを表示する
    Private Sub AppendTextRichTextBox1(ByVal message As String)
        RichTextBox1.AppendText(message)
        RichTextBox1.SelectionStart = RichTextBox1.TextLength
    End Sub

    '別スレッドからボタンの許可設定処理するためデリゲートを利用
    Delegate Sub SetButton1EnabledDelegate(ByVal bln As Boolean)
    Private SetButton1Delegate As New SetButton1EnabledDelegate(AddressOf _
                                                                SetButton1Enabled)
    '[Buttin1]のEnabled設定
    Private Sub SetButton1Enabled(ByVal blnEnabled As Boolean)
        Me.Button1.Enabled = blnEnabled
    End Sub

    '[Start NamePipe]ボタンクリックイベント
    Private Sub Button1_Click(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) Handles Button1.Click
        '名前付きパイプによる通信スレッド開始
        Dim server As New Thread(AddressOf ServerThread)
        server.Start()
        AppendTextRichTextBox1("名前付きパイプ通信待機中・・・" & vbNewLine)

        '[Start NamePipe]ボタン不可設定
        Me.Button1.Enabled = False
    End Sub

    '名前付きパイプによる通信スレッド
    Private Sub ServerThread()
        'サーバー用のパイプを双方向でクライアント数は2個で生成
        Dim pipeServer As New NamedPipeServerStream("PIPE_TEST", PipeDirection.InOut, 2)
        Try
            'パイプスレッド取得
            Dim threadId As Integer = Thread.CurrentThread.ManagedThreadId
            Me.Invoke(RichTextBox1Delegate, _
                      New Object() {"パイプ生成完了(スレッドID :" & threadId & ")" & vbNewLine})

            'パイプの接続を待つ
            pipeServer.WaitForConnection()
            Me.Invoke(RichTextBox1Delegate, _
                      New Object() {"パイプ接続完了" & vbNewLine})

            'ストリーム読込を生成
            Dim pipeStreamReader As New StreamReader(pipeServer)
            Try
                While True
                    'パイプからの文字列受信(入力)
                    Dim strRead As String = pipeStreamReader.ReadLine()
                    'ストリームの末尾に到達した場合はNullなので、通信を終了する
                    'If strRead Is Nothing Then
                    If strRead = "" Then
                        Exit While
                    End If
                    '受信文字列の表示
                    Me.Invoke(RichTextBox1Delegate, _
                              New Object() {"受信文字列:" & strRead & vbNewLine})
                End While

            Catch ex As IOException
                MsgBox(ex.Message)
            Finally
                pipeStreamReader.Close()
            End Try

            Me.Invoke(RichTextBox1Delegate, _
                      New Object() {"パイプ通信の終了" & vbNewLine})

            '[Button1]の許可設定
            Me.Invoke(SetButton1Delegate, New Object() {True})

        Catch ex As Exception
            MsgBox(ex.Message)
        Finally
            pipeServer.Close()
        End Try
    End Sub

End Class


クライアント側プログラムではパイプ開始ボタンとメッセージ送信ボタンを設置します。
パイプ開始ボタン押下で名前付きパイプ・クライアント生成と接続、及びストリームライタ生成を行います。
この生成されるクラスは、メッセージ送信ボタン押下時にも使用されるため、関数の外で静的変数として宣言します。
メッセージ送信ボタン押下時にはストリームライタでメッセージを送信します。 メッセージが空白の場合はストリームライタ、名前付きパイプ・クライアントを閉じます。

クライアント側プログラム
Imports System.IO.Pipes
Imports System.IO
Imports System.Security.Principal

Public Class frmNameClient

    '名前付きパイプ・クライアント
    Private pipeStream As NamedPipeClientStream = Nothing
    'パイプ用ストリームライタ
    Private pipeSw As StreamWriter = Nothing

    'フォームロード時イベント
    Private Sub frmNameClient_Load(sender As Object, e As EventArgs) Handles Me.Load
        '[Start Named Pipe]ボタンを許可設定
        Me.Button1.Enabled = True
        '[Send Message]ボタンを不可設定
        Me.Button2.Enabled = False
    End Sub

    '[Start Named Pipe]ボタンクリック時イベント
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Try
            '名前付きパイプ・クライアント生成
            pipeStream = New NamedPipeClientStream(".", _
                                                   "PIPE_TEST", _
                                                   PipeDirection.InOut, _
                                                   PipeOptions.None, _
                                                   TokenImpersonationLevel.Impersonation)
            '名前付きパイプ接続
            pipeStream.Connect(1000)
            'ストリームライタ生成
            pipeSw = New StreamWriter(pipeStream)
            pipeSw.AutoFlush = True

            '[Start Named Pipe]ボタンを不可設定
            Me.Button1.Enabled = False
            '[Send Message]ボタンを許可設定
            Me.Button2.Enabled = True

        Catch ex As Exception
            MsgBox(ex.Message)
        End Try
    End Sub

    '[Send Message]ボタンクリック時イベント
    Private Sub Button2_Click(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) Handles Button2.Click
        Try
            '送信文字列取得
            Dim strTX As String = Me.TextBox1.Text.Trim
            pipeSw.WriteLine(strTX)
            If strTX = "" Then
                'ストリームライタを閉じる
                pipeSw.Close()
                '名前付きパイプを閉じる
                pipeStream.Close()

                '[Start Named Pipe]ボタンを許可設定
                Me.Button1.Enabled = True
                '[Send Message]ボタンを不可設定
                Me.Button2.Enabled = False
            End If

        Catch ex As Exception
            MsgBox(ex.Message)
        End Try
    End Sub

    'フォームクローズ時イベント
    Private Sub frmNameClient_FormClosed(sender As Object, _
                                         e As FormClosedEventArgs) Handles Me.FormClosed
        'ストリームライタを閉じる
        If Not pipeSw Is Nothing Then
            pipeSw.Close()
        End If
        '名前付きパイプを閉じる
        If Not pipeStream Is Nothing Then
            pipeStream.Close()
        End If
    End Sub

End Class

クライアント側でフォームを閉じることでパイプの接続が閉じるため、サーバー側に通信の終了が通知できます。

プログラムの実行は以下の様になります。

関連する記事

Remoting の IPC を使ったプロセス間通信について
Remoting の IPC を使ったプロセス間通信についてその2(HTTPチャネル)
PR

コメント

コメントを書く