[]linux Socket network programming, file transfer, data transmission of the C language example __HTML5

Source: Internet
Author: User
Tags function prototype socket error htons

What is a socket
The socket interface is a TCP/IP network Api,socket interface that defines many functions or routines that programmers can use to develop applications on a TCP/IP network. To learn about TCP/IP network programming on the Internet, you must understand the socket interface.
The socket interface designer first placed the interface inside the UNIX operating system. If you understand the input and output of UNIX systems, you can easily understand the socket. Socket data transfer In a network is a special i/o,socket and a file descriptor. The socket also has a function call socket () that is similar to opening a file, which returns an integral socket descriptor, and subsequent connection creation, data transfer, and so on are implemented through the socket. There are two types of sockets that are commonly used: Streaming sockets (SOCK_STREAM) and datagram Sockets (SOCK_DGRAM). A stream is a connection-oriented socket that is applied to a connection-oriented TCP service; A datagram socket is a connectionless socket that corresponds to a connectionless UDP service application.

Socket build
In order to establish a socket, the program can call the socket function, which returns a handle similar to a file descriptor. The socket function prototype is:
int socket (int domain, int type, int protocol);
Domain indicates the protocol family used, usually pf_inet, that represents the Internet Protocol family (TCP/IP protocol family); type parameter specifies the kind of socket: Sock_stream or SOCK_DGRAM, The socket interface also defines the original socket (SOCK_RAW), allowing the program to use low-level protocols; Protocol is usually assigned "0". The socket () call returns an integral socket descriptor that you can use later in the call.
The socket descriptor is a pointer to an internal data structure that points to the description chart entry. When you call the socket function, the socket executor creates a socket, in effect "building a socket" means allocating storage space for a socket data structure. The socket executor manages the descriptor table for you.
A network connection between two network programs includes five types of information: communication Protocol, local protocol address, local host port, remote host address, and remote protocol port. The socket data structure contains these five kinds of information.

Socket configuration
After you return a socket descriptor through a socket call, you must configure the socket before you can use the socket for network transport. The connection-oriented socket client saves local and remote information in the socket data structure by calling the Connect function. Clients and services that do not have a connection to the socket, and the server that faces the connection socket, configure local information by calling the BIND function.
The BIND function associates the socket with a port on this computer, and you can then listen for service requests on that port. The BIND function prototype is:
int bind (int sockfd,struct sockaddr *my_addr, int addrlen);
SOCKFD is the socket descriptor that calls the socket function return, MY_ADDR is a pointer to a SOCKADDR type that contains information such as native IP address and port number, and Addrlen is often set to sizeof (struct sockaddr).
The struct sockaddr struct type is used to hold the socket information:
struct SOCKADDR {
unsigned short sa_family; * * Address family, AF_XXX * *
Char sa_data[14]; /* 14-BYTE protocol address * *
};
Sa_family is typically af_inet, representing the Internet (TCP/IP) address family, and Sa_data contains the IP address and port number of the socket.
There is another type of structure:
struct SOCKADDR_IN {
short int sin_family; /* Address Family * *
unsigned short int sin_port; /* Port Number * *
struct IN_ADDR sin_addr; /* IP Address * *
unsigned char sin_zero[8]; /* Fill 0 to keep the same size as struct sockaddr * *
};
This structure is more convenient to use. Sin_zero is used to populate the SOCKADDR_IN structure with the same length as the struct sockaddr, which can be set to zero with the bzero () or memset () function. Pointers to sockaddr_in and pointers to SOCKADDR can be converted to each other, which means that if a function requires a type of argument that is sockaddr, you can convert a pointer to a sockaddr_in to a pointer to a sockaddr when the function calls. , or vice versa.
When using the BIND function, you can use the following assignment to automatically obtain a native IP address and randomly obtain a port number that is not occupied:
My_addr.sin_port = 0; /* System randomly select an unused port number * *
MY_ADDR.SIN_ADDR.S_ADDR = Inaddr_any; /* Fill in the local IP address * *
By placing the My_addr.sin_port at 0, the function will automatically select an unused port for you to use. Similarly, by placing the my_addr.sin_addr.s_addr as Inaddr_any, the system automatically fills in the native IP address.
Note that using the BIND function is necessary to convert Sin_port and sin_addr into network byte precedence, while SIN_ADDR does not need to convert.
The computer data store has two byte precedence: High byte priority and low byte priority. Data on the Internet is transmitted over the network in high byte order, so a machine that stores data internally at low byte priority needs to be converted when data is transferred over the Internet, otherwise data inconsistency occurs.
The following are several byte-order conversion functions:
htonl (): Converts a 32-bit value from host byte sequence to network byte order
htons (): Converts a 16-bit value from host byte sequence to network byte order
Ntohl (): Converts a 32-bit value from a network byte sequence to a host byte sequence
Ntohs (): Converts a 16-bit value from a network byte sequence to a host byte sequence
The Bind () function returns 0 when it is successfully invoked, returns "-1" When an error occurs, and errno the corresponding error number. Note that when you call the BIND function, you do not typically place the port number to a value less than 1024, since 1 to 1024 is the reserved port number, and you can choose any port number greater than 1024 that is not occupied.

Connection established
A connection-oriented client uses the Connect function to configure the socket and establish a TCP connection to the remote server with the function prototype:
int connect (int sockfd, struct sockaddr *serv_addr,int addrlen);
SOCKFD is the socket descriptor returned by the socket function; Serv_addr is a pointer to the remote host IP address and port number; The Addrlen is the length of the remote geologic structure. The Connect function returns-1 when an error occurs, and sets errno to the appropriate error code. Do client programming without invoking bind (), because in this case only need to know the destination machine's IP address, and the client through which port to establish a connection with the server does not need to care, the socket Executive Body for your program automatically select an unused port, and notify your program data when to interrupt the mouth.
The Connect function initiates a direct connection to the remote host. You need to connect this socket to a remote host only if you are using a socket for a connection-oriented client. A connectionless protocol never establishes a direct connection. A connection-oriented server also never initiates a connection, it simply listens to the client's request on the protocol port.
The listen function causes the socket to be in a passive listening mode and establishes an input data queue for the socket, saving the incoming service requests to the queue until the program processes them.
int listen (int sockfd, int backlog);
SOCKFD is the socket descriptor returned by the socket system call; backlog Specifies the maximum number of requests allowed in the request queue, and incoming connection requests wait in the queue for accept () (refer to the following). Backlog limits the number of requests waiting for a service in the queue, and most system defaults are 20. If a service request arrives, the input queue is full, the socket rejects the connection request, and the customer receives an error message.
When an error occurs, the Listen function returns-1 and the corresponding errno error code is placed.
The Accept () function lets the server receive a client's connection request. After the input queue is established, the server calls the Accept function and then sleeps and waits for the client's connection request.
int accept (int sockfd, void *addr, int *addrlen);
SOCKFD is a listening socket descriptor, addr is usually a pointer to a SOCKADDR_IN variable that holds information about the host that made the connection request service (a host issued the request from a port), and Addrten typically a pointing value of sizeof ( Integer pointer variable for struct sockaddr_in). When an error occurs, the Accept function returns-1 and the corresponding errno value is placed.
First, when the Accept function watches the socket receiving the connection request, the socket executor creates a new socket, the execution body connects the new socket to the address of the requesting connection process, and the initial socket that receives the service request can still continue to the previous The socket is listening, and the data can be transferred on the new socket descriptor.

Data transmission
The two functions, Send () and recv (), are used for data transfer on a connection-oriented socket.
The Send () function prototype is:
int send (int sockfd, const void *msg, int len, int flags);
SOCKFD is the socket descriptor you want to use to transmit data; MSG is a pointer to the data to be sent; Len is the length of the data in bytes; Flags are typically set to 0 (the use of this parameter can refer to the Man manual).
The Send () function returns the number of bytes actually sent, possibly less than the data you want to send. You should compare the return value of Send () to the number of bytes you want to send in your program. This situation should be handled when the Send () return value does not match Len.
Char *msg = "hello!";
int Len, bytes_sent;
......
Len = strlen (msg);
Bytes_sent = Send (SOCKFD, msg,len,0);
......
The recv () function prototype is:
int recv (int sockfd,void *buf,int len,unsigned int flags);
SOCKFD is the socket descriptor that accepts data; BUF is the buffer in which the data is received; Len is the length of the buffer. The flags were also set to 0. RECV () returns the number of bytes actually received and, when an error occurs, returns-1 and resets the corresponding errno value.
Sendto () and recvfrom () are used to transmit data in a connectionless datagram socket. Since the local socket does not have a connection to the remote machine, it should refer to the address of the eyesight when sending the data.
The SendTo () function prototype is:
int sendto (int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
This function has two more parameters than the Send () function to represent the IP address and port number information of the target machine, and Tolen is often assigned to sizeof (struct sockaddr). The Sendto function also returns the actual byte length of the data being sent or returns 1 if a Send error occurs.
The Recvfrom () function prototype is:
int recvfrom (int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int);
From is a struct sockaddr type variable that holds the IP address and port number of the source machine. Fromlen are often placed as sizeof (struct sockaddr). When Recvfrom () returns, the Fromlen contains the number of bytes of data actually deposited in from. The Recvfrom () function returns the number of bytes received or returns 1 when an error occurs, and the corresponding errno is placed.
If you call the Connect () function on the datagram socket, you can also use Send () and recv () for data transfer, but the socket is still a datagram socket and uses the Transport layer's UDP service. But when the data is sent or received, the kernel automatically adds the target and source address information.

End Transmission
When all the data operations are finished, you can call the close () function to release the socket, stopping any data operations on the socket:
Close (SOCKFD);
You can also call the shutdown () function to close the socket. This function allows you to stop data transfers only in one direction, while data transfer in a single direction continues. If you can close the write operation of a socket and allow to continue to accept data on the socket until all data is read.
int shutdown (int sockfd,int how);
SOCKFD is the descriptor for the socket that needs to be closed. Parameter how allows you to select the following ways for shutdown operations:
0-------is not allowed to continue receiving data
·1-------is not allowed to continue sending data
2-------does not allow you to continue sending and receiving data.
• Call Close () if all is allowed
Shutdown returns 0 when the operation succeeds, returns 1 when an error occurs, and resets the corresponding errno.

Socket Programming Instance
The server in the code instance sends a string to the client via a socket connection "Hello, you are connected!". As long as the server software is running on the server, client software is running on the client, and clients will receive the string.
The server software code is as follows:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define SERVPORT 3333/* Server listener port number * *
#define BACKLOG 10/* Maximum simultaneous connection requests * *
Main ()
{
int sockfd,client_fd; /*SOCK_FD: Monitor SOCKET;CLIENT_FD: Data transfer Socket * *
struct sockaddr_in my_addr; /* Native Address information * *
struct sockaddr_in remote_addr; /* Client Address information * *
if ((SOCKFD = socket (af_inet, sock_stream, 0)) = = 1) {
Perror ("Socket creation error. "); Exit (1);
}
My_addr.sin_family=af_inet;
My_addr.sin_port=htons (Servport);
MY_ADDR.SIN_ADDR.S_ADDR = Inaddr_any;
Bzero (& (My_addr.sin_zero), 8);
if (bind (SOCKFD, struct sockaddr *) &my_addr, sizeof (struct sockaddr)) = = 1) {
Perror ("Bind error.") ");
Exit (1);
}
if (Listen (SOCKFD, BACKLOG) = = 1) {
Perror ("Listen error.") ");
Exit (1);
}
while (1) {
sin_size = sizeof (struct sockaddr_in);
if ((client_fd = Accept (SOCKFD, (struct sockaddr *) &remote_addr, &sin_size)) = = = 1) {
Perror ("Accept error");
Continue
}
printf ("received a connection from%s/n", Inet_ntoa (REMOTE_ADDR.SIN_ADDR));
if (!fork ()) {/* Child Process Code Snippet * *
if (Send (CLIENT_FD, "Hello, you are connected!/n", 26, 0) = = 1)
Perror ("Send error.) ");
Close (CLIENT_FD);
Exit (0);
}
Close (CLIENT_FD);
}
}
}
The server workflow is this: first invoke the socket function to create a socket, then call the bind function to bind it to the native address and a local port number, and then invoke listen to listen on the corresponding socket, when Accpet receives a connection service request, A new socket will be generated. The server displays the IP address of the client and sends a string "Hello,you are connected!" to the client via a new socket. Finally, close the socket.
The fork () function in the code instance generates a subprocess to process the data Transfer section, and the fork () statement returns a value of 0 for the child process. So the IF statement containing the fork function is part of the subprocess code and is executed concurrently with the parent Process Code section following the IF statement.

The client program code is as follows:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define Servport 3333
#define MAXDATASIZE 100/* Maximum data transfer per time * *
Main (int argc, char *argv[]) {
int SOCKFD, recvbytes;
Char Buf[maxdatasize];
struct Hostent *host;
struct sockaddr_in serv_addr;
if (ARGC < 2) {
fprintf (stderr, "Please enter the server ' s hostname!/n");
Exit (1);
}
if ((Host=gethostbyname (argv[1])) ==null) {
Herror ("gethostbyname error.") ");
Exit (1);
}
if ((SOCKFD = socket (af_inet, sock_stream, 0)) = = 1) {
Perror ("Socket creation error. ");
Exit (1);
}
Serv_addr.sin_family=af_inet;
Serv_addr.sin_port=htons (Servport);
serv_addr.sin_addr = * (struct in_addr *) host->h_addr);
Bzero (& (Serv_addr.sin_zero), 8);
if (Connect (SOCKFD, struct sockaddr *) &serv_addr,/
sizeof (struct sockaddr)) = = 1) {
Perror ("Connect error.") ");
Exit (1);
}
if ((Recvbytes=recv (SOCKFD, buf, maxdatasize, 0)) ==-1) {
Perror ("recv error.") ");
Exit (1);
}
Buf[recvbytes] = '/0 ';
printf ("Received:%s", buf);
Close (SOCKFD);
}
The client program first obtains the server's IP address through the server domain name, then creates a socket, calls the Connect function to establish the connection with the server, after the connection succeeds receives the data which sends over from the server, closes the socket finally.
The function gethostbyname () completes the domain name conversion. Because the IP address is difficult to remember and read and write, so for convenience, people often use the domain name to represent the host, which requires the domain name and IP address conversion. The function prototype is:
struct hostent *gethostbyname (const char *name);
function returns the struct type of the Hosten, which is defined as follows:
struct Hostent {
Char *h_name; /* Host's official domain name * *
Char **h_aliases; /* A null-terminated host alias Array/*
int h_addrtype; /* return type of address, in the context of the Internet for af-inet * *
int h_length; /* Address byte length * *
Char **h_addr_list; /* A 0-terminated array containing all the addresses of the host.
};
#define H_ADDR H_addr_list[0]/* The first address in H-addr-list * *
When the GetHostName () call succeeds, it returns a pointer to struct Hosten, which returns 1 when the call fails. When calling gethostbyname, you cannot use the perror () function to output the error message, but instead use the Herror () function to output it.

A connectionless client/server program is the same as a client/server that is connected, and the difference is that customers in a connectionless client/server typically do not need to establish a connection, and when sending receive data, they need to specify the address of the remote machine.

Blocking and non-blocking
The blocking function does not allow the program to call another function until it completes its specified task. For example, when a program performs a function call that reads data, the next program statement is not executed until the function completes a read operation. When the server runs to the Accept statement, and no client connection service requests arrive, the server stops waiting for the connection service request to arrive on the accept statement. This condition is called blocking (blocking). Non-blocking operations can be completed immediately. For example, if you want the server to simply check whether a client is waiting for a connection, accept the connection, or do something else, you can do so by setting the socket to non-blocking. Non-blocking sockets cause the accept call to return immediately when no client is waiting.
#include <unistd.h>
#include <fcntl.h>
......
SOCKFD = socket (af_inet,sock_stream,0);
Fcntl (Sockfd,f_setfl,o_nonblock);
......
You can implement polling for several sockets by setting the socket as non-blocking. When attempting to read data from a non-blocking socket with no data waiting to be processed, the function returns immediately with a return value of-1 and a errno value of Ewouldblock. But this polling causes the CPU to be in a busy wait mode, which degrades performance and wastes system resources. Calling select () effectively solves the problem by allowing you to hang up the process itself while keeping the system kernel listening to any activity of the required set of file descriptors, as long as you confirm that there is activity on any of the monitored file descriptors, select () The call returns information that indicates that the file descriptor is ready, thus achieving a random change to the process without wasting CPU overhead by the process itself testing the input. The Select function prototype is:
int select (int numfds,fd_set *readfds,fd_set *writefds,
Fd_set *exceptfds,struct timeval *timeout);
Where Readfds, Writefds, and Exceptfds are the collection of file descriptors for read, write, and exception handling, monitored by select () respectively. If you want to determine whether you can read data from standard input and a socket descriptor, you only need to add the standard input file descriptor 0 and the corresponding SOCKDTFD to the Readfds collection; The Numfds value is the highest number of file descriptions that need to be checked alphanumeric 1, The value of Numfds in this example should be sockfd+1, and when the select returns, Readfds will be modified to indicate that a file descriptor is ready to be read and you can test it by Fd_issset (). To implement the setup, reset, and test of the corresponding file descriptor in Fd_set, it provides a set of macros:
Fd_zero (fd_set *set)----Clears a set of file descriptors;
Fd_set (int fd,fd_set *set)----Add a file descriptor to the set of file descriptors;
FD_CLR (int fd,fd_set *set)----Clears a file descriptor from the file descriptor;
Fd_isset (int fd,fd_set *set)----Try to determine whether the file descriptor is placed.
The timeout parameter is a pointer to the struct TIMEVAL type, which enables the select () to return without a file descriptor ready after waiting for timeout long time. The struct TIMEVAL data structure is:
struct Timeval {
int tv_sec; * Seconds * *
int tv_usec; * Microseconds * *
};

POP3 Client Instance
The following code instance connects to the mail server and retrieves the mail for the specified user account based on the POP3 client protocol. Commands that interact with mail servers are stored in the string array popmessage, and the program sends these commands in a do-while loop.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define POP3SERVPORT 110
#define MAXDATASIZE 4096

Main (int argc, char *argv[]) {
int sockfd;
struct Hostent *host;
struct sockaddr_in serv_addr;
Char *popmessage[]={
"USER userid/r/n",
"Pass password/r/n",
"Stat/r/n",
"List/r/n",
"RETR 1/r/n",
"DELE 1/r/n",
"Quit/r/n",
Null
};
int ilength;
int imsg=0;
int iend=0;
Char Buf[maxdatasize];

if ((Host=gethostbyname ("Your.server")) ==null) {
Perror ("gethostbyname error");
Exit (1);
}
if ((SOCKFD = socket (af_inet, sock_stream, 0)) = = 1) {
Perror ("socket error");
Exit (1);
}
Serv_addr.sin_family=af_inet;
Serv_addr.sin_port=htons (Pop3servport);
serv_addr.sin_addr = * (struct in_addr *) host->h_addr);
Bzero (& (Serv_addr.sin_zero), 8);
if (Connect (SOCKFD, (struct sockaddr *) &serv_addr,sizeof (struct sockaddr)) ==-1) {
Perror ("Connect error");
Exit (1);
}

do {
Send (Sockfd,popmessage[imsg],strlen (popmessage[imsg]), 0);
printf ("Have sent:%s", popmessage[imsg]);

Ilength=recv (sockfd,buf+iend,sizeof (BUF)-iend,0);
Iend+=ilength;
Buf[iend]= '/0 ';
printf ("Received:%s,%d/n", buf,imsg);

imsg++;
while (Popmessage[imsg]);

Close (SOCKFD);
}


Very good very powerful Linux/unix Network File Transfer example: has been learning UNIX network programming, at the beginning of the file to transfer pictures always appear more bytes, after the test and reference online article found that the processing of the file read and write the last did not deal well with these problems. Now that you've improved your program, you've tested other types of files that you can transfer. The next step is to write a video transmission program. SERVER.C #include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> #define MAXSIZE int main (int argc,char **argv)
{int server_sockfd,client_sockfd;
int Server_len,client_len;
Char Ch[maxsize];
struct sockaddr_in server_address;
struct sockaddr_in client_address; if (argc!= 2)
{
printf ("Server:file name/n");
Exit (1);
} SERVER_SOCKFD = socket (af_inet,sock_stream,0);
if (SERVER_SOCKFD <0)
{
printf ("Creating Socket error!/n");
Exit (1);
}
Bzero (&server_address,sizeof (struct sockaddr_in));
server_address.sin_family = af_inet;
SERVER_ADDRESS.SIN_ADDR.S_ADDR = htonl (Inaddr_any);
Server_address.sin_port = htons (5000);
Server_len = sizeof (server_address); int opt = 1;
SetSockOpt (server_sockfd,sol_socket,so_reuseaddr,&opt,sizeof (opt)); if (Bind (SERVER_SOCKFD, (struct sockaddr *) &server_address,server_len) < 0)
{
Perror ("Bind error.");
Exit (1);
} if (listen (server_sockfd,5) = =-1)
{
printf ("Listen error!/n");
Exit (1);
}

Client_len = sizeof (client_address);
CLIENT_SOCKFD = Accept (SERVER_SOCKFD, (struct sockaddr *) &client_address, &client_len);
if (CLIENT_SOCKFD = = 1)
{
printf ("Accept error!/n");
Exit (1);
}

FILE *fd = fopen (argv[1], "RB");
if (fd = NULL)
{
printf ("File Open error!/n");
Exit (2);
}
while (!feof (FD))
{
int len = fread (CH,1,MAXSIZE,FD);
Write (Client_sockfd,ch,len);
Close (CLIENT_SOCKFD);
Fclose (FD);
return 0; } client.c #include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#define MAXSIZE int main (int argc,char **argv)
{
int Sockfd,len;
int result,count;
Char Recvs[maxsize];
struct SOCKADDR_IN address;
struct Hostent *host;

if (argc!= 3)
{
printf ("Client IP filename/n");
Exit (1);
}

Host = gethostbyname (argv[1]);
if ((SOCKFD = socket (af_inet,sock_stream,0)) = = 1)
{
printf ("Socket create error!/n");
Exit (1);
}

Bzero (address) (&address,sizeof);
address.sin_family = af_inet;
address.sin_addr = * (struct in_addr *) host->h_addr);
Address.sin_port = htons (5000); int opt = 1;
SetSockOpt (sockfd,sol_socket,so_reuseaddr,&opt,sizeof (opt));

Len = sizeof (address);
result = Connect (SOCKFD, (struct sockaddr *) &address,len);
if (result = = 1)
{
Perror ("Client");
Exit (1);
}


FILE *fd = fopen (argv[2], "WB");
if (fd = NULL)
{
printf ("Create file error!/n");
Exit (1);
}

while (1) {
Count = Read (sockfd,recvs,maxsize);
if (Count ==0)
Break

Fwrite (RECVS,1,COUNT,FD);
}

Close (SOCKFD);
Fclose (FD);
return 0; }
Transferred from http://hi.baidu.com/huangwen2003/blog/item/33048ced709ca5362697919b.html

Related Article

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.