Scalable SOCKBASE design and implementation

xiaoxiao2021-03-06  45

Scalable SOCKBASE design and implementation (1)

table of Contents

Summary

Problems in Sockets Network Programming

Scalable SockBase design

SockBase programming implementation

Inheritance from SockBase and Its Usage

Summary

System.net Namespace provides a simple programming interface for multiple protocols used on the current network. If you need more programming for underlying control, developers need to use System.Net.Sockets namespace. System.net.sockets provides a managed implementation of a Windows Sockets (Winsock) interface for developers who need to strictly control network access. However, Sockets programming is both annoyance, no scalability, requires developers to control the acceptance and sending of messages, and processing, these work related to business logic, need to write code when programming, once demand changes, Change the reception message list and processing of Sockets. At the same time, both constructs of the command string require the underlying programmers to control, not only easy to make mistakes, but also difficult to change. In the face of complex and variable business logic, such architectures are unusable, while presenting a high requirement for programmers, a large extent of workload is placed on the work of the underlying repetitiveness. Therefore, in order to provide an easy-to-expanded Sockets programming architecture, the developer puts the attention to the business logic, and we propose the design scalable SockBase ideas, and achieve this architecture. Experience shows that not only solves the above existence. Question, and have achieved very good results.

Problems in Sockets Network Programming

In a general Sockets network programming, it is not difficult to appear:

While (sock! = null) {

Temp = readmsg (); // Call the sock.recevie (..) function, turn Byte [] into a string

IF (Temp.trim () == "login")

{

// Do some thing ...

Sock.send (Transmsg ("OK");

}

Else IF (Temp.trim () == "Show")

{

// Do some thing ...

Sock.send (Transmsg (IPS));

}

Else IF (Temp.trim () == "Upload") {

// Do some thing ...

Sock.send (Transmsg ("OK");

// Do some thing ...

Sock.send (Transmsg ("OK");

}

ELSE IF (Temp.trim () == "List") {

// Do some thing ...

Sock.send (Transmsg (Files));

}

Else IF (Temp.trim () == "get") {

// Do some thing ...

Sock.send (Transmsg ("OK");

Temp = read msg (). Trim ();

// Do some thing ...

}

}

From the above code, we can notice that for all news from the client, it is unified in this while () loop ... ...

For the reception of the message command, it is clear that it is similar to the above code mode, uniformly put it into a place. But the above code is the structure of the Switch Case used for the message. This brings a problem. Switch case is Writing in the program code preparation phase, that is, the so-called hard compaction. This is not modified during the program run. This makes the program to send different messages to users in different input / different conditions during operation, or User-defined, or newly added extended commands after the program is completed. At the same time, it is also due to the Switch Case structure, so that the processing of the message is also fixed, and the message processing function is also not dynamically modified. This makes The extensibility of the program is very poor, and for the underlying as described above, for the Socket operation, it cannot be directly used in other software. (Because the message command, the processing function is not necessarily exactly the same). That is, In the usual Sockets network development, developers control the acceptance and transmission of messages yourself, these work related to business logic needs to write code when programming, once the demand changes, and has to rewrite the list of Sockets And process. At the same time, both constructs of the command string require the underlying programmers to control, not only easy to make mistakes, but also difficult to change. In the face of complex and variable business logic, such architectures are unusable, while presenting a high requirement for programmers, a large extent of workload is placed on the work of the underlying repetitiveness.

Scalable SockBase design

For the above questions, we propose scalable sockbase. Scalability is primarily to receive any messages, and can have different processing functions to the same message in different situations..

We think of Windows's message processing mechanism. When we want to handle a system message, or when we handle our custom message, first we add custom messages to the list of procedures, while passing Windows programming The message map in the message map, run the function to handle this message. After receiving this message, you can find the message processing function we bind to it, and then call ..

Go back to Sockets, let's make a thing similar to a Windows message mapping table. There are two elements, one is the message command received, the other is to receive the handler after receiving this message, in the program developer During the development process, as long as the message mapping table is initialized before the specific message is received, it is enough. SockBase automatically calls the corresponding message processing function.

SockBase programming implementation

The above part is theory. Now let's start completing the implementation code of SockBase.

1. Define message mapping table

Based on the above mentioned, there is a need for a similar message mapping table. Here, we use HashTable to store data for messages and process functions .. Since HashTable is a key / value collection, we do the message command For the key, the corresponding message processing function is made as a value. Since there are many kinds, and we want all messages, we can call the corresponding processing functions in one place. So we use the .NET delegate as a HashTable Value.

Delegation is as follows:

Public Delegate Command (String Args);

The method is as follows:

Hashtable commands = new hashtable ();

Commands.add (/ * message command * /, new command (/ * specific processing function * /));

As long as it is called

(Command) Commands [/ * message command * /]) (/ * parameter * /);

Yes ~

2, SOCKBASE specific implementation

Ok, now, the preparation of the message mapping table has been completed. Now SockBase is implemented: P

(1) Confirmation of constructor and variables, implementation

Public class socketbase: idisposable {

// The command processing set to be processed

Protected hashtable m_commandhandlerlist;

Protected NetWorkstream Readstream;

Protected NetWorkstream Writestream;

Protected socket m_sock;

// passed the instance of the socket through the constructor.

Public SocketBase (socket Sock) {

M_Sock = SOCK; ReadStream = New NetworkStream (M_SOCK);

WriteStream = New NetworkStream (M_Sock);

}

Public void dispose ()

{

// Turn the local set of junction

Try

{

IF (m_sock! = NULL)

{

IF (m_sock.connected)

{

m_sock.shutdown (socketshutdown.both);

m_sock.close ();

}

m_sock = NULL;

}

}

Catch (Exception EX)

{

}

}

}

(2) Send and receive functions

The preparation has been completed. Now we start to receive messages for M_Sock, and send messages.

The first is the message transmission and reception. Due to the uncertainty of socket, it is easy to send multiple messages sent together, so we decided to send a fixed size package every message, receive the corresponding package, receive the corresponding Size package.

Define a fixed size of a package in SockBase:

Private static int defaultebuffersize = 5120;

Public Int buffersize

{

Get {

IF (m_buffersize! = 0)

Return M_Buffersize;

Else

Return Defaultebuffersize;

}

Set {m_buffersize = value;

}

Another is sending, receiving functions

Public string receivemsg ()

{

BYTE [] RECS = New byte [buffersize];

INT count = 0;

Int Num;

Do {

Num = Readstream.read (Recs, Count, Buffersize-Count);

IF (NUM == 0) {

Throw new Exception ("Close Close");

}

Count = NUM;

WHILE (Count

Return System.Text.Encoding.Getencoding (Encoding) .getstring (Recs) .Replace ("/ 0", ");

}

Public Void Send (String MSG)

{

BYTE [] Sender = new byte [buffersize];

Byte [] temp = system.text.encoding.Unicode.getbytes (msg);

Array.copy (Temp, Sender, Temp.length);

WriteStream.write (Sender, 0, Sender.length);

Writestream.flush ();

}

(3) Message distribution function

Ok, the following is the function that is sent to the message:

Public void cmdhandler (String ClientMessage)

{

// Analyze the command

String [] cmdlist = clientMessage.split (';');

String cmdtext = "";

For (INT I = 1; I

{

IF (i == cmdlist.length-1)

{

cmdtext = cmdlist [i];

}

Else

{

CMDText = Cmdlist [i] ":";

}

}

// Looking for a suitable matching process

IF (m_commandhandlerlist.containskey (cmdlist [0])) {(Command) m_connamdhandlerlist [cmdlist [0]) (cmdText);

}

}

We judge through all the keys (ie, the message command) in M_CommandHandlerList, if the command received, the corresponding value (ie Command delegate) is directly called directly (ie Command delegation).

(4) SockBase starting starting point

The last part, the starting point of the entire SockBase run:

Public void listensocket ()

{

Try

{

While (m_sock! = null && m_sock.connected)

{

// Intercept the message and make the corresponding processing

CmdHandler (ReceiveMSG ());

}

}

}

Now we just join the commands and handlers of the messages we want to process directly in the m_commandrandlerlist, run listEnsocket (), and proceed accordingly..

Inheritance from SockBase and Its Usage

The basic architecture of SockBase is achieved above. For most Sockets network programming, it can be applied. The following is the method of use..

Here, we have inherited from SockBase to a client_listenthread. In this class, we pass the constructor to pass the corresponding socket instance to m_sock. Initialize the message mapping table, use a thread to run listensockt to receive The message is sent, and the handler is called.

Public Class Client_ListentHread: SocketBase

{

#Region All fields contains the command field

#ndregion

#Region All methods

Public client_listenthread (socket client_socket): Base (socket)

{

LoadcommandrandlerList ();

}

/ / Load all command processing queues

Public void loadingcommandhandlerlist ()

{

CommandHandlerItem.com ("getfile", new command (getfilehandler);

CommandHandlerItem.com.Add ("Fileok", Command (FileokHandler);

}

/ / The following is all command processing functions

Private void getfileHandler (String cmdtext)

{

/ / Check if the file exists

IF ((New FileManager ()). CheckfileExist (cmdtxt)))

{

Send ("OK");

}

Else

{

Send ("failure");

}

}

Private void fileokhandler (String cmdtext)

{

Dispose ();

}

#ndregion

}

Run it by the following function:

Listen_socket = new socket (addressfamily.internetwork, sockettype.stream, protocoltype.tcp);

...

Listen_socket.listen (-1);

While (true) {

Client_Listenthread ClientthRead = new client_listenthread (listen_socket.accept ());

IF (clientthread.sock.connected)

{

Thread filethread = new thread (New ThreadStart (ClientthRead.Listensocket);

FileThread.isBackground = true;

FileThread.start ();

}

to sum up

Through the above SockBase, we can dynamically modify the message mapping table without changing the SockBase. This makes developers to focus on business logic, which greatly facilitates Sockets-based network programming development.

Scalable SockBase design and implementation (2)

table of Contents

Summary

Using HashTable to establish a message mapping table

Message mapping design and implementation

Message map class in SockBase

Summary

In the previous article << Scalable SockBase Design and Implementation (1) >> Our message mapping table is built by simple HashTable table. This is relatively simple, and it is not convenient to expand. And HashTable Some of the features are unnecessary. So here, we use the custom message mapping class (set class CommandLerList and message command / processing function class commandhandler.) To establish a message mapping table.

Using HashTable to establish a message mapping table

Our message mapping table, requires a message command to correspond to one / or more processing functions. At the same time, for the storage of the entire message mapping table, it is necessary to easily add / delete the entries. And can be more convenient The processing function corresponding to the message corresponding to the message is used in the form.

Hashtable is a key / value set of .NET Framework. When a project is joined, the HashTable is called the GetHashCode method of the key value. Since all classes are inherited from System.Objec, the method is called. The hash code of the class can be determined and stored in this code. From the perspective of performance, because the key value search is limited to the key value having the same hash code, HashTable can quickly retrieve any element from the collection, thereby reducing the number of key values ​​that must be checked to find the match. However, because each object-key value inserted into the collection must generate the corresponding hash code, the cost of the project insert is a bit higher. Therefore, HashTable is mainly used in conjunction with a large number of relatively static data in response to any key value.

Since all of our messages are strings, the comparison of the object is made as long as the string matches, it is not necessary to get the HashCode by HashTable. At the same time, because the HashTable is only the key / value pair, directly use cannot bring more Big scalability. So we choose to give up directly using HashTable. Use our custom class.

Message mapping design and implementation

From the above instructions, we have to customize the message mapping class. First define each message in each message mapping table and the class thereof.

Since the message and the actual processing function are not in the same class, we should use the .NET provided by the .NET provided. Because the messages sent / received by Sockets are strings, The parameters of the processing function are also received, so the parameters of the function corresponding to the delegate are String types. And for some special circumstances, such as receiving a message can trigger multiple processing functions. We define it For events. Definitions are as follows:

Public Delegate Void CommandeventHandler (Object Sender, CommandHandlerargs E);

Public Class CommandHandlerarggs: System.EventArgs

{

PRIVATE STRING M_CMDTEXT;

Public String CommandText

{

Get {return m_cmdtext;}

Set {m_cmdtext = value;

}

Public CommandHandlerargs (String cmdText)

{

m_cmdtext = cmdtext;

}

}

In this CommandHandlerargs, m_cmdtext is the parameter of the message.

You can now define our CommandHandler class.

Public Class CommandHandler

{

PRIVATE STRING M_CMDPREFIX;

Private event commandeventhandler m_execmd; Public String CommandPrefix, PUBLIC STRING COMMANDPREFIX

{

Get {return m_cmdprefix;}

Set {m_cmdprefix = value;

}

Public Event Commandeventhandler Commandevent

{

Add {m_execmd = value;

REMOVE {m_execmd- = value;

}

Public CommandHandler (String cmdprefix)

{

m_cmdprefix = cmdprefix;

}

Public void Execommand (String cmdtext)

{

CommandHandlerargs E = New CommandHandlerargs (cmdtext);

IF (m_execmd! = NULL)

m_execmd (this, e);

}

}

In CommandHandler, we define an event to set the message processing function. At the same time, in order to match the message received and the message in the message mapping table, an m_cmdprefix is ​​added to set the message command. And one is one Directly run function execummand, pass the message to the message processing function for the Socket by the parameter cmdtext.

Now there is only the implementation of the message mapping table. Since it is a table, where the store is stored. For the convenience, we inherit directly from CollectionBase. The code is as follows:

Public Class CommandHandlerList: CollectionBase

{

Public CommandHandler Add (CommandHandler Cmdhandler)

{

List.add (cmdhandler);

Return cmdhandler;

}

Public Void Removeat (int index)

{

List.Removeat (Index);

}

Public Void Remove (CommandHandler Cmdhandler)

{

List.remove (cmdhandler);

}

Public int LENGTH

{

Get {return this.count;}

}

Public void clear ()

{

List.clear ();

}

}

In fact, this COMMANDHANDLERLIST class is very simple, which is directly used to store each CommandHandler.

Ok, until now, the structure of the basic message mapping table is complete. Now it is created and runs our message mapping table.

Message mapping table Used in SockBase

First, change the original use of the lux using our custom to CommandLostlerList using our customizes.

// The command processing set to be processed

Protected commandhandlerlist m_commandhandlerlist;

Modify the message distribution function CMDHandler query the code called the message, the modified code is as follows:

Foreach (CommandHandler cmd in m_commandhandlerlist)

{

IF (cmd.commandprefix == cmdlist [0])

{

Cmd.exeCommand (cmdtext);

Break;

}

}

Finally, the part of the initialization message mapping table. The LoadCommandLIST function in class client_listenthread, the modified code is as follows:

Public void loadingcommandhandlerlist ()

{

m_commandhandlerlist.add (New CommandHandler ("getfile") .commandevent = new commandeventhandler (getFileHandler);

m_commandhandlerList.add ("Fileok"). CommandEvent = New CommandEventHandler (Fileokhandler);

At this time, our message mapping table that puts the original HashTable to our custom class is completed.

to sum up

Since Hashtalbe is not very suitable for our custom message mapping table, we have replaced our own CommandHandler and CommandHandlerList.

Scalable SockBase design and implementation (3)

table of Contents

Summary

Problems brought by embedded messages

Design implementation of custom message command classes

Custom message command class

Summary

In the previous article, we are done directly through the embedded string through the SockBase. For example, "login", "logout", this question is that if you accident, write a wrong one Words, in the compile period, cannot be checked.

Problems brought by embedded messages

Since this is a sockets-based network programming, we lose any message commands through Sockets. In the SockBase implementation, we send a message directly through the send (STRING) function. When the SockBase's Send function, The code we wrote is like this:

Private void getfileHandler (String cmdtext)

{

/ / Check if the file exists

IF ((New FileManager ()). CheckfileExist (cmdtxt)))

{

Send ("OK");

}

Else

{

Send ("failure");

}

}

In this, we can see the news we send is to write strings such as "OK", "failure" directly in the code. Of course, our Send function can only send a string .. In this program, It is no problem.

If, in the later version, we modified the message command body, change the "OK" to "OKEY", "Failure" is replaced with "fail", then we have to pass the search function of the vs.net ID or I have to replace the original characters in the current character. If this program code is not much, it is more than 100, it is very simple. I will get it. But if this program is bigger? Do you have a thousand lines of code? How many places have been used in many places?

At this time, the modification program has become a nightmare. It is very careful in the process of modification. It is slightly inadvertent. The program will appear. This situation is because the message is uncertain, and we In the code, the direct embedded message (in the form of a string), and the logic code is directly mixed, affecting the program's modification, scalability.

Design implementation of custom message command classes

Based on the above analysis, we do not use the direct embedded message command in the form of a string. Reveate it, we use our custom message command class.

Our message command has two parts, part of the message body, and the other is the corresponding parameters corresponding to this message. So, we define the message command class CommandBuilder as follows:

Public Class CommandBuilder

{

PRIVATE STRING M_COMMAND;

PRIVATE STRING M_COMMANDTEXT;

PRIVATE STRING M_ALLCMDTEXT;

// Command prefix, ie command

Public String Command

{

Get {return m_command;}

Set {m_command = value;}

}

// Command content

Public String CommandText

{

Get {return m_commandtext;}

Set {m_commandtext = value;

}

}

Initialize it through the constructor:

// External message sends

Public CommandBuilder (String CommandPrefix, String CommandText)

{

m_command = commandprefix;

m_commandtext = commandText;}

// Constructor is a complete command

Public CommandBuilder (String AllcommandText)

{

m_allcmdtext = allcommandtext;

}

Since we are sent through SockBase, including messages and parameters, we have to connect the message body and parameters. In order to facilitate the resolution of the message body and parameters, we have to add a separator in the message body and parameters. Based on the same reason. We also do not use the direct embedded string, and use a member variable inside the CommandBuilder to save the divider.

Private string m_splitechars = ";";

In this way, if we want to modify, just modify the value of this member, and we can also store the separator in the configuration file. Read directly from the configuration file when you use. This is more flexible. But here, We use the above way.

Ok, the basic data member of the message command class is complete.

Now provide a function that directly outputs the finals that can be sent directly through the SOCKBASE's send function:

Public String Sendermsg

{

get

{

IF (m_allcmdtext! = null && m_allcmdtext! = String.empty)

Return M_AllcmdText;

Else

{

RETURN M_COMMAND M_SPLITECHARS M_COMMANDTEXT;

}

}

}

Now the message command class is basically completed, and now wrap the command. We can write directly into a static member of a class, all the reference to the message directly uses the static members in this class. Similarly, we can also Write into a configuration file. Here, for simple, we use the static members of the class directly. Defining the commandList is as follows:

Public class commandlist

{

// Command string

Public static string getfile = "getfile";

Public static string ok = "ok";

Public static string failure = "failure";

}

Ok, you can start using CommandBuilder and CommandList instead of the inline string.

Custom message command class

Here, we don't have to modify SockBase, just modify the Client_ListentHread class in the previous article. The modified code is as follows:

Private void getfileHandler (String cmdtext)

{

CommandBuilder Cmdbuilder;

/ / Check if the file exists

IF ((New FileManager ()). CheckfileExist (cmdtxt)))

{

Cmdbuilder = New CommandBuilder (CommandList.Getfile, CommandList.ok);

}

Else

{

Cmdbuilder = New CommandBuilder (CommandList.Getfile);

}

Send (cmdbuilder.sendermsg);

}

to sum up

Packaging the string (static) variables such as strings in the program are a good habit, especially in more program development. This is conducive to the multiplexing of the code, and the maintenance of the code modification.

转载请注明原文地址:https://www.9cbs.com/read-57079.html

New Post(0)