c#多進(jìn)程通訊的實(shí)現(xiàn)示例
引言
在c#中,可能大多數(shù)人針對(duì)于多線程之間的通訊,是熟能生巧,對(duì)于AsyncLocal 和ThreadLocal以及各個(gè)靜態(tài)類中支持線程之間傳遞的GetData和SetData方法都是信手拈來,那多進(jìn)程通訊呢,實(shí)際上也是用的比較多的地方,但是能夠熟能生巧的人和多線程的相比的話呢,那還是有些差距的,所以我昨天整理了一下我所認(rèn)知的幾個(gè)多進(jìn)程之間的通訊方式,這其中是不包括各種消息中間件以及數(shù)據(jù)庫方面的,還有Grpc,WebSocket或者Signalr等方式,僅僅是以c#代碼為例,c#的多進(jìn)程通訊呢,大致上是分為這幾類的,共享內(nèi)存,借助Windows的MSMQ消息隊(duì)列服務(wù),以及命名管道和匿名管道,以及IPC HTTP TCP的Channel的方式,還有常用的Socket,借助Win32的SendMessage的Api來實(shí)現(xiàn)多進(jìn)程通訊,還有最后一種就是多進(jìn)程之間的信號(hào)量相關(guān)的Mutex,代碼我會(huì)放在文章的末尾,大家有需要的話可以去下載來看看,接下來就為大家一一奉上。
共享內(nèi)存
共享內(nèi)存呢,實(shí)際上c#中可以有很多種實(shí)現(xiàn)方式,主要是借助于Win32的Api來實(shí)現(xiàn)以及,使用MemoryMappedFile這個(gè)類來實(shí)現(xiàn)共享內(nèi)存,前者需要引入多個(gè)Win32的dll的方法,后者使用起來就比較簡(jiǎn)單,只需要調(diào)用類的CreatNew方法設(shè)置好內(nèi)存映射文件名稱以及大小,以及操作權(quán)限就可以實(shí)現(xiàn),同時(shí)支持Accessor和Stream的方式去進(jìn)行讀寫,但是性能方面肯定是Win32的性能好,而且Win32的話不受語言的限制,至于這個(gè)類是否受限于語言,目前我是不太清楚的。接下來,咱們就看看客戶端和服務(wù)端使用共享內(nèi)存的方式和獲取數(shù)據(jù)的代碼。
服務(wù)端:
MemoryMappedFile memoryAccessor = MemoryMappedFile.CreateNew("ProcessCommunicationAccessor", 500, MemoryMappedFileAccess.ReadWrite);//創(chuàng)建共享內(nèi)存映射文件對(duì)象,第一個(gè)參數(shù)為映射的名稱,與客戶端需要對(duì)應(yīng),500為大小,單位為字節(jié),MemoryMappedFileAccess為訪問權(quán)限,是讀寫還是只讀 只寫,此處不能使用Using 否則脫離Using 就會(huì)釋放,客戶端無法獲取到此名稱的內(nèi)存映射對(duì)象 using (var accessor = memoryAccessor.CreateViewAccessor())//獲取映射文件對(duì)象的視圖 { var helo = Encoding.UTF8.GetBytes("Accessor"); accessor.WriteArray(0, helo, 0, helo.Length);//將給定的值寫入此視圖中 richTextBox1.Text += Environment.NewLine + "Accessor Send Val:Accessor"; } MemoryMappedFile memoryStream = MemoryMappedFile.CreateNew("ProcessCommunicationStream", 500, MemoryMappedFileAccess.ReadWrite);//創(chuàng)建流的映射文件對(duì)象 using (var stream = memoryStream.CreateViewStream())//獲取映射文件的流 { var helo = Encoding.UTF8.GetBytes("Stream"); stream.Write(helo, 0, helo.Length);//將給定的值寫入此內(nèi)存流中 richTextBox1.Text += Environment.NewLine + "Accessor Send Val:Stream"; }
客戶端:
MemoryMappedFile memoryAccessor = MemoryMappedFile.OpenExisting("ProcessCommunicationAccessor");//獲取服務(wù)端定義的ProcessCommunicationAccessor名稱的內(nèi)存映射文件然后調(diào)用ReadArray方法讀取到服務(wù)端寫入的數(shù)據(jù) using (var accessor = memoryAccessor.CreateViewAccessor()) { var s = new byte[999]; var read = accessor.ReadArray(0, s, 0, s.Length); var str = Encoding.UTF8.GetString(s); richTextBox1.Text += Environment.NewLine + "Accessor Read Val:" + str.ToString(); } MemoryMappedFile memoryStream = MemoryMappedFile.OpenExisting("ProcessCommunicationStream");//獲取服務(wù)端定義的ProcessCommunicationStream名稱的內(nèi)存映射文件然后調(diào)用ReadToEnd方法讀取到服務(wù)端寫入的數(shù)據(jù) using (var stream = memoryStream.CreateViewStream()) { using (var reader = new StreamReader(stream)) { var str = reader.ReadToEnd(); richTextBox1.Text += Environment.NewLine + "Stream Read Val:" + str + "\r\n"; } }
可以看到我們?cè)诜?wù)端定義了一個(gè)是Accessor類型的MemoryMappedFile在寫入數(shù)據(jù)的時(shí)候是用MemortViewAccessor的方式去寫入的,然后又定義了一個(gè)使用Stream的方式去進(jìn)行寫入數(shù)據(jù),在客戶端中,我們直接使用OpenExisting方法去判斷是否存在這個(gè)對(duì)象,如果存在的話,就使用了服務(wù)端定義的CreatNew這個(gè)對(duì)象,如果不存在則是Null,當(dāng)然了也可以使用其他的方式去進(jìn)行獲取,例如CreateOrOpen判斷是否是獲取的還是重新創(chuàng)建的方式,我們?cè)诳蛻舳耸褂肦eadArray和ReadToEnd的方式讀取了服務(wù)端寫入的Accessor和Stream的數(shù)據(jù),然后我們就可以在客戶端和服務(wù)端之間進(jìn)行一個(gè)數(shù)據(jù)傳輸?shù)囊粋€(gè)通訊。
Windows的MSMQ
使用MSMQ的前提是需要在本計(jì)算機(jī)安裝了消息隊(duì)列,安裝方式需要在控制面板,程序和功能那里啟用或關(guān)閉程序,在列表中找到我們需要的消息隊(duì)列(MSMQ)服務(wù)器然后安裝,安裝完成后,我們點(diǎn)擊我的電腦右鍵管理找到最下面的服務(wù)和應(yīng)用程序就可以看到我們安裝的消息隊(duì)列了,然后找到專用隊(duì)列,我們?cè)谶@里新建一個(gè)隊(duì)列,然后就可以在我們的代碼中使用了,這里呢我只是簡(jiǎn)單寫一個(gè)示范,實(shí)際上在Messaging命名空間里,還支持對(duì)消息隊(duì)列權(quán)限的控制,等等的操作,接下來我們看看如何在代碼中使用消息隊(duì)列。
服務(wù)端中我們定義了我們需要使用的消息隊(duì)列的類型以及名稱,名稱規(guī)范的話也可以參考官網(wǎng)對(duì)名稱定義的介紹,還支持其他方式名稱的定義,定義好之后呢,我們便發(fā)送了一個(gè)消息Message HelloWorld的一條消息
MessageQueue queue = new MessageQueue(".\\Private$\\MessageQueue");//右鍵我的電腦,點(diǎn)擊管理 找到服務(wù)和應(yīng)用程序找到專用隊(duì)列,創(chuàng)建的專用隊(duì)列名稱就是MessageQueue queue.Send("Message HelloWorld");//然后發(fā)送消息 richTextBox1.Text += Environment.NewLine + "MessageQueue Send Val:Message HelloWorld";
客戶端中,我們也是和服務(wù)端定義了一個(gè)消息隊(duì)列的一個(gè)對(duì)象,然后我們監(jiān)聽這個(gè)消息隊(duì)列的收到消息的事件,開始異步接收消息,在接收完畢之后呢,會(huì)走到我們寫的ReceiveCompleted的完成事件中,然后我們結(jié)束異步接收的,獲取到服務(wù)端發(fā)送的消息,然后使用XmlMessageFormatter對(duì)象去格式化我們服務(wù)端發(fā)送的消息,這里的Type是服務(wù)端發(fā)送的消息類型,兩者需要對(duì)應(yīng),在接受并展示到UI之后,我們?cè)陂_始異步接收。
var context = WindowsFormsSynchronizationContext.Current; MessageQueue myQueue = new MessageQueue(".\\Private$\\MessageQueue");//定義消息隊(duì)列對(duì)象,和服務(wù)端的地址一樣, myQueue.ReceiveCompleted += (a, b) =>//定義接受完成的時(shí)間 { var cts = context; var queue = a as MessageQueue;//隊(duì)列對(duì)象 queue.EndReceive(b.AsyncResult); var msg = b.Message;//接收到的消息對(duì)象 msg.Formatter = new XmlMessageFormatter() { TargetTypes = new Type[] { typeof(string) } };//設(shè)置接收到的消息使用什么方式格式化 var msgVal = msg.Body;//此處是服務(wù)端發(fā)送的具體的消息對(duì)象 cts.Send(new System.Threading.SendOrPostCallback(s => { richTextBox1.Text += Environment.NewLine + "MessageQueue Read Val:" + msgVal + "\r\n"; }), null); queue.BeginReceive(); }; myQueue.BeginReceive();
命名管道
命名管道和匿名管道位于System.Io.Pipe命名空間下,顧名思義,命名管道是需要我們給管道命名一個(gè)名稱的以便于客戶端來進(jìn)行連接,我們需要定義管道的名稱,指定管道的方向,是輸入還是輸出 還是輸入輸出,還可以定義最大的服務(wù)端實(shí)例數(shù)量,以及傳輸?shù)南㈩愋褪荁yte還是Message,以及是否開啟異步等。接下來我們看看服務(wù)端和客戶端之間通訊的代碼。
服務(wù)端:我們定義了管道名稱是ProcessCommunicationPipe,并且定義是可以輸入也可以輸出,10個(gè)實(shí)例,以及使用Message傳輸類型,開啟異步通訊,然后我們異步的等待客戶端鏈接,在鏈接成功之后呢,我們通知UI客戶端已經(jīng)鏈接到了服務(wù)端,然后異步去接收客戶端發(fā)來的消息,并且展示到UI上面。
///定義一個(gè)命名管道,第一個(gè)參數(shù)是管道名稱,第二個(gè)參數(shù)代表是輸入類型還是輸出類型 還是輸入輸出類型,以及設(shè)置最大的服務(wù)器實(shí)例,設(shè)置傳輸類型,以及開啟可以異步的進(jìn)行讀取和寫入 namedPipeServerStream = new NamedPipeServerStream("ProcessCommunicationPipe", PipeDirection.InOut, 10, PipeTransmissionMode.Message, PipeOptions.Asynchronous); //異步等待客戶端鏈接,如果上面的Options不是Asynchronous 異步則會(huì)報(bào)錯(cuò) namedPipeServerStream.WaitForConnectionAsync().ContinueWith(s => { var cts = synchronizationContext; //刷新UI 告知有客戶端鏈接 cts.Send(new System.Threading.SendOrPostCallback(b => { richTextBox1.Text += Environment.NewLine + "Client Is Connected;"; }), null); var valByte = new byte[1024]; //異步讀取客戶端發(fā)送的消息 namedPipeServerStream.ReadAsync(valByte, 0, valByte.Length).ContinueWith(m => { var val = valByte; var str = Encoding.UTF8.GetString(val); cts.Send(new System.Threading.SendOrPostCallback(b => { richTextBox1.Text += Environment.NewLine + "Server Receive Val:" + str; }), null); }); });
服務(wù)端發(fā)送代碼:我們定義了一個(gè)Send的發(fā)送按鈕,以及一個(gè)發(fā)送內(nèi)容的文本框,然后我們只需要調(diào)用Server的WriteAsync就可以將我們的數(shù)據(jù)寫入到Server中發(fā)送到客戶端。
//命名管道發(fā)送消息到客戶端 var data = Encoding.UTF8.GetBytes(textBox1.Text); //發(fā)送消息到客戶端 namedPipeServerStream.WriteAsync(data, 0, data.Length); richTextBox1.Text += Environment.NewLine + "Server Send Val:" + textBox1.Text;
客戶端:
我們定義了一個(gè)Client的對(duì)象,.代表是當(dāng)前計(jì)算機(jī),以及和服務(wù)端一樣的管道名稱,同樣定義為開啟異步,以及是輸入輸出類型的。然后異步的去鏈接服務(wù)端,然后更新UI,通知已經(jīng)鏈接成功,并且異步等待服務(wù)端給客戶端發(fā)送消息,從而顯示到UI上面。
var cts = WindowsFormsSynchronizationContext.Current; //定義管道對(duì)象,如果需要是網(wǎng)絡(luò)之間通信.替換為服務(wù)端的服務(wù)器名稱和pipeName namedPipeClientStream = new NamedPipeClientStream(".", "ProcessCommunicationPipe", PipeDirection.InOut, PipeOptions.Asynchronous); //異步鏈接服務(wù)端 namedPipeClientStream.ConnectAsync().ContinueWith(s => { var cs = cts; cs.Send(new System.Threading.SendOrPostCallback(b => { richTextBox1.Text += Environment.NewLine + "Server Is Connected;"; }), null); var valByte = new byte[1024]; //異步等待收到服務(wù)端發(fā)送的消息 然后更新到UI namedPipeClientStream.ReadAsync(valByte, 0, valByte.Length).ContinueWith(sb => { var val = valByte; var str = Encoding.UTF8.GetString(val); cts.Send(new System.Threading.SendOrPostCallback(b => { richTextBox1.Text += Environment.NewLine + "Client Receive Val:" + str; }), null); }); });
客戶端發(fā)送代碼:同服務(wù)端一樣,寫入我們的數(shù)據(jù),服務(wù)端就會(huì)走到ReadAsync的方法中去,服務(wù)端就可以接收到我們發(fā)送的數(shù)據(jù)并且展示到UI,
//命名管道發(fā)送消息到服務(wù)端 var data = Encoding.UTF8.GetBytes(textBox1.Text); namedPipeClientStream.WriteAsync(data, 0, data.Length); richTextBox1.Text += Environment.NewLine + "Client Send Val:" + textBox1.Text;
匿名管道
匿名管道是我們服務(wù)端是父進(jìn)程,需要我們服務(wù)端去使用Process啟用開啟我們的子進(jìn)程,然后傳入我們客戶端的句柄到客戶端,客戶端再根據(jù)傳入的參數(shù)鏈接到服務(wù)端,從而可以實(shí)現(xiàn)通訊,但是匿名管道不支持網(wǎng)絡(luò)之間的通訊,以及不支持輸入輸出,僅支持要么輸入要么輸出,同時(shí),匿名管道提供了PipeAccessRule來控制訪問權(quán)限。接下來,我們看一下客戶端和服務(wù)端是如何通訊,以及服務(wù)端如何去啟動(dòng)客戶端。
服務(wù)端:服務(wù)端去定義Process設(shè)置我們需要啟動(dòng)的子進(jìn)程,然后定義我們的匿名管道,然后將客戶端鏈接的Handlestring傳到客戶端,然后啟動(dòng)我們的客戶端,在定義異步接收消息之后的回調(diào),然后展示到頁面上。
//定義客戶端子進(jìn)程 Process Client = new Process(); //子進(jìn)程路徑 Client.StartInfo.FileName = @"E:\CoreRepos\ProcessCommunicationClient\bin\Debug\ProcessCommunicationClient.exe"; //定義匿名管道, AnonymousPipeServerStream anonymousPipeServerStream = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable); Client.StartInfo.Arguments = anonymousPipeServerStream.GetClientHandleAsString(); Client.StartInfo.UseShellExecute = false; Client.Start(); //關(guān)閉本地復(fù)制的客戶端 anonymousPipeServerStream.DisposeLocalCopyOfClientHandle(); var byteVal = new byte[1024]; //異步接受收到的消息 anonymousPipeServerStream.ReadAsync(byteVal, 0, byteVal.Length).ContinueWith(s => { var cts = synchronizationContext; var val = byteVal; var str = Encoding.UTF8.GetString(val); cts.Send(new System.Threading.SendOrPostCallback(b => { richTextBox1.Text += Environment.NewLine + "匿名 Server Receive Val:" + str; }), null); });
客戶端:客戶端中我們需要將Winform的Program的Main方法中添加一個(gè)string數(shù)組的參數(shù)然后傳入到我們的窗體中,這樣匿名客戶端管道鏈接服務(wù)端就可以鏈接成功。
//此處定義匿名管道的對(duì)象,Vs[0]來自服務(wù)端的Process的Arguments屬性的值 anonymousPipeClientStream = new AnonymousPipeClientStream(PipeDirection.Out, Vs[0]);
客戶端發(fā)送代碼:
我們直接調(diào)用WriteAsync方法寫入我們的數(shù)據(jù),服務(wù)端就可以接收到我們發(fā)送的信息。
//發(fā)送消息到匿名管道服務(wù)端 var vss = Encoding.UTF8.GetBytes(textBox2.Text); anonymousPipeClientStream.WriteAsync(vss, 0, vss.Length); richTextBox1.Text += Environment.NewLine + "匿名Client Send Val:" + textBox2.Text;
Channel
Channel下面是有IPC,HTTP和TCP三種類型,三種類型都提供了ClientChannel 以及ServerChannel和Channel的類,Channel類是簡(jiǎn)化了Server和Client的操作,可以直接使用Channel來進(jìn)行定義服務(wù)端和客戶端通訊的對(duì)象,接下面我們看看Ipc通訊的方式。
IPC
我們定義了一個(gè)IpcChannel的對(duì)象并且指定ip為127.0.0.1端口是8081,然后我們需要向管道服務(wù)注冊(cè)我們的管道信息,然后注冊(cè)我們需要注入的類型,以及資源的URL地址,還有生命周期是單例還是每次獲取都不一樣,只有這兩種周期,然后我們看看客戶端使用的代碼。
服務(wù)端:
///定義IPC信道,端口和ip,也可以直接定義端口 ipcChannel = new IpcChannel("127.0.0.1:8081"); //向信道注冊(cè)當(dāng)前管道 ChannelServices.RegisterChannel(ipcChannel, true); //注入對(duì)象到服務(wù)端,并且指定此對(duì)象的URL,以及生命周期,是單例還是每次獲取都不一樣 RemotingConfiguration.RegisterWellKnownServiceType(typeof(ProcessCommunicationIpc), "Ipc.rem", WellKnownObjectMode.Singleton); richTextBox1.Text += Environment.NewLine + "IPCServer Is Open;";
客戶端:
我們定義了一個(gè)空的管道信息并且注冊(cè)進(jìn)去,然后定義我們需要獲取的類型,以及類型的URL資源地址,并且調(diào)用RegisterWellKnownClientType方法,這個(gè)方法我的見解是相當(dāng)于告知服務(wù)端我們需要使用的資源,然后我們直接New這個(gè)對(duì)象,調(diào)用SetName方法,就可以實(shí)現(xiàn)通訊,那如果服務(wù)端怎么獲取到數(shù)據(jù)呢,那有的同學(xué)就會(huì)問了,莫急,我們看下一段代碼。
IpcChannel ipcChannel = new IpcChannel();//定義一個(gè)IPC管道對(duì)象同樣需要注冊(cè)到管道服務(wù)中 ChannelServices.RegisterChannel(ipcChannel, true); WellKnownClientTypeEntry entry = new WellKnownClientTypeEntry(typeof(ProcessCommunicationIpc), "ipc://127.0.0.1:8081/Ipc.rem");//定義我們需要獲取的類型以及此類型的Url RemotingConfiguration.RegisterWellKnownClientType(entry);//相當(dāng)于告知服務(wù)端我們需要用的對(duì)象 ProcessCommunicationIpc processCommunicationIpc = new ProcessCommunicationIpc();//定義一個(gè)這個(gè)對(duì)象 processCommunicationIpc.SetName(textBox3.Text);//然后調(diào)用這個(gè)SetNama方法 richTextBox1.Text += Environment.NewLine + "IPCClient Send Val:" + textBox3.Text;
服務(wù)端接收代碼:我們直接調(diào)用Activator的GetObject方法從我們服務(wù)端定義的地址獲取到我們注冊(cè)的類型,然后調(diào)用Name屬性就可以看到Name是我們客戶端寫入的數(shù)據(jù),因?yàn)槲覀兌x的生命周期是單例的,所以這里可以實(shí)現(xiàn)客戶端和服務(wù)端之間的通訊,實(shí)際上Http和Tcp的使用方式同IPC一樣,都是大同小異,我們可以看看HTTP和TCP使用的代碼就會(huì)明白了。
//從我們定義的IPCurl獲取代理對(duì)象,然后判斷值是否改變 var processCommunicationIpc = Activator.GetObject(typeof(ProcessCommunicationIpc), "ipc://127.0.0.1:8081/Ipc.rem") as ProcessCommunicationIpc; var name = processCommunicationIpc.Name; richTextBox1.Text += Environment.NewLine + "IPCServer Receive Val:" + name;
Http
服務(wù)端:
///定義HTTP信道,端口 HttpChannel httpChannel = new HttpChannel(8082); //向信道注冊(cè)當(dāng)前管道 ChannelServices.RegisterChannel(httpChannel, false); //注入對(duì)象到服務(wù)端,并且指定此對(duì)象的URL,以及生命周期,是單例還是每次獲取都不一樣 RemotingConfiguration.RegisterWellKnownServiceType(typeof(ProcessCommunicationHttp), "Http.rem", WellKnownObjectMode.Singleton); richTextBox1.Text += Environment.NewLine + "HttpServer Is Open;";
服務(wù)端接收:
//從我們定義的Http url獲取代理對(duì)象,然后判斷值是否改變 var processCommunicationIpc = Activator.GetObject(typeof(ProcessCommunicationHttp), "http://127.0.0.1:8082/Http.rem") as ProcessCommunicationHttp; var name = processCommunicationIpc.Name; richTextBox1.Text += Environment.NewLine + "HttpServer Receive Val:" + name;
客戶端:
HttpChannel httpChannel=new HttpChannel();//定義一個(gè)HTTP管道對(duì)象同樣需要注冊(cè)到管道服務(wù)中 ChannelServices.RegisterChannel(httpChannel, false); WellKnownClientTypeEntry entry = new WellKnownClientTypeEntry(typeof(ProcessCommunicationHttp), "http://127.0.0.1:8082/Http.rem");//定義我們需要獲取的類型以及此類型的Url RemotingConfiguration.RegisterWellKnownClientType(entry);//相當(dāng)于告知服務(wù)端我們需要用的對(duì)象 ProcessCommunicationHttp processCommunicationIpc = new ProcessCommunicationHttp();//定義一個(gè)這個(gè)對(duì)象 processCommunicationIpc.SetName(textBox4.Text);//然后調(diào)用這個(gè)SetNama方法 richTextBox1.Text += Environment.NewLine + "HttpClient Send Val:" + textBox4.Text;
TCP
服務(wù)端:
///定義Tcp信道,端口 TcpChannel tcpChannel = new TcpChannel(8083); //向信道注冊(cè)當(dāng)前管道 ChannelServices.RegisterChannel(tcpChannel, true); //注入對(duì)象到服務(wù)端,并且指定此對(duì)象的URL,以及生命周期,是單例還是每次獲取都不一樣 RemotingConfiguration.RegisterWellKnownServiceType(typeof(ProcessCommunicationTcp), "Tcp.rem", WellKnownObjectMode.Singleton); richTextBox1.Text += Environment.NewLine + "TcpServer Is Open;";
服務(wù)端接收:
//從我們定義的Tcp url獲取代理對(duì)象,然后判斷值是否改變 var processCommunicationIpc = Activator.GetObject(typeof(ProcessCommunicationTcp), "tcp://127.0.0.1:8083/Tcp.rem") as ProcessCommunicationTcp; var name = processCommunicationIpc.Name; richTextBox1.Text += Environment.NewLine + "TcpServer Receive Val:" + name;
客戶端:
TcpChannel tcpChannel = new TcpChannel();//定義一個(gè)TCP管道對(duì)象同樣需要注冊(cè)到管道服務(wù)中 ChannelServices.RegisterChannel(tcpChannel, true); WellKnownClientTypeEntry entry = new WellKnownClientTypeEntry(typeof(ProcessCommunicationTcp), "tcp://127.0.0.1:8083/Tcp.rem");//定義我們需要獲取的類型以及此類型的Url RemotingConfiguration.RegisterWellKnownClientType(entry);//相當(dāng)于告知服務(wù)端我們需要用的對(duì)象 ProcessCommunicationTcp processCommunicationIpc = new ProcessCommunicationTcp();//定義一個(gè)這個(gè)對(duì)象 processCommunicationIpc.SetName(textBox5.Text);//然后調(diào)用這個(gè)SetNama方法 richTextBox1.Text += Environment.NewLine + "TcpClient Send Val:" + textBox5.Text;
可以看到基本上都是一樣的,但是有些地方是不一樣的,這里我是沒有寫那部分的代碼,例如Http是可以配置HttpHandler的,其他方面使用起來都是大同小異。
Socket
Socket可能是大家用的最多的進(jìn)程通訊了,它也不僅僅是進(jìn)程之間,同時(shí)也是支持網(wǎng)絡(luò)之間的通訊,同時(shí)協(xié)議類型支持的也是比較多的,并且支持雙向通訊,可以發(fā)送文件等,這里就不作過多的介紹了,直接上代碼
服務(wù)端:
我們直接定義服務(wù)端對(duì)象,并且指定地址和端口開始監(jiān)聽并且異步等待鏈接,
//定義Socket對(duì)象,以及協(xié)議,傳輸類型 Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp); var ipAddress = IPAddress.Parse("127.0.0.1"); var endpoint = new IPEndPoint(ipAddress, 8084); //指定綁定的ip和端口 socket.Bind(endpoint); //鏈接的最大長(zhǎng)度 socket.Listen(10); socket.BeginAccept(Accept, socket);//異步等待鏈接 richTextBox1.Text += Environment.NewLine + "Socket Server Is Listening;";
服務(wù)端異步接受代碼:在有連接之后我們直接去獲取到鏈接的客戶端對(duì)象的Socket并且賦值給我們的Socket全局變量,然后更新UI,并且異步的去讀取客戶端發(fā)送的消息。
private void Accept(IAsyncResult asyncResult) { var socket = asyncResult.AsyncState as Socket; var client = socket.EndAccept(asyncResult);//獲取鏈接的客戶端 if (client != null) { var cs = synchronizationContext; Client=client; //更新UI 提示已經(jīng)鏈接 cs.Send(new System.Threading.SendOrPostCallback(b => { richTextBox1.Text += Environment.NewLine + "Socket Client Is Connected;"; }), null); //異步接受消息 client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, Read, client); } socket.BeginAccept(Accept, socket); }
服務(wù)端接收數(shù)據(jù)代碼:
我們?cè)诮邮盏搅丝蛻舳税l(fā)的消息之后,我們解析成字符串,然后更新到UI上面。
var cts = synchronizationContext; var client = asyncResult.AsyncState as Socket; var data=client.EndReceive(asyncResult);//獲取接受的數(shù)據(jù)長(zhǎng)度 var str = Encoding.UTF8.GetString(buffer);//轉(zhuǎn)換為字符然后顯示到界面 cts.Send(new System.Threading.SendOrPostCallback(b => { richTextBox1.Text += Environment.NewLine + "Socket Server Receive Val:" + str; }), null);
服務(wù)端發(fā)送代碼:
我們直接調(diào)用我們獲取到的Client的Socket對(duì)象,發(fā)送我們需要發(fā)送的消息即可。
//將消息發(fā)送到客戶端 var sendVal=Encoding.UTF8.GetBytes(textBox2.Text); Client.Send(sendVal,SocketFlags.None); richTextBox1.Text += Environment.NewLine + "Socket Server Send Val:" + textBox2.Text;
客戶端:定義好服務(wù)端的IP和端口然后我們異步鏈接,在鏈接成功之后我們?cè)诎l(fā)送我們的數(shù)據(jù)到服務(wù)端,并且異步等待服務(wù)端給我們發(fā)送消息。
var cs = cts; //定義Socket客戶端對(duì)象 Socket socket = new Socket(SocketType.Stream,ProtocolType.Tcp); var ipAddress = IPAddress.Parse("127.0.0.1"); var endpoint = new IPEndPoint(ipAddress, 8084); //定義需要鏈接的服務(wù)端的IP和端口然后異步鏈接服務(wù)端 socket.ConnectAsync(endpoint).ContinueWith(s => { //鏈接之后發(fā)送消息到服務(wù)端 var arg = new SocketAsyncEventArgs(); var sendVal=Encoding.UTF8.GetBytes(textBox6.Text); arg.SetBuffer(sendVal,0, sendVal.Length); socket.SendAsync(arg); cs.Send(new System.Threading.SendOrPostCallback(b => { richTextBox1.Text += Environment.NewLine + "Socket Client Send Val:" + textBox6.Text; }), null); //異步等待服務(wù)端發(fā)送的消息 socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, Read, socket); });
客戶端接收代碼:
我們直接從我們的服務(wù)端Socket對(duì)象中讀取我們的數(shù)據(jù)然后展示到UI上面。
var cs = cts; var client = asyncResult.AsyncState as Socket; var data = client.EndReceive(asyncResult); //獲取服務(wù)端給客戶端發(fā)送的消息 var str = Encoding.UTF8.GetString(buffer); cs.Send(new System.Threading.SendOrPostCallback(b => { richTextBox1.Text += Environment.NewLine + "Socket Client Receive Val:" + str; }), null);
Win32 Api SendMessage
在窗體程序中,我們可以重寫窗體的DefWndProc方法,來實(shí)現(xiàn)進(jìn)程之間的消息通訊,需要引入Win32的SendMessage方法來實(shí)現(xiàn),這個(gè)方法可以實(shí)現(xiàn)給一個(gè)或者多個(gè)窗體之間發(fā)送消息,我們可以指定我們需要發(fā)送的窗體的句柄,以及我們發(fā)送的消息類型的Code也可以自己寫,以及我們需要傳過去的參數(shù),可以定義為結(jié)構(gòu)體進(jìn)行傳送,接收方,再從內(nèi)存中將句柄轉(zhuǎn)為對(duì)應(yīng)的結(jié)構(gòu)體就可以使用,這里我使用的傳輸數(shù)據(jù)類型是Int類型的數(shù)據(jù),如果需要傳結(jié)構(gòu)體的話,引入的Dll設(shè)置SendMessage方法處可以設(shè)置,以及在接收方需要使用內(nèi)存的操作類Marshal類進(jìn)行轉(zhuǎn)為結(jié)構(gòu)體,接下來我們看看客戶端是如何和服務(wù)端進(jìn)行通訊的。
服務(wù)端:我們重寫這個(gè)方法之后,等待客戶端給我們發(fā)送消息就行,m.msg是和客戶端商定好的消息類型。
protected override void DefWndProc(ref System.Windows.Forms.Message m) { if (m.Msg == 0x1050) { var paraA =(int) m.WParam; var paramB = (int)m.LParam; richTextBox1.Text += Environment.NewLine + "Win32 Msg Receive Val:"+paraA; richTextBox1.Text += Environment.NewLine + "Win32 Msg Receive Val:" + paramB; } base.DefWndProc(ref m); }
客戶端代碼:
我們需要引入我們使用的SendMessage方法
[DllImport("user32.dll", EntryPoint = "SendMessage")] private static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam,int lParam);
發(fā)送代碼:
我們需要獲取到我們要發(fā)送給那個(gè)進(jìn)程,然后獲取到主程序的句柄,然后傳入我們的消息code,以及我們的參數(shù)信息,這樣服務(wù)端就可以接收到我們客戶端發(fā)送過去的10,20的數(shù)據(jù),
//獲取到我們需要發(fā)送到的窗體的進(jìn)程,然后獲取他的主窗體句柄,將我們的消息10,20發(fā)送到指定的窗體中,然后會(huì)執(zhí)行DefWndProc方法,然后在方法中判斷msg類型是否和我們這邊發(fā)送的0x1050一致,就可以收到客戶端發(fā)送的消息,第二個(gè)參數(shù)是我們定義的消息類型,可以自己定義數(shù)字 也可以根據(jù)Win32 api里面規(guī)定的對(duì)應(yīng)的功能用哪些也可以 var process=Process.GetProcessesByName("ProcessCommunication").FirstOrDefault(); SendMessage(process.MainWindowHandle, 0x1050, 10,20);
Mutex信號(hào)量
在前面的多線程博文中,我有講過Mutex是進(jìn)程之間也可以,是操作系統(tǒng)層面的,我們可以使用WaitOne進(jìn)入到我們的代碼段中,并且只有一個(gè)線程可以進(jìn)入,在結(jié)束后我們需要釋放調(diào)這個(gè)鎖,從而其他線程就可以獲取到,既然Mutex是進(jìn)程之間也可以,那多個(gè)進(jìn)程之間也可以共享一個(gè)Mutex對(duì)象,A進(jìn)程使用WaitOnd的時(shí)候B進(jìn)程是只能等待A進(jìn)程釋放才可以使用。
服務(wù)端代碼:
我們定義了Mutex的對(duì)象,然后開啟了一個(gè)線程去進(jìn)行死循環(huán)刷新UI信息,然后循環(huán)內(nèi)部我們鎖定鎖,然后通知UI,然后在釋放鎖,這樣客戶端同樣的代碼必須等到ReleaseMutex之后才可以進(jìn)去到循環(huán)內(nèi)部更新UI的部分。
var isNew = false; //定義Mutex對(duì)象,參數(shù)一是否具有初始權(quán),第二個(gè)為系統(tǒng)中的名稱,第三個(gè)代表是否是新建的; var mutex = new Mutex(false, "ProcessCommunication", out isNew);//用來和客戶端用同一個(gè)對(duì)象,在循環(huán)中有且僅有一個(gè)進(jìn)程可以使用這個(gè)對(duì)象,即子進(jìn)程在使用WaitOne方法的時(shí)候 父進(jìn)程是沒有辦法進(jìn)入到循環(huán)體中,只有調(diào)用了子進(jìn)程調(diào)用ReleaseMutex方法,父進(jìn)程才可以使用;通常可以用這個(gè)可以實(shí)現(xiàn)多進(jìn)程訪問同一個(gè)文件 等。 Task.Run(() => { var cs = synchronizationContext; int i = 0; while (true) { mutex.WaitOne(); cs.Send(new SendOrPostCallback(s => { richTextBox1.Text += Environment.NewLine + i; }), null); i++; mutex.ReleaseMutex(); } });
客戶端:
客戶端和服務(wù)端代碼一樣,但是運(yùn)行起來加斷點(diǎn)是可以看到客戶端進(jìn)入了cs.send之后,服務(wù)端是沒有辦法進(jìn)入的,必須等待客戶端ReleaseMutex之后才可以進(jìn)入,這也就是我前面說的可以用這個(gè)去實(shí)現(xiàn)多進(jìn)程操作對(duì)象的一個(gè)場(chǎng)景。
var isNew = false; //創(chuàng)建Mutex對(duì)象 var mutex = new Mutex(false,"ProcessCommunication",out isNew);//用來和客戶端用同一個(gè)對(duì)象,在循環(huán)中有且僅有一個(gè)進(jìn)程可以使用這個(gè)對(duì)象,即子進(jìn)程在使用WaitOne方法的時(shí)候 父進(jìn)程是沒有辦法進(jìn)入到循環(huán)體中,只有調(diào)用了子進(jìn)程調(diào)用ReleaseMutex方法,父進(jìn)程才可以使用;通??梢杂眠@個(gè)可以實(shí)現(xiàn)多進(jìn)程訪問同一個(gè)文件 等。 Task.Run(() => { var cs = cts; int i = 0; while (true) { mutex.WaitOne(); cs.Send(new SendOrPostCallback(s => { richTextBox1.Text += Environment.NewLine+i; }), null); i++; mutex.ReleaseMutex(); } });
結(jié)束
今天的多進(jìn)程的分享就到這里了,那實(shí)際上還有很多種方式可以實(shí)現(xiàn)多進(jìn)程,網(wǎng)絡(luò)之間的通訊,消息隊(duì)列,WebSocket,Api以及Grpc等等,這里只是演示一下c#中并且大多數(shù)支持FrameWork下的多進(jìn)程通訊,如果有不明白的地方,可以添加群找到我,或者查看加的所有的Net群是否有一個(gè)叫四川觀察的,那也是我,有不明白的可以隨時(shí)問我,我都在,代碼我給大家共享出來,大家可以去看一下。
代碼地址:ProcessCommunicationServerAndClient_jb51.rar
到此這篇關(guān)于c#多進(jìn)程通訊的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)c#多進(jìn)程通訊內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#實(shí)現(xiàn)Json轉(zhuǎn)DataTable并導(dǎo)出Excel的方法示例
這篇文章主要介紹了C#實(shí)現(xiàn)Json轉(zhuǎn)DataTable并導(dǎo)出Excel的方法,結(jié)合實(shí)例形式總結(jié)分析了Json轉(zhuǎn)換DataTable,以及DataTable導(dǎo)出Excel相關(guān)操作技巧,需要的朋友可以參考下2019-02-02C#如何給新建的winform程序添加資源文件夾Resources
這篇文章主要介紹了C#如何給新建的winform程序添加資源文件夾Resources,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09Unity Shader實(shí)現(xiàn)徑向模糊效果
這篇文章主要為大家詳細(xì)介紹了Unity Shader實(shí)現(xiàn)徑向模糊效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08C#獲取鼠標(biāo)在listview右鍵點(diǎn)擊單元格的內(nèi)容方法
下面小編就為大家?guī)硪黄狢#獲取鼠標(biāo)在listview右鍵點(diǎn)擊單元格的內(nèi)容方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-01-01c#實(shí)現(xiàn)sqlserver事務(wù)處理示例
這篇文章主要介紹了c#實(shí)現(xiàn)sqlserver事務(wù)處理的示例,大家參考使用吧2014-01-01