The distributed command mode is a recommended mode for architecture design. Compared with the design of common applications, it should be considered more in the interconnected system. The goal of this model is to have the same design for both an independent system and an interconnected system. This mode allows developers to focus on designing a common application that follows the command mode without considering that the application will be connected to other systems. This mode frees developers from considering transmission and communication protocols when designing commands, and keeps the system simple. When some commands are executed, the designer does not need to worry about how to send necessary data to the server, and how to process the data received from the server and then respond to the UI of the interconnected application. When a command is executed on a local running system, the distributed command mode considers the obstacle of executing the same command on all interconnected systems to keep them in the same state. From the perspective of a designer, this architecture is still as simple as those of desktop applications that use the common command mode and are not aware of the network. However, if a command has been called within a system, it can execute all necessary communications to execute commands on all interconnected systems.
Command mode Overview
Command mode is a widely used design mode in Web and desktop systems. It allows developers to think about and design the architecture in terms of user and system operations. For example, when a file is opened and a fileopencommand occursCodeIt only serves this command. This simplified architecture design is due to the fact that when an operation is implemented, the tasks to be completed are clearly defined. The command mode also forces the design of a reusable system, because the "classes" of small commands are reusable and can be executed by users or systems. For example, a user may generate a fileopencommand by selecting the open command under the File menu, which may be automatically executed by a plug-in or macro, or other commands to open a file for execution. The result is that the command is only written once and maintained in one place, but it provides a wide range of reusability.
Distributed System Problems
When designing an interconnected system architecture, you must consider how to execute commands. Not only the "Source System" itself, but also all other interconnected systems. For example, a chat system is connected to a chat room. If a user sends a message, the message must be displayed on the user's screen and displayed on the screens of all online users in the chat room. This raises an architectural design problem-a command needs to be executed in the "Source System" and necessary data needs to be sent to the server to "spread" messages to all other connected users. Every connected user needs to receive data from the server, transmit it, and display it on the screen.
This leads to an additional architectural design and coding for the communication performed for each operation. First, follow the command mode. We can only execute commands on the same system. More specifically, we can only execute commands on the same process. Then, we need to prepare network transmission. Therefore, some special protocols need to be developed to maintain the communication between the server and the client. Both the server and client need to maintain a piece of code to process the protocol. When a message is received from the network, the message must be processed first, and then some special code must be written to execute commands for the message.
Here is a design that shows how a traditional chat application is developed:
The disadvantage of this design is:
(1) for each operation, there will be a command, and there will be a Protocol Resolution for transmitting necessary data to the server or receiving data from the server.
(2) If a command changes, the Protocol also needs to be modified to reflect the change in execution.
(3) developers need to consider three issues for each operation:
I a command needs to be developed for local applicationProgramRun on
II logic for sending a message to the server and packaging required data
III. A receiver used to receive the message. It processes the message logic and performs the necessary operations.
Distributed command mode
Distributed command mode solves these problems by introducing an architecture. When a system executes a command, it can automatically execute the same command on the interconnected system. This mode provides a way to execute commands in a process from one connection system to another. You do not need to send a custom message (the message contains required data to the server to inform the server of command execution ), the client does not need to read or convert a message to extract necessary data. All commands executed on the system will be serialized and transmitted to other systems, and then executed locally to simulate the occurrence of this command from the "Source System ".
The distributed command mode has the following advantages:
(1) A command "class" is always developed in the same way as a simple independent system without network awareness.
(2) For designers, there is no need to develop custom protocols or design messages to send and receive each command with the necessary data.
(3) When a command is executed, it will not only be executed on a local process, but also on a process connected to another system.
(4) It enables Interconnected Systems to "Synchronize internally" for each other without the need to know the existence of other systems.
(5) It frees developers from thinking and implementing some problems. The problems are: how to make all interconnected systems execute commands in "synchronous" mode, it is like a local call to each connected system.
(6) Developers do not need to worry about running commands on the same process or other processes in the future.
(7) It rejects a large amount of code built for communication and sends and receives any command in a unified manner.
(8) It is very likely that the Undo/Redo command can be implemented seamlessly. If an application is "undo", all connected systems perform the "undo" operation in the same way.
Here is an illustration showing how a command is executed in a unified manner on all interconnected systems:
The following simple code of the command type shows that commands are always unconscious to interconnected systems:
[Serializable] <br/> public class sendmessagecommand: uicommand, icommand <br/>{< br/> Public String from; <br/> Public String message; </P> <p> Public sendmessagecommand (<br/> string from, <br/> string message) <br/>{< br/> This. from = from; <br/> This. message = message; <br/>}</P> <p> void icommand. execute () <br/>{< br/> base. UI. chathistorytextbox. appendtext (this. from <br/> + "says:" + environment. newline); <br/> base. UI. chathistorytextbox. appendtext (this. message <br/> + environment. newline); <br/>}< br/>}
Development of distributed command mode
The following shows the development of the distributed command mode step by step, and gradually shows how to convert a common command mode into a distributed command mode
Step 1Common command mode
This is a common command mode. Here, the caller creates a command and directly calls the command execution method to execute the command.
Icommand command = new filesavecommand (filecontent); <br/> command. Execute ();
The following is another variant command mode, which is not directly executed by the caller. It uses an executor to execute the command and name it the facade of the command [appearance ]. The changes are as follows:
The difference in this mode is that a command will not be executed by the caller. The caller creates an instance for the command and then passes the instance to the command facade. Command facade always calls the command execution method.
Icommand command = new filesavecommand (filecontent); <br/> commandfacade. Execute (command );
The advantage of this mode is that command facade can interact with the execution of any command and it can provide additional "metadata" required for executing certain contextual commands and commands ". It also plays the role of the Central Command Execution room. In distributed command mode, this special "attribute" is very useful.
Step 2Introduce a bus
In distributed command mode, command facade is actually a command bus. The command bus is created in the message bus mode. The message bus introduces a bus to know how to format a message and how to use some channels to send the message. In distributed command mode, the command bus is similar to the Message bus. It holds the pipeline of a command executor 【Pipeline]. Whenever a command is sent to the Message bus, it notifies all executors in the pipeline to execute the command. The execution of an executor is similar to a channel. In fact, it is used to execute some commands.
The command bus does not change the way commands are executed. The caller writes the same code as above:
Icommand command = new filesavecommand (filecontent); <br/> commandbus. Execute (command );
However, inside the command bus, it will call all the executors in the pipeline to execute the command:
Commandbus.exe cute (icommand command) <br/>{< br/> foreach (icommandexecutor executor in this. pipeline) <br/>{< br/> executor. execute (command); <br/>}< br/>}
Step 3Introduction to Actuators
The executor provides an execution environment for commands. Although command execution requires the necessary code, the command executor provides the necessary support during command execution. For example, draw a Windows form with a text box. Some text must be added to the text box when a command is executed. But the command is an independent class, and it does not reference the text box in the form. Therefore, this form is the executor of the command. It intercepts the command from the command bus and provides a text box reference to the command object. It then runs the following command:
Class mainform: icommandexecutor <br/>{< br/> icommandexecutor. execute (icommand command) <br/>{< br/> textaddcommand cmd = command as textaddcommand; <br/> cmd. thetextbox = This. messagetextbox; <br/> cmd. execute (); <br/>}< br/>}
This is a method of local actuator. Here the command is executed in the process. Of course, there is another way to execute commands, known as distributed command executors, which have the responsibility to send and receive commands through the transmitter. This type of actuator is responsible for maintaining internal synchronization of all interconnected systems and providing distributed services for commands.
However, distributed executors do not directly execute commands. It uses only one transmitter to send commands to another connection system. At the other end, there is another distributed command executor that receives commands from its own transmitter. Then notify the command bus-a command is received! Then the bus sends the command again through the pipeline. This time, the local executor at the other end receives the command and then executes the command in its own context. This results in the execution of the same command on another computer as if the command was created there.
Step 4Introduction Transmitter
The transmitter provides communication services. It is located between the distributed command executor and the network communication library and provides transparent message transmission service. A transmitter is created based on the communication media used by the application. For example, when a client program uses a TCP/IP to connect to the server, it uses a binary transmitter with Sockets for sending and receiving data. After a connection is successfully established, a distributed actuator is created based on the transmitter, and then the actuator is pushed into the command bus to pass commands. For example:
Itransporter transporter = new binarytransporter (address, Port); <br/> icommandexecutor executor = new binarydistributedexecutor (transporter); <br/> commandbus. addexecutor (Executor); </P> <p> commandbus. execute (New hicommand ());
The above pseudo code shows how to associate a distributed executor with a transmitter. When the actuator is added to the bus, it is ready to receive and send any command for execution on the bus. Therefore, when a command on the bus is executed, the command will be sent to the distributed executor. The executor sends the command to the transmitter, and it is its turn to use the socket it creates to transmit messages.
The distributed command mode architecture is a pipeline architecture, which can be seen as the following form:
Technical Architecture
The technical architecture of DCP is as follows:
Execution sequence
When a command is sent to the command bus, the following execution will occur:
(1) The command bus transmits the command to every executor in the Command pipeline.
(2) If the actuator is a local actuator, it will directly execute the command
(3) If the actuator is a distributed actuator, it will use a remotecommand class to decorate the command [in the decorator mode ]. This class contains actual commands and a transmitter ID to identify commands that have been sent.
(4) remote commands are then sent to the transmitter to send the commands to the destination using a communication protocol.
(5) At the other end, the transmitter receives messages. It creates command Objects Based on messages.
(6) The commands sent to the distributed command executor are attached to the transmitter. The executor receives the command, modifies the source attribute of remotecommand, and sets it to the ID of the transmitter that receives the command.
(7) The executor notifies its command bus that the command has arrived.
(8) The command bus notifies all command executors in its pipeline to execute commands.
(9) during this period, the actuator that originally notified the command bus to receive the command will be called again by the command bus because it is in the pipeline. However, it can be seen from the source attribute of Remote Command that it itself sends commands to the bus. Therefore, it does not send commands to the transmitter again to prevent infinite loops.
How to apply this mode
Applications that are interested in implementing this mode need to do the following:
(1) create a command bus only once and once it is created, it cannot be changed
(2) to create a transport layer, it must implement itransporter only once, and once it is created, it cannot be changed
(3) create a local command executor to execute local commands. Once the execution is complete, it usually does not need to be modified.
(4) create a distributed command executor and use the transmitter to send commands to other connection systems. Once the execution is complete, it usually does not need to be changed.
(5) commands required for development
Simple Chat Application
This is a good example of the distributed command mode. It is a chat application. The chat application has two modes: SERVER mode and client mode.
Server Mode
The server enables a transmitter to listen for connections from the client. It also creates a local command bus and registers itself to receive commands sent to the bus so that it can execute local commands.
// 1. create the command bus and attach myself as one of the command executors </P> <p> This. _ mybus = new commandbus (true); <br/> This. _ mybus. addexecutor (this); </P> <p> // 2. establish a collaboration server, this is the transporter </P> <p> This. _ Server = new collaboratorserver (<br/> Int. parse (myporttextbox. text), <br/> guid. newguid (). tostring (), SERVER_NAME, <br/> New packetreceivehandler (packetreceived), <br/> New newclienthandler (newclientconnected), <br/> New clientclosedhandler (clientclosed )); </P> <p> // 3. goto listen mode </P> <p> This. _ server. listen ();
Whenever a client is connected to the server, the transmitter created for the client is attached with a newly created distributed command executor, which is then added to the command bus. In the end, any command sent to the bus can be executed locally or sent to the connected client.
Private bool newclientconnected (collaborationclient client, <br/> out string uniqueid, out string name) <br/>{< br/> uniqueid = guid. newguid (). tostring (); <br/> name = uniqueid; </P> <p> // This client will participate in Distributed Command Execution </P> <p> icommandexecutor executor = new distributedexecutorhttp (client); <br/> This. _ mybus. addexecutor (Executor); </P> <p> // accept the client </P> <p> return true; <br/>}
As long as the client sends any command, it will be received by the bus, and then the bus will send it again through the pipeline to execute the command. Commands are executed on the local server and broadcast to all other clients associated with the server. This will result in a client saying -- hi, then all clients and servers will display hi.
Client Mode
The client first creates a command bus and regards itself as a command executor so that commands can be executed locally.
when it is connected to the server, it creates a distributed command executor and then adds the executor to the command bus. In this way, any commands executed on the client will be sent to the server.
// 1. create the command bus and attach myself as one executor of commands </P> <p> This. _ mybus = new commandbus (false); <br/> This. _ mybus. addexecutor (this); </P> <p> // 2. establish connection </P> <p> This. _ client = new collaborationclient (<br/> servertextbox. text, <br/> Int. parse (serverporttextbox. text), <br/> guid. newguid (). tostring (), <br/> nametextbox. text, <br/> New packetreceivehandler (packetreceived), <br/> New clientclosedhandler (clientclosed); </P> <p> // 3. this client will provide distributed Command Execution Service </P> <p> This. _ mybus. addexecutor (New distributedexecutorhttp (this. _ client); </P> <p> // 4. create the command to inform new client </P> <p> icommand command = new newclientcommand (<br/> New connectedclient (this. _ client. uniqueid, nametextbox. text); </P> <p> // 5. execute the command </P> <p> This. _ mybus. execute (command );
Run the command to send a message.
It is very easy to execute a command. It can keep all clients and servers in sync without any extra effort. For example, to send a message, the following code is enough:
This. _ mybus. Execute (New sendmessagecommand (nametextbox. Text, <br/> messagetextbox. Text ));
Similarly, in order to disconnect and notify everyone-you are leaving the chat room, the following code is enough:
This. _ mybus. Execute (New clientleftcommand (client ));
Local Command executor
Local Command executors receive commands from the command bus and execute them locally. For example, the main form is a local command executor. It basically means receiving commands, decorating the necessary UI support, and finally executing the commands. For example, the following general code is sufficient for a local command executor:
Void icommandexecutor. execute (icommand command) <br/>{< br/> icommand actualcommand = command; </P> <p> // if this is a remote command, </P> <p> // then the actual command is packed inside it </P> <p> If (command is remotecommand) <br/>{< br/> remotecommand = command as remotecommand; <br/> actualcommand = remotecommand. actualcommand; <br/>}</P> <p> // call the execute Method on UI thread </P> <p> If (this. invokerequired) <br/> This. invoke (New methodinvoker (command. execute); <br/> else <br/> command. execute (); <br/>}
Transport Layer-HTTP Transmission
This application has a very useful transport layer that uses HTTP for communication. There are two classes in total. One is collaborationserver, which plays the role of the server, and the other is collaborationclient, which plays the client of the server.
The transport layer is a fully common class library. It receives HTTP packets and sends packets over the network. This class library can also be used in multiple applications to exchange text and binary data. It can also be used to create an HTTP server without writing any communication code, because it already uses the HTTP protocol to exchange data. The following is a simple basic session:
Request:
GET/HTTP/1.1 <br/> correlation-ID: 0b83745d-2aab-4bce-8ac9-b8a590f07768
Response:
HTTP/1.1 200 OK <br/> correlation-ID: 0b83745d-2aab-4bce-8ac9-b8a590f07768
This class library is customized and enhances client-server collaboration. The particularity of this library is that it only opens one TCP/IP socket, but provides two-way communication on the same socket. Therefore, it is a firewall-friendly solution for point-to-point communication.
The collaboration library is fully object-oriented. It receives and sends commands in the form of HTTP messages. Here is an httpmessage, which contains the details of the sent and received messages. The httpmessage is inherited by a class called packet. There are two types of packages. packetrequest is used to send messages to the other party, and packetresponse is returned from the other party.
An application only needs to extend packetrequest and genercisuccesspacket (optional) to provide its own messages. For example, the whatisyournamerequest class can be expanded to packetrequest to send identifiable requests, and the other end can return myidentityresponse (Extended genercisuccesspacket) to respond to the requests.
Commandpacketrequest carries a command and serializes the command to convert it into an HTTP message package. Simply put, it obtains an httpmessage and constructs a command from the message weight.
/// <Summary> </P> <p> // HTTP request packet which encapsulates a distributed command </P> <p> // </Summary> </ p> <p> public class commandpacketrequest: packetrequest <br/>{< br/> private remotecommand _ command; </P> <p> Private Static binaryformatter _ serializer = new binaryformatter (); </P> <p> Public remotecommand command <br/>{< br/> get {return this. _ command ;}< br/> set {This. _ command = value ;}< br/>}</P> <p> Public commandpacketrequest (httpmessage MSG): Base (MSG) <br/>{< br/> // deserialize the message body and extract the command from it </P> <p> memorystream stream = new memorystream (base. body); <br/> This. _ command = _ serializer. deserialize (Stream) as remotecommand; <br/>}</P> <p> Public commandpacketrequest (remotecommand command) <br/>{< br/> // serialize the command </P> <p> memorystream stream = new memorystream (256); <br/> _ serializer. serialize (stream, command); </P> <p> // store the data in the body of the message </P> <p> base. body = stream. getbuffer (); <br/>}< br/>}
The following classes provide distributed command execution over HTTP:
What is in this simple application?
In this simple application, you will see the following content:
(1) A class library for Distributed Command Execution
(2) A very powerful communication library that provides two-way HTTP Communication
(3) A chat application for direct use
Source codeDownload