1. 程式人生 > >【作業系統】程序間通訊(C#)

【作業系統】程序間通訊(C#)

程序間通訊

命名管道

程序間通訊的一種方式,Pipes:管道,分為無名管道:在父子程序間交換資料;有名管道:可在不同主機間交換資料,分為伺服器方和客戶方,在Win9X下只支援有名管道客戶。

命名管道的命名

命名管道是一個有名字的,單向或雙向的通訊管道。管道的名稱有兩部分組成:計算機名和管道名,例如\\[host_name]\pipe\[pipe_name]\(括號內為引數)。對於同一主機來講允許有多個同一命名管道的例項並且可以由不同的程序開啟,但是不同的管道都有屬於自己的管道緩衝區而且有自己的通訊環境互不影響,並且命名管道可以支援多個客戶端連線一個伺服器端。命名管道客戶端不但可以與本機上的伺服器通訊也可以同其他主機上的伺服器通訊。

命名管道的連線

在伺服器端第一次建立命名管道後等待連線,當客戶端連線成功後伺服器端的命名管道就用作通訊用途。如果需要再次等待連線,伺服器端就需要再次開啟命名管道(建立一個命名管道的例項)並等待連線。

對於客戶端每次開啟命名管道後建立與伺服器間的連線,然後就可以利用命名管道進行通訊,如果需要建立第二個連線則需要再次開啟管道和再次建立連線。

建立命名管道時需要指定一個主機名和管道名,對於客戶端來說可以是如下格式:\\[host_name]\pipe\[pipe_name]\也可以是\\.\pipe\pipe_name\其中.表示本機。而伺服器端只能夠在指定本機作為主機名,即只能使用下面的格式:\\.\pipe_name\。此外需要記住,在同一主機上管道名稱是唯一的,一個命名管道一旦被建立就不允許相同名稱的管道再被建立。

主要函式

管道伺服器首次呼叫CreateNamedPipe()函式時,使用nMaxInstance引數指定了能同時存在的管道例項的最大數目。伺服器可以重複呼叫CreateNamedPipe()函式去建立管道新的例項,直至達到設定的最大例項數。

伺服器方通過該函式建立命名管道和開啟已經存在的命名管道,其中lpName為管道名稱,dwOpenMode為建立方式,可以是下面值的組合:

PIPE_ACCESS_INBOUND:管道只能用作接收資料。

PIPE_ACCESS_OUTBOUND:管道只能用作傳送資料。

PIPE_ACCESS_DUPLEX:管道既可以傳送也可以接收資料。(上面這三個值只能夠取其中一個)

FILE_FLAG_WRITE_THROUGH:管道用於同步傳送和接收資料,只有在資料被髮送到目標地址時傳送函式才會返回,如果不設定這個引數那麼在系統內部對於命名管道的處理上可

能會因為減少網路附和而在資料積累到一定量時才傳送,並且對於傳送函式的呼叫會馬上返回。

管道的連線管理,客戶方在呼叫CreateFile後立即就能夠建立伺服器的連線,而伺服器方一旦管道開啟或建立後可以用

BOOL ConnectNamedPipe(
HANDLE hNamedPipe, // handle to named pipe
LPOVERLAPPED lpOverlapped // overlapped structure
);

來等待客戶端的連線建立。如果希望在伺服器方檢測是否有連線到達,可以呼叫

BOOL WaitNamedPipe(
LPCTSTR lpNamedPipeName, // pipe name
DWORD nTimeOut // time-out interval
);

這裡的lpNamePipeName直接使用建立管道時的名稱,如果在伺服器方希望關閉連線則呼叫

BOOL DisconnectNamedPipe(
HANDLE hNamedPipe // handle to named pipe
);
一旦連線被關閉,伺服器方可以再次呼叫ConnectNamedPipe來建立連線。如果要關閉管道則直接呼叫CloseHandle。請注意這裡提到的關閉管道和關閉連線是不同的意思,在同一個管道上可以依次反覆建立連線,而且可以減小系統的負荷。而且如果指定了管道最大數量限制那麼在開啟的管道達到最大限制後如果不關閉舊管道就無法開啟新管道。 對於客戶方則無法關閉連線,而只能直接呼叫CloseHandle關閉管道。

資料的傳送,不論是伺服器還是客戶方都可以通過ReadFile和WriteFile進行管道讀寫來達到通訊的目的。

【實驗說明】

在 第一次實驗 的基礎上,進行程序通訊實驗,用管道方式在兩個程序間進行通訊,要求能傳遞一個數據結構,結構如下:

struct
{
     int [9][9];
     byte[16];
     string;
}

不管用什麼方式進行編碼和解碼,需要兩個程序能夠相互傳遞


【實驗步驟】

1.查閱有關程序間通訊的資料

用管道實現程序間通訊,需要編寫伺服器及客戶端。

伺服器端設計:


客戶端設計:

namespace MyPipe
{
    class Program
    {
        static int numThreads = 2;
        static void Main(string[] args)
        {
            Thread newThread = new Thread(ServerThread);
            newThread.Start();
            Console.WriteLine("Press enter to exit.");
            Console.ReadLine();
        }
        private static void ServerThread(object data)
        {
            NamedPipeServerStream pipeServer =
                new NamedPipeServerStream("testpipe", PipeDirection.InOut, numThreads);
            Console.WriteLine("NamedPipeServerStream thread created.");

            //等待客戶端連線
            pipeServer.WaitForConnection();

            Console.WriteLine("Client connected.");
            try
            {
                StreamReader sr = new StreamReader(pipeServer);
                StreamWriter sw = new StreamWriter(pipeServer);
                sw.AutoFlush = true;
                //客戶端通過此訊息進行確認
                sw.WriteLine("My Server!");

                // Obtain the filename from the connected client.
                string content = sr.ReadLine();
                Console.WriteLine("Reading {0}", content);
                pipeServer.Disconnect();
            }
            catch (IOException e)
            {
                Console.WriteLine("ERROR: {0}", e.Message);
            }
            pipeServer.Close();
        } 
    } 
}

3.客戶端程式碼

在實驗一的基礎上,客戶端使用兩個執行緒,一個執行緒中例項一個客戶端,使用管道通訊向客戶端傳送資料(資料結構轉為string型別);另一個執行緒中接收伺服器端管道中的資料,再將string轉為定義的資料結構。

//定義要傳遞的資料結構
       PassStruct myPass = new PassStruct(
           new int[,]{
                    {1,2,3,4,5,6,7,8,9},
                    {1,2,3,4,5,6,7,8,9},
                    {1,2,3,4,5,6,7,8,9},
                    {1,2,3,4,5,6,7,8,9},
                    {1,2,3,4,5,6,7,8,9},
                    {1,2,3,4,5,6,7,8,9},
                    {1,2,3,4,5,6,7,8,9},
                    {1,2,3,4,5,6,7,8,9},
                     {1,2,3,4,5,6,7,8,9}
                }, 
                new byte[16] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7 },
                "Authror:小魏");
private void 開啟aToolStripMenuItem_Click(object sender, EventArgs e)
        {
            A = new Thread(new ThreadStart(exePa));
            A.IsBackground = true; 
            A.Start();
        }

        //宣告一個委託,用以解決控制元件繫結到特定執行緒丟擲的InvalidOperationException異常
        delegate void setRtbHandler(string s);
        private void setRtb(string s)
        {
            tabPage2.Controls[0].Text += s;
        }

        /// <summary>
        /// 程序Pa
        /// </summary>
        private void exePa()
        {
            ////原本用作測試的
            //this.tabPage2.Controls[0].Text = "aaa";
            info += "執行緒A(寫入執行緒)開啟\n";
            try{
                //這裡第一個引數是我的計算機名
                NamedPipeClientStream pipeClientA =
                new NamedPipeClientStream("WEI-THINKPAD", "testpipe",    
                PipeDirection.InOut, PipeOptions.None,
                TokenImpersonationLevel.Impersonation);
                StreamWriter sw = new StreamWriter(pipeClientA);
                StreamReader sr = new StreamReader(pipeClientA);
                pipeClientA.Connect();
                sw.AutoFlush = true;
                
                //確認伺服器連線
                if (sr.ReadLine() == "My Server!")
                {
                    //向管道中寫入資料(先轉化為字串)
                    toWrite = StructToString(myPass);
                    sw.Write(toWrite);
                }
                else
                {
                    info +="Server could not be verified.\n";
                }
                //關閉客戶端
                pipeClientA.Close();
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

       /// <summary>
       /// 關閉A
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
        private void 關閉AToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (A.IsAlive)
            {
                info += "執行緒A(寫入執行緒)已關閉\n";
                A.Abort();
            }
        }

        private void 開啟aToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            B = new Thread(new ThreadStart(exePb));
            B.IsBackground = true;
            B.Start();
        }

        /// <summary>
        /// 執行緒B
        /// </summary>
        private void exePb()
        {
            info += "執行緒B(讀出執行緒)開啟\n";
            try
            {
                NamedPipeClientStream pipeClientB =
                new NamedPipeClientStream("WEI-THINKPAD", "testpipe",
                PipeDirection.InOut, PipeOptions.None,
                TokenImpersonationLevel.Impersonation);
                StreamWriter sw = new StreamWriter(pipeClientB);
                StreamReader sr = new StreamReader(pipeClientB);
                pipeClientB.Connect();
                sw.AutoFlush = true;
                if (sr.ReadLine() == "My Server!")
                {

                    PassStruct getPass = StringToStruct(toWrite);
                    string structToShow="";
                    
                    //將讀到的資料結構以一定的格式顯示到螢幕上
                    for (int i = 0; i < 9; i++)
                {
                        for (int j = 0; j < 9; j++)
                        {
                            structToShow += getPass.arrayInt[i, j].ToString() + " ";
                        }
                        structToShow += "\n";
                    }
                    for (int k = 0; k < 16; k++)
                        structToShow += getPass.arrayByte[k].ToString() + " ";
                    structToShow += "\n";
                    structToShow += getPass.ss;
                    structToShow += "\n";

                    info += structToShow;
                }
                else
                {
                    info += "Server could not be verified.\n";
                }
                pipeClientB.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

        }

        /// <summary>
        ///關係執行緒B
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void 關?閉À?ToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            if (B.IsAlive)
            {
                B.Abort();
                A.Abort();
                info += "執行緒B(讀出執行緒)已關閉\n";
                this.tabPage2.Controls[0].Text = info;
            }
        }

4.資料結構與string相互轉化的函式

//資料結構轉為字串(所有的數字變為字串,用“,”隔開)
        public string StructToString(PassStruct ps)
        {
            string s = "";
            for (int i = 0; i < 9; i++)
                for (int j = 0; j < 9; j++)
                    s =s+(ps.arrayInt[i, j]).ToString() + ",";
            for (int k = 0; k < 16; k++)
                s = s + (ps.arrayByte[k]).ToString() + ",";
            s = s + ps.ss;
            return s;
        }

        //將string轉為定義的資料結構
        public PassStruct StringToStruct(string s)
        {
            int[,] x = new int[9, 9];
            for (int i = 0; i < 9; i++)
            {
                for (int j = 0; j < 9; j++)
                {
                    int p = s.IndexOf(',');// 通過‘,’找到分割
                    string tmp = s.Substring(0, p);// 擷取‘,’之前的部分轉為int
                    x[i, j] = int.Parse(tmp);
                    s = s.Remove(0, p + 1);// 通過Remove移除已轉為int的部分
                }
            }
            //同樣的方法得到byte部分
            byte[] y = new byte[16];
            for (int k = 0; k < 16; k++)
            {
                int p = s.IndexOf(',');
                string tmp = s.Substring(0, p);
                y[k] = byte.Parse(tmp);
                s = s.Remove(0, p + 1);
            }
            //剩下的部分為結構中字串的部分
            PassStruct getPass = new PassStruct(x, y, s);
            return getPass;
        }

【實驗結果】

伺服器端輸出截圖:

 

客戶端截圖:


【實驗中遇到的問題】

實驗的大部分地方都是用try catch來處理異常,catch中通過MessageBox顯示可以很快看到錯誤問題。實驗中遇到:


網上查閱了一些資料,瞭解訊號燈也是程序間通訊的一種方式。“訊號燈與其它程序間通訊方式有所不同,它主要用於程序間同步。通常所說的系統V訊號燈實際上是一個訊號燈的集合,可用於多種共享資源的程序間同步。每個訊號燈都有一個值,可以用來表示當前該訊號燈代表的共享資源可用(available)數量,如果一個程序要申請共享資源,那麼就從訊號燈值中減去要申請的數目,如果當前沒有足夠的可用資源,程序可以睡眠等待,也可以立即返回。”(http://www.cnblogs.com/thinkingworld/articles/1861739.html

不少人遇到過這個故障(資料庫中或硬碟讀盤),但最終還是沒有讀懂,不知道自己為什麼會遇到這個問題。猜測可能還是不同執行緒向視窗寫入東西(改變空間屬性)引起的問題。於是將輸出的資訊改為一次性輸出,暫時沒有再出現問題。(具體見程式碼中註釋)