I. BACKGROUND
Csapp's network programming is a rough introduction to some of the knowledge about network programming, and in the last section, a small webserver is implemented. This server is called tiny, it is a small but full-featured webserver. In just 300 lines of code, combined with a lot of ideas, such as Process Control, Unix I/O, sockets, HTTP, and so on, it is exciting that it can provide the Web browser with static and dynamic content, This means that files such as HTML to be opened in the browser can be directly displayed in the form via tiny.
I've always wanted to learn about network programming, which is probably the first thing to make, and it's exciting to think about it, but the code in the original book isn't going to work directly. There is also detail on how to use it. How to get the browser to point to your server. The book does not say in detail, I do not know, I have been looking for a long time on the Internet and finally clear how to use. The code in the book is not entirely. is not straightforward to perform successfully. Over here. I write down my process and think that someone will need it.
Ii. Compiling and building
1. Compile the full code
GCC Tinyserver.c-o tinyserver
2. The test program ADDER.C compiled into a running program, ADDER.C need to be placed in the same folder with Tinyserver under the Cgi-bin folder (and then why put it again)
GCC Adder.c-o adder
3. Execute the Tinyserver program and specify the port used (1024--49151 available, other for system use. Generally not occupied)
./tinyserver 2000
4. Enter the address in the address bar in the browser
Http:localhost:2000/cgi-bin/adder?
30&72
5. Implementation results
Displayed in the browser:
Background Server information displays:
Suppose you want to show other files, like slices, articles, and so on.
http:localhost:2000/testpic.jpg
But assume that in the test process will encounter the following situation, background display has been refreshing
watermark/2/text/ahr0cdovl2jsb2cuy3nkbi5uzxqvenp1y3nsawfuzw==/font/5a6l5l2t/fontsize/400/fill/i0jbqkfcma==/ Dissolve/70/gravity/center ">
I think it may be because this server is a single-threaded reason, when the receipt of a request, in main because it is continuously refreshed, this will happen, but I am not very sure, I do not know which big God can explain the next ...
The above is the simple use of the situation, I believe that the test success at the moment, is not a sense of accomplishment is very big there ah?
?
Third, source code analysis
Tiny is an iterative server that listens for connection requests on the port identified on the command line. After opening a listening socket through the OPEN_LISTENEDFD function, tiny runs a typical infinite service loop and repeatedly accepts a connection (accept) request. Run the transaction (doit), and finally close the connection description descriptor (Close)
1. header file:
/* tiny-a simple, iterative http/1.0 Web server*/#ifndef __csapp_h__ #define __CSAPP_H__ #include <stdio.h> # Include <stdlib.h> #include <unistd.h> #include <string.h> #include <ctype.h> #include <set jmp.h> #include <signal.h> #include <sys/time.h> #include <sys/types.h> #include <SYS/WAIT.H&G T #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <errno.h> #include < ;math.h> #include <semaphore.h> #include <sys/socket.h> #include <netdb.h> #include <netinet/i n.h> #include <arpa/inet.h>//above the header file is supposed to be in the "csapp.h", but I tried not to, so I directly wrote a # define Def_mode s_irusr| s_iwusr| s_irgrp| s_iwgrp| s_iroth| S_iwoth #define Def_umask s_iwgrp| s_iwoth typedef struct SOCKADDR SA; #define RIO_BUFSIZE 8192 typedef struct {int rio_fd; /* Description Descriptor for Internal buffer */int rio_cnt; /* The number of bytes left in the internal buffer is still unread */char *rio_bufptr; /* point to the next unread byte in the internal buffer */char rio_buf[rio_bufsize]; /* Internal buffer */} rio_t; extern char **environ; #define MAXLINE 8192/* Maximum characters per line */#define MAXBUF 8192 */I/O buffers maximum capacity */#define LISTENQ 1024/* Second number of monitors */* Helper functions */ssize_t rio_writen (int fd,void *usrbuf,size_t n); void Rio_readinitb (rio_t *rp,int FD); Associate the internal buffer of the program with the descriptive descriptor. ssize_t Rio_readlineb (rio_t *rp,void *usrbuf,size_t maxlen); /* Reads a line of text from the internal buffer into the BUF. Ends the line of text with a null character. Of course, the maximum number of characters per line cannot exceed maxline.*/int open_clientfd (char *hostname, int portno); int open_listenfd (int portno); #endif void doit (int fd), void Read_requesthdrs (rio_t *rp); Read and ignore request header int Parse_uri (char *uri, Char *filename, char *cgiargs); Parses the URI, the file name is stored in filename, and the number of references is stored in Cgiargs. void serve_static (int fd, char *filename, int filesize); Provides static services. void Get_filetype (char *filename, char *filetype), void serve_dynamic (int fd, char *cause, char *cgiargs); Provide dynamic services. void Clienterror (int fd, char *cause, Char *errnum, Char *shortmsg, char *longmsg);/* Tiny is an iterative server that listens for connection requests on ports determined on the command line.
After opening a listening socket through the OPEN_LISTENEDFD function. Tiny runs a typical infinite service loop, accepts a connection (accept) request repeatedly, runs the transaction (doit), and finally closes the connection description descriptor (Close)/////SSCANF (BUF, "%s%s%s", method,uri,version): As a sample, generally at this time buf is stored in "get/http/1.1", so the method is "GET", the URI is "/". Version is "http/1.1".
Among the functions of sscanf: The strings in the BUF are transferred to the method, Uri, and version respectively by a space delimiter. STRCASECMP (method, "get"): ignores uppercase and lowercase compared to the size of "get", the same word returns 0.
Stat (FILENAME,&SBUF): Fill in sbuf with each meta-data in file filename, assuming the file is not found to return 0.
S_isreg (Sbuf,st_mode): This file is a normal file. S_IRUSR & Sbuf.st_mode: Have Read permission.
*/
the main function of 2.Tiny
int main (int argc, char const *argv[]) {int LISTENFD, CONNFD, Port, clientlen;struct sockaddr_in clientaddr;if (argc! = 2) { fprintf (stderr, "Usage:%s\n", argv[0]); exit (1);} Port = atoi (Argv[1]), LISTENFD = OPEN_LISTENFD (port), while (1) {Clientlen = sizeof (CLIENTADDR); connfd = Accept (LISTENFD, ( SA *) &clientaddr,&clientlen);d oit (CONNFD); close (CONNFD);}}
the doit function of 3.Tiny
void doit (int fd) {int is_static;struct stat Sbuf;char buf[maxline],method[maxline],uri[maxline],version[maxline]; Char filename[maxline],cgiargs[maxline];rio_t RIO;RIO_READINITB (&RIO,FD); Rio_readlineb (&rio,buf,MAXLINE) ; sscanf (buf, "%s%s%s", method,uri,version), if (strcasecmp (method, "GET")) {Clienterror (Fd,method, "501", "not Implemented "," Tiny does not implement this method "); return;} Read_requesthdrs (&rio); is_static = Parse_uri (Uri,filename,cgiargs); if (stat (FILENAME,&SBUF) < 0) { Clienterror (fd,filename, "404", "Not Found", "Tiny coundn ' t find this file"); if (is_static) {if (!) ( S_isreg (sbuf.st_mode)) | | ! (S_irusr & Sbuf.st_mode)) {Clienterror (Fd,filename, "403", "Forbidden", "Tiny coundn ' t read the file"), return; Serve_static (fd,filename,sbuf.st_size);} else {if (! ( S_isreg (sbuf.st_mode)) | | ! (S_ixusr & Sbuf.st_mode)) {Clienterror (Fd,filename, "403", "Forbidden", "Tiny coundn ' t run the CGI program"); Serve_dynamic (Fd,filename,cgiargs);}} /* from the doit function, our tIny webserver only supports the "GET" method, and an error message is sent when other methods request it. The main program returns. And wait for the next request.Otherwise, we read and ignore the request header.
(In fact, when we request a service, we do not have to write the request header directly, just to conform to the HTTP protocol standard). We then parse the URI into a file name and a potentially empty CGI parameter, and set a flag bit indicating whether the requested static or dynamic content. The stat function infers whether a file exists. Finally, assuming that the request is static, we need to verify that it is a plain file and readable. The condition passes, then our server sends the static content to the customer service side. Similarly, assuming the requested dynamic content, I verify that the file is a running file. Let's say you run the file. and provides dynamic functionality.
*/
the Clienterrorh function of 4.Tiny
void Clienterror (int fd, char *cause, Char *errnum, Char *shortmsg, char *longmsg) {char buf[maxline],body[maxbuf];sprintf (Body, "
5.Tiny of
void Read_requesthdrs (rio_t *rp) {char buf[maxline];rio_readlineb (rp,buf,maxline), while (strcmp (buf, "\ r \ n")) {Rio_ Readlineb (rp,buf,maxline);p rintf ("%s", buf);} return;} /* Tiny does not need to request any information in the header. This function is to skip these request headers and read the request headers until the empty line. and then return. */
6.Tiny of
int parse_ Uri (char *uri, char *filename,char *cgiargs) {char *ptr;if (!strstr (URI, "Cgi-bin")) {strcpy (Cgiargs, ""); strcpy ( FileName, "."); strcat (Filename,uri); if (Uri[strlen (URI)-1] = = '/') {strcat (filename, "home.html"); } return 1;} else {ptr = index (URI, '? '); if (PTR) {strcpy (cgiargs,ptr+1); *ptr = ' + ';} else {strcpy (Cgiargs, "");} strcpy (FileName, "."); strcat (Filename,uri); return 0;}} /* Infer whether the requested static or dynamic content is based on the presence of Cgi-bin in the URI.
assumes no cgi-bin. Indicates that the requested static content.
Then. We need to place the Cgiargs null and then get the file name, assuming that the URI we requested is finally "/". Then join the home.html on their own initiative. For example, when I request "/", the file name returned is "./home.html" and we Request "/logo.gif", then the file name returned is "./logo.gif". The
Assumes that the URI contains Cgi-bin. Indicates that the requested dynamic content.
Then we need to copy the parameters to Cgiargs and write the path of the file to be run to ilename. For example, the URI is/cgi-bin/adder?
12&45, the Cigargs stored in 12&45,filename is the "./cgi-bin/adder" index (URI, '?
'): Find the first occurrence of the URI string '? ' Address. and return this address.
*/
the serve_static function of 7.Tiny
void serve_static (int fd, char *filename, int filesize) {int Srcfd;char *srcp,filetype[maxline],buf[maxbuf];get_ FileType (filename,filetype); sprintf (buf, http/1.0 ok\r\n); sprintf (buf, "%sserver:tiny Web server\r\n", buf); sprintf (buf, "%scontent-length:%d\r\n", buf,filesize); sprintf (buf, "%scontent-type:%s\r\n\r\n", buf,filetype); Rio_ Writen (Fd,buf,strlen (BUF)); srcfd = open (filename,o_rdonly,0); SRCP = Mmap (0,filesize, Prot_read, map_private,srcfd,0) ; Close (SRCFD); Rio_writen (fd,srcp,filesize); Munmap (srcp,filesize);} /* Open the file named filename, map it to a virtual memory space, map the first filesize bytes of the file to the virtual storage area starting from address SRCP. Close the file description descriptor Srcfd, and write the data of the virtual store to the FD descriptive descriptor. Finally, the virtual memory area is freed.*/void Get_filetype (Char *filename, char *filetype) {if (strstr (filename, ". html")) strcpy (filetype, "text/html"); else if (strstr (filename, ". gif")) strcpy (filetype, "image/gif"), Else if (strstr (filename, ". jpg")) strcpy (filetype, "image/ JPG "); else strcpy (filetype," text/plain ");}
The server_dynamic function of 8.Tiny
void serve_dynamic (int fd, char *filename, char *cgiargs) {char buf[maxline],*emptylist[] = {null};sprintf (buf, "http/1.0 Ok\r\n "), Rio_writen (Fd,buf,strlen (BUF)), sprintf (buf," Server:tiny Web server\r\n "), Rio_writen (Fd,buf,strlen ( BUF)); if (fork () = = 0) {setenv ("query_string", cgiargs,1);d up2 (Fd,stdout_fileno); Execve (Filename,emptylist,environ) ;} Wait (NULL);} /* Tiny executes a CGI program (executable file) by deriving a child process and executing it in the context of the child process. To provide various types of dynamic content. Setenv ("Query_string", cgiargs,1): Sets the QUERY_STRING environment variable. dup2 (FD. Stdout_fileno): Redirects its standard output to a connected descriptive descriptor. At this time Anything written to the standard output is written directly to the client. Execve (Filename,emptylist,environ): Loads the execution CGI program. */
9. Some other functions
ssize_t rio_writen (int fd, void *usrbuf, size_t n) {size_t nleft = n; ssize_t Nwritten; char *BUFP = usrbuf; while (Nleft > 0) {if (Nwritten = Write (fd, BUFP, nleft)) <= 0) {if (errno = = eintr) Nwritten = 0; elsereturn-1; }nleft-= NWRITTEN;BUFP + = Nwritten; } return n;} Static ssize_t Rio_read (rio_t *rp, Char *usrbuf, size_t n) {int cnt; while (rp->rio_cnt <= 0) {/* assumes the buffer is empty, then fills */rp->rio_cnt again = Read (RP->RIO_FD, rp->rio_buf, sizeof (Rp->ri O_BUF)); if (rp->rio_cnt < 0) {if (errno! = eintr) return-1;} else if (rp->rio_cnt = = 0)/* EOF */return 0;else rp->rio_bufptr = rp->rio_buf; /* Once again set the buffer pointer */}/* from the internal buffer copy min (n, rp->rio_cnt) bytes to usrbuf*/cnt = n; if (rp->rio_cnt < n) cnt = rp->rio_cnt; memcpy (Usrbuf, rp->rio_bufptr, CNT); Rp->rio_bufptr + = cnt; RP->RIO_CNT-= CNT; return CNT;} void Rio_readinitb (rio_t *rp, int fd) {rp->rio_fd = FD; rp->rio_cnt = 0; Rp->rio_bufptr = Rp->rio_buf;} ssize_t Rio_readlineb (rio_t *rp, void *usrbuf, size_t maxlen) {int n, RC; Char C, *BUFP = Usrbuf; for (n = 1; n < maxlen; n++) {if (rc = Rio_read (RP, &c, 1) = = 1) {*bufp++ = C; if (c = = ' \ n ') break; else if (rc = = 0) {if (n = = 1) return 0;/* EOF, no data read */elsebreak; /* EOF, some data was read */} else return-1; /* ERROR */} *BUFP = 0; return n;} int Open_clientfd (char *hostname, int port) {int clientfd; struct Hostent *hp; struct sockaddr_in serveraddr; if (CLIENTFD = socket (af_inet, sock_stream, 0)) < 0) return-1; if (HP = gethostbyname (hostname)) = = NULL) return-2; Bzero (char *) &serveraddr, sizeof (SERVERADDR)); serveraddr.sin_family = af_inet; Bcopy (char *) hp->h_addr_list[0], (char *) &serveraddr.sin_addr.s_addr, hp->h_length); Serveraddr.sin_port = htons (port); if (Connect (CLIENTFD, (SA *) &serveraddr, siZeof (serveraddr)) < 0) return-1; return CLIENTFD;} int open_listenfd (int port) {int LISTENFD, optval=1; struct sockaddr_in serveraddr; /* Create a socket descriptive narrator */if ((LISTENFD = socket (af_inet, sock_stream, 0)) < 0) return-1; /* eliminates "Address already in use" error from bind. */if (setsockopt (LISTENFD, Sol_socket, so_reuseaddr, (const void *) &optval, sizeof (int)) < 0) return-1; /* LISTENFD'll be a endpoint for all requests to port on any IP address for this host */bzero ((char *) & SERVERADDR, sizeof (SERVERADDR)); serveraddr.sin_family = af_inet; SERVERADDR.SIN_ADDR.S_ADDR = htonl (Inaddr_any); Serveraddr.sin_port = htons ((unsigned short) port); if (Bind (LISTENFD, (SA *) &serveraddr, sizeof (SERVERADDR)) < 0) return-1; /* Make it a listening sockets ready to accept connection requests */if (Listen (LISTENFD, Listenq) < 0) return-1; return LISTENFD;}
Tiny Server: Small Web server