1、项目介绍
HTTP协议是应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。协议的详细内容,前面一篇HTTP协议详解已经详细介绍了,这里不再赘述。
项目总体描述:HTTP支持客户端/服务器模式,终端用户可通过浏览器或网络爬虫与服务器建立连接,所以首先需要自主实现服务器Server端,具体由头文件httpd.h、main函数文件httpd.c、模块功能函数文件httpd.c组成,主要实现客户端与服务器通过socket建立通信机制。首先由用户主动发起一个到服务器上指定端口(默认端口为80)的请求,服务器则在那个端口监听客户端发送过来的请求。服务器一行一行读取请求,通过请求信息判断用户请求资源的方法和路径,若方法和路径没有问题,则方法和路径通过CGI模式或非CGI向用户提供不同的HTML网页信息。处理完请求客户端向用户发送响应,包括状态行如:“HTTP/1.1 200 OK”、响应报头、消息正文,消息体即为服务器上的资源。
实现功能一:静态首页展示(图片、文字文字信息);
实现二:支持表单提交,可以借助浏览器或telnet工具使用GET、POST方法访问服务器,实现数据的简单计算功能;
实现三:引入MYSQL,用户可通过页面表单进行数据操作,服务器拿到客户提交的数据后,会把数据存入到远端数据库,客户端也可请求查看数据库信息。
整个项目的文件目录:
目录:
conf:配置文件,存放需要绑定的服务器的ip和port ;
log:shell的日志文件以及http错误处理的日志文件 ;
sql_client:mysql部分的API及CGI实现;
thread_pool:线程池实现;
wwwroot:web服务器工作的根目录,包含各种资源页面(例如默认的index.html页面,差错处理的404页面),以及执行cgi的可执行程序。下面还有一个 cgi-bin目录,是存放CGI脚本的地方。这些脚本使WWW服务器和浏览器能运行外部程序,而无需启动另一个程序。它是运行在Web服务器上的一个程序,并由来自于浏览者的输入触发。
整个项目的框架图:
2、各模块功能介绍
头文件httpd.h,包含该项目代码所使用的全部函数的头文件以及宏定义,和函数声明;
1 #ifndef _HTTPD_ 2 #define _HTTPD_ 3 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <sys/socket.h> 7 #include <sys/types.h> 8 #include <netinet/in.h> 9 #include <arpa/inet.h> 10 #include <fcntl.h> 11 #include <errno.h> 12 #include <string.h> 13 #include <unistd.h> 14 #include <sys/stat.h> 15 #include <sys/wait.h> 16 17 #define SUCCESS 0 18 #define NOTICE 1 19 #define WARNING 2 20 #define ERROR 3 21 #define FATAL 4 22 23 #define SIZE 1024 24 25 void print_log(char *msg, int level); //打印日志 26 int startup(const char *ip, int port); //创建监听套接字 27 void *handler_request(void *arg); //处理请求 28 29 #endif
main函数文件main.c实现主要通信逻辑,通过socket建立连接的,监听和接受套接字,然后创建新线程处理请求。
1 #include <pthread.h> 2 #include "httpd.h" 3 4 static void usage(const char *proc) 5 { 6 printf("Usage: %s [local_ip] [local_port] ", proc); 7 } 8 9 int main(int argc, char *argv[]) 10 { 11 if(argc != 3){ 12 usage(argv[0]); 13 return 1; 14 } 15 16 int listen_sock = startup(argv[1], atoi(argv[2]));//监听套接字 17 //daemon(0, 0); 18 while(1){ 19 struct sockaddr_in client; 20 socklen_t len = sizeof(client); 21 int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);//接收套接字 22 if(new_sock < 0){ 23 print_log(strerror(errno), NOTICE); 24 continue; 25 } 26 27 printf("get client [%s:%d] ", 28 inet_ntoa(client.sin_addr), 29 ntohs(client.sin_port)); //链接到一个客户端之后打印其IP及端口号 30 31 pthread_t id; 32 int ret = pthread_create(&id, NULL, //创建新线程 33 handler_request, (void *)new_sock); 34 if(ret != 0){ 35 print_log(strerror(errno), WARNING); 36 close(new_sock); 37 }else{ 38 pthread_detach(id); //将子线程分离,该线程结束后会自动释放所有资源 39 } 40 } 41 close(listen_sock); 42 return 0; 43 }
模块功能函数在httpd.c文件
1 #include "httpd.h" 2 3 void print_log(char *msg, int level) 4 { 5 #ifdef _STDOUT_ 6 const char * const level_msg[]={ 7 "SUCCESS", 8 "NOTICE", 9 "WARNING", 10 "ERROR", 11 "FATAL", 12 }; 13 printf("[%s][%s] ", msg, level_msg[level%5]); 14 #endif 15 } 16 17 int startup(const char *ip, int port) // 18 { 19 int sock = socket(AF_INET, SOCK_STREAM, 0); //创建套接字 20 if(sock < 0){ 21 print_log(strerror(errno), FATAL); //strerror()将错误码转换为对应的错误码描述 22 exit(2); 23 } 24 25 int opt = 1; 26 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //将该套接字设置为地址复用状态,若服务器挂掉可实现立即重启 27 28 struct sockaddr_in local; 29 local.sin_family = AF_INET; 30 local.sin_port = htons(port); //端口号转换 31 local.sin_addr.s_addr = inet_addr(ip); //ip转换 32 if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){ //绑定 33 print_log(strerror(errno), FATAL); 34 exit(3); 35 } 36 if(listen(sock, 10) < 0){ //监听 37 print_log(strerror(errno), FATAL); 38 exit(4); 39 } 40 return sock; 41 } 42 43 //ret > 1, line != ' '读成功,正常字符; ret=1&line=' ' ret<=0&&line==' ' 44 static int get_line(int sock, char line[], int size) //得到一行请求内容 45 { 46 // read 1 char , one by one 47 char c = '