preface

Tinyhttp is a super lightweight server written in C language, for beginners with C language foundation, is a very good practice project, carefully read the code on LINUX network programming also have a preliminary understanding. Because I have no understanding of network programming before, so look at the source code, are to consult the relevant concepts while learning. The first half of this article summarizes all the knowledge points in my learning process, with these basic feelings to read the source code is quite helpful.

HTTP

This is an important point; you can’t understand the logic in your code until you know the format of the data being parsed. Due to this part of content is too much, so we focus to see the two pictures below, other details can refer to link, the reference links: www.cnblogs.com/linliquan/p…

Request message format

  • Only two request methods can be identified in this articleGETandPOST.
  • GET: used when the client wants to read a resource from the serverGETMethods.GETMethod requires that the server willURLThe located resource is sent back to the client as part of the response packet, that is, to request a certain resource from the server. useGETMethod, the request parameters and their corresponding values are appended toURLLater, use a question mark (“? ) represents the end of the URL and the beginning of the request parameters, which are limited in length. For example, GET/HTTP/1.1
  • POST: Can be used when the client provides too much information to the serverPOSTMethod,GETGenerally used to obtain/query resource information,POSTUser data is attached, typically used to update resource information. Example: POST /color.cgi HTTP/1.1

Response message format

  • "HTTP / 1.0 200 OK \ r \ n"It stands for successful reception.

Socket programming

This is the foundation of Linux network programming, and here is a flowchart for using TCP transport.

  • socketThe function creates ascoketCommunication, that is, to register with the system, notify the system to establish a communication port.
  • bindFunction is used to givescoketSet a name for the port.
  • listenFunction is used to waitscoketWired, and can specify the maximum connection requirements to be processed simultaneously.
  • acceptThe connection () function accepts a connection request from a remote computer to establish a communication connection with a client. When a function accepts a request, it generates a new socket, which is used for data transmission.

The fork function

Fork creates a child process with exactly the same code as the parent process. It executes the statement following fork once and returns twice.

  • In the parent process, fork returns the ID of the newly created child process
  • In the child process, fork returns 0
  • If an error occurs, fork returns a negative value.

The pipe

Pipe is a basic IPC mechanism that acts between related processes to transfer data.

  • This is essentially a pseudo-file (actually a kernel buffer)
  • It is referenced by two file descriptors, one for the read side and one for the write side.
  • Data flows in from the write end and flows out from the read end

Principle: the real kernel uses ring queue mechanism, with the kernel buffer implementation

Limitations:

  • Data can be read but not written
  • You can only read it once. It doesn’t exist
  • Using half duplex communication, can only flow in one direction
  • Pipes can only be used between processes that have a common ancestor

The working process

In this section, we will introduce the function call process in the program after the client or server performs relevant operations. The specific implementation of each function will be introduced in the next section.

Source code analysis

To avoid verbose code, only important code is explained and shown.

main()function

int main(void) { ... u_short port = 4000; //#1 server_sock = startup(&port); //#2 while (1) { client_sock = accept(server_sock, //#3 (struct sockaddr *)&client_name, &client_name_len); if (pthread_create(&newthread , NULL, (void //#4 *)accept_request, (void *)(intptr_t)client_sock) ! = 0) perror("pthread_create"); } close(server_sock); //#5 return(0); }Copy the code
  • This part is written in full accordance with the TCP flow explained in the section of socket Programming.
  • # 1 ~ # 2:startupThe function completesocket,bind,listen. And set the port number to 4000.
  • # 3 ~ # 4: accpetAction, which generates a new one when a client sends a requestsocketPort and thread, through which responses to that request are processed, and then continue listening until the next request is made.
  • This is how the server works.

startup()function

The startup function completes socket, bind, and listen.

int startup(u_short *port) { ... httpd = socket(PF_INET, SOCK_STREAM, 0); //#1 name.sin_family = AF_INET; //#2 name.sin_port = htons(*port); //#3 name.sin_addr.s_addr = htonl(INADDR_ANY); //#4 if ((setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, //#5 &on, sizeof(on))) < 0) { error_die("setsockopt failed"); } if (bind(httpd, (struct sockaddr *)&name, //#6 sizeof(name)) < 0) error_die("bind"); if (listen(httpd, 5) < 0) //#7 error_die("listen"); return(httpd); / / # 8}Copy the code
  • # 1: socketThe function creates ascoketThe communication,PF_INETRepresents IPv4 communication,SOCK_STREAMIndicates TCP.
  • # 2 ~ # 6: Sets the port information, port number, and IP address.
  • # 7:listenFunction is used to waitscoketWired, and can specify the maximum connection requirements to be processed simultaneously.

accept_request()function

The accept_request() function is a thread handler. The main function is to parse the request command and then perform the corresponding operation. The HTTP request format has been described in the previous article.

void accept_request(void *arg) { int client = (intptr_t)arg; char buf[1024]; size_t numchars; char method[255]; char url[255]; char path[512]; size_t i, j; struct stat st; int cgi = 0; /* becomes true if server decides this is a CGI * program */ char *query_string = NULL; */ numchars = get_line(client, buf, sizeof(buf)); i = 0; j = 0; /* Extract method name until space is detected */ while (! ISspace(buf[i]) && (i < sizeof(method) - 1)) { method[i] = buf[i]; i++; } j=i; method[i] = '\0'; /* When neither GET nor POST is requested, Unimplemented () function */ if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) {unimplemented(client); return; } if (strcasecmp(method, "POST") == 0) cgi = 1; i = 0; / * skip Spaces * / while (ISspace (buf) [j] && (j < j++ numchars)); /* Fetch domain name */ while (! ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars)) { url[i] = buf[j]; i++; j++; } url[i] = '\0'; /* if (strcasecmp(method, "GET") == 0) {query_string = url; /* Find the first one? While ((*query_string! = '? ') && (*query_string ! = '\0')) query_string++; if (*query_string == '? ') { cgi = 1; *query_string = '\0'; query_string++; / / sprintf(path, "htdocs%s", url); / / sprintf(path, "htdocs%s", url) if (path[strlen(path) - 1] == '/') strcat(path, "index.html"); printf("%s\n", path); /* Get the file properties of the specified file. */ if (stat(path, &st) == -1) {while ((numchars > 0) &&strcmp ("\n", buf)) /* read & discard headers */ numchars = get_line(client, buf, sizeof(buf)); not_found(client); } else {if ((st.st_mode & S_IFMT) == S_IFDIR) /*S_IFMT = mask, S_IFDIR: D irectory indicates that the path is a folder */ strcat(path, "/index.html"); If ((st. st_mode & S_IXUSR) | | / file owner have execute permissions * * X said/(st. st_mode & S_IXGRP) | | (st. st_mode & S_IXOTH)) cgi = 1; /* Use the CGI flag to determine if the executable file needs to be executed. If the CGI program is not needed, serve_file will send the file directly, and if so, execute the cgi program via execute_cgi. */ if (! cgi) serve_file(client, path); else execute_cgi(client, path, method, query_string); } /* HTTP is connectionless, so after executing a request, close the port */ close(client); }Copy the code

execute_cgi()function

Execute the corresponding CGI function

void execute_cgi(int client, const char *path, const char *method, const char *query_string) { char buf[1024]; int cgi_output[2]; int cgi_input[2]; pid_t pid; int status; int i; char c; int numchars = 1; int content_length = -1; buf[0] = 'A'; buf[1] = '\0'; if (strcasecmp(method, "GET") == 0) while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ numchars = get_line(client, buf, sizeof(buf)); else if (strcasecmp(method, "POST") == 0) /*POST*/ { numchars = get_line(client, buf, sizeof(buf)); / * according to the request message format for all the head, until meet "\ n" * / while ((numchars > 0) && STRCMP (" \ n ", buf)) {buf [15] = '\ 0'; if (strcasecmp(buf, "Content-Length:") == 0) content_length = atoi(&(buf[16])); numchars = get_line(client, buf, sizeof(buf)); } if (content_length == -1) { bad_request(client); return; If (pipe(cgi_output) < 0) {cannot_execute(client); if (pipe(cgi_output) < 0) {cannot_execute(client); return; } if (pipe(cgi_input) < 0) { cannot_execute(client); return; } /*fork a child process */ if ((pid = fork()) < 0) {cannot_execute(client); return; } /* Send a response message according to the response message format */ sprintf(buf, "HTTP/1.0 200 OK\r\n"); send(client, buf, strlen(buf), 0); if (pid == 0) /* child: CGI script */ { char meth_env[255]; char query_env[255]; char length_env[255]; Dup2 (cgi_output[1], STDOUT); dup2(cgi_output[1], STDOUT); dup2(cgi_input[0], STDIN); close(cgi_output[0]); close(cgi_input[1]); sprintf(meth_env, "REQUEST_METHOD=%s", method); putenv(meth_env); If (strcasecmp(method, "GET") == 0) {sprintf(query_env, "QUERY_STRING=%s", QUERY_STRING); putenv(query_env); } else { /* POST */ sprintf(length_env, "CONTENT_LENGTH=%d", content_length); putenv(length_env); } execl(path, NULL); /* Execute file function */ exit(0); } else { /* parent */ close(cgi_output[1]); close(cgi_input[0]); If (strcasecmp(method, "POST") == 0){/* The parent process receives data from the scoket port and pipes it to the CGI program */ for (I = 0; i < content_length; i++) { recv(client, &c, 1, 0); write(cgi_input[1], &c, 1); }} / * can't read data blocks * / while (read (cgi_output [0], & c, 1) > 0) {send (client, & c, 1, 0). printf("%s", &c); } close(cgi_output[0]); close(cgi_input[1]); /* Wait for the child process to finish before terminating the parent process */ waitpid(pid, &status, 0); }}Copy the code

get_line()function

Read data from the port until ‘\r\n’, ‘\n’ and ‘\r’ are encountered.

int get_line(int sock, char *buf, int size) { int i = 0; char c = '\0'; int n; while ((i < size - 1) && (c ! = '\n')) { n = recv(sock, &c, 1, 0); If (n > 0) / * if read data, judge whether the '\ r \ n', '\ n' and '\ r * / {the if (c = =' \ r) {/ * preview of the next character, but not read and judge whether the '\ n', if read out, if it is not returned directly, Unread characters */ n = recV (sock, &C, 1, MSG_PEEK); if ((n > 0) && (c == '\n')) recv(sock, &c, 1, 0); else c = '\n'; } buf[i] = c; i++; } else /* Return */ c = '\n'; } buf[i] = '\0'; return(i); }Copy the code

The test results

To facilitate testing, I add printf to the data transfer part of the program.

Start the server

  • You can see the port number 4000 that we specified.

Client connection

  • You can see that the first request from the client to the server isGET / HTTP/1.1.
  • This instruction will be executed according to program parsingserve_file()Function,htdocs/index.htmlThe file is sent to the client, and the contents of the file are shown in the first diagram.

Submit on the web page Enter a color and submit

  • When the client submits a request, the server receives itPOST/color. Cgi HTTP / 1.1Request instructions.
  • It then keeps reading the entire request header according to the request format.

  • Read the header, and then read the datacolor = redWhen the CGI program is finished, read from the pipe reads the content to be sent to the client in THE HTML format of a new page, consistent with how the page is displayed.