<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在c#中,可能大多數人針對於多執行緒之間的通訊,是熟能生巧,對於AsyncLocal 和ThreadLocal以及各個靜態類中支援執行緒之間傳遞的GetData和SetData方法都是信手拈來,那多程序通訊呢,實際上也是用的比較多的地方,但是能夠熟能生巧的人和多執行緒的相比的話呢,那還是有些差距的,所以我昨天整理了一下我所認知的幾個多程序之間的通訊方式,這其中是不包括各種訊息中介軟體以及資料庫方面的,還有Grpc,WebSocket或者Signalr等方式,僅僅是以c#程式碼為例,c#的多程序通訊呢,大致上是分為這幾類的,共用記憶體,藉助Windows的MSMQ訊息佇列服務,以及命名管道和匿名管道,以及IPC HTTP TCP的Channel的方式,還有常用的Socket,藉助Win32的SendMessage的Api來實現多程序通訊,還有最後一種就是多程序之間的號誌相關的Mutex,程式碼我會放在文章的末尾,大家有需要的話可以去下載來看看,接下來就為大家一一奉上。
共用記憶體呢,實際上c#中可以有很多種實現方式,主要是藉助於Win32的Api來實現以及,使用MemoryMappedFile這個類來實現共用記憶體,前者需要引入多個Win32的dll的方法,後者使用起來就比較簡單,只需要呼叫類的CreatNew方法設定好記憶體對映檔名稱以及大小,以及操作許可權就可以實現,同時支援Accessor和Stream的方式去進行讀寫,但是效能方面肯定是Win32的效能好,而且Win32的話不受語言的限制,至於這個類是否受限於語言,目前我是不太清楚的。接下來,咱們就看看使用者端和伺服器端使用共用記憶體的方式和獲取資料的程式碼。
伺服器端:
MemoryMappedFile memoryAccessor = MemoryMappedFile.CreateNew("ProcessCommunicationAccessor", 500, MemoryMappedFileAccess.ReadWrite);//建立共用記憶體對映檔案物件,第一個引數為對映的名稱,與使用者端需要對應,500為大小,單位為位元組,MemoryMappedFileAccess為存取許可權,是讀寫還是唯讀 只寫,此處不能使用Using 否則脫離Using 就會釋放,使用者端無法獲取到此名稱的記憶體對映物件 using (var accessor = memoryAccessor.CreateViewAccessor())//獲取對映檔案物件的檢視 { 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);//建立流的對映檔案物件 using (var stream = memoryStream.CreateViewStream())//獲取對映檔案的流 { var helo = Encoding.UTF8.GetBytes("Stream"); stream.Write(helo, 0, helo.Length);//將給定的值寫入此記憶體流中 richTextBox1.Text += Environment.NewLine + "Accessor Send Val:Stream"; }
使用者端:
MemoryMappedFile memoryAccessor = MemoryMappedFile.OpenExisting("ProcessCommunicationAccessor");//獲取伺服器端定義的ProcessCommunicationAccessor名稱的記憶體對映檔案然後呼叫ReadArray方法讀取到伺服器端寫入的資料 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");//獲取伺服器端定義的ProcessCommunicationStream名稱的記憶體對映檔案然後呼叫ReadToEnd方法讀取到伺服器端寫入的資料 using (var stream = memoryStream.CreateViewStream()) { using (var reader = new StreamReader(stream)) { var str = reader.ReadToEnd(); richTextBox1.Text += Environment.NewLine + "Stream Read Val:" + str + "rn"; } }
可以看到我們在伺服器端定義了一個是Accessor型別的MemoryMappedFile在寫入資料的時候是用MemortViewAccessor的方式去寫入的,然後又定義了一個使用Stream的方式去進行寫入資料,在使用者端中,我們直接使用OpenExisting方法去判斷是否存在這個物件,如果存在的話,就使用了伺服器端定義的CreatNew這個物件,如果不存在則是Null,當然了也可以使用其他的方式去進行獲取,例如CreateOrOpen判斷是否是獲取的還是重新建立的方式,我們在使用者端使用ReadArray和ReadToEnd的方式讀取了伺服器端寫入的Accessor和Stream的資料,然後我們就可以在使用者端和伺服器端之間進行一個資料傳輸的一個通訊。
使用MSMQ的前提是需要在本計算機安裝了訊息佇列,安裝方式需要在控制面板,程式和功能那裡啟用或關閉程式,在列表中找到我們需要的訊息佇列(MSMQ)伺服器然後安裝,安裝完成後,我們點選我的電腦右鍵管理找到最下面的服務和應用程式就可以看到我們安裝的訊息佇列了,然後找到專用佇列,我們在這裡新建一個佇列,然後就可以在我們的程式碼中使用了,這裡呢我只是簡單寫一個示範,實際上在Messaging名稱空間裡,還支援對訊息佇列許可權的控制,等等的操作,接下來我們看看如何在程式碼中使用訊息佇列。
伺服器端中我們定義了我們需要使用的訊息佇列的型別以及名稱,名稱規範的話也可以參考官網對名稱定義的介紹,還支援其他方式名稱的定義,定義好之後呢,我們便傳送了一個訊息Message HelloWorld的一條訊息
MessageQueue queue = new MessageQueue(".\Private$\MessageQueue");//右鍵我的電腦,點選管理 找到服務和應用程式找到專用佇列,建立的專用佇列名稱就是MessageQueue queue.Send("Message HelloWorld");//然後傳送訊息 richTextBox1.Text += Environment.NewLine + "MessageQueue Send Val:Message HelloWorld";
使用者端中,我們也是和伺服器端定義了一個訊息佇列的一個物件,然後我們監聽這個訊息佇列的收到訊息的事件,開始非同步接收訊息,在接收完畢之後呢,會走到我們寫的ReceiveCompleted的完成事件中,然後我們結束非同步接收的,獲取到伺服器端傳送的訊息,然後使用XmlMessageFormatter物件去格式化我們伺服器端傳送的訊息,這裡的Type是伺服器端傳送的訊息型別,兩者需要對應,在接受並展示到UI之後,我們在開始非同步接收。
var context = WindowsFormsSynchronizationContext.Current; MessageQueue myQueue = new MessageQueue(".\Private$\MessageQueue");//定義訊息佇列物件,和伺服器端的地址一樣, myQueue.ReceiveCompleted += (a, b) =>//定義接受完成的時間 { var cts = context; var queue = a as MessageQueue;//佇列物件 queue.EndReceive(b.AsyncResult); var msg = b.Message;//接收到的訊息物件 msg.Formatter = new XmlMessageFormatter() { TargetTypes = new Type[] { typeof(string) } };//設定接收到的訊息使用什麼方式格式化 var msgVal = msg.Body;//此處是伺服器端傳送的具體的訊息物件 cts.Send(new System.Threading.SendOrPostCallback(s => { richTextBox1.Text += Environment.NewLine + "MessageQueue Read Val:" + msgVal + "rn"; }), null); queue.BeginReceive(); }; myQueue.BeginReceive();
命名管道和匿名管道位於System.Io.Pipe名稱空間下,顧名思義,命名管道是需要我們給管道命名一個名稱的以便於使用者端來進行連線,我們需要定義管道的名稱,指定管道的方向,是輸入還是輸出 還是輸入輸出,還可以定義最大的伺服器端範例數量,以及傳輸的訊息型別是Byte還是Message,以及是否開啟非同步等。接下來我們看看伺服器端和使用者端之間通訊的程式碼。
伺服器端:我們定義了管道名稱是ProcessCommunicationPipe,並且定義是可以輸入也可以輸出,10個範例,以及使用Message傳輸型別,開啟非同步通訊,然後我們非同步的等待使用者端連結,在連結成功之後呢,我們通知UI使用者端已經連結到了伺服器端,然後非同步去接收使用者端發來的訊息,並且展示到UI上面。
///定義一個命名管道,第一個引數是管道名稱,第二個引數代表是輸入型別還是輸出型別 還是輸入輸出型別,以及設定最大的伺服器範例,設定傳輸型別,以及開啟可以非同步的進行讀取和寫入 namedPipeServerStream = new NamedPipeServerStream("ProcessCommunicationPipe", PipeDirection.InOut, 10, PipeTransmissionMode.Message, PipeOptions.Asynchronous); //非同步等待使用者端連結,如果上面的Options不是Asynchronous 非同步則會報錯 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]; //非同步讀取使用者端傳送的訊息 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); }); });
伺服器端傳送程式碼:我們定義了一個Send的傳送按鈕,以及一個傳送內容的文字方塊,然後我們只需要呼叫Server的WriteAsync就可以將我們的資料寫入到Server中傳送到使用者端。
//命名管道傳送訊息到使用者端 var data = Encoding.UTF8.GetBytes(textBox1.Text); //傳送訊息到使用者端 namedPipeServerStream.WriteAsync(data, 0, data.Length); richTextBox1.Text += Environment.NewLine + "Server Send Val:" + textBox1.Text;
使用者端:
我們定義了一個Client的物件,.代表是當前計算機,以及和伺服器端一樣的管道名稱,同樣定義為開啟非同步,以及是輸入輸出型別的。然後非同步的去連結伺服器端,然後更新UI,通知已經連結成功,並且非同步等待伺服器端給使用者端傳送訊息,從而顯示到UI上面。
var cts = WindowsFormsSynchronizationContext.Current; //定義管道物件,如果需要是網路之間通訊.替換為伺服器端的伺服器名稱和pipeName namedPipeClientStream = new NamedPipeClientStream(".", "ProcessCommunicationPipe", PipeDirection.InOut, PipeOptions.Asynchronous); //非同步連結伺服器端 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]; //非同步等待收到伺服器端傳送的訊息 然後更新到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); }); });
使用者端傳送程式碼:同伺服器端一樣,寫入我們的資料,伺服器端就會走到ReadAsync的方法中去,伺服器端就可以接收到我們傳送的資料並且展示到UI,
//命名管道傳送訊息到伺服器端 var data = Encoding.UTF8.GetBytes(textBox1.Text); namedPipeClientStream.WriteAsync(data, 0, data.Length); richTextBox1.Text += Environment.NewLine + "Client Send Val:" + textBox1.Text;
匿名管道是我們伺服器端是父程序,需要我們伺服器端去使用Process啟用開啟我們的子程序,然後傳入我們使用者端的控制程式碼到使用者端,使用者端再根據傳入的引數連結到伺服器端,從而可以實現通訊,但是匿名管道不支援網路之間的通訊,以及不支援輸入輸出,僅支援要麼輸入要麼輸出,同時,匿名管道提供了PipeAccessRule來控制存取許可權。接下來,我們看一下使用者端和伺服器端是如何通訊,以及伺服器端如何去啟動使用者端。
伺服器端:伺服器端去定義Process設定我們需要啟動的子程序,然後定義我們的匿名管道,然後將使用者端連結的Handlestring傳到使用者端,然後啟動我們的使用者端,在定義非同步接收訊息之後的回撥,然後展示到頁面上。
//定義使用者端子程序 Process Client = new Process(); //子程序路徑 Client.StartInfo.FileName = @"E:CoreReposProcessCommunicationClientbinDebugProcessCommunicationClient.exe"; //定義匿名管道, AnonymousPipeServerStream anonymousPipeServerStream = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable); Client.StartInfo.Arguments = anonymousPipeServerStream.GetClientHandleAsString(); Client.StartInfo.UseShellExecute = false; Client.Start(); //關閉本地複製的使用者端 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方法中新增一個string陣列的引數然後傳入到我們的表單中,這樣匿名使用者端管道連結伺服器端就可以連結成功。
//此處定義匿名管道的物件,Vs[0]來自伺服器端的Process的Arguments屬性的值 anonymousPipeClientStream = new AnonymousPipeClientStream(PipeDirection.Out, Vs[0]);
使用者端傳送程式碼:
我們直接呼叫WriteAsync方法寫入我們的資料,伺服器端就可以接收到我們傳送的資訊。
//傳送訊息到匿名管道伺服器端 var vss = Encoding.UTF8.GetBytes(textBox2.Text); anonymousPipeClientStream.WriteAsync(vss, 0, vss.Length); richTextBox1.Text += Environment.NewLine + "匿名Client Send Val:" + textBox2.Text;
Channel下面是有IPC,HTTP和TCP三種型別,三種型別都提供了ClientChannel 以及ServerChannel和Channel的類,Channel類是簡化了Server和Client的操作,可以直接使用Channel來進行定義伺服器端和使用者端通訊的物件,接下面我們看看Ipc通訊的方式。
我們定義了一個IpcChannel的物件並且指定ip為127.0.0.1埠是8081,然後我們需要向管道服務註冊我們的管道資訊,然後註冊我們需要注入的型別,以及資源的URL地址,還有生命週期是單例還是每次獲取都不一樣,只有這兩種週期,然後我們看看使用者端使用的程式碼。
伺服器端:
///定義IPC通道,埠和ip,也可以直接定義埠 ipcChannel = new IpcChannel("127.0.0.1:8081"); //向通道註冊當前管道 ChannelServices.RegisterChannel(ipcChannel, true); //注入物件到伺服器端,並且指定此物件的URL,以及生命週期,是單例還是每次獲取都不一樣 RemotingConfiguration.RegisterWellKnownServiceType(typeof(ProcessCommunicationIpc), "Ipc.rem", WellKnownObjectMode.Singleton); richTextBox1.Text += Environment.NewLine + "IPCServer Is Open;";
使用者端:
我們定義了一個空的管道資訊並且註冊進去,然後定義我們需要獲取的型別,以及型別的URL資源地址,並且呼叫RegisterWellKnownClientType方法,這個方法我的見解是相當於告知伺服器端我們需要使用的資源,然後我們直接New這個物件,呼叫SetName方法,就可以實現通訊,那如果伺服器端怎麼獲取到資料呢,那有的同學就會問了,莫急,我們看下一段程式碼。
IpcChannel ipcChannel = new IpcChannel();//定義一個IPC管道物件同樣需要註冊到管道服務中 ChannelServices.RegisterChannel(ipcChannel, true); WellKnownClientTypeEntry entry = new WellKnownClientTypeEntry(typeof(ProcessCommunicationIpc), "ipc://127.0.0.1:8081/Ipc.rem");//定義我們需要獲取的型別以及此型別的Url RemotingConfiguration.RegisterWellKnownClientType(entry);//相當於告知伺服器端我們需要用的物件 ProcessCommunicationIpc processCommunicationIpc = new ProcessCommunicationIpc();//定義一個這個物件 processCommunicationIpc.SetName(textBox3.Text);//然後呼叫這個SetNama方法 richTextBox1.Text += Environment.NewLine + "IPCClient Send Val:" + textBox3.Text;
伺服器端接收程式碼:我們直接呼叫Activator的GetObject方法從我們伺服器端定義的地址獲取到我們註冊的型別,然後呼叫Name屬性就可以看到Name是我們使用者端寫入的資料,因為我們定義的生命週期是單例的,所以這裡可以實現使用者端和伺服器端之間的通訊,實際上Http和Tcp的使用方式同IPC一樣,都是大同小異,我們可以看看HTTP和TCP使用的程式碼就會明白了。
//從我們定義的IPCurl獲取代理物件,然後判斷值是否改變 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通道,埠 HttpChannel httpChannel = new HttpChannel(8082); //向通道註冊當前管道 ChannelServices.RegisterChannel(httpChannel, false); //注入物件到伺服器端,並且指定此物件的URL,以及生命週期,是單例還是每次獲取都不一樣 RemotingConfiguration.RegisterWellKnownServiceType(typeof(ProcessCommunicationHttp), "Http.rem", WellKnownObjectMode.Singleton); richTextBox1.Text += Environment.NewLine + "HttpServer Is Open;";
伺服器端接收:
//從我們定義的Http url獲取代理物件,然後判斷值是否改變 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();//定義一個HTTP管道物件同樣需要註冊到管道服務中 ChannelServices.RegisterChannel(httpChannel, false); WellKnownClientTypeEntry entry = new WellKnownClientTypeEntry(typeof(ProcessCommunicationHttp), "http://127.0.0.1:8082/Http.rem");//定義我們需要獲取的型別以及此型別的Url RemotingConfiguration.RegisterWellKnownClientType(entry);//相當於告知伺服器端我們需要用的物件 ProcessCommunicationHttp processCommunicationIpc = new ProcessCommunicationHttp();//定義一個這個物件 processCommunicationIpc.SetName(textBox4.Text);//然後呼叫這個SetNama方法 richTextBox1.Text += Environment.NewLine + "HttpClient Send Val:" + textBox4.Text;
伺服器端:
///定義Tcp通道,埠 TcpChannel tcpChannel = new TcpChannel(8083); //向通道註冊當前管道 ChannelServices.RegisterChannel(tcpChannel, true); //注入物件到伺服器端,並且指定此物件的URL,以及生命週期,是單例還是每次獲取都不一樣 RemotingConfiguration.RegisterWellKnownServiceType(typeof(ProcessCommunicationTcp), "Tcp.rem", WellKnownObjectMode.Singleton); richTextBox1.Text += Environment.NewLine + "TcpServer Is Open;";
伺服器端接收:
//從我們定義的Tcp url獲取代理物件,然後判斷值是否改變 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();//定義一個TCP管道物件同樣需要註冊到管道服務中 ChannelServices.RegisterChannel(tcpChannel, true); WellKnownClientTypeEntry entry = new WellKnownClientTypeEntry(typeof(ProcessCommunicationTcp), "tcp://127.0.0.1:8083/Tcp.rem");//定義我們需要獲取的型別以及此型別的Url RemotingConfiguration.RegisterWellKnownClientType(entry);//相當於告知伺服器端我們需要用的物件 ProcessCommunicationTcp processCommunicationIpc = new ProcessCommunicationTcp();//定義一個這個物件 processCommunicationIpc.SetName(textBox5.Text);//然後呼叫這個SetNama方法 richTextBox1.Text += Environment.NewLine + "TcpClient Send Val:" + textBox5.Text;
可以看到基本上都是一樣的,但是有些地方是不一樣的,這裡我是沒有寫那部分的程式碼,例如Http是可以設定HttpHandler的,其他方面使用起來都是大同小異。
Socket可能是大家用的最多的程序通訊了,它也不僅僅是程序之間,同時也是支援網路之間的通訊,同時協定型別支援的也是比較多的,並且支援雙向通訊,可以傳送檔案等,這裡就不作過多的介紹了,直接上程式碼
伺服器端:
我們直接定義伺服器端物件,並且指定地址和埠開始監聽並且非同步等待連結,
//定義Socket物件,以及協定,傳輸型別 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); //連結的最大長度 socket.Listen(10); socket.BeginAccept(Accept, socket);//非同步等待連結 richTextBox1.Text += Environment.NewLine + "Socket Server Is Listening;";
伺服器端非同步接受程式碼:在有連線之後我們直接去獲取到連結的使用者端物件的Socket並且賦值給我們的Socket全域性變數,然後更新UI,並且非同步的去讀取使用者端傳送的訊息。
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 提示已經連結 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); }
伺服器端接收資料程式碼:
我們在接收到了使用者端發的訊息之後,我們解析成字串,然後更新到UI上面。
var cts = synchronizationContext; var client = asyncResult.AsyncState as Socket; var data=client.EndReceive(asyncResult);//獲取接受的資料長度 var str = Encoding.UTF8.GetString(buffer);//轉換為字元然後顯示到介面 cts.Send(new System.Threading.SendOrPostCallback(b => { richTextBox1.Text += Environment.NewLine + "Socket Server Receive Val:" + str; }), null);
伺服器端傳送程式碼:
我們直接呼叫我們獲取到的Client的Socket物件,傳送我們需要傳送的訊息即可。
//將訊息傳送到使用者端 var sendVal=Encoding.UTF8.GetBytes(textBox2.Text); Client.Send(sendVal,SocketFlags.None); richTextBox1.Text += Environment.NewLine + "Socket Server Send Val:" + textBox2.Text;
使用者端:定義好伺服器端的IP和埠然後我們非同步連結,在連結成功之後我們在傳送我們的資料到伺服器端,並且非同步等待伺服器端給我們傳送訊息。
var cs = cts; //定義Socket使用者端物件 Socket socket = new Socket(SocketType.Stream,ProtocolType.Tcp); var ipAddress = IPAddress.Parse("127.0.0.1"); var endpoint = new IPEndPoint(ipAddress, 8084); //定義需要連結的伺服器端的IP和埠然後非同步連結伺服器端 socket.ConnectAsync(endpoint).ContinueWith(s => { //連結之後傳送訊息到伺服器端 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); //非同步等待伺服器端傳送的訊息 socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, Read, socket); });
使用者端接收程式碼:
我們直接從我們的伺服器端Socket物件中讀取我們的資料然後展示到UI上面。
var cs = cts; var client = asyncResult.AsyncState as Socket; var data = client.EndReceive(asyncResult); //獲取伺服器端給使用者端傳送的訊息 var str = Encoding.UTF8.GetString(buffer); cs.Send(new System.Threading.SendOrPostCallback(b => { richTextBox1.Text += Environment.NewLine + "Socket Client Receive Val:" + str; }), null);
在表單程式中,我們可以重寫表單的DefWndProc方法,來實現程序之間的訊息通訊,需要引入Win32的SendMessage方法來實現,這個方法可以實現給一個或者多個表單之間傳送訊息,我們可以指定我們需要傳送的表單的控制程式碼,以及我們傳送的訊息型別的Code也可以自己寫,以及我們需要傳過去的引數,可以定義為結構體進行傳送,接收方,再從記憶體中將控制程式碼轉為對應的結構體就可以使用,這裡我使用的傳輸資料型別是Int型別的資料,如果需要傳結構體的話,引入的Dll設定SendMessage方法處可以設定,以及在接收方需要使用記憶體的操作類Marshal類進行轉為結構體,接下來我們看看使用者端是如何和伺服器端進行通訊的。
伺服器端:我們重寫這個方法之後,等待使用者端給我們傳送訊息就行,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);
傳送程式碼:
我們需要獲取到我們要傳送給那個程序,然後獲取到主程式的控制程式碼,然後傳入我們的訊息code,以及我們的引數資訊,這樣伺服器端就可以接收到我們使用者端傳送過去的10,20的資料,
//獲取到我們需要傳送到的表單的程序,然後獲取他的主表單控制程式碼,將我們的訊息10,20傳送到指定的表單中,然後會執行DefWndProc方法,然後在方法中判斷msg型別是否和我們這邊傳送的0x1050一致,就可以收到使用者端傳送的訊息,第二個引數是我們定義的訊息型別,可以自己定義數位 也可以根據Win32 api裡面規定的對應的功能用哪些也可以 var process=Process.GetProcessesByName("ProcessCommunication").FirstOrDefault(); SendMessage(process.MainWindowHandle, 0x1050, 10,20);
在前面的多執行緒博文中,我有講過Mutex是程序之間也可以,是作業系統層面的,我們可以使用WaitOne進入到我們的程式碼段中,並且只有一個執行緒可以進入,在結束後我們需要釋放調這個鎖,從而其他執行緒就可以獲取到,既然Mutex是程序之間也可以,那多個程序之間也可以共用一個Mutex物件,A程序使用WaitOnd的時候B程序是隻能等待A程序釋放才可以使用。
伺服器端程式碼:
我們定義了Mutex的物件,然後開啟了一個執行緒去進行死迴圈重新整理UI資訊,然後迴圈內部我們鎖定鎖,然後通知UI,然後在釋放鎖,這樣使用者端同樣的程式碼必須等到ReleaseMutex之後才可以進去到迴圈內部更新UI的部分。
var isNew = false; //定義Mutex物件,引數一是否具有初始權,第二個為系統中的名稱,第三個代表是否是新建的; var mutex = new Mutex(false, "ProcessCommunication", out isNew);//用來和使用者端用同一個物件,在迴圈中有且僅有一個程序可以使用這個物件,即子程序在使用WaitOne方法的時候 父程序是沒有辦法進入到迴圈體中,只有呼叫了子程序呼叫ReleaseMutex方法,父程序才可以使用;通常可以用這個可以實現多程序存取同一個檔案 等。 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(); } });
使用者端:
使用者端和伺服器端程式碼一樣,但是執行起來加斷點是可以看到使用者端進入了cs.send之後,伺服器端是沒有辦法進入的,必須等待使用者端ReleaseMutex之後才可以進入,這也就是我前面說的可以用這個去實現多程序操作物件的一個場景。
var isNew = false; //建立Mutex物件 var mutex = new Mutex(false,"ProcessCommunication",out isNew);//用來和使用者端用同一個物件,在迴圈中有且僅有一個程序可以使用這個物件,即子程序在使用WaitOne方法的時候 父程序是沒有辦法進入到迴圈體中,只有呼叫了子程序呼叫ReleaseMutex方法,父程序才可以使用;通常可以用這個可以實現多程序存取同一個檔案 等。 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(); } });
今天的多程序的分享就到這裡了,那實際上還有很多種方式可以實現多程序,網路之間的通訊,訊息佇列,WebSocket,Api以及Grpc等等,這裡只是演示一下c#中並且大多數支援FrameWork下的多程序通訊,如果有不明白的地方,可以新增群找到我,或者檢視加的所有的Net群是否有一個叫四川觀察的,那也是我,有不明白的可以隨時問我,我都在,程式碼我給大家共用出來,大家可以去看一下。
程式碼地址:ProcessCommunicationServerAndClient_jb51.rar
到此這篇關於c#多程序通訊的實現範例的文章就介紹到這了,更多相關c#多程序通訊內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45