采摘处:http://blog.chinaunix.net/space.php?uid=22568683&do=blog&id=84698
相信大家对Apache都有所听闻,Apache是目前使用最为广泛我Web服务器。大家可以从http://news.netcraft.com/这个网站得到证实。这是腾讯的http://uptime.netcraft.com/up/graph?site=www.qq.com.Apache强大的功能和高效的性能并且开放源代码的这种模式对我很有吸引力,但无赖自己水平有限,无法从Apache庞大的source code里面理清头绪,于是乎,我就冒出了个自己动手写一个小型的简单的Web服务器的想法,希望对这方面也和我一样感兴趣的朋友有所帮助。
我的实验环境为:
OS:Red Hat Enterprise Linux 5
gcc:4.1.2
libc:2.5
editor:Vim
lang:C
阅读该源代码需要以下预备知识:
C语言基础
Linux编程基础
socket编程基础(Linux)
TCP/IP基本原理
HTTP基本原理
关键字(Key Words):
Linux C, Web HTTP Server, Linux Socket.
-----------------------------------------------------------------------------------
下面是Mutu的第一个版本(0.1 Alpha),实现了WEB 服务器的最基本功能
包括以下源文件:
webserver.c----程序入口
init_socket.h init_socket.c----完成一些WEB服务器的初始化工作
get_time.h get_time.c----获得服务器的时间
http_session.h http_session.c----处理一次HTTP会话
以下是各文件源码:
webserver.c:
/* * file:webserver.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include "get_time.h" #include "init_socket.h" #include "http_session.h" int main(int argc, char *argv[]) { int listen_fd; int connect_fd; struct sockaddr_in server_addr; struct sockaddr_in client_addr; bzero(&server_addr, sizeof(struct sockaddr_in)); bzero(&client_addr, sizeof(struct sockaddr_in)); if(init_socket(&listen_fd, &server_addr) == -1) { perror("init_socket() error. in webserver.c"); exit(EXIT_FAILURE); } socklen_t addrlen = sizeof(struct sockaddr_in); pid_t pid; while(1) { if((connect_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &addrlen)) == -1) { perror("accept() error. in webserver.c"); continue; } if( (pid = fork()) > 0) { close(connect_fd); continue; } else if(pid == 0) { close(listen_fd); printf("pid %d process http session from %s : %d\n", getpid(), inet_ntoa(client_addr.sin_addr), htons(client_addr.sin_port)); if(http_session(&connect_fd, &client_addr) == -1) { perror("http_session() error. in webserver.c"); shutdown(connect_fd, SHUT_RDWR); printf("pid %d loss connection to %s\n", getpid(), inet_ntoa(client_addr.sin_addr)); exit(EXIT_FAILURE); /* exit from child process, stop this http session */ } printf("pid %d close connection to %s\n", getpid(), inet_ntoa(client_addr.sin_addr)); shutdown(connect_fd, SHUT_RDWR); exit(EXIT_SUCCESS); } else { perror("fork() error. in webserver.c"); exit(EXIT_FAILURE); } } shutdown(listen_fd, SHUT_RDWR); return 0; }
/* * file:init_socket.h */ #ifndef INIT_SOCKET_H #define INIT_SOCKET_H #include <netinet/in.h> #define BACKLOG 20 /* length of listening queue on socket */ #define PORT 8080 /* web server listening port */ /* initialize the socket on server, include below socket(); bind(); listen(); */ /* listen_fd : the web server listen file decriptor server_addr: the web server ipv4 address RETURNS: success on 0, error on -1 */ int init_socket(int *listen_fd, struct sockaddr_in *server_addr); #endif
/* * file:init_socket.c */ #include <stdio.h> #include <strings.h> #include <unistd.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include "init_socket.h" int init_socket(int *listen_fd, struct sockaddr_in *server_addr) { if((*listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket() error. in init_socket.c"); return -1; } /* set reuse the port on server machine */ int opt = SO_REUSEADDR; if(setsockopt(*listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) { perror("setsockopt() error. in init_socket.c"); return -1; } server_addr->sin_family = AF_INET; server_addr->sin_port = htons(PORT); server_addr->sin_addr.s_addr = htonl(INADDR_ANY); if(bind(*listen_fd, (struct sockaddr *)server_addr, sizeof(struct sockaddr_in)) == -1) { perror("bind() error. in init_socket.c"); return -1; } if(listen(*listen_fd, BACKLOG) == -1) { perror("listen() error. in init_socket.c"); return -1; } return 0; }
get_time.h
/* * file: get_time.h */ #ifndef GET_TIME_H #define GET_TIME_H #define TIME_BUFFER_SIZE 40 /* buffer size of time_buffer */ char *get_time_str(char *time_buf); #endif
get_time.c
/* * file:get_time.c */ #include <time.h> #include <stdio.h> #include <string.h> #include "get_time.h" /* get the time on server, return: the ascii string of time , NULL on error argument: time_buf the buffer to store time_string */ char *get_time_str(char *time_buf) { time_t now_sec; struct tm *time_now; if( time(&now_sec) == -1) { perror("time() in get_time.c"); return NULL; } if((time_now = gmtime(&now_sec)) == NULL) { perror("localtime in get_time.c"); return NULL; } char *str_ptr = NULL; if((str_ptr = asctime(time_now)) == NULL) { perror("asctime in get_time.c"); return NULL; } strcat(time_buf, str_ptr); return time_buf; }
http_session.c
/* * file: http_session.h */ #ifndef HTTP_SESSION_H #define HTTP_SESSION_H #include <netinet/in.h> #define RECV_BUFFER_SIZE 1024 /* 1KB of receive buffer */ #define SEND_BUFFER_SIZE 1050000 /* 1.xMB of send buffer */ #define URI_SIZE 128 /* length of uri request from client browse */ #define TIME_OUT_SEC 10 /* select timeout of secend */ #define TIME_OUT_USEC 0 /* select timeout of usecend */ #define FILE_OK 200 #define FILE_FORBIDEN 403 /* there are no access permission*/ #define FILE_NOT_FOUND 404 /* file not found on server */ #define UNALLOW_METHOD 405 /* un allow http request method*/ #define FILE_TOO_LARGE 413 /* file is too large */ #define URI_TOO_LONG 414 /* */ #define UNSUPPORT_MIME_TYPE 415 #define UNSUPPORT_HTTP_VERSION 505 #define FILE_MAX_SIZE 1048576 /* 1MB the max siee of file read from hard disk */ #define ALLOW "Allow:GET" /* the server allow GET request method*/ #define SERVER "Server:Mutu(0.1 Alpha)/Linux" /* if the connect protocol is http then this function deal with it */ int http_session(int *connect_fd, struct sockaddr_in *client_addr); /* if http protocol return 1, else return 0 */ int is_http_protocol(char *msg_from_client); /* get the request header's uri */ char *get_uri(char *req_header, char *uri_buf); /* get the uri status,access return 0, not exist return 1, permission deny return 2, error return -1 */ int get_uri_status(char *uri); /* get the mime type of the file request in uri from client's browse */ char *get_mime_type(char *uri); /* read the file which requested by client in uri ,and store in entity_buf. success return bytes readed,error return -1 */ int get_file_disk(char *uri, unsigned char *entity_buf); /* set http replay header's status: 200:ok 404:file not found */ int set_rep_status(); int set_error_information(unsigned char *send_buf, int errorno); int reply_normal_information(unsigned char *send_buf, unsigned char *file_buf, int file_size, char *mime_type); #endif
http_session.c
/* * file:http_session.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/select.h> #include <sys/time.h> #include <netinet/in.h> #include "http_session.h" #include "get_time.h" int http_session(int *connect_fd, struct sockaddr_in *client_addr) { char recv_buf[RECV_BUFFER_SIZE + 1]; /* server socket receive buffer */ unsigned char send_buf[SEND_BUFFER_SIZE + 1]; /* server socket send bufrer */ unsigned char file_buf[FILE_MAX_SIZE + 1]; memset(recv_buf, '\0', sizeof(recv_buf)); memset(send_buf, '\0', sizeof(send_buf)); memset(file_buf, '\0', sizeof(file_buf)); char uri_buf[URI_SIZE + 1]; /* store the the uri request from client */ memset(uri_buf, '\0', sizeof(uri_buf)); int maxfd = *connect_fd + 1; fd_set read_set; FD_ZERO(&read_set); struct timeval timeout; timeout.tv_sec = TIME_OUT_SEC; timeout.tv_usec = TIME_OUT_USEC; int flag = 1; int res = 0; int read_bytes = 0; int send_bytes = 0; int file_size = 0; char *mime_type; int uri_status; FD_SET(*connect_fd, &read_set); while(flag) { res = select(maxfd, &read_set, NULL, NULL, &timeout); switch(res) { case -1: perror("select() error. in http_sesseion.c"); close(*connect_fd); return -1; break; case 0: /* time out, continue to select */ continue; break; default: /* there are some file-descriptor's status changed */ if(FD_ISSET(*connect_fd, &read_set)) { memset(recv_buf, '\0', sizeof(recv_buf)); if((read_bytes = recv(*connect_fd, recv_buf, RECV_BUFFER_SIZE, 0)) == 0) { /* client close the connection */ return 0; } else if(read_bytes > 0) /* there are some data from client */ { if(is_http_protocol(recv_buf) == 0) /* check is it HTTP protocol */ { fprintf(stderr, "Not http protocol.\n"); close(*connect_fd); return -1; } else /* http protocol */ { memset(uri_buf, '\0', sizeof(uri_buf)); if(get_uri(recv_buf, uri_buf) == NULL) /* get the uri from http request head */ { uri_status = URI_TOO_LONG; } else { printf("URI:%s\n", uri_buf); uri_status = get_uri_status(uri_buf); switch(uri_status) { case FILE_OK: printf("file ok\n"); mime_type = get_mime_type(uri_buf); printf("mime type: %s\n", mime_type); file_size = get_file_disk(uri_buf, file_buf); send_bytes = reply_normal_information(send_buf, file_buf, file_size, mime_type); // send(*connect_fd, send_buf, send_bytes, 0); break; case FILE_NOT_FOUND: /* file not found on server */ printf("in switch on case FILE_NOT_FOUND\n"); send_bytes = set_error_information(send_buf, FILE_NOT_FOUND); break; case FILE_FORBIDEN: /* server have no permission to read the request file */ break; case URI_TOO_LONG: /* the request uri is too long */ break; default: break; } send(*connect_fd, send_buf, send_bytes, 0); } } } } } } return 0; } int is_http_protocol(char *msg_from_client) { /* just for test */ return 1; int index = 0; while(msg_from_client[index] != '\0' && msg_from_client[index] != '\n') { index++; printf("%d%c",index - 1, msg_from_client[index - 1]); } if(strncmp(msg_from_client + index - 10, "HTTP/", 5) == 0) /* HTTP Request firt line like this 'GET /index.html HTTP/1.1' , so last 10 byte are HTTP/1.1\r\n*/ { return 1; } return 0; } char *get_uri(char *req_header, char *uri_buf) { int index = 0; while( (req_header[index] != '/') && (req_header[index] != '\0') ) { index++; } int base = index; while( ((index - base) < URI_SIZE) && (req_header[index] != ' ') && (req_header[index] != '\0') ) { index++; } if( (index - base) >= URI_SIZE) { fprintf(stderr, "error: too long of uri request.\n"); return NULL; } if((req_header[index - 1] == '/') && (req_header[index] == ' ')) { strcpy(uri_buf, "index.html"); return uri_buf; } strncpy(uri_buf, req_header + base + 1, index - base - 1); return uri_buf; } int get_uri_status(char *uri) { if(access(uri, F_OK) == -1) { fprintf(stderr, "File: %s not found.\n", uri); return FILE_NOT_FOUND; } if(access(uri, R_OK) == -1) { fprintf(stderr, "File: %s can not read.\n", uri); return FILE_FORBIDEN; } return FILE_OK; } char *get_mime_type(char *uri) { int len = strlen(uri); int dot = len - 1; while( dot >= 0 && uri[dot] != '.') { dot--; } if(dot == 0) /* if the uri begain with a dot and the dot is the last one, then it is a bad uri request,so return NULL */ { return NULL; } if(dot < 0) /* the uri is '/',so default type text/html returns */ { return "text/html"; } dot++; int type_len = len - dot; char *type_off = uri + dot; switch(type_len) { case 4: if(!strcmp(type_off, "html") || !strcmp(type_off, "HTML")) { return "text/html"; } if(!strcmp(type_off, "jpeg") || !strcmp(type_off, "JPEG")) { return "image/jpeg"; } break; case 3: if(!strcmp(type_off, "htm") || !strcmp(type_off, "HTM")) { return "text/html"; } if(!strcmp(type_off, "css") || !strcmp(type_off, "CSS")) { return "text/css"; } if(!strcmp(type_off, "png") || !strcmp(type_off, "PNG")) { return "image/png"; } if(!strcmp(type_off, "jpg") || !strcmp(type_off, "JPG")) { return "image/jpeg"; } if(!strcmp(type_off, "gif") || !strcmp(type_off, "GIF")) { return "image/gif"; } if(!strcmp(type_off, "txt") || !strcmp(type_off, "TXT")) { return "text/plain"; } break; case 2: if(!strcmp(type_off, "js") || !strcmp(type_off, "JS")) { return "text/javascript"; } break; default: /* unknown mime type or server do not support type now*/ return "NULL"; break; } return NULL; } int get_file_disk(char *uri, unsigned char *file_buf) { int read_count = 0; int fd = open(uri, O_RDONLY); if(fd == -1) { perror("open() in get_file_disk http_session.c"); return -1; } unsigned long st_size; struct stat st; if(fstat(fd, &st) == -1) { perror("stat() in get_file_disk http_session.c"); return -1; } st_size = st.st_size; if(st_size > FILE_MAX_SIZE) { fprintf(stderr, "the file %s is too large.\n", uri); return -1; } if((read_count = read(fd, file_buf, FILE_MAX_SIZE)) == -1) { perror("read() in get_file_disk http_session.c"); return -1; } printf("file %s size : %lu , read %d\n", uri, st_size, read_count); return read_count; } int set_error_information(unsigned char *send_buf, int errorno) { register int index = 0; register int len = 0; char *str = NULL; switch(errorno) { case FILE_NOT_FOUND: printf("In set_error_information FILE_NOT_FOUND case\n"); str = "HTTP/1.1 404 File Not Found\r\n"; len = strlen(str); memcpy(send_buf + index, str, len); index += len; len = strlen(SERVER); memcpy(send_buf + index, SERVER, len); index += len; memcpy(send_buf + index, "\r\nDate:", 7); index += 7; char time_buf[TIME_BUFFER_SIZE]; memset(time_buf, '\0', sizeof(time_buf)); get_time_str(time_buf); len = strlen(time_buf); memcpy(send_buf + index, time_buf, len); index += len; str = "\r\nContent-Type:text/html\r\nContent-Length:"; len = strlen(str); memcpy(send_buf + index, str, len); index += len; str = "\r\n\r\n<html><head></head><body>404 File not found<br/>Please check your url,and try it again!</body></html>"; len = strlen(str); int htmllen = len; char num_len[5]; memset(num_len, '\0', sizeof(num_len)); sprintf(num_len, "%d", len); len = strlen(num_len); memcpy(send + index, num_len, len); index += len; memcpy(send_buf + index, str, htmllen); index += htmllen; break; default: break; } return index; } int reply_normal_information(unsigned char *send_buf, unsigned char *file_buf, int file_size, char *mime_type) { char *str = "HTTP/1.1 200 OK\r\nServer:Mutu/Linux(0.1)\r\nDate:"; register int index = strlen(str); memcpy(send_buf, str, index); char time_buf[TIME_BUFFER_SIZE]; memset(time_buf, '\0', sizeof(time_buf)); str = get_time_str(time_buf); int len = strlen(time_buf); memcpy(send_buf + index, time_buf, len); index += len; len = strlen(ALLOW); memcpy(send_buf + index, ALLOW, len); index += len; memcpy(send_buf + index, "\r\nContent-Type:", 15); index += 15; len = strlen(mime_type); memcpy(send_buf + index, mime_type, len); index += strlen(mime_type); memcpy(send_buf + index, "\r\nContent-Length:", 17); index += 17; char num_len[8]; memset(num_len, '\0', sizeof(num_len)); sprintf(num_len, "%d", file_size); len = strlen(num_len); memcpy(send_buf + index, num_len, len); index += len; memcpy(send_buf + index, "\r\n\r\n", 4); index += 4; memcpy(send_buf + index, file_buf, file_size); index += file_size; return index; }
|