2012年9月24日 星期一

System.IO系列:局域網內多線程使用命名管道在進程之間通信實例


本文介紹命名管道使用實例,文中例子是幾個客戶端都通過一台服務器獲得新生成的int類型id。
服務器端功能:當客戶端請求一個新的id時,將現有id自增1,然後返回給客戶端。
服務器端實現:在程序啟動時,啟動n個線程,在每個線程中都聲明一個NamedPipeServerStream的實例,並循環的WaitForConnection(),將新的id寫入到命名管道中,然後斷開連接。在程序退出時釋放NamedPipeServerStream實例
如下代碼實現: 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
 
using System.IO;
using System.IO.Pipes;
using System.Threading;
 
namespace IDServer
{
    class Program
    {
        /// <summary>
        /// 命名管道名字
        /// </summary>
        private const string PIPE_NAME = "testNetworkPipe";
 
        //定義線程數,也是NamedPipeServerStream的允許最多的實例數
        private const int MAX_THREADS_COUNT = 3;
        private static volatile int _runingThreadCount = 0;
 
        private static volatile int _newId = 0;
 
        //實例數組
        private static NamedPipeServerStream[] _serverStreams;
 
        static void Main(string[] args)
        {
            _serverStreams = new NamedPipeServerStream[MAX_THREADS_COUNT];
 
            //在進程退出時釋放所有NamedPipeServerStream實例
            AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit);
             
            //啟動線程
            StartServers();
 
            Console.Read();
        }
 
        /// <summary>
        /// 在進程退出時釋放命名管道
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void CurrentDomain_ProcessExit(object sender, EventArgs e)
        {
            if (_serverStreams != null)
            {
                foreach (NamedPipeServerStream item in _serverStreams)
                {
                    item.Dispose();
                }
            }
        }
 
        /// <summary>
        /// 啟動服務器端線程
        /// </summary>
        private static void StartServers()
        {
            for (int i = 0; i < MAX_THREADS_COUNT; i++)
            {
                Thread thread = new Thread(new ThreadStart(StartNewIDServer));
                thread.Start();
            }
        }
 
 
        /// <summary>
        /// 啟動一個NamedPipeServerStream實例
        /// </summary>
        private static void StartNewIDServer()
        {
            NamedPipeServerStream stream = null;
            Console.WriteLine("start server in thread " + Thread.CurrentThread.ManagedThreadId);
 
            stream = _serverStreams[_runingThreadCount] = new NamedPipeServerStream(PIPE_NAME,
                 PipeDirection.InOut,
                 MAX_THREADS_COUNT,
                 PipeTransmissionMode.Message,
                 PipeOptions.None);
            int threadNo = _runingThreadCount;
            _runingThreadCount += 1;
 
            while (true)
            {
                stream.WaitForConnection();
                int newId = ++_newId;
 
                byte[] bytes = BitConverter.GetBytes(newId);
                stream.Write(bytes, 0, bytes.Length);
                stream.Flush();
                Console.Write("threadNo:" + Thread.CurrentThread.ManagedThreadId + "\r");
                stream.Disconnect();
            }
        }
 
    }
}
客戶端的功能是不斷的發出獲得新id的請求,並打印新id,在客戶端可以配置服務端的服務器IP。
如下代碼:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
 
namespace IDClient
{
    class Program
    {
        private const string PIPE_NAME = "testNetworkPipe";
 
        static void Main(string[] args)
        {
            Console.WriteLine("請輸入任何字符回車開始執行程序..");
            Console.Read();
            do
            {
                //內網服務器ip,必須是局域網
                string serverName = "127.0.0.1";
                //聲明NamedPipeClientStream實例
                using (var clientStream = new System.IO.Pipes.NamedPipeClientStream(serverName, PIPE_NAME))
                {
                    //連接服務器
                    clientStream.Connect(1000);
                    //設置為消息讀取模式
                    clientStream.ReadMode = System.IO.Pipes.PipeTransmissionMode.Message;
 
                    do
                    {
                        byte[] bytes = new byte[4];
                        clientStream.Read(bytes, 0, 4);
                        int val = BitConverter.ToInt32(bytes, 0);
                        Console.Write("NewID == " + val + "\r");
                    } while (!clientStream.IsMessageComplete);
                }
 
                Thread.Sleep(1);
            } while (true);
        }
    }
}
在sql server中就使用了命名管道在局域網內掛進程通訊。
在聲明NamedPipeServerStream實例是可以指定其實例個數,如果實例數超過這個數,就會拋出「所有管道範例都在使用中」的IO異常。
本例不可以在實際項目中使用。