透過一個模擬程式讓你明白WCF大致的執行流程
在《透過一個模擬程式讓你明白ASP.NET MVC是如何執行的》一文中我透過一個普通的ASP.NET Web程式模擬了ASP.NET MVC的執行流程,現在我們透過類似的原理建立一個用於模擬WCF服務端和客戶端工作原理的模擬程式。[原始碼從這裡下載]
目錄
一、基本的元件和執行流程
二、建立自定義HttpHandler實現對服務呼叫請求的處理
三、定義建立WCF元件的工廠
四、定義HttpModule對映WcfHandler
五、建立自定義的真實代理實現服務的呼叫
六、定義服務代理工廠
七、服務“寄宿”和呼叫
一、基本的元件和執行流程
我們只模擬WCF完成一個簡單的服務呼叫所必需的元件和流程,右圖反映了進行服務呼叫的必要步驟和使用的相關WCF元件。下面列出了服務端涉及的元件和流程:
請求訊息的接收和回覆訊息的傳送:服務端在傳輸層監聽與接收來自客戶的請求,並將經過編碼後的回覆訊息透過傳輸層傳送到客戶端;
請求訊息的解碼和回覆訊息的編碼:將接收到的位元組陣列透過解碼生成請求訊息物件,並將回覆訊息透過編碼轉化成位元組陣列。訊息的編碼和解碼透過訊息編碼器(MessageEncoder)完成,而訊息編碼器工廠(MessageEncoderFactory)負責建立該物件;
請求訊息的反序列化和回覆訊息的序列化:對請求訊息進行反序列化,為服務操作的執行生成相應的輸入引數,以及將服務操作執行的結果(返回值或輸出/引用引數)序列化,並生成回覆訊息。序列化和反序列化透過分發訊息格式化器(DispatchMessageFormatter)完成;
服務物件的建立:建立或啟用服務物件例項,例項提供者(InstanceProvider)用於服務物件的建立或獲取,本例直接透過反射建立服務例項;
服務操作的執行:呼叫建立的服務物件的操作方法,並傳入經過反序列化生成的輸入引數。操作呼叫器(OperationInvoker)完成對服務操作的最終執行。
相較於服務端的請求監聽、訊息接收、服務例項啟用和操作呼叫流程,客戶端的處理流程顯得相對簡單,僅僅包含以下3個必需的步驟:
請求訊息的序列化和回覆訊息的反序列化:生成請求訊息並將輸入引數序列化到請求訊息中,以及對回覆訊息進行反序列化,轉化成方法呼叫的返回值或輸出/引用引數。序列化和反序列化透過ClientMessageFormatter完成;
請求訊息的編碼和回覆訊息的解碼:對請求訊息進行編碼生成位元組陣列供傳輸層傳送,以及將傳輸層接收到的位元組陣列解碼生成回覆訊息。訊息的編碼和解碼透過訊息編碼器完成,而訊息編碼器工廠負責建立該物件;
請求訊息的傳送和回覆訊息的接收:在傳輸層將經過編碼的請求訊息傳送到服務端,以及接收來自服務端的回覆訊息。
本例項的解決方法依然採用包含Service.Interface、Service和Client三個專案的結構,不過Service專案現在是一個Web應用。也就是說我們透過一個Web應用的方式實現WCF端對服務呼叫請求的整個處理流程。
二、建立自定義HttpHandler實現對服務呼叫請求的處理
對於一個ASP.NET Web應用來說,對請求的處理最終都落實到一個具體的HttpHandler物件上,所以我們透過實現介面System.Web.IHttpHandler自定義瞭如下一個WcfHandler用於處理針對WCF服務請求的處理。
1: public class WcfHandler: IHttpHandler
2: {
3: //其他成員
4: public Type ServiceType { get; private set; }
5: public MessageEncoderFactory MessageEncoderFactory { get; private set; }
6: public IDictionaryMethods { get; private set; }
7: public IDictionaryMessageFormatters { get; private set; }
8: public IDictionaryOperationInvokers { get; private set; }
9:
10: public bool IsReusable
11: {
12: get { return false; }
13: }
14:
15: public WcfHandler(Type serviceType, MessageEncoderFactory messageEncoderFactory)
16: {
17: this.ServiceType = serviceType;
18: this.MessageEncoderFactory = messageEncoderFactory;
19: this.Methods = new Dictionary();
20: this.MessageFormatters = new Dictionary();
21: this.OperationInvokers = new Dictionary();
22: }
23: }
如上面程式碼所示,上述的關於WCF服務端框架所需的元件以只讀屬性的方式體現在WcfHandler上。ServiceType屬性表示服務的型別,基於這個型別透過反射建立服務例項。訊息編碼器工廠透過MessageEncoderFactory屬性表示,兩個字典型別的屬性MessageFormatters和OperationInvokers代表基於操作的分發訊息格式化器和操作呼叫器列表,字典的Key為操作請求訊息的
針對WCF服務的請求處理實現在如下的ProcessRequest方法中,執行的邏輯也不算複雜。我們直接透過訊息編碼器工廠建立的訊息編碼從當前HTTP請求的輸入流中讀取出訊息。然後根據當前訊息的
接著直接透過反射的方式根據服務型別建立服務例項物件。同樣根據當前訊息的
操作的執行結果透過分發訊息格式化器進行序列化生成的訊息最終透過訊息編碼器寫入當前HTTP回覆的輸出流中返回給客戶端。
1: public class WcfHandler: IHttpHandler
2: {
3: //其他成員
4: public void ProcessRequest(HttpContext context)
5: {
6: //對HttpPRequest進行解碼生成請求訊息物件
7: Message request = this.MessageEncoderFactory.Encoder.ReadMessage(context.Request.InputStream, int.MaxValue, "application/soap+xml; charset=utf-8");
8:
9: //透過請求訊息得到代表服務操作的Action
10: string action = request.Headers.Action;
11:
12: //透過Action從MethodInfo字典中獲取服務操作對應的MethodInfo物件
13: MethodInfo method = this.Methods[action];
14:
15: //得到輸出引數的數量
16: int outArgsCount = 0;
17: foreach (var parameter in method.GetParameters())
18: {
19: if (parameter.IsOut)
20: {
21: outArgsCount++;
22: }
23: }
24:
25: //建立陣列容器,用於儲存請求訊息反序列後生成的輸入引數物件
26: int inputArgsCount = method.GetParameters().Length - outArgsCount;
27: object[] parameters = new object[inputArgsCount];
28: try
29: {
30: this.MessageFormatters[action].DeserializeRequest(request, parameters);
31: }
32: catch
33: {}
34:
35: List
36: object[] outArgs = new object[outArgsCount];
37: //建立服務物件,在WCF中服務物件透過InstanceProvider建立
38:
39: object serviceInstance = Activator.CreateInstance(this.ServiceType);
40:
41: //執行服務操作
42: object result = this.OperationInvokers[action].Invoke(serviceInstance,parameters, out outArgs);
43:
44: //將操作執行的結果(返回值或者輸出引數)序列化生成回覆訊息
45: Message reply = this.MessageFormatters[action].SerializeReply(request.Version, outArgs, result);
46: context.Response.ClearContent();
47: context.Response.ContentEncoding = Encoding.UTF8;
48: context.Response.ContentType = "application/soap+xml; charset=utf-8";
49:
50: //對回覆訊息進行編碼,並將編碼後的訊息透過HttpResponse返回
51: this.MessageEncoderFactory.Encoder.WriteMessage(reply, context.Response.OutputStream);
52: context.Response.Flush();
53: }
54: }
三、定義建立WCF元件的工廠
對於本例來說,客戶端和服務端需要的元件主要有四類,即訊息編碼器工廠、分發訊息格式化器、客戶端訊息格式化器和操作呼叫器。我們透過具有如下定義的靜態的工廠類ComponentBuilder來建立它們。我們呼叫操作行為DataContractSerializerOperationBehavior的GetFormatter方法來建立基於指定操作的訊息格式化器。不過該方法是一個內部方法,所以我們是透過反射的方式來呼叫的。isProxy參數列示建立的是客戶端訊息格式化器(True)還是分發訊息格式化器(False)。
訊息編碼器工廠透過基於文字編碼方式繫結元素TextMessageEncodingBindingElement的CreateMessageEncoderFactory建立,傳入的引數分別表示訊息的版本和文字編碼型別。我們採用SyncMethodInvoker以同步的方式進行操作的執行。由於SyncMethodInvoker是一個內部型別,所以我們不得不採用反射的方式來建立它。
1: public static class ComponentBuilder
2: {
3: public static object GetFormatter(OperationDescription operation, bool isProxy)
4: {
5: bool formatRequest = false;
6: bool formatReply = false;
7: DataContractSerializerOperationBehavior behavior = new DataContractSerializerOperationBehavior(operation);
8: MethodInfo method = typeof(DataContractSerializerOperationBehavior).GetMethod("GetFormatter", BindingFlags.Instance | BindingFlags.NonPublic);
9: return method.Invoke(behavior, new object[] { operation, formatRequest, formatReply, isProxy });
10: }
11:
12: public static MessageEncoderFactory GetMessageEncoderFactory(MessageVersion messageVersion, Encoding writeEncoding)
13: {
14: TextMessageEncodingBindingElement bindingElement = new TextMessageEncodingBindingElement(messageVersion, writeEncoding);
15: return bindingElement.CreateMessageEncoderFactory();
16: }
17: public static IOperationInvoker GetOperationInvoker(MethodInfo method)
18: {
19: string syncMethodInvokerType = "System.ServiceModel.Dispatcher.SyncMethodInvoker, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
20: Type type = Type.GetType(syncMethodInvokerType);
21: return (IOperationInvoker)Activator.CreateInstance(type, new object[]{method});
22: }
23: }
四、定義HttpModule對映WcfHandler
我們透過HttpModule的方式將用於處理WCF服務請求的對映到相應的WCF服務呼叫請求,為此我們定義瞭如下一個實現了System.Web.IHttpModule介面的WcfHttpModule型別。WcfHttpModule透過註冊HttpApplication的BeginRequest事件的方式將建立的WcfHandler對映為處理當前HTTP請求的HttpHandler。
1: public class WcfHttpModule: IHttpModule
2: {
3: public void Dispose() {}
4:
5: public void Init(HttpApplication context)
6: {
7: context.BeginRequest += (sender, args) =>
8: {
9: string relativeAddress = HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.Remove(0,2);
10: Type serviceType = RouteTable.Routes.Find(relativeAddress);
11: if (null == serviceType)
12: {
13: return;
14: }
15: IHttpHandler handler = this.CreateHttpHandler(serviceType);
16: context.Context.RemapHandler(handler);
17: };
18: }
19: protected IHttpHandler CreateHttpHandler(Type serviceType)
20: {
21: MessageEncoderFactory encoderFactory = ComponentBuilder.GetMessageEncoderFactory(MessageVersion.Default, Encoding.UTF8);
22: WcfHandler handler = new WcfHandler(serviceType, encoderFactory);
23: Type interfaceType = serviceType.GetInterfaces()[0];
24: ContractDescription contract = ContractDescription.GetContract(interfaceType);
25: foreach (OperationDescription operation in contract.Operations)
26: {
27: IDispatchMessageFormatter messageFormatter = (IDispatchMessageFormatter)ComponentBuilder.GetFormatter(operation, false);
28: handler.MessageFormatters.Add(operation.Messages[0].Action, messageFormatter);
29:
30: IOperationInvoker operationInvoker = ComponentBuilder.GetOperationInvoker(operation.SyncMethod);
31: handler.OperationInvokers.Add(operation.Messages[0].Action, operationInvoker);
32:
33: handler.Methods.Add(operation.Messages[0].Action, operation.SyncMethod);
34: }
35: return handler;
36: }
37: }
至於WcfHandler的建立,需要確定服務的型別。而服務的型別只能根據請求的地址來確定,這個IIS寄宿根據.svc檔案來建立ServiceHost的原理是一樣的。對於本例來說,我們需要對請求的地址和服務型別作一個對映,為此我們定義瞭如下一個RouteMapping的型別表示這個對映。
1: public class RouteMapping
2: {
3: public string Address { get; private set; }
4: public Type ServiceType { get; private set; }
5: public RouteMapping(string address, Type serviceType)
6: {
7: this.Address = address;
8: this.ServiceType = serviceType;
9: }
10: }
而對映表則透過如下一個繼承自Collection
1: public class RouteTable: Collection
2: {
3: public static RouteTable Routes{get; private set;}
4: static RouteTable()
5: {
6: Routes = new RouteTable();
7: }
8: public Type Find(string address)
9: {
10: RouteMapping routeMapping = (from route in this
11: where string.Compare(route.Address, address,true) == 0
12: select route).FirstOrDefault();
13: return null == routeMapping? null: routeMapping.ServiceType;
14: }
15:
16: public void Register(string address)
17: {
18: this.Add(new RouteMapping(address, typeof(T)));
19: }
20: }
五、建立自定義的真實代理實現服務的呼叫
ChannelFactory
1: public class ServiceChannelProxy: RealProxy
2: {
3: //其他成員
4: public Uri Address { get; private set; }
5: public MessageVersion MessageVersion { get; private set; }
6: public IDictionaryMessageFormatters { get; private set; }
7: public MessageEncoderFactory MessageEncoderFactory { get; private set; }
8:
9: public ServiceChannelProxy(Uri address, MessageVersion messageVersion, MessageEncoderFactory encoderFactory): base(typeof(TChannel))
10: {
11: this.Address = address;
12: this.MessageVersion = messageVersion;
13: this.MessageEncoderFactory = encoderFactory;
14: this.MessageFormatters = new Dictionary();
15: }
16: }
和WcfHttpHandler類似,進行服務呼叫所需的元件透過相應的只讀屬性表示。屬性MessageEncoderFactory表示訊息編碼器工廠,而字典型別的MessageFormatters表示基於每個操作的客戶端訊息格式化器列表,其中的Key為操作的名稱。屬性Address表示被呼叫服務的地址。
針對透明代理的方法呼叫最終都會轉移到針對真實真實代理的Invoke方法,所以我們將所有的服務呼叫操作實現在如下的Invoke方法中。我們首先獲取代表當前呼叫方法的MethodBase上應用的OperationContractAttribute特性,並藉此獲得操作名稱。
根據獲取的操作名稱從屬性MessageFormatters屬性中獲得基於當前操作的客戶端訊息格式化器,並將方法呼叫轉化訊息。接著根據Address屬性表示的服務呼叫地址建立EndpointAddress物件並將其附加到請求訊息中。除此之外,還需要為請求訊息新增一些必要的報頭(比如
接下來透過訊息編碼器工廠建立的訊息編碼器對訊息進行編碼,並將得到的位元組資料透過建立的HttpWebRequest物件傳送出去。對於得到的HttpWebResponse,則透過訊息編碼器進行解碼以生成回覆訊息。回覆訊息最終透過客戶端訊息格式化器進行反序列化,得到的物件對映為方法返回值和輸出/引用引數返回。
1: public class ServiceChannelProxy: RealProxy
2: {
3: //其他成員
4: public override IMessage Invoke(IMessage msg)
5: {
6: IMethodCallMessage methodCall = (IMethodCallMessage)msg;
7:
8: //得到操作名稱
9: object[] attributes = methodCall.MethodBase.GetCustomAttributes(typeof(OperationContractAttribute), true);
10: OperationContractAttribute attribute = (OperationContractAttribute)attributes[0];
11: string operationName = string.IsNullOrEmpty(attribute.Name) ? methodCall.MethodName : attribute.Name;
12:
13: //序列化請求訊息
14: Message requestMessage = this.MessageFormatters[operationName].SerializeRequest(this.MessageVersion, methodCall.InArgs);
15:
16: //新增必要的WS-Address報頭
17: EndpointAddress address = new EndpointAddress(this.Address);
18: requestMessage.Headers.MessageId = new UniqueId(Guid.NewGuid());
19: requestMessage.Headers.ReplyTo = new EndpointAddress("");
20: address.ApplyTo(requestMessage);
21:
22: //對請求訊息進行編碼,並將編碼生成的位元組傳送透過HttpWebRequest向服務端傳送
23: HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(this.Address);
24: webRequest.Method = "Post";
25: webRequest.KeepAlive = true;
26: webRequest.ContentType = "application/soap+xml; charset=utf-8";
27: ArraySegmentbytes = this.MessageEncoderFactory.Encoder.WriteMessage(requestMessage, int.MaxValue, BufferManager.CreateBufferManager(long.MaxValue, int.MaxValue));
28: webRequest.ContentLength = bytes.Array.Length;
29: webRequest.GetRequestStream().Write(bytes.Array, 0, bytes.Array.Length);
30: webRequest.GetRequestStream().Close();
31: WebResponse webResponse = webRequest.GetResponse();
32:
33: //對HttpResponse進行解碼生成回覆訊息.
34: Message responseMessage = this.MessageEncoderFactory.Encoder.ReadMessage(webResponse.GetResponseStream(), int.MaxValue);
35:
36: //回覆訊息進行反列化生成相應的物件,並對映為方法呼叫的返回值或者ref/out引數
37: object[] allArgs = (object[])Array.CreateInstance(typeof(object),methodCall.ArgCount);
38: Array.Copy(methodCall.Args, allArgs, methodCall.ArgCount);
39: object[] refOutParameters = new object[GetRefOutParameterCount(methodCall.MethodBase)];
40: object returnValue = this.MessageFormatters[operationName].DeserializeReply(responseMessage, refOutParameters);
41: MapRefOutParameter(methodCall.MethodBase, allArgs, refOutParameters);
42:
43: //透過ReturnMessage的形式將返回值和ref/out引數返回
44: return new ReturnMessage(returnValue, allArgs, allArgs.Length, methodCall.LogicalCallContext, methodCall);
45: }
46:
47: private int GetRefOutParameterCount(MethodBase method)
48: {
49: int count = 0;
50: foreach (ParameterInfo parameter in method.GetParameters())
51: {
52: if (parameter.IsOut || parameter.ParameterType.IsByRef)
53: {
54: count++;
55: }
56: }
57: return count;
58: }
59:
60: private void MapRefOutParameter(MethodBase method, object[] allArgs,object[] refOutArgs)
61: {
62: ListrefOutParamPositionsList = new List ();
63: foreach (ParameterInfo parameter in method.GetParameters())
64: {
65: if (parameter.IsOut || parameter.ParameterType.IsByRef)
66: {
67: refOutParamPositionsList.Add(parameter.Position);
68: }
69: }
70: int[] refOutParamPositionArray = refOutParamPositionsList.ToArray();
71: for (int i = 0; i
72: {
73: allArgs[refOutParamPositionArray[i]] = refOutArgs[i];
74: }
75: }
76: }
六、定義服務代理工廠
WCF的服務代理物件是透過ChannelFactory
建立的,我們來建立如下一個與之對應的ServiceProxyFactory 類,泛型引數依然表示契約介面型別。CreateChannel方法中透過表示服務地址的Uri,契約介面型別和預設訊息版本建立上述的真實代理ServiceChannelProxy 物件,並返回其透明代理作為進行服務呼叫的代理物件。 1: public class ServiceProxyFactory
2: {
3: public Uri Address { get; private set; }
4:
5: public ServiceProxyFactory(Uri address)
6: {
7: this.Address = address;
8: }
9: public TChannel CreateChannel()
10: {
11: MessageEncoderFactory encoderFactory = ComponentBuilder.GetMessageEncoderFactory(MessageVersion.Default, Encoding.UTF8);
12: ServiceChannelProxyproxy = new ServiceChannelProxy (this.Address, MessageVersion.Default, encoderFactory);
13: ContractDescription contract = ContractDescription.GetContract(typeof(TChannel));
14: foreach (OperationDescription operation in contract.Operations)
15: {
16: IClientMessageFormatter messageFormatter = (IClientMessageFormatter)ComponentBuilder.GetFormatter(operation, true);
17: proxy.MessageFormatters.Add(operation.Name, messageFormatter);
18: }
19: return (TChannel)proxy.GetTransparentProxy();
20: }
21: }
七、服務“寄宿”和呼叫
現在我們建立一個服務寄宿在我們自定義的迷你版本的WCF中。依然採用我們熟悉的計算服務,下面是分別定義的Service.Interface和Service專案中的契約介面定義和服務型別定義。
1: //契約介面
2: [ServiceContract(Namespace = "")]
3: public interface ICalculator
4: {
5: [OperationContract]
6: double Add(double x, double y);
7: [OperationContract]
8: double Subtract(double x, double y);
9: [OperationContract]
10: double Multiply(double x, double y);
11: [OperationContract]
12: double Divide(double x, double y);
13: }
14:
15: //服務型別
16: public class CalculatorService: ICalculator
17: {
18: public double Add(double x, double y)
19: {
20: return x + y;
21: }
22: public double Subtract(double x, double y)
23: {
24: return x - y;
25: }
26: public double Multiply(double x, double y)
27: {
28: return x * y;
29: }
30: public double Divide(double x, double y)
31: {
32: return x / y;
33: }
34: }然後我們為Web專案Service中新增一個Global.asax檔案,並透過如下的定義讓Web應用啟動的時候註冊寄宿的服務型別CalculatorService和地址(calculatorservice)之間的對映關係。然後在IIS中建立一個Web應用(比如起名為WcfServices)並將物理路徑對映為Service專案的根目錄。
1: public class Global : System.Web.HttpApplication
2: {
3: protected void Application_Start(object sender, EventArgs e)
4: {
5: RouteTable.Routes.Register("calculatorservice");
6: }
7: }由於最終處理服務呼叫請求的WcfHandler是透過WcfHttpModule進行對映的,所以我們需要將WcfHttpModule型別配置在Service專案的Web.config中。
1:
2:
3:
4:
5:
6:
7:在客戶端我們只需要按照如下的方式透過指定正確的呼叫地址(Web應用地址+在Global.asax檔案中新增的路由對映的地址)建立ServiceProxyFactory
物件,並用它來建立用於盡心服務呼叫的代理物件即可。 1: Uri address = new Uri("http://localhost/WcfServices/CalculatorService");
2: ServiceProxyFactoryfactory = new ServiceProxyFactory (address);
3: ICalculator proxy = factory.CreateChannel();
4: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, proxy.Add(1, 2));
5: Console.WriteLine("x - y = {2} when x = {0} and y = {1}", 1, 2, proxy.Subtract(1, 2));
6: Console.WriteLine("x * y = {2} when x = {0} and y = {1}", 1, 2, proxy.Multiply(1, 2));
7: Console.WriteLine("x / y = {2} when x = {0} and y = {1}", 1, 2, proxy.Divide(1, 2));上面的程式碼執行之後,就像你真正呼叫WCF服務一樣,同樣可以得到如下的運算結果。
1: x + y = 3 when x = 1 and y = 2
2: x - y = -1 when x = 1 and y = 2
3: x * y = 2 when x = 1 and y = 2
4: x / y = 0.5 when x = 1 and y = 2
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1806/viewspace-2806148/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 一篇文字讓你明白程式與執行緒的關係執行緒
- 一篇讓你明白程式與執行緒之間的區別與聯絡執行緒
- 模擬主執行緒等待子執行緒的過程執行緒
- 一文帶你懟明白程式和執行緒通訊原理執行緒
- Proteus模擬執行stm32程式
- 一個簡單java程式的執行全過程Java
- 一個對話讓你明白架構師是做什麼的?架構
- 【Spring】SpringIoC大致流程Spring
- 一個可以在多平臺執行的任天堂GameBoy模擬器GAM
- 透過adb設定模擬器的HTTP代理HTTP
- 一行程式碼幫你檢測Android模擬器行程Android
- 如何透過PMP認證?5個準備步驟讓你輕鬆透過考試!
- WEB程式執行的基本流程Web
- MapReduce程式執行流程
- 一文讓你明白 Git 分支是如何工作的Git
- [譯]通過HTTPS協議執行你的Flask程式HTTP協議Flask
- 一文讓你明白Redis持久化(RDB、AOF)Redis持久化
- JVM的特性,透過程式碼來揭秘執行時資料區JVM
- 007 Rust 非同步程式設計,透過 join 執行 FutureRust非同步程式設計
- Linux系統透過CrossOver執行windows系統exe程式LinuxROSWindows
- Vue之專案大致流程Vue
- 一個網站的滲透測試思路,流程(給你一個網站,怎麼做?)網站
- Fuzz前置技能-unicorn模擬執行
- 資料視覺化如何進行?大致流程是怎樣的?視覺化
- Java並行流:一次搞定多執行緒程式設計難題,讓你的程式飛起來!Java並行執行緒程式設計
- 透過DNS TXT記錄執行powershellDNS
- 你用過不寫程式碼就能完成一個簡單模組的元件麼?元件
- 申請和審批大致流程
- JMeter100個執行緒竟然只模擬出1個併發JMeter執行緒
- 使用Genymotion模擬器或者手機執行ionic4程式
- 一個51程式設計和模擬——流水燈程式設計
- 一篇文章讓你明白你多級快取的分層架構快取架構
- Spark學習(一)——執行模式與執行流程Spark模式
- 一篇文章讓你明白運維發展方向運維
- 一條更新語句的執行流程
- 一個 java 檔案的執行過程詳解Java
- 多執行緒執行任務時,某個執行緒拋異常,如何讓程式立即退出執行緒
- flowable流程引擎透過模型ID部署流程模型