2013年9月15日 星期日

[named pipe] .Net 的 named pipe,堪用的第一步。Server 端

首先要先定義 pipe name 與 pipe in 及 out 的關係。

image

Server 與 Client 之間有兩條 pipe,所以會需要兩個名字,然而,為方便使用者輸入的簡化,當使用者輸入了<pipe name> 之後,實際建立的兩條 pipe 的名字是 <pipe name>_1 與 <pipe name>_2。假設使用者輸入的 pipe name 是 test,Server 端會建立 test_1 與 test_2 兩條 pipe 等待連接。

在這兩條 pipe 裡,Server 端以<pipe name>_1 為輸入端,<pipe name>_2 為輸出端。.Net 為我們準備好的 Server 端物件是 NamedPipeServerStream。設定 PipeServer 的方式僅需給予 pipe 的名字及方向。

當使用者輸入 pipe name 之後,按下 Open 鈕,會執行以下 sub:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    If TextBox1.Text <> "" Then
        IWantStop = False
        ServerSetup()
    End If
End Sub

Sub ServerSetup()
    Dim pipename1 As String = TextBox1.Text & "_1"
    Dim pipename2 As String = TextBox1.Text & "_2"
    PipeServerIn = New NamedPipeServerStream(pipename1, PipeDirection.In)
    PipeServerOut = New NamedPipeServerStream(pipename2, PipeDirection.Out)
    ThreadWaitConnectionIn = New Thread(AddressOf SubWaitConnectionIn)
    ThreadWaitConnectionOut = New Thread(AddressOf SubWaitConnectionOut)
    ThreadWaitConnectionIn.Start()
    ThreadWaitConnectionOut.Start()
    UpdateConnectionState(ConnectionState.Waiting)
End Sub

ServerSetup 前面 4 行就是在處理組合 pipe name 及設定 PipeServer。原本接下來應該是呼叫 WaitForConnection 等待連接,因為此呼叫會 block,所以另外用 thread 來做呼叫的處理。對於 PipeServerOut 的 WaitForConnection 比較單純,我僅使用一個 sub 來處理,在連接來時,若 PipeServerIn 也連接上時,才更新 GUI 上的狀況說明。

Sub SubWaitConnectionOut()
    PipeServerOut.WaitForConnection()
    If PipeServerIn.IsConnected Then
        UpdateConnectionState(ConnectionState.Connected)
    End If
End Sub

在 PipeServerOut 實例化後,執行以下動作,使用另一個 thread 來等待:

ThreadWaitConnectionOut = New Thread(AddressOf SubWaitConnectionOut)
ThreadWaitConnectionOut.Start()

在接收端 PipeServerIn 這個物件,在被連接後,要開始處理接收輸入的動作。然後,處理接收這件事,也要用一個 thread 來處理。於是 PipeServerIn 的 WaitForConnection 處理 sub 如下:

Sub SubWaitConnectionIn()
    PipeServerIn.WaitForConnection()
    ThreadIn = New Thread(AddressOf SubHandleRead)
    ThreadIn.Start()
    If PipeServerOut.IsConnected Then
        UpdateConnectionState(ConnectionState.Connected)
    End If
End Sub

在處理輸入這個 SubHandleRead,有一半是真的處理輸入,另一半是處理 PipeServer 重設的工作。不小心,又用了專用的 thread 來做事。因為 SubHandleRead 被 ThreadIn 所使用,在做重設定時 ThreadIn 必須先結束才能重設。我是不得已才這麼做才又多一個 thread 來呼叫 ReSetUp 的。

Sub SubHandleRead()
    Dim RStream As StreamReader
    RStream = New StreamReader(PipeServerIn)
    While True
        Dim ReadStringLine As String = RStream.ReadLine
        If ReadStringLine Is Nothing Then
            Exit While
        End If
        UpdateTextBox2State(ReadStringLine)
    End While
    UpdateConnectionState(ConnectionState.Disconnected)
    CleanPipeServer()
    If IWantStop = False Then
        If Not ThreadReSetup Is Nothing Then
            If ThreadReSetup.IsAlive Then
                ThreadReSetup.Abort()
            End If
        End If
        ThreadReSetup = New Thread(AddressOf ReSetup)
        ThreadReSetup.Start()
    End If
    CleanThread()
End Sub

Sub ReSetup()
    Thread.Sleep(500)
    ServerSetup()
End Sub

在處理輸入這一半,我使用了非常非常偷懶的 ReadLine 方法,在 StreamReader 的幫助下,只要讀到換行符號,就會從 ReadLine 方法得到資料,該資料不含換行符號。如果,Client 端的連接斷掉了,ReadLine 得到的是 Nothing。所以,得到 Nothing 的時候,就要離開處理迴圈。假如,Client 斷開的原因是故意的,那就不直接重建 Server。但,Client 斷開的原因是不小心的, IWantStop 會是 False,此時要開始執行重建 Server 的動作,回到等待連接的狀態。

而什麼時候是故意斷開的呢?就是按下 Close 鈕的時候:

Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
    IWantStop = True
    CleanPipeServer()
    CleanThread()
    UpdateConnectionState(ConnectionState.Disconnected)
End Sub

在清理 Thread 這件事,我用了很簡單的方法,就是用力地 Abort!為了保證每個 thread 一定都會執行 Abort,我用 Try Catch 把每行執行都包起來,就減少了一些 thread 物件是不是有實例化的檢查。

Sub CleanThread()
    Try
        ThreadWaitConnectionIn.Abort()
    Catch ex As Exception

    End Try
    Try
        ThreadWaitConnectionOut.Abort()
    Catch ex As Exception

    End Try
    Try
        ThreadIn.Abort()
    Catch ex As Exception

    End Try
    ThreadWaitConnectionIn = Nothing
    ThreadWaitConnectionOut = Nothing
    ThreadIn = Nothing
End Sub

然而在清理 PipeServerIn、PipeServerOut 這兩個物件時,比較複雜一點點。當有 Client 端連接上時,讓 PipeServerIn 直接呼叫 Close 方法,就可以斷開。雖然我很直覺地試過要先呼叫 Disconnect,結果就是 block 住,反而無法動彈。PipeServerOut 則需要呼叫 Disconnect,它可以保證所有寫出的字都被讀取之後才斷開。

另一個流程是,PipeServerIn、PipeServerOut 沒有連接上,也就是正在等待連接,而 Server 程式想要關掉,此時 WaitForConnection 卻無法取消!因為 WaitForConnection 的無法取消,導致 thread 的資源沒有釋放乾淨。為了要離開 WaitForConnection,網路上有人提出一個解法,就是自己連上馬上斷掉。而我也採用了這個解法。準備了 PipeClientIn、PipeClientOut 連接,然後 Close。

Sub CleanPipeServer()
    Try
        If PipeServerIn.IsConnected Then
            'PipeServerIn.Disconnect() ' no need
            PipeServerIn.Close()
        Else
            PipeServerIn.Close() 'It Needs to close first. Client_Close_First will need this line.
            Dim pipename1 As String = TextBox1.Text & "_1"
            Dim PipeClientOut = New NamedPipeClientStream(".", pipename1, PipeDirection.Out)
            PipeClientOut.Connect(500)
            PipeClientOut.Close()
            PipeClientOut = Nothing
        End If
    Catch ex As Exception

    End Try
    Try
        If PipeServerOut.IsConnected Then
            PipeServerOut.Disconnect()
            PipeServerOut.Close()
        Else
            Dim pipename2 As String = TextBox1.Text & "_2"
            Dim PipeClientIn = New NamedPipeClientStream(".", pipename2, PipeDirection.In)
            PipeClientIn.Connect(500)
            PipeClientIn.Close()
            PipeClientIn = Nothing
        End If
    Catch ex As Exception

    End Try
    PipeServerIn = Nothing
    PipeServerOut = Nothing
End Sub

到這裡,Server 端程式就說明完畢了。

沒有留言:

張貼留言