Redirect dup and dup2 Functions
#include <unistd.h>int dup(int file_descriptor);int dup2(int file_descriptor_one, int file_descriptor_two);
- Dup creates a new file descriptor that points to the same file, pipeline, or network connection as the original file_descriptor.
- The file descriptor returned by dup always takes the minimum integer currently available by the system.
The dup2 function uses the file_descriptor_two parameter to specify the new descriptor value. If file_descriptor_two is enabled, disable it first. If file_descriptor_one = file_descriptor_two, do not close it.
If the call fails,-1 is returned and errno is set.
// Use dup to simulate a basic CGI server # include <stdio. h> # include <stdlib. h> # include <unistd. h> # include <string. h> # include <errno. h> # include <sys/socket. h> # include <arpa/inet. h> # include <netinet/in. h> # include <assert. h> int main (int argc, char ** argv) {if (argc! = 3) {fprintf (stderr, "Usage: % s id port \ n", basename (argv [0]); return 1 ;} const char * ip = argv [1]; int port = atoi (argv [2]); struct sockaddr_in address; bzero (& address, sizeof (address); address. sin_family = AF_INET; address. sin_port = htons (port); inet_ton (AF_INET, ip, & address. sin_addr); int sock = socket (PF_INET, SOCK_STREAM, 0); assert (sock> = 0); int ret = bind (sock, (struct sockaddr *) & address, siz Eof (address); assert (ret! =-1); ret = listen (sock, 5); assert (ret! =-1); struct sockaddr_in client; socklen_t client_addrlength = sizeof (client); int connfd = accept (sock, (struct sockaddr *) & client, & client_addrlength ); if (connfd <0) {printf ("error: % s \ n", strerror (errno);} else {close (STDOUT_FILENO ); // disable the standard output dup (connfd); // redirect 1 to connfd, so that the standard output content of the server is directly sent to the client socket, which is the basic principle of CGI printf ("abc. close stdout_fileno test... dup to client \ n "); // printf will directly send the output to the client close (connfd);} close (sock); return 0 ;}
Distributed read readv and concentrated write writev Functions
Readv reads data from file descriptors to discrete memory blocks, that is, distributed reads.
Writev writes multiple scattered memories into the file descriptor, that is, concentrated writing.
#include <sys/uio.h>ssize_t readv(int fd, const struct iovec *vector, int count);ssize_t writev(int fd, const struct iovec *vector, int count);
The fd parameter is the file descriptor to be operated on.
The vector parameter is an iovec struct:
# Include <sys/uio. h> struct iovec {void * iov_base; // point to a buffer zone, which stores the data received by readv or the size_t iov_len data to be sent by writev; // receipt length and actual write length };
The count parameter is the length of the vector Array, that is, the amount of memory data that needs to be read from or written to fd.
If the call succeeds, the number of bytes read/write to fd is returned. If the call fails,-1 is returned, and errno is set. Similar to the simplified version of recvmsg and sendmsg.
The following simple WEB server simulation uses the centralized write method. The receiving and parsing of HTTP requests are omitted, and the target file is directly transmitted to the server program as 3rd parameters. The client can telnet to the server to obtain the file.
# Include <stdio. h> # include <stdlib. h> # include <string. h> # include <unistd. h> # include <assert. h> # include <sys/socket. h> # include <netinet/in. h> # include <arpa/inet. h> # include <errno. h> # include <sys/stat. h> # include <sys/types. h> # include <fcntl. h> # define BUFFER_SIZE 1024 // defines two HTTP status codes and status information static const char * status_line [2] = {"200 OK", "500 Internal server error "}; int main (int argc, char ** argv) {if (argc! = 4) {fprintf (stderr, "Usage: % s ip port filename \ n", basename (argv [0]); return 1 ;} const char * ip = argv [1]; int port = atoi (argv [2]); const char * file_name = argv [3]; // pass the target file as the third parameter of the program into struct sockaddr_in address; bzero (& address, sizeof (address); address. sin_family = AF_INET; address. sin_port = htons (port); inet_ton (AF_INET, ip, & address. sin_addr); int sock = socket (PF_INET, SOCK_STREAM, 0); assert (sock> = 0); printf ("create socket success \ n"); int reuse = 1; setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, & reuse, sizeof (reuse )); int ret = bind (sock, (struct sockaddr *) & address, sizeof (address); assert (ret! =-1); fprintf (stderr, "bind address success \ n"); ret = listen (sock, 5); assert (ret! =-1); fprintf (stderr, "listen success \ n"); struct sockaddr_in client; socklen_t client_addrlength = sizeof (client); fprintf (stderr, "start accept... \ n "); int connfd = accept (sock, (struct sockaddr *) & client, & client_addrlength); if (connfd <0) {printf (" error: % s \ n ", strerror (errno);} else {char header_buf [BUFFER_SIZE]; // stores the status line, header field, and an empty buffer memset (header_buf, '\ 0', BUFFER_SIZE) for the HTTP response; char * file_buf = NU LL; // cache for storing the content of the target file struct stat file_stat; // get the attribute bool valid of the target file = true; // whether the target file is valid int len = 0; // record the byte space currently used by header_buf if (stat (file_name, & file_stat) <0) {// the target file does not exist valid = false ;} else {if (S_ISDIR (file_stat.st_mode) {// the target file is the directory valid = false;} else if (file_stat.st_mode & S_IROTH) {// whether the current user has read permission, compared with the target file int fd = open (file_name, O_RDONLY); file_buf = new char [file_stat.st_size + 1]; memset (file_buf, '\ 0', file_stat.st_size + 1); fprintf (stderr, "reading % s file... ", file_name); if (read (fd, file_buf, file_stat.st_size) <0) {valid = false ;}} else {valid = false ;}} if (valid) {// target file valid ret = snprintf (header_buf, BUFFER_SIZE-1, "% s \ r \ n", "HTTP/1.1", status_line [0]); len + = ret; ret = snprintf (header_buf + len, BUFFER_SIZE-1-len, "content-Length: % ld \ r \ n", file_stat.st_size); len + = ret; ret = snprintf (he Ader_buf + len, BUFFER_SIZE-1-len, "% s", "\ r \ n"); // write the header_buf and file_buf together into struct iovec iv [2]; iv [0]. iov_base = header_buf; iv [0]. iov_len = strlen (header_buf); iv [1]. iov_base = file_buf; iv [1]. iov_len = file_stat.st_size; fprintf (stderr, "read % s success \ nsending % s file to client... \ n ", file_name, file_name); ret = writev (connfd, iv, 2); // set write} else {// the target file is invalid ret = snprintf (header_buf, BUFFER_SIZE-1, "% S % s \ r \ n "," HTTP/1.1 ", status_line [1]); len + = ret; ret = snprintf (header_buf + len, BUFFER_SIZE-1-len, "% s", "\ r \ n"); fprintf (stderr, "read % s failed \ nsending error message to client... \ n ", file_name); send (connfd, header_buf, strlen (header_buf), 0) ;}close (connfd); if (file_buf! = NULL) {delete [] file_buf; file_buf = NULL ;}} close (sock); return 0 ;}
Sendfile Function
Sendfile directly transmits data between two file descriptors and operates completely in the kernel, thus avoiding copying from the kernel buffer to the user buffer. Therefore, sendfile is highly efficient and is called zero copy.
#include <sys/sendfile.h>ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
The out_fd parameter is the file descriptor of the content to be written.
The in_fd parameter is the file descriptor of the content to be read.
The offset parameter specifies the position from which the file stream is read. If it is null, it is read from the default start position of the file stream.
The count parameter specifies the number of transmitted bytes.
If the call is successful, the number of transmitted bytes is returned. If the call fails, the value is-1, and errno is set.
Note: The man Manual states that in_fd must be a file descriptor that supports functions similar to mmap,
That is, it must point to a real file.And the out_fd must be a socket. It can be seen that sendfile is designed for transferring files over the network.
// Use the sendfile function to transmit the file # include <stdio. h> # include <stdlib. h> # include <unistd. h> # include <string. h> # include <assert. h> # include <errno. h> # include <sys/socket. h> # include <netinet/in. h> # include <arpa/inet. h> # include <sys/types. h> # include <sys/stat. h> # include <sys/sendfile. h> # include <fcntl. h> int main (int argc, char ** argv) {if (argc <= 3) {fprintf (stderr, "Usage: % s ip port filename \ n ", basename (argv [0]); ret Urn 1 ;}const char * ip = argv [1]; int port = atoi (argv [2]); const char * file_name = argv [3]; int filefd = open (file_name, O_RDONLY); assert (filefd> 0); struct stat stat_buf; fstat (filefd, & stat_buf); struct sockaddr_in address; bzero (& address, sizeof (address); address. sin_family = AF_INET; address. sin_port = htons (port); inet_ton (AF_INET, ip, & address. sin_addr); int sock = socket (PF_INET, SOCK_STREAM, 0); Sert (sock> = 0); int ret = bind (sock, (struct sockaddr *) & address, sizeof (address); assert (ret! =-1); ret = listen (sock, 5); assert (ret! =-1); struct sockaddr_in client; socklen_t client_addrlength = sizeof (client); int connfd = accept (sock, (struct sockaddr *) & client, & client_addrlength ); if (connfd <0) {fprintf (stderr, "errno is: % s \ n", strerror (errno);} else {sendfile (connfd, filefd, NULL, stat_buf.st_size); close (connfd);} close (sock); return 0 ;}
Splice Function
Splice is used to move data between two file descriptors, which is also a zero copy.
#include <fcntl.h>ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);
The fd_in parameter is the descriptor to be input. If it is an MTS file descriptor, off_in must be set to NULL; otherwise, off_in indicates where to read the input data stream. If it is NULL, it is read from the current offset position of the input data stream.
Fd_out/off_out is the same as above, but it is used for output.
The len parameter specifies the length of the moving data.
The flags parameter controls how data is moved:
- SPLICE_F_NONBLOCK: non-blocking splice operations, but the actual effect will be affected by the blocking status of the file descriptor itself.
- SPLICE_F_MORE: Give the kernel a prompt: more data will be transmitted in subsequent splice System calls.
- SPLICE_F_MOVE: If appropriate, move data by page memory. This is just a prompt for the kernel. However, because of its implementation BUG, it has no effect since kernel 2.6.21.
Note: When Using splice, at least one of fd_in and fd_out must be the pipeline file descriptor.
When the call is successful, the number of bytes to be moved is returned. It may return 0, indicating that no data needs to be moved. This usually occurs when the data is read from the pipeline and the pipeline is not written.
-1 is returned in case of failure, and errno is set.
The possible errno produced by the splice Function and Its Meaning
Error |
Description |
EBADF |
The file descriptor specified by the parameter is incorrect. |
EINVAL |
The target file system does not support splice, or the target file is opened in append mode, or both file descriptors are not pipeline file descriptors, or, the offset parameter is used for devices (such as character devices) that do not support random access) |
ENOMEM |
Insufficient memory |
ESPIPE |
The fd_in (or fd_out) parameter is the pipeline file descriptor, while off_in (or off_out) is not NULL. |
The following code reads the client content into the pipeline through splice, and then reads the content from the pipeline to the client, so as to achieve efficient and simple echo service. Recv/send is not executed throughout the process. Therefore, data copying from user space to kernel space is not involved.
// The echo server implemented by splice # include <stdio. h> # include <stdlib. h> # include <unistd. h> # include <sys/socket. h> # include <netinet/in. h> # include <arpa/inet. h> # include <assert. h> # include <errno. h> # include <string. h> # include <fcntl. h> int main (int argc, char ** argv) {if (argc <= 2) {printf ("usage: % s ip port \ n ", basename (argv [0]); return 1;} const char * ip = argv [1]; int port = atoi (argv [2]); struct sockaddr_in address; B Zero (& address, sizeof (address); address. sin_family = AF_INET; address. sin_port = htons (port); inet_ton (AF_INET, ip, & address. sin_addr); int sock = socket (PF_INET, SOCK_STREAM, 0); assert (sock> = 0); int reuse = 1; setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, & reuse, sizeof (reuse); int ret = bind (sock, (struct sockaddr *) & address, sizeof (address); assert (ret! =-1); ret = listen (sock, 5); assert (ret! =-1); struct sockaddr_in client; socklen_t client_addrlength = sizeof (client); int connfd = accept (sock, (struct sockaddr *) & client, & client_addrlength ); if (connfd <0) {printf ("errno is: % s \ n", strerror (errno);} else {int pipefd [2]; ret = pipe (pipefd); // create an MPS queue assert (ret! =-1); // redirects the client data on connfd to the pipeline. ret = splice (connfd, NULL, pipefd [1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE ); assert (ret! =-1); // export the MPs queue to connfd. ret = splice (pipefd [0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE ); assert (ret! =-1); close (connfd);} close (sock); return 0 ;}
Tee Function
Tee copies data between two MPs queue file descriptions, which is also a zero copy operation.
#include <fcntl.h>ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);
Its parameters are the same as splice, but both fd_in and fd_out must be the file descriptor of the pipeline. When the call is successful, the number of copied bytes is returned. If the return value is 0, no task data is copied. -1 is returned in case of failure, and errno is set.
Use tee and splice to implement programs from standard input to terminals and files:
# Include <stdio. h> # include <unistd. h> # include <string. h> # include <errno. h> # include <fcntl. h> # include <assert. h> int main (int argc, char ** argv) {if (argc! = 2) {fprintf (stderr, "Usage: % s <file> \ n", argv [0]); return 1 ;}uid_t uid = getuid (); uid_t euid = geteuid (); printf ("userid: % d, valid userid: % d \ n", uid, euid); int filefd = open (argv [1], o_CREAT | O_WRONLY | O_TRUNC, 0666); assert (filefd> 0); int pipefd_stdout [2]; int ret = pipe (pipefd_stdout); assert (ret! =-1); int pipefd_file [2]; ret = pipe (pipefd_file); assert (ret! =-1); // output the standard input content to the pipeline ret = splice (STDOUT_FILENO, NULL, pipefd_stdout [1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE ); assert (ret! =-1); // copy the pipefd_stdout output of the pipeline to the ret = tee (pipefd_stdout [0], pipefd_file [1], 32768, SPLICE_F_NONBLOCK), assert (ret! =-1); // export the pipefd_file output of the pipeline to the file descriptor filefd, write the file ret = splice (pipefd_file [0], NULL, filefd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE); assert (ret! =-1); // export the pipefd_stdout output of the pipeline to the standard output. The content of the pipeline is consistent with that of the written file. ret = splice (pipefd_stdout [0], NULL, STDOUT_FILENO, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE); assert (ret! =-1); close (filefd); close (pipefd_file [0]); close (pipefd_file [1]); close (pipefd_stdout [0]); close (pipefd_stdout [1]); return 0 ;}