一个简单的webserver,详细讲解和代码实现

NOTE: 1.本文所涉及项目来自于互联网教育资源,本文作者将其实现在自己的机器上。

            2.本文所涉及网页资源,作者出于版权考虑不便提供,可以考虑换成其它网页资源,本项目的重点的在服务器,而不是前端页面。

WEB 服务器项目


一 项目开发流程
1 需求分析:做个什么东西,实现什么功能,达到什么效果,解决什么问题
2 概要设计:技术选型,采用什么框架,主要的技术路线,大体结构
3 制定计划:评估每个功能模块的投入,编写研发计划
4 详细设计:设计每个模块的功能细节(文件,库,函数,数据类型,类)
5 编写代码:根据详细设计,给出代码实现
6 系统测试:黑盒测试,白盒测试
7 发布产品
8 项目总结

二 需求分析
    基于HTTP协议的WEB服务器,提供静态页面的下载
    服务器 和 浏览器进行通信,传输层使用tcp协议,应用层,使用HTTP协议,浏览器
    向服务器发送http请求,服务器接受请求后,对请求内容进行解析,明确浏览器目的
    服务器需要从请求中提取关键数据.判断对方所请求的文件是否存在并确定类型,
    构造响应,并回传给浏览器.服务器要支持并发访问.

三 概要设计
    主模块启动服务器模块,服务器模块主要负责接受请求开线程
    线程模块借助通信模块,接受客户端发送的http请求
    线程模块借助http模块,对http请求进行解析
    线程模块借助资源模块,对文件进行判断
    线程模块借助http模块,构造响应
    线程模块借助通信模块,发送响应

四 详细设计
    http模块:       http.h              http.c
    通信模块:       socket.h            socket.c
    资源模块:       resource.h          resource.c
                           mime.h
    信号模块:       signals.h           signals.c
    线程模块:       client.h            client.c
    服务器模块:     server.h            server.c
    主模块:                             main.c

环境搭建

本文涉及到的服务器运行在ubuntu16.04上。在桌面准备文件夹如下:其中home目录存储的是静态网页文件,code目录存储的是服务器程序。

通信模块实现

头文件定义:


#ifndef __SOCKET_H
#define __SOCKET_H

//init a socket
int init_socket(short port);

//accept client request
int accept_client(void);

//store the request into buffer
char * recv_request(int connect_socket);

//send http head
int send_head(int connect_socket , const char * head);

//send http body
int send_body(int connect_socket , char * path);

//close socket
void close_socket(void);


#endif 

实现

#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "socket.h"

static int s_sock = -1; //listening socket

int init_socket(short port){
	printf("pid:%d,tid%ld : construct socket\n", getpid(),syscall(SYS_gettid));
	
    //init a socket
    s_sock = socket(AF_INET , SOCK_STREAM , 0 );
	if (s_sock == -1){
		perror("socket:");
		return -1;	
	}
	
	printf("pid:%d,tid%ld : set socket\n", getpid(),syscall(SYS_gettid));
	// if server exit in the case of some reasons , then the server would need restart.
	// But when the socket used last time is not closed immediately , restart would fail.
	// So need multi-use of a single port by some sockets.
	int on = 1;
	if (setsockopt(s_sock , SOL_SOCKET , SO_REUSEADDR , &on , sizeof(on)) == -1 ){
	
		perror("setsockopt:");
		return -1;
	}
	
	printf("pid:%d,tid%ld : construct addr\n", getpid(),syscall(SYS_gettid));
	
    //prepare the address for server
	struct sockaddr_in ser;
	ser.sin_family = AF_INET;
	ser.sin_port = htons(port);  // bytes order 
	ser.sin_addr.s_addr = INADDR_ANY;
	
	printf("pid:%d,tid%ld : bind addr\n", getpid(),syscall(SYS_gettid));
    
    // bind the socket with server address
	if(bind(s_sock , (struct sockaddr * )&ser , sizeof (ser)) == -1){
		perror("bind:");
		return -1;
	}

	printf("pid:%d,tid%ld : start listening\n", getpid(),syscall(SYS_gettid));

    //listening client request
	if (listen(s_sock, 1024) == -1){
		perror("listen");
		return -1;
	}
	return 0;
}

int accept_client(void){
	printf("pid:%d,tid%ld : wait for clients\n", getpid(),syscall(SYS_gettid));
	struct sockaddr_in cli;  // the info of client 
	socklen_t len = sizeof(cli);	
    // get the socket for  client-server communicating 
	int conn = accept(s_sock , (struct sockaddr*)&cli , &len);
	if (conn == -1 ){
		perror("accept:");
		return -1;	
	}
	
	printf("pid:%d,tid%ld : accepted a client , ip : %s port %d \n", getpid(),syscall(SYS_gettid), inet_ntoa(cli.sin_addr) , ntohs(cli.sin_port) );
	return conn;
}

char * recv_request(int connect_socket){
	char * req = NULL;
	ssize_t len = 0;
	for(;;){
		char buffer[1024] = {} ;
		ssize_t size = recv(connect_socket , buffer , sizeof (buffer) - 1 , 0);
		if ( size == -1 ){
			perror("recv:");
			free(req);
			return NULL;
		}
		if ( size == 0 ){
			printf("pid:%d,tid%ld : client closed\n", getpid(),syscall(SYS_gettid));
			free(req);
			return NULL;		
		}
		req = realloc(req , len + size + 1 );   
		memcpy(req + len , buffer , size);
		len += size;

		// \r\n\r\n for the end of recv
		if (strstr(req , "\r\n\r\n")){
			break;
		}
	}

	return req;
}

int send_head(int connect_socket , const char * head){

	if (send(connect_socket , head , strlen(head) , 0) == -1 ){
		perror("send:");
		return -1;
	}
	return 0;
}

int send_body(int connect_socket , char * path){
	int fd = open( path , O_RDONLY );
	if ( fd == -1 ){
		perror("open:");
		return -1;
	}
	char buffer[1024] = {};
	ssize_t len;
    // len == 0 indicates for the end of file from which the fun reads.
	while( ( len=read(fd , buffer , sizeof(buffer) )) > 0 ){
		if (send(connect_socket , buffer , len , 0) == -1){
			perror("send");
			return -1;		
		}
	}
	if (len == -1){
		perror("read:");
		return -1;
	}
	close(fd);
	return 0;
}

void close_socket(void){
	close(s_sock);
	return ;
}


http模块实现

头文件

#ifndef __HTTP_H
#define __HTTP_H
#include<limits.h>
#include<sys/types.h>

//struct of parse result

typedef struct http_request{
	char method[32];
	char path[PATH_MAX+1];
	char protocol[32];
	char connection[32];
}HTTP_REQUEST;

//parse http requests 

int parse_request(const char * request , HTTP_REQUEST * result);

//struct of http head
typedef struct http_response{
	char protocol[32];
	int status;
	char desc[128];
	char type[64];
	off_t length;   
	char connection[32];
}HTTP_RESPONSE;

//construct http response
int construct_head(const HTTP_RESPONSE * response , char * head);

#endif 

实现

#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>

#define __USE_GNU    //for the use of strcasestr

#include <string.h>
#include <time.h>
#include "http.h"

int parse_request(const char * request , HTTP_REQUEST * result){
	sscanf(request, "%s%s%s" , result->method , result->path , result->protocol);
	char * connection = strcasestr(request, "connection");
	if (connection){
		sscanf(connection , "%*s%s" , result->connection);	
	}else
		return -1;

	printf("pid:%d,tid%ld : [%s][%s][%s][%s]\n", getpid(),syscall(SYS_gettid),
	result->method , result->path , result->protocol , result->connection);

	if (strcasecmp(result->method,"get")){
		printf("pid:%d,tid%ld : invaild method\n", getpid(),syscall(SYS_gettid));
		return -1;
	}
	
	if (strcasecmp(result->protocol,"http/1.0") && strcasecmp(result->protocol,"http/1.1")){
		printf("pid:%d,tid%ld : invaild protocol\n", getpid(),syscall(SYS_gettid));
		return -1;
	}
	return 0;
}

int construct_head(const HTTP_RESPONSE * response , char * head){
	
	char date_time[32] = {};
	time_t now = time(NULL);
	
	strftime(date_time , sizeof(date_time) , "%a %d %b %Y %T" , gmtime(&now) );
	
	sprintf(head , "%s %d %s\r\n"
		       "Server: SUNSERVER1.0\r\n"
		       "Date: %s\r\n"
		       "Content-Type: %s\r\n"
		       "Content-Length: %ld\r\n"
		       "Connection: %s\r\n\r\n",response->protocol,response->status ,response->desc,date_time,response->type,response->length,response->connection);
	
	return 0;
}



客户端线程模块实现

头文件

#ifndef __CLIENT_H_
#define __CLIENT_H_


// the arg of thread process fun
typedef struct client_arg{
	const char * home;
	int connect_socket;
} CLIENT_ARG;

//thread process fun
void * client(void* arg);

#endif

实现

#include <unistd.h>
#include <sys/syscall.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "http.h"
#include "socket.h"
#include "resource.h"
#include "client.h"

void * client(void* arg){

	CLIENT_ARG * ca =(CLIENT_ARG *)arg;
	printf("pid:%d,tid%ld : client thread running\n", getpid(),syscall(SYS_gettid));
	for(;;){
		printf("pid:%d,tid%ld : recv http request\n", getpid(),syscall(SYS_gettid));
		char * buffer_req = recv_request(ca->connect_socket);
		if (buffer_req == NULL){
			break;		
		}
		printf("pid:%d,tid%ld : request diagram : \n %s \n", getpid(),syscall(SYS_gettid) , buffer_req);
		
		//parse request
		printf("pid:%d,tid%ld : parse http request\n", getpid(),syscall(SYS_gettid));
		HTTP_REQUEST hreq = {};
		if (parse_request(buffer_req , &hreq) == -1 ){
			free(buffer_req);    //release mem of http req 
			break;
		}
		free(buffer_req);
		
		//path prepare
		// /home/huijun/Desktop/sunWebServer/home
		char root[PATH_MAX+1]={};
		char path[PATH_MAX+1]={};
		strcpy(root,ca->home);
		if (root[strlen(root)-1] == '/'){
			root[strlen(root)-1] = '\0' ;	
		}
		strcpy(path , root );
		strcat(path , hreq.path);
		
		//request of "/" , then return index page		
		if (strcmp(hreq.path , "/") == 0){
			strcat(path , "index.html");
		}

		//char protocol[32];

		HTTP_RESPONSE hres = {"HTTP/1.1",200,"OK","text/html"};
		//search resource
		if(search_resource(path) == -1){
			hres.status = 404;
			strcpy(hres.desc,"NOT FOUND");
			strcpy(path , root);
			strcat(path , "/404.html");
					
		}else if(identify_type(path,hres.type) == -1){
			hres.status = 404;
			strcpy(hres.desc,"NOT FOUND");
			strcpy(path , root);
			strcat(path , "/404.html");
		}
		
		//get length of file 

		struct stat st;
		if (stat(path , &st) == -1){
			perror("stat:");
			break;
		}
		hres.length = st.st_size;
		
		//connection state
		if (strlen(hreq.connection)){
			strcpy(hres.connection , hreq.connection);
		}else if (strcasecmp(hreq.protocol, "http/1.0") == 0){
			strcpy(hres.connection, "Close");
		}else{
			strcpy(hres.connection, "Keep-alive");		
		}
		
		//construct response
		printf("pid:%d,tid%ld : construct response\n", getpid(),syscall(SYS_gettid));
		char head[1024] = {};
		if (construct_head(&hres,head) == -1){
			break;		
		}
		printf("pid:%d,tid%ld : response diagram : \n %s\n", getpid(),syscall(SYS_gettid) ,head );	

		if (send_head(ca->connect_socket , head) == -1 ){
			break;		
		}

		if (send_body(ca->connect_socket , path) == -1 ){
			break;		
		}
		
		if(strcasecmp(hres.connection, "Close") == 0){
			break;		
		}
			
	}
	close(ca->connect_socket);
	free(ca);
	printf("pid:%d,tid%ld : client thread end\n", getpid(),syscall(SYS_gettid));
	return NULL;
	
}

资源模块实现

头文件

#ifndef __RESOURCE_H_
#define __RESOURCE_H_

//serarch the requested-resources in the home file
int search_resource(const char * path);

//identify the type of requested-resource
int identify_type(const char * path , char * type);


#endif

实现

#include <unistd.h>
//#include <fcntl.h>
#include <sys/syscall.h>
#include <stdio.h>
#define __USE_GNU
#include <string.h>

#include "mime.h"
#include "resource.h"

int search_resource(const char * path){
	return access(path , R_OK);
}

int identify_type(const char * path , char * type){
	int len = strlen(path)-1;
	//char * ptr = path;
	
	const char * suffix; //= strrchr(path , '.');

	while(len>=0 && path[len]!='.') --len;
	
	if (len < 0) suffix = NULL;
	else {
		suffix = &path[len];
	}
	printf("\n***file type : %s \n" , suffix);
	if (suffix == NULL){
		printf("pid:%d,tid%ld : failed to get file extension\n", getpid(),syscall(SYS_gettid));
		return -1;
	}
	for(int i = 0 ; i < sizeof(s_mime) / sizeof(s_mime[0]) ; ++i ){
		if ( strcasecmp(suffix , s_mime[i].suffix ) == 0){
			strcpy(type , s_mime[i].type);
			printf("\n***path %s ***type: %s \n", path ,type);
			return 0;
		}	
	}

	printf("pid:%d,tid%ld : failed to identify file extension\n", getpid(),syscall(SYS_gettid));
	return -1;
}





信号模块实现

头文件

#ifndef __SIGNALS_H_
#define __SIGNALS_H_


//to protect the server from unexpected killing signals
int init_signals(void);



#endif

实现


#include <unistd.h>
#include <signal.h>
#include <sys/syscall.h>
#include <stdio.h>
#include "signals.h"





int init_signals(void){
	
	printf("pid:%d,tid%ld : ignore most signals\n", getpid(),syscall(SYS_gettid));
	for (int sn = 1 ; sn <= 64 ; ++sn){
		if ( sn!= 2 && sn != 15)
			signal(sn , SIG_IGN);
	}
	return 0;

}

服务器模块实现

头文件

#ifndef __SERVER_H_
#define __SERVER_H_


int init_server(short port);

int run_server(const char * home);

void close_server(void);
#endif

实现


#include <unistd.h>
#include <pthread.h>
#include <sys/resource.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "socket.h"
#include "signals.h"
#include "client.h"
#include "server.h"

//init max - fd - num
static int init_max_files(void){
	// res limits
	struct rlimit rl;
	if (getrlimit(RLIMIT_NOFILE,&rl) == -1){
		perror("getrlimit:");
		return -1;	
	}
	if (rl.rlim_cur < rl.rlim_max){
		rl.rlim_cur = rl.rlim_max;
		if (setrlimit(RLIMIT_NOFILE,&rl) == -1){
			perror("setrlimit:");
			return -1;		
		}
	}
	
	return 0;
}

int init_server(short port){


	if (init_max_files() == -1 ){
		return -1;
	}

	init_signals();

	if (init_socket(port) == -1){
		return -1;
	}
	
	return 0;
}

int run_server(const char * home){
	
	for(;;){
		
		int connect_socket = accept_client();
		if (connect_socket == -1){
			return -1;		
		}
		pthread_t tid;
		pthread_attr_t attr;
		pthread_attr_init(&attr);
		pthread_attr_setdetachstate(&attr , PTHREAD_CREATE_DETACHED);
		
		CLIENT_ARG * ca = malloc(sizeof(CLIENT_ARG));
		ca->connect_socket = connect_socket;
		ca->home = home;
		int error = pthread_create(&tid , &attr ,client , ca);
		if (error){
			fprintf(stderr,"pthread_create:%s\n" , strerror(error));
			return -1;
		}
	}
	
	return 0;
}

void close_server(void){
	close_socket();
	return;
}



主函数

#include<stdio.h>

#include<stdlib.h>
#include"server.h"

int main(int argc , char* argv[]){
	
	if (init_server(argc<2 ? 80 : atoi(argv[1])) == -1 ){
		return -1;
	}
        if (run_server(argc<3 ? "../home" : argv[2]) == -1){
		return -1;
	}
	close_server();
	return 0;
}

编译运行

写完上述文件.h和.c后,在code文件夹下执行以下操作

1.编译所有的 .c 文件到.o文件

gcc -c *.c

2.删除main.o文件,否则,在最后生产二进制文件时,会有重定义错误

rm main.o

3.编译main.c 文件 连接其它的.o文件,注意由于用到了线程库,需要在编译时加命令

gcc main.c *.o -o sun_webserver -pthread

4.运行服务器

sudo ./sun_webserver

运行后,等待客户端连接:

在同一局域网下输入服务器的网址,就可以实现浏览静态网页了。此为服务器运行时输出的信息:

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值