The interesting solution of Linux socket IO model _linux

Source: Internet
Author: User
Tags bind call back goto readable socket connect strlen htons advantage

Preface

Before seeing a very humorous way to explain the windows of the socket IO model, to borrow this story to explain the Linux socket IO model;

Lao Chen has a daughter who works in the field and can't often come back, Lao Chen and her through letter contact.
Their letters will be delivered by the postman to the mailroom in front of their neighborhood. This is very similar to the socket model.

The following is the old Chen to receive letters as an example of Linux Socket I/O model.

one, synchronous blocking model

Old Chen's daughter went to work in the field for the first time, after sending away her, old Chen very worry she arrived safely did not;
So Lao Chen does nothing, has been in the community doorway waiting for her daughter's newspaper safe letter to;

This is the synchronous blocking mode of Linux;

In this mode, the user-space application executes a system call and blocks until the system call completes (data transfer completes or an error occurs).

The socket is set to blocking mode, and the process or thread enters the wait state until the operation completes when the socket cannot complete the I/O operation immediately.

As shown in Figure 1:

* * \brief * TCP client/#include #include #include #include #include #define servport 8080 #define Maxdatasi
 ZE int main (int argc, char *argv[]) {int sockfd, recvbytes; Char Rcv_buf[maxdatasize];
 /*./client 127.0.0.1 Hello */char snd_buf[maxdatasize];  struct Hostent *host; /* struct hostent * {* char *h_name;//General hostname * char **h_aliases;//hostname ' s alias * T H_addrtype; 
     af_inet * int h_length;
     * Char **h_addr_list;
     * };

 * * struct sockaddr_in server_addr;
 if (ARGC < 3) {printf ("usage:%s [IP Address] [any string]\n", argv[0]);
 return 1;
 } *snd_buf = ' I ';

 strcat (Snd_buf, argv[2]);
 if ((SOCKFD = socket (af_inet, sock_stream, 0)) = = 1) {perror ("socket:");
 Exit (1);
 } server_addr.sin_family = Af_inet;
 Server_addr.sin_port = htons (Servport);
 Inet_pton (Af_inet, argv[1], &server_addr.sin_addr);

 memset (& (Server_addr.sin_zero), 0, 8); /* Create the connection by socket * MEANS so Connect "SOCKFD" to "server_addr" * Synchronous blocking Mode */if (connect sockfd, (struct sockaddr *) &server_addr, sizeof (
 struct sockaddr) = = = 1) {perror ("connect");
 Exit (1);
 }/* Synchronous blocking Mode/if (send (SOCKFD, snd_buf, sizeof (SND_BUF), 0) = = 1) {perror ("send:");
 Exit (1);

 printf ("send:%s\n", snd_buf);
 /* Synchronous Blocking Mode */if (recvbytes = recv (SOCKFD, Rcv_buf, maxdatasize, 0)) = = 1) {perror ("recv:");
 Exit (1);
 } Rcv_buf[recvbytes] = ';

 printf ("recv:%s\n", rcv_buf);
 Close (SOCKFD);
return 0; 

 }

Obviously, connect, send, and recv in the code are all synchronized blocking mode,

When the result is not returned, the program does nothing.
This model is very classic and is widely used.

The advantage is very simple, waiting for the process to occupy a very small amount of system resources, the program call back, must be able to get the data; But simplicity also brings some drawbacks, the program in the data arrived and ready before, can not do other operations, need to have a thread dedicated to wait, This cost is difficult to accept for servers that need to handle a large number of connections.

second, synchronous non-blocking model

After receiving the letter of Peace, Lao Chen felt a little relieved, instead of waiting in front of the mailroom, I went to the mail room every once in a while, so that old Chen could rest for a while, or drink a cup of tea, watch TV, do something else, and that was the synchronous non-blocking model.

A slightly less efficient variant of synchronous blocking I/O is synchronous non-blocking I/O, in which system calls are opened in non-blocking form, which means that the I/O operation does not complete immediately, and the operation may return an error code stating that the command is not immediately satisfied (Eagain or Ewouldblock), non-blocking implementations are that I/O commands may not be immediately satisfied, requiring application calls many times to wait for the operation to complete.

This may not be efficient, because in many cases, when the kernel executes this command, the application must be busy waiting until the data is available, or attempting to perform other work. This can result in a reduction in overall data throughput because the data becomes available in the kernel and there is a gap between the user's call to the read return data.

As shown in Figure 2:

* * * \brief * TCP client/#include #include #include #include #include the #include #include #include #include
 #define SERVPORT 8080 #define MAXDATASIZE int main (int argc, char *argv[]) {int sockfd, recvbytes; Char Rcv_buf[maxdatasize];
 /*./client 127.0.0.1 Hello */char snd_buf[maxdatasize];  struct Hostent *host; /* struct hostent * {* char *h_name;//General hostname * char **h_aliases;//hostname ' s alias * T H_addrtype; 
     af_inet * int h_length;
     * Char **h_addr_list;
     * };
 * * struct sockaddr_in server_addr;
 int flags;

 int Addr_len;
 if (ARGC < 3) {printf ("usage:%s [IP Address] [any string]\n", argv[0]);
 return 1;
 } *snd_buf = ' I ';

 strcat (Snd_buf, argv[2]);
 if ((SOCKFD = socket (af_inet, sock_stream, 0)) = = 1) {perror ("socket:");
 Exit (1);
 } server_addr.sin_family = Af_inet;
 Server_addr.sin_port = htons (Servport);
 Inet_pton (Af_inet, argv[1], &server_addr.sin_addr); memset (& (server_addr.sin_zero), 0, 8);

 Addr_len = sizeof (struct sockaddr_in);
 /* Setting Socket to Nonblock * * * flags = FCNTL (SOCKFD, F_GETFL, 0); Fcntl (SOCKFD, flags|

 O_nonblock); /* Create the connection by socket * means so Connect "sockfd" to "server_addr" * Synchronous blocking Mode */if (connect SOCKFD, (s
 Truct sockaddr *) &server_addr, sizeof (struct sockaddr)) = =-1) {perror ("connect");
 Exit (1);
 /* Synchronous non-blocking Mode/while (send (SOCKFD, snd_buf, sizeof (SND_BUF), msg_dontwait) = = 1) {sleep (10);
 printf ("sleep\n");

 printf ("send:%s\n", snd_buf);
 /* Synchronous non-blocking Mode */while ((Recvbytes = recv (SOCKFD, Rcv_buf, Maxdatasize, msg_dontwait)) = = = 1) {sleep (10);
 printf ("sleep\n");
 } Rcv_buf[recvbytes] = ';

 printf ("recv:%s\n", rcv_buf);
 Close (SOCKFD);
return 0; 

 }

This mode when no data can be received, you can do some other operations, such as having more than one socket, you can see other sockets have no data to receive; In practical applications, the direct use of this I/O model is not common, because it requires constant query, Most of these queries are unnecessary calls and waste system resources; non-blocking I/o should be a cushion for I/O multiplexing and signal driven to lay the foundation for non-blocking use.

We can use Fcntl (FD, F_SETFL, flag | O_nonblock); Turn the socket flag into non-blocking, call recv, and return 1 if the device has no data to read at the moment, and place errno as Ewouldblock (or Eagain, the values of the two macros are the same), indicating that it should have been blocked here (would block, subjunctive, in fact, is not blocked but directly returns the error, the caller should try to read again (again).

This behavior is called polling (Poll), and the caller simply queries rather than blocks the Deng here, so that multiple devices can be monitored simultaneously:

while (1)
{
Non-blocking read (device 1);
if (device 1 has data to arrive)
Processing of data;

Non-blocking read (device 2);
if (device 2 has data to arrive)
Processing of data;

..............................
}

If read (device 1) is blocked, if device 1 has no data arriving, it blocks on the read call to device 1, even if device 2 has data arriving and cannot handle it, using non-blocking I/O prevents device 2 from being processed in a timely manner. Non-blocking I/O has a disadvantage, if all devices have no data to arrive, the caller needs to repeatedly query do not work, if blocking there, the operating system can dispatch other process execution, will not do no work, in the actual application of non-blocking I/O model less use

III. I/O multiplexing (asynchronous blocking) mode

Frequent going to the mailroom is too tiring for old Chen, and there is little to do in the interval, and the efficiency of the letter is low.

As a result, Lao Chen made suggestions to the Community property;

Community properties have improved their mailbox system:

The tenant first registers with the community property, and after that the community property will add a reminder device to the home of the registered tenant, and whenever a new letter of registered housing arrives, the device will issue a "new letter arrival" to remind Lao Chen to see if his letter has arrived. This is the asynchronous blocking model.

In this model, the configured non-blocking I/O is then used to block the select system call to determine when an I/O descriptor is operational. What makes a select call interesting is that it can be used to provide notifications for multiple descriptors, not just for a descriptor. For each prompt, we can request that the descriptor be able to write the data, that the read data is available, and whether the error is notified

I/O multiplexing models allow one or more sockets to be readable or writable, and applications can be notified;

The I/O multiplexing model is implemented in the early stage with select, and its workflow is as follows:

Select to manage multiple I/O, select to block when no data is available, select to return if the data arrives within the timeout period, then call recv for data replication, recv return processing data.

The following example of the C language implementation, which accepts data from the network to write to a file:

 * * * \brief * TCP client/#include #include #include #include #include the #include #include #include #include #include #define SERVPORT 8080 #define Maxdatasize #define TFILE "Data_from_socket.txt" int main (int argc, char *a
 Rgv[]) {int sockfd, recvbytes; Char Rcv_buf[maxdatasize];
 /*./client 127.0.0.1 Hello */char snd_buf[maxdatasize];  struct Hostent *host; /* struct hostent * {* char *h_name;//General hostname * char **h_aliases;//hostname ' s alias * T H_addrtype; 
     af_inet * int h_length;
     * Char **h_addr_list;
     * };

 * * struct sockaddr_in server_addr;
 * */Fd_set readset, Writeset;
 int check_timeval = 1; struct Timeval timeout={check_timeval,0};
 Block Select, wait 1 seconds, 1 seconds polling int maxfd;
 int FP;
 int cir_count = 0;

 int ret;
 if (ARGC < 3) {printf ("usage:%s [IP Address] [any string]\n", argv[0]);
 return 1;
 } *snd_buf = ' I ';

 strcat (Snd_buf, argv[2]); if (fp = open (tfile,o_wronly)) < 0)//not with FOPEn {perror ("fopen:");
 Exit (1);
 The IF ((SOCKFD = socket (af_inet, sock_stream, 0)) = = 1) {perror ("socket:");
 Exit (1);
 } server_addr.sin_family = Af_inet;
 Server_addr.sin_port = htons (Servport);
 Inet_pton (Af_inet, argv[1], &server_addr.sin_addr);

 memset (& (Server_addr.sin_zero), 0, 8); /* Create the connection by socket * means so Connect "sockfd" to "server_addr" */if (connect sockfd, struct Socka
 DDR *) &server_addr, sizeof (struct sockaddr)) = = 1) {perror ("connect");
 Exit (1);
 /**/if (send (SOCKFD, snd_buf, sizeof (SND_BUF), 0) = = 1) {perror ("send:");
 Exit (1);

 printf ("send:%s\n", snd_buf);  while (1) {Fd_zero (&readset); The collection must be emptied at each loop, otherwise the descriptor change Fd_set (SOCKFD, &readset) cannot be detected;
 Add descriptor Fd_zero (&writeset);

 Fd_set (FP, &writeset); MAXFD = SOCKFD > fp? (sockfd+1): (fp+1); Descriptor maximum plus 1 ret = select (MAXFD, &readset, NULL, NULL, NULL);
 Blocking mode switch (ret) {case-1: Exit (-1);
 Break
 Case 0:break; DefaulT:if (Fd_isset (SOCKFD, &readset))//test whether the sock is readable, that is, whether there is data on the network {recvbytes = recv (SOCKFD, Rcv_buf, Maxdatasize, Msg_don
  twait);
  Rcv_buf[recvbytes] = ' the ';

  printf ("recv:%s\n", rcv_buf);
 if (Fd_isset (FP, &writeset)) {write (FP, Rcv_buf, strlen (RCV_BUF));//Not with fwrite} goto end;
 }} cir_count++;
 printf ("CNT:%d \ n", Cir_count);
 End:close (FP);
 Close (SOCKFD);
return 0; 

 }

Perl implementation:

 #! /usr/bin/perl ############################################################################### # \File # tcp_ client.pl # \descript # Send message to server #######################################################################
######## use Io::socket;

Use Io::select; #hash to install IP Port%srv_info = (# "srv_ip" => "61.184.93.197", "Srv_ip" => "192.168.1.73", "Srv_port" => "

8080 ",);
My $srv _addr = $srv _info{"Srv_ip"};

My $srv _port = $srv _info{"Srv_port"}; My $sock = io::socket::inet->new (peeraddr => "$srv _addr", Peerport => "$srv _port", Type => Sock_stream, B Locking => 1, # Timeout => 5, Proto => "tcp") or Die "Can not create socket connect."

$@";
$sock->send ("Hello server!\n", 0) or warn "Send failed: $!, $@";

$sock->autoflush (1);
My $sel = Io::select->new ($sock);
 while (my @ready = $sel->can_read) {foreach I $fh (@ready) {if ($fh = = $sock) {while () {print $_;
 $sel->remove ($FH);
 Close $fh; }}} $sock; Close (); 

 

Iv. Signal-driven I/O models

After the old Chen received the new letter, the general procedure is:

Open the envelope--pull out the stationery--read the letter--reply to the letter ... In order to further reduce the user burden, the community property has developed a new technology: residents as long as the residential property to inform the operation of the letter, community property mailbox will follow these steps to deal with the letter, no longer need the user personally eagerness/read/reply! This is the signal-driven I/O model.

We can also use the signal to let the kernel send a sigio signal when the descriptor is ready to notify us.

First, the signal-driven I/O function of the socket is turned on, and a signal processing function is installed via sigaction system call. The system call will return immediately and our process continues to work, that is, not blocked. When the datagram is ready to be read, the kernel generates a Sigio signal for the process, and we can then call the recvfrom to read the datagram in the signal processing function, notify the main loop that the data is ready to be processed, or immediately notify the main loop to read the datagram.


Regardless of the processing of the Sigio signal, the advantage of this model is that the process is not blocked while waiting for the datagram to arrive, and the main loop can continue to execute as long as it waits for notification from the signal processing function: Either the data is ready to be processed or the datagram is ready to be read.

v. Asynchronous non-blocking mode

Linux asynchronous Io is actually used very little.

The main difference from the previous signal-driven model is that the signal-driven I/O is the kernel's notification of when an I/O operation can be started, and the asynchronous I/O model is the kernel that notifies us when I/O operations are complete.

Let's take a look at its process:

This is the asynchronous non-blocking mode

Take the read system call as an example

Steps

A. Call read;
B. Read request will be returned immediately, stating that the request has been successfully initiated.
C. The application can perform other processing operations during the time that the read operation is completed in the background.
D. When Read's response arrives, a signal is generated or a thread-based callback function is executed to complete the I/O process.

 * * * \brief * TCP client/#include #include #include #include #include the #include #include #include #include #include #define SERVPORT 8080 #define Maxdatasize #define TFILE "Data_from_socket.txt" int main (int argc, char *a
 Rgv[]) {int sockfd, recvbytes; Char Rcv_buf[maxdatasize];
 /*./client 127.0.0.1 Hello */char snd_buf[maxdatasize];    struct Hostent *host; /* struct hostent * {* char *h_name;//General hostname * char **h_aliases;//hostname ' s alias * int h_addrtype; 
          af_inet * int h_length;
          * Char **h_addr_list;
          * };

 * * struct sockaddr_in server_addr;
 * */Fd_set readset, Writeset;
 int check_timeval = 1; struct Timeval timeout={check_timeval,0};
 Block Select, wait 1 seconds, 1 seconds polling int maxfd;
 int FP;
 int cir_count = 0;

 int ret;
 if (ARGC < 3) {printf ("usage:%s [IP Address] [any string]\n", argv[0]);
 return 1;
 } *snd_buf = ' I ';

 strcat (Snd_buf, argv[2]); if ((fp = open (tfile,o_wronly)) < 0)//not with fopen {perror ("fopen:");
 Exit (1);
 The IF ((SOCKFD = socket (af_inet, sock_stream, 0)) = = 1) {perror ("socket:");
 Exit (1);
 } server_addr.sin_family = Af_inet;
 Server_addr.sin_port = htons (Servport);
 Inet_pton (Af_inet, argv[1], &server_addr.sin_addr);

 memset (& (Server_addr.sin_zero), 0, 8); /* Create the connection by socket * means so Connect "sockfd" to "server_addr" */if (connect sockfd, struct Socka
 DDR *) &server_addr, sizeof (struct sockaddr)) = = 1) {perror ("connect");
 Exit (1);
 /**/if (send (SOCKFD, snd_buf, sizeof (SND_BUF), 0) = = 1) {perror ("send:");
 Exit (1);

 printf ("send:%s\n", snd_buf);   while (1) {Fd_zero (&readset);  The collection must be emptied at each loop, otherwise the descriptor change Fd_set (SOCKFD, &readset) cannot be detected;
 Add descriptor Fd_zero (&writeset);

 Fd_set (FP, &writeset); MAXFD = SOCKFD > fp? (sockfd+1): (fp+1); Descriptor maximum plus 1 ret = select (MAXFD, &readset, NULL, NULL, &timeout);
 Non-blocking mode switch (ret){case-1: Exit (-1);
  Break
  Case 0:break; Default:if (Fd_isset (SOCKFD, &readset))//test whether the sock is readable, that is, whether there is data on the network {recvbytes = recv (SOCKFD, Rcv_buf, maxdatasize
   , msg_dontwait);
   Rcv_buf[recvbytes] = ' the ';

   printf ("recv:%s\n", rcv_buf);
  if (Fd_isset (FP, &writeset)) {write (FP, Rcv_buf, strlen (RCV_BUF));//Not with fwrite} goto end; } timeout.tv_sec = Check_timeval;
 Must be reset, because the timeout time will be placed 0 cir_count++;
 printf ("CNT:%d \ n", Cir_count);
 End:close (FP);

 Close (SOCKFD);
return 0; 

 }

Server-side programs:

 * * \brief * TCP server/#include #include #include #include #include #include #include #define SERVPORT 80  #define BACKLOG//MAX NUMBEF of Client connection #define MAXDATASIZE int main (char argc, char *argv[]) {int

 SOCKFD, CLIENT_FD, Addr_size, recvbytes;
 Char Rcv_buf[maxdatasize], snd_buf[maxdatasize];
 Char* Val;
 struct sockaddr_in server_addr;
 struct sockaddr_in client_addr;

 int breuseaddr = 1;

 Char ipdotdec[20];
 /* Create a new socket and regiter it to OS.
 * Sock_stream means that supply TCP service, * and must connect () before data transfort.
 */if (SOCKFD = socket (af_inet, sock_stream, 0)) = = 1) {perror ("socket:");
 Exit (1);   }/* Setting server ' s socket */server_addr.sin_family = af_inet;
 IPV4 network protocol Server_addr.sin_port = htons (Servport); SERVER_ADDR.SIN_ADDR.S_ADDR = Inaddr_any;

 Auto IP Detect memset (& (Server_addr.sin_zero), 0, 8); SetSockOpt (SOCKFD, Sol_socket, SO_REUSEADDR, (const char*) &breuSEADDR, sizeof (int));
 if (Bind (SOCKFD, (struct sockaddr*) &server_addr, sizeof (struct sockaddr)) = =-1) {perror ("bind:");
 Exit (1); } * * Watting for connection, * and server permit to recive the requestion from SOCKFD/if (Listen, BA
 Cklog) = = 1)//BACKLOG assign THD max number of connection {perror ("listen:");                 
 Exit (1);         

 while (1) {addr_size = sizeof (struct sockaddr_in); * * Accept the SOCKFD ' s connection, * return a new socket and assign far host to Clien          
 T_ADDR */printf ("Watting for connect...\n"); if ((client_fd = Accept (SOCKFD, (struct sockaddr *) &client_addr, &addr_size)) = = = 1) {/* No              
  nblocking Mode * * PERROR ("Accept:");                
 Continue }/* Network-digital to IP address/inet_ntop (af_ineT, (void*) &client_addr, Ipdotdec, 16);  

 printf ("Connetion from:%d:%s\n", client_addr.sin_addr, Ipdotdec); if (!fork ()) {/* child process handle with the client connection * * Reci                  
  ve the client ' s data by CLIENT_FD */if (recvbytes = recv (client_fd, Rcv_buf, maxdatasize, 0)) = = 1) {              
  Perror ("recv:");                
  Exit (1);            
  } rcv_buf[recvbytes]= ';           

  printf ("recv:%s\n", rcv_buf);               
  *snd_buf= ' ";           

  strcat (Snd_buf, "Welcome");                
  Sleep (3);                  
  * Send the message to far-hosts by CLIENT_FD */if (CLIENT_FD, Snd_buf, strlen (SND_BUF), 0) = = 1) {              
  Perror ("Send:");                
  Exit (1);           

  printf ("send:%s\n", snd_buf);              
  Close (CLIENT_FD);                
 Exit (1); }                  

 //Close (CLIENT_FD);                 
return 0; 

 }

Once the user process initiates the read operation, you can begin to do something else immediately. On the other hand, from the kernel point of view, when it receives a asynchronous read, first it returns immediately, so no block is generated for the user process. Then, kernel waits for the data to be ready and then copies the data to the user's memory, and when all is done, kernel sends a signal to the user process, telling it that the read operation is complete.

Vi. Summary

So far, four IO model has been introduced.

Now back to answer two questions:
What is the difference between blocking and non-blocking?
What is the difference between synchronous IO and asynchronous IO?

First answer the simplest one: blocking vs non-blocking.

In fact, the previous introduction has clearly explained the difference between the two.

Calling blocking IO will block the corresponding process until the operation is complete,
Non-blocking IO returns immediately when the kernel is still preparing the data.

Before you explain the difference between synchronous IO and asynchronous IO, you need to give a definition of both.

The definition given by Stevens (in fact, the POSIX definition) is this:

Copy Code code as follows:
A synchronous I/O operation causes the requesting process to is blocked until that I/O operation completes;

Copy Code code as follows:
An asynchronous I/O operation does not cause the requesting process to be blocked;

The difference between the two is:

Synchronous IO will block the process when doing IO operation.

According to this definition, the blocking io,non-blocking Io,io multiplexing previously described belong to synchronous IO.

One might say that non-blocking io has not been blocked. There is a very "tricky" place where the definition of "IO operation" refers to the real IO operation, which is the example of the Recvfrom system call.

Non-blocking IO does not block the process if the kernel data is not ready when executing recvfrom this system call.

However, when the data is ready in the kernel, recvfrom will copy the data from the kernel into the user's memory, at which point the process is blocked, during which the process is block.

and asynchronous IO is not the same, when the process initiates an IO operation, the direct return is ignored until kernel sends a signal telling the process that IO is complete. Throughout this process, the process has not been blocked at all.

The comparison of each IO model is shown in the following illustration:

As described above, the distinction between non-blocking IO and asynchronous IO can be found to be obvious:

In non-blocking io, although the process will not be block for most of the time, it still requires the process to take active check, and when the data is ready to be completed, it also requires the process to actively call the Recvfrom again to copy the data to the user's memory.

and asynchronous Io is completely different. It's like a user process handing over the entire IO operation to someone else (kernel) and then sending a signal when the other person finishes. During this time, the user process does not need to check the status of IO operations, nor does it need to actively copy data.

Finally, give a few examples that are not quite appropriate to illustrate the five IO Model:

There are a,b,c,d,e five persons fishing:

A with the most old-fashioned fishing rod, so it has to be kept, wait until the fish hooked on the lever;
B's Fishing rod has a function, can show whether there is fish bait, so, B and next to the MM chat, will see if there is no fish bait, and some words on the quick lever;
C with the fishing rod and b about, but he thought a good way, is to put several fishing rods, and then keep beside, once there is a show said the fish bait, it will be the corresponding fishing rod pull up;
D is a rich man, he has no patience, but also like to catch the thrill of the fish, so hired a person, once the person found that there is a fish bait, will inform D come to catch up with fish;
E is also a rich man, simply hired a person to help him fishing, once the man fishing up the fish, send a text message to E.

The above is the entire content of this article, I hope to help you learn, but also hope that we support the cloud habitat community.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.