Abstract: This paper introduces a new design method of message queue-based duplicate server communication software, which is different from the parallel server and general duplicate server communication software, this new software has the advantage of generating a small number of sub-processes, and is easy to manage the connection between the client and the server. It is suitable for the case where the number of clients is large and the random data communication occurs, it can effectively improve the server operation efficiency.
Keywords: TCP/IP network duplicate server communication software socket connection shared memory Message Queue
Difference between concurrent server and duplicate server
Generally, TCP/IP server communication software works in parallel, that is, a daemon is responsible for listening to client connection requests, then one or more sub-processes are generated by the daemon to establish a connection with the client to complete communication. The disadvantage is that as the number of connected clients increases, the number of communication sub-processes generated will increase, and the server running efficiency will inevitably be affected when the number of clients is large. A duplicate server is a connection established with a server after receiving a connection request from the client, then, you can receive the request connection from another client after processing the communication task with the client. The advantage is that you do not have to generate a communication sub-process, the disadvantage is that the client needs to establish a connection with the server before each communication, which is overhead and cannot be used for random data communication and busy business processing.
The new type of duplicate server proposed in this article is different from the general duplicate server. It abandons the disadvantages of the above two types of servers and combines their advantages, the communication software of this server has the characteristics of common duplicate servers, but can process random access from clients. It will take advantage of this software in applications with a large number of clients and busy businesses. The Communication Software of the duplicate server can establish a connection with all clients using only three processes, and always maintain these connections.
How to establish a connection between communication software of duplicate servers and clients
Basic Ideas
When the first client requests a connection to the server, the server daemon establishes an initial connection (L0) with it. The client uses l0 to send two port numbers to the server, the daemon registers the IP address and port number of the client in the shared memory record, and then disables l0. After obtaining the Client IP address and port number from the shared memory, the two communication sub-processes generated by the daemon request the connection to the client and establish a connection (L1) read from the client) and a connection (L2) written to the client, and records the handles of the two connected sockets in the shared memory. When another client requests a connection, the daemon does not generate a communication sub-process, but registers the Client IP address and port number in the shared memory. In a large loop, the communication sub-process first queries whether there are new records in the shared memory. If yes, it establishes a connection with this client and then polls all the read sockets with established connections, check whether the data is readable. If yes, read the data and indicate which records of the shared memory are obtained from the read socket, another communication sub-process obtains the corresponding write socket from the shared memory according to the record number, and finally writes the result data to the socket to the client.
Establish a connection
(1) the initial process of the server communication software first establishes a socket on the public port, establishes a listening queue on the socket, and generates a daemon tcp_s, then the initial process is exited. The daemon blocks the function accept until there is a client connection request. When there is a connection request, it calls the server function for processing, and then continues to wait for the request from another client. To avoid repeated connections after the TCP/IP connection is removed, the connection is usually stored in the outdated connection table, to avoid time_wait (out-of-date connection) after the connection is removed, you can use setsockopt to set the linger delay mark of the socket and set the delay time to 0. The server registers a globally recognized public port number in the/etc/services file: tcp_server 2000/TCP.
Struct servent * sp; Struct sockaddr_in peeraddr_in, myaddr_in; Linkf = 0; SP = getservbyname ("tcp_server", "TCP "); Ls = socket (af_inet, sock_stream, 0);/* Create a listener socket */ Myaddr_in.sin_addr.s_addr = inaddr_any; Myaddr_in.sin_port = Sp-> s_port;/* public port number */ BIND (LS, & myaddr_in, sizeof (struct sockaddr_in )); Listen (LS, 5 ); Qid3 = msgget (msgkey3, 0x1ff);/* Get the ID of the Message Queue */ Qid4 = msgget (msgkey4, 0x1ff ); Signal (sigcld, sig_ign);/* prevent sub-processes from becoming zombie after exiting */ Addrlen = sizeof (struct sockaddr_in ); Lingerlen = sizeof (struct linger ); Linger. l_onoff = 1; Linger. l_linger = 0; Setpgrp (); Switch (Fork () {/* generate daemon */ Case-1: exit (1 ); Case 0:/* daemon */ For (;;){ S = accept (LS, & peeraddr_in, & addrlen ); Setsockopt (S, sol_socket, so_linger, & linger, lingerlen ); Server (); Close (s ); } Default: Fprintf (stderr, "the initial process exits, and the daemon listens to client connection requests./N "); } |
(2) The client runs the communication program tcp_c: tcp_c rhostname in this form. rhostname is the host name of the server to be connected by the client. The/etc/services file on the client must also be registered: tcp_server 2000/tcp, and the public port number 2000 must be the same as the server.
Int qid1, qid2, s_c1, s_c2, cport1, cport2; Struct servent * sp; Struct hostent * HP; Memset (char *) & myaddr_in, 0, sizeof (struct sockaddr_in )); Memset (char *) & peeraddr_in, 0, sizeof (struct sockaddr_in )); Addrlen = sizeof (struct sockaddr_in ); SP = getservbyname ("tcp_server", "TCP "); HP = gethostbyname (argv [1]);/* obtain the Server IP address from/etc/hosts */ Qid1 = msgget (msgkey1, 0x1ff ); Qid2 = msgget (msgkey2, 0x1ff ); Cport1= 6000; S = rresvport (& cport1 ); Peeraddr_in.sin_family = hp-> h_addrtype; Bcopy (HP-> h_addr_list [0], (caddr_t) & peeraddr_in.sin_addr, HP-> h_length ); Peeraddr_in.sin_port = Sp-> s_port; Connect (S, (struct sockaddr *) & peeraddr_in, sizeof (peeraddr_in )); Cport1 --; S_c1 = rresvport (& cport1 ); Cport2 = cport1; S_c2 = rresvport (& cport2 ); Sprintf (cportstr, "% DX % d", cport1, cport2 ); Write (S, cportstr, strlen (cportstr) + 1 ); Close (s ); |
Set the variable cport1 to an integer and call the rresvport function. This function first checks whether the port number cport1 is occupied. If it is occupied, it will try again and again until an unused port number is found, then a socket is generated, and the socket is connected to the port number to form a semi-correlation at the client end. Then, the connect function is called to send a connection request to the server. Before the client sends a connection request, it has used the functions gethostbyname and getservbyname to obtain the Server IP address and its public port number. In this way, a complete correlation is formed and an initial connection with the server can be established. Next, create two sockets s_c1 and s_c2, and use the initial connection to send the port numbers of the two sockets of the client to the server as strings, in this case, you can close the initial connection after the task is completed. The above completes the initial connection with the server. Next, the client waits for two connection requests from the server.
(3) After receiving the connection request from the client, the tcp_s listening queue reads the two port numbers sent by the client and generates two communication sub-processes tcp_s1 and tcp_s2 during the first call, it will not be generated in the future, which is the biggest difference with the concurrent server. The tcp_s process registers the two port numbers and IP addresses of the client in the last record of the shared memory. The sub-process obtains these two ports through the shared memory, then establish a connection with the client separately. Tcp_s continues to be in the listening status to respond to connection requests from other clients. Both sub-processes should disable socket s inherited from the parent process but not used.
Server (){ Int F; char C; Cport1 = cport2 = f = 0; For (;;){ Read (S, & C, 1 ); If (C = 0) break; If (C = 'X '){ F = 1; continue; } If (f) cport2 = (cport2 * 10) + (c-'0 '); Else cport1 = (cport1 * 10) + (c-'0 '); } /* Register the client port number and IP address in the shared memory */ Shm_login (cport1, cport2, peeraddr_in.sin_addr.s_addr ); If (linkf = 0) {/* only two sub-processes are generated */ If (Fork () = 0) {/* sub-process tcp_s2 */ Close (s); server_send (); } Else If (Fork () = 0) {/* sub-process tcp_s1 */ Close (s); server_receive (); } } Linkf = 1; } |
The shared memory structure is as follows. The communication sub-process tcp_s1 reads from s_socket1 and tcp_s2 writes to the corresponding s_socket2.
Struct s_linkinfo { Int ID;/* mark of the connection, which is sequentially numbered from 1 */ Int s_socket1;/* server read socket */ Int linkf1;/* indicates the connection to the client's cport1. The value 0 indicates that no connection is established, and the value 1 indicates that no connection is established */ Int cport1;/* The first port number of the client */ Int s_socket2;/* server write socket */ Int linkf2;/* indicates the connection to the client's cport2 */ Int cport2;/* The second port number of the client */ U_long client_addr;/* Client IP Address */ Char flag;/* indicates the memory usage, 'I': occupied, 'O': not occupied */ }; |
(4) tcp_c uses listen (s_c1, 5) to create the first listening queue of the client on socket s_c1, waiting for the connection request from the server. After establishing the first connection with the server, use listen (s_c2, 5) to establish the second listening queue and establish the second connection with the server.
Listen (s_c1, 5 ); S_w = accept (s_c1, & peeraddr_in, & addrlen ); Close (s_c1);/* only one connection request is allowed */ Linger. l_onoff = 1; linger. l_linger = 0; Setsockopt (s_w, sol_socket, so_linger, & linger, sizeof (struct linger )); Listen (s_c2, 5 ); S_r = accept (s_c2, & peeraddr_in, & addrlen ); Close (s_c2 ); Setsockopt (s_r, sol_socket, so_linger, & linger, sizeof (struct linger )); |
The Worker Process tcp_s1 calls the server_receive function to continuously query whether new clients are registered in the shared memory in a loop. The method is to determine whether the linkf1 mark of the last record in the shared memory is 0, if the value is 0, call the connect_to_client function to establish the first connection with the client, and then Round-Robin all the read sockets. If there is data, read, and no data, read the next read socket.
Server_receive (){ Int S1, Len, I, linkn, linkf1, N; Struct msg_buf * Buf, mbuf; Buf = & mbuf; For (;;){ Linkn = shm_info (0, getlinkn ); Linkf1 = shm_info (linkn, getlinkf1 ); If (linkf1 = 0 ){ If (I = connect_to_client (linkn, 1) <0 ){ Shm_logout (linkn); continue; } } For (n = 1; n <= linkn; n ++ ){ S1 = shm_info (n, gets1 ); I = read (S1, Buf, msgsize ); If (I = 0 ){ Fprintf (stderr, "a client exit! /N "); Shutdown (S1, 1); close (S1 ); Shm_logout (N ); Linkn --; continue; } If (I =-1) continue; Buf-> mtype = msgtype; Buf-> SID = N; Len = strlen (BUF-> mdata ); Fprintf (stderr, "mdata = % s/n", Buf-> mdata ); I = msgsnd (qid3, Buf, Len + bufctlsize + 1, 0 ); } } } |
Because the read flag of the read socket has been set to o_ndelay, the READ function will return-1 if no data is readable. In this way, we can receive random data from the client and respond to connection requests from the new client in a timely manner. This is the key to the implementation of repeated servers. If the READ function returns 0, it indicates that the client communication program has exited or another reason, such as the client's server or network communication failure. At this time, it is necessary to clear the corresponding client records from the shared memory. If the above fault occurs during connection establishment, the corresponding client records should be cleared from the shared memory. When data is readable, set the SID flag to N, indicating that the data is read from the nth client, in this way, tcp_s2 can write data to the nth client based on the message Sid mark.
The Worker Process tcp_s2 calls the server_send function and continuously queries whether new client connections are registered in the shared memory in a loop, the method is to determine whether the linkf2 mark of the last record in the shared memory is 0. If it is 0, the connect_to_client function is called to establish a second connection with the client, and then read data from the message queue. Because there is only one tcp_s2 process reading the message queue, there is no need to differentiate messages, and data is read. Then, according to the Message Sid mark, find the socket from the shared memory and write the data to the socket. Because the write socket is created in the tcp_s2 process, you only need to use the socket handle to access the socket. In the msgrcv function, you must set the ipc_nowait flag to avoid blocking when no data is available. In this way, you can continue to execute the following program to establish a connection with the next client in a timely manner. This is also a key point. Tcp_s2 calls the server_send function to send data, while tcp_s1 calls the server_recvice function to receive data.
Server_send (){ Int S2, linkn, linkf2, I; Struct msg_buf * Buf, mbuf; Buf = & mbuf; For (;;){ Linkn = shm_info (0, getlinkn ); Linkf2 = shm_info (linkn, getlinkf2 ); If (linkf2 = 0 ){ If (I = connect_to_client (linkn, 2) <0 ){ Shm_logout (linkn); continue; } } I = msgrcv (qid4, Buf, msgsize, msgtype, 0x1ff | ipc_nowait ); If (I =-1) continue; S2 = shm_info (BUF-> Sid, gets2 ); If (write (S2, Buf, I + 1 )! = I + 1 ){ Perror ("write"); close (S2 ); } } } |
The connect_to_client (n, type) function indicates that the server establishes the type connection with the nth client. This function is called by two sub-processes at the same time. The IP address and port number of the client are identified from the shared memory respectively, and a connection is established with the client. The connection is in the data space of each sub-process, shared Memory is used to register the connection string handle in the shared memory so that the two sockets connected to the same client can form a one-to-one correspondence.
In this way, tcp_s2 can query the corresponding write socket based on the socket that the data is read into, in order to correctly send the processing result to the corresponding client. Tcp_s1 calls this function with type = 1 and uses cport1 of the N records in the shared memory to establish the first connection with the client, at the same time, register the socket (read socket) of the connection server in s_socket1 of the N records in the shared memory, and set the connection flag linkf1 to 1.
Tcp_s2 calls this function with type = 2 and uses the cport2 of the N records in the shared memory and the client IP address to establish the second connection with the client, also, register the socket (write socket) of the connection server in s_socket2 of the n record of the shared memory, and set the connection flag linkf2 to 1. Because this function is called by two sub-processes at the same time, in order to maintain synchronization between processes, when type = 2, it must wait until the linkf1 of the n record is 1 to continue execution, that is, the first connection must be established before the second connection can be established. This is determined by the client communication program, because the client communication program monitors and establishes the first connection before listening and establishing the second connection. The sub-processes tcp_s1 and tcp_s2 communicate with each other through the shared memory. In practice, the last record of shared memory is always used.
② :( 5991,5990, 168.1.1.71) ┌ ── ① :( 5991,5990) 168.1.1.21
── ─
│ Tcp_s │ initial connection l0 │ client 1 │
│ Shared memory ── ─ ┘ │ ├ ── ┬ ┤
│ ID S1 linkf1 cport1 S2 linkf2 cport2 ip_address flag │ 5999 │ 5998 │
│ ─ ── ┬ ── ┐ │ └ ── ┴ ┘
│ 1 │ 12 │ 1 │ 5999 │ 13 │ 1 │ 5998 │ 168.1.1.21 │ I │ 168.1.1.22
│ ─ ── ┼ ── ┤ │ ┌ ── ┐
│ 2 │ 14 │ 1 │ 5995 │ 17 │ 1 │ 5994 │ 168.1.1.22 │ I │ clinet 2 │
│ ─ ── ┼ ── ┤ │ ├ ── ┬ ┤
Choose → Release 3 │ 0/22 │ 0/1 │ 5991 │ 0/23 │ 0/1 │ 5990 │ 168.1.1.71 │ I │ 5995 │ 5994 │
─-┴ ── ┼ ┴ ┬ ─ ┼ ┴ ─ ── ┘
⑤ :( 22,1) sampled │ 201710000⑥ :( 5990,168.1 .1.71) │ 168.1.1.71
│ ── ─ ┐ │ ┌ ── ─ ┐
│ 3,:() ── ┴ ─ └ ┤ client 3 │
│ └ ── ─ ┤ │ 13 │ ── ┬ ┤
│ ③ :( 5991,168.1 .1.71) │ communication ── sampled │ 5991 │ 5990 │
│ ── ┐ │ Sub-process │ 17 │ └ ── ┴ ── ┬ ┘
┤ │ 12 │ tcp_s2 ├ ── ┤ │ L2 ⑦ 7
│ Communication ── sampled │ 23 ── ─ provided
│ Sub-process │ 14 │ ── ┴ ── ┘ │
│ Tcp_s1 ├ ── rjl1 (read socket 22) (write socket 23) │
│ 22 ├ ── ─
── ┴ ── ┘ ④
Figure 1 process of establishing a connection between the server and the client
Here, you must set the reading flag of the socket to o_ndelay. In this way, when reading data, the READ function will not be blocked. This is the key to the implementation of the duplicate server. Because UNIX systems process sockets in the same way as common files, they can use the fcntl function that sets the file flag to process sockets.
Int connect_to_client (n, type ){ U_long client_addr;/* type = 1, 2 */ Int S2, cport, sport, I; If (type = 2 ){ For (;) if (shm_info (n, getlinkf1) = 1) break; } Transport = 6000-1; S2 = rresvport (& Sport ); Cport = shm_info (n, getcport1 + type-1 ); Client_addr = shm_info (n, getcaddr ); Peeraddr_in.sin_port = htons (short) cport ); Peeraddr_in.sin_addr.s_addr = client_addr; Connect (S2, (struct sockaddr *) & peeraddr_in, sizeof (peeraddr_in )); Flags = fcntl (S2, f_getfl, 0 ); Fcntl (S2, f_setfl, flags | o_ndelay ); If (type = 1) I = shm_update (n, S2, 0, 0 ); If (type = 2) I = shm_update (n, 0, S2, 0, 1 ); Return (I ); } |
After receiving two connections to the server, ⑺ tcp_c generates the sub-process tcp_c1 and calls the client_receive function to receive data. tcp_c calls the client_send function to send data. If the client_receive function exits from the loop, it indicates that the server communication software has exited. Therefore, the child process must first kill the parent process before exiting.
CPID = getpid ();/* process ID of the parent process */ If (Fork () = 0) {/* tcp_c1 */ Close (s_w ); Client_receive (); Sprintf (cmdline, "Kill-9% D", CPID ); System (cmdline ); } Else { Close (s_r ); Client_send (); } |
How the client server receives and sends data
Data Transfer Process
Hardware Division:
├ ── Server ── → zookeeper network → zookeeper ── client ── → listener
── ┐ ⑥ ┐ ⑦ ┐
Latency → maximum qid4 latency → maximum L2 latency → maximum qid2 latency-latency
⑤ │ ── ┘ ↓
── ┐ ┴ ┐ → ┌ ── ┴ ┐ ──
│ DB worker route → ┤ s_process │ c_process worker route → worker end user │
── ┘ ┬ ┘ ─ └ ┬ ┘ ──
④ ↑ ── ┐ │ ①
When-then qid3 was used when L1 was used when qid1 was used when
Software Division: ── ┘ ③ └ ── ┘ ② └ ── ┘
├ ← ── S_process ── → extends tcp_s → extends tcp_c → extends c_process → extends
Figure 2 Data Transfer Process between client servers
S_process and c_process are the server business programs running on the server and the customer business processes running on the client respectively. Qid3, qid4, qid1, and qid2 are message queues on servers and clients respectively.
Tcp_s and tcp_c are communication software running on the server and client respectively. The two connections established between the client and the server are L1 and L2. L1 is dedicated to the client to the server, and L2 is dedicated to the server to the client.
The following describes the data transfer process shown in Figure 2 and the four functions used for receiving and sending data. Because the business program does not know when to receive or send messages, these four functions continuously attempt to receive or send data in a loop. The data structure of the message is sg_buf. The message consists of the Message Type mtype and the body segment mdata.
The data stored in the body section is unstructured and must be defined as a data structure (struct). mdata is divided by various variables in the structure, so that the data in mdata can be understood and used. You can also rename some areas before mdata for other purposes. A message plays a similar role as a carrier in the entire data transmission process.
# Define msgsize 200 Struct msg_buf { Long mtype;/* Message Type */ Long CPID;/* Customer business process ID */ Long Sid;/* shared memory record number */ Long msgid;/* Message ID */ Char mdata [MSGSIZE-16];/* Data zone */ } |
① The customer business program c_process receives data from the end user and stores the data in a structure first. Then, the content of the structure is merged into the Buf-> mdata according to a certain format, then, put the Buf in Message Queue qid1.
PIDC = getpid ();/* process ID of c_process */ Buf-> mtype = 1;/* The message category is 1 */ Buf-> SID = 0;/* Sid is useless on the client */ Buf-> msgid = ++ msgid; Buf-> CPID = PIDC; Msgsnd (qid1, Buf, msgsize, 0 ); |
② The process tcp_c calls the client_send function to get the message from qid1 and then writes it to L1 to the server. Messages obtained from qid1 are not differentiated. All messages in qid1 must be sent by the tcp_c process.
For (;) {/* get the message with mtype = 1 */ Msgrcv (qid1, Buf, msgsize, 1, 0 ); Write (s_w, Buf, I + 1 ); } |
③ Process tcp_s1 calls the server_receive function to read data from L1 to Buf and put the Buf as a message into qid3.
For (n = 1; n <= linkn; n ++ ){ S1 = shm_info (n, gets1 ); I = read (S1, Buf, msgsize ); If (I =-1) continue; If (I = 0).../* determines that the client has exited */ /* N is the serial number of the entry S1 registered in the shared memory */ Buf-> SID = N; Msgsnd (qid3, Buf, msgsize, 0 ); } |
④ The server business program s_process receives messages from the Message Queue qid3 to the Buf, converts the Buf-> mdata into a structure, and operates the database according to the structure content. S_process is in a loop. When a message exists, it is removed for the operation required by the message. messages are not differentiated. If no message function is available, msgrcv is blocked.
⑤ S_process access the database based on the message content and put the result in a structure, and then copy the content of this structure to Buf-> mdata, then, the buffer Buf is stored in Message Queue qid4 in the form of messages. Finally, s_process continues to receive new messages in a loop.
For (;;){ Msgrcv (qid3, Buf, msgsize, 1, 0 ); ...... /* Explain the Buf-> mdata content, operate the database, and store the results in Buf-> mdata */ Buf-> mtype = 1; Msgsnd (qid4, Buf, msgsize, 0 ); } |
6. The process tcp_s2 calls server_send to take the first message of mtype = 1 from qid4 and write it back to the client in L2.
For (;;){ I = msgrcv (qid4, Buf, msgsize, 1, 0 ); If (I =-1) continue; S2 = shm_info (BUF-> Sid, gets2 ); Write (S2, Buf, I + 1 ); } |
7. The process tcp_c1 calls the client_receive function to read data from L2 to Buf and put the Buf as a message into qid2. If the function read returns 0, it indicates that the server's communication program has exited, And the loop is interrupted. Here, the Message Type mtype must be set to the process id cpid of the customer's business process to facilitate customer business program identification.
For (;;){ I = read (s_r, Buf, msgsize ); If (I = 0 ){ Close (s_r); Return (1 ); } Buf-> mtype = Buf-> CPID; Msgsnd (qid2, Buf, I + 1, 0 ); } |
⑧ The customer business program c_process removes the first message of mtype = PIDC (its own process number) from Message Queue qid2 and puts it in the buffer Buf. Then, it divides the data in Buf-> mdata into structures, after processing the structure, the final result is displayed to the user. In ①, when will the result be obtained from qid2 after c_process sends data? After a message is sent, the customer's business program immediately sends the result to qid2. if the message is not sent to the customer, the message will be blocked until the message arrives. Here, the program is designed to issue a clock alarm after 20 seconds of congestion, and call the function overtime to make a timeout response. When the clock alarm is triggered, if the msgrcv function is in the blocking status, it also exits and returns-1.
There is another problem here. After sending a new message, c_process may first receive the previous message that failed to be received due to timeout, the simplest way to solve this problem is to give each message number before sending the message. If the number of the received message is different from the number of the sent message, the message will be deleted from the message queue, you can also extract the message and place it in a certain place for further processing. Then, wait for the message to be correctly numbered. The method for deleting a message is simple. You only need to retrieve the message from the message queue. If the process c_process is killed, the late message will always exist in the message queue until the client is shut down because its mtype indicates that the c_process is no longer running, therefore, when necessary, it is necessary to handle the consequences of these unowned messages.
Alarm (20 ); Signal (sigalrm, overtime ); For (;;){ I = msgrcv (qid2, Buf, msgsize, PIDC, 0 ); If (I =-1) break; If (BUF-> msgid = msgid) break; } Alarm (0 ); Printf ("% s/n", Buf-> mdata );Overtime (INT sig ){ Strcpy (BUF-> mdata, "overtime "); } |
Solutions to two key problems
A server usually needs to connect to multiple clients, and each client will run multiple c_process processes at the same time because it supports multiple users. How can the server accurately send messages to which client? How can every c_process running on another client correctly obtain the message sent to itself? These are two key issues. The first problem has been discussed earlier, mainly through the SID mark of the message. The second problem is solved in this way. In Step 1, The c_process process first places its own process number pidc in the Buf-> CPID, and the value remains unchanged during subsequent transmission, in step 7, assign the CPID value to the Message class mtype. In this way, the c_process process removes messages whose message type mtype is equal to its own process number PIDC from Message Queue qid2 at the time of termination, without sending messages to the c_process process of the same client by mistake. (Figure 3)
┌ ── ─ ┐ ┌ ── ─
│ Server ── ┤ ├ ── ─ ┐ ── ┐ │
│ Tcp_s │ ── ─ ┤ tcp_c %│ │ c_process2 │
│ ┌ ── ─ ┐ └ ── ┬ ── ┤ │ ├ ── ┤ │ ── ┘ │
│ S_process │ ── ┴ ┐ │ ┌ ── → ┤ tcp_c1 │ ── ┐ │
│ Service programs │ shared memory │ L2 ── sampled │ c_process1 │
│ ─ ── ┬ ── ┘ ── ┬ ┘ │ ↓ 7│ ── ┬ ┘ │
│ ⑤ Sampled Sampled-sampled L1 │ Sampled-sampled │
│ ── │ ┌ ── ┤ Tcp_s1 ── ┘ │ ② ↑ │
│ ── ③ │ L1 L1 L1 L1 '│ ── ① │
│ Qid3 │ ── sampled │ qid1 │ ├ ← ┘ │
│ ── ┤ ┌ T┤ tcp_s2 ├ ── ┼ │ ── ┤ │
│ Qid4 │ sampled → ── sampled │ qid2 │ sampled ── sampled │
│ ── ⑥ ┘ ⑥ ── ┤ │ └ │ ── ┴ │
│ └ ── ┘ │ └ → ┤ Client2 │ └ ── ┘ Client1 │
└ ── ─ ┘ L2 '└ ── ┘ └ ── ─
Figure 3 Process of message transmission between the server and client
Message Queue and shared memory
Before running the server communication software, you should first create shared memory and message queue. For the method of creating shared memory, see [3]. This article shares four Memory sharing operation functions: shm_login (cport1, cport2, client_addr) to apply for a record in the shared memory and register the three parameters, set the flag to 'I, indicating that it is in use and the position of the root data record is assigned to the record ID. Shm_logout (ID) deletes the ID record in the shared memory, moves the subsequent record forward, and recalculates the number of each record. Shm_info (ID, type) queries the content of the ID record based on type. For example, if type is gets1, it indicates to query the value of s_socket1. When type is equal to getlinkn, it counts the total number of records in the shared memory. Shm_update (ID, s_socket1, s_socket2, linkf1, linkf2) modifies the content of the ID record. If a parameter is zero, this parameter is not modified, such as shm_update (n, S2, 0, 1, 0) Only the values of s_socket1 and linkf1 are modified. The remaining contents are not modified. When the business is busy, it is necessary to expand the storage capacity of the Message Queue. The following example doubles the capacity of the Message Queue qid3.
Struct msqid_ds sbuf1, * sbuf; int qid3; Sbuf = & sbuf1; Qid3 = msgget (msgkey3, 02000 ); Msgctl (qid1, ipc_stat, sbuf ); Sbuf-> msg_qbytes * = 2; Msgctl (qid3, ipc_set, sbuf ); |
Other questions
Because the connection between the server and the client is registered in the shared memory, you can control the number of connections between the server and the client. After the server receives the connection request from the client, you can first query the shared memory, if the number of connections established with the same client has reached the limit, the server daemon can close the initial connection established with the client, at the same time, the client's port number and IP address are no longer registered in the shared memory, so that the process will not establish a connection with the client.
In addition, this type of duplicate server communication software uses a read-only socket and a write socket, because each socket has an independent read buffer and write buffer, the length is 24 K. Therefore, the read-only socket will not use the write buffer, and the write socket will not use the read buffer. To save system resources, it is necessary to set the socket to only one buffer zone, for example, set the write buffer length of a read-only socket to 0.
Int I, bufsize; I = sizeof (INT ); Getsockopt (LS, sol_socket, so_sndbuf, & bufsize, & I ); Fprintf (stderr, "size = % d/N", bufsize ); Bufsize = 0; Setsockopt (LS, sol_socket, so_sndbuf, & bufsize, I ); Getsockopt (LS, sol_socket, so_sndbuf, & bufsize, & I ); Fprintf (stderr, "size = % d/N", bufsize ); |
Figure 2 shows only one of the application modes. The repetitive server communication software mentioned in this Article can also be used in more complex scenarios. For example, when the client needs to communicate with another client, the server can be used as a transit station, so that no connection is required between the client. For example, if the communication sub-process tcp_s1 finds that the target client is registered in the X record of the shared memory, the SID of the received message is set to X, in this way, the process tcp_s2 can send messages to the X client. Of course, the source client should specify the IP address of the target client in the sent message. This is useful when the communication between clients is not frequent, because it can reduce the overhead of all clients to establish connections to each other, which is conducive to improving the efficiency of the entire network. In a specific application, after receiving a service request from the client, the server cannot process the request for some reason, so the message is stored, the server can process the customer's request and return the result to the client only when the conditions are ripe. At this time, the client cannot regard this as a late message and should handle it separately.