C语言实现并发http服务器项目

设置端口号

我这里设置的是1234

#define SERVER_PORT 1234

创建套接字

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
int main(){
    
    
    //创建套接字
    int serv_sock = socket(AF_INET, SOCK_STREAM, 0);
    
    //将套接字和IP、端口绑定
    struct sockaddr_in serv_addr;
    
    //每个字节都用0填充
    bzero(&serv_addr, sizeof(serv_addr));
    
    
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //监听本地所有IP地址
    serv_addr.sin_port = htons(SERVER_PORT);  //端口
    
    //绑定 
    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    
    
    //进入监听状态,等待用户发起请求
    listen(serv_sock, 20);
    
    printf("wait client connect...\n");
    return 0;
}

接收客户端请求

int main(){
    
    
	//...
    //printf("wait client connect...\n");
    
    int done = 1;
    
    while (done){
    
    
	    //接收客户端请求
	    struct sockaddr_in clnt_addr;
	    socklen_t clnt_addr_size = sizeof(clnt_addr);
	    int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
    	
    	char client_ip[64];
    	char buf[256];
    	
    	//打印客户端ip地址和端口号 
    	printf("client ip: %s\t port: %d\n", 
			inet_ntop(AF_INET, &clnt_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)), 
			ntohs(clnt_addr.sin_port));
   
	    //关闭套接字
	    close(clnt_sock);
	}
	
	//关闭套接字
	close(serv_sock);
    return 0;
}

获取服务器的ip地址

ifconfig

在这里插入图片描述
编译代码并运行

gcc server.c -o server
./server

在这里插入图片描述

在浏览器上输入

http://ip:port

在这里插入图片描述
程序输出如下:
在这里插入图片描述

获取客户端http请求数据

客户端请求消息
客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。
在这里插入图片描述
定义一个get_http_line(int clnt_sock, char * buf, int size)的函数,读取socket缓冲区的一行数据,放回-1读取失败,否则返回这一行数据的字符个数。

int get_http_line(int clnt_sock, char * buf, int size){
    
    
	int cnt = 0;  //这一行数据的字符个数
	char ch = '\0';
	int len = 0;
	
	while (cnt < size - 1){
    
    
		//读取socket缓冲区的一个字符
		len = read(clnt_sock, &ch, 1);
		if (len == 1){
    
      //读取成功
			if (ch == '\r'){
    
      //吸收回车符
				continue;
			}else if (ch == '\n'){
    
    
				break;
			}
			buf[cnt++] = ch;
		}else if (len == -1){
    
      //读取失败
			fprintf(stderr, "read failed\n");
			cnt = -1;
			break;
		}else {
    
      //客户端主动关闭
			fprintf(stderr, "client close\n");
			cnt = -1;
			break;
		}
	}
	
	if (cnt >= 0){
    
    
		buf[cnt] = '\0';
	}
	
	return cnt;
}

定义一个do_http_request(int clnt_sock)的函数,实现获取客户端http请求的数据

void do_http_request(int clnt_sock){
    
    
	int len = 0;
	char buf[256];
	//打印数据
	do{
    
    
		len = get_http_line(clnt_sock, buf, sizeof(buf));
		if (debug) printf("%s\n", buf);
	}while (len > 0);
}

在main函数这个位置调用do_http_request函数

//获取客户端http请求数据 
do_http_request(clnt_sock);

在这里插入图片描述
编译并运行程序,输出如下:
在这里插入图片描述

实现Http解析

根据 HTTP 标准,HTTP 请求可以使用多种请求方法。

HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD 方法。

HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。
在这里插入图片描述
这里我只完成了GET的请求方法,其他请求 暂不处理。
重写do_http_request(int clnt_sock)函数

#include <sys/stat.h>
#include <errno.h>
struct stat st;
static int debug = 1;
static char parent_path[] = ".";  //存放html文件的主目录,"."为相对路径
void do_http_request(int clnt_sock){
    
    
	int len = 0;
	char buf[256];
	char method[16];  //请求方法
	char url[256];  //请求方法
	char path[260];  //请求的文件、路径
	
	//获取请求行(request line)
	len = get_http_line(clnt_sock, buf, sizeof(buf));
	if (len > 0){
    
    
		int i = 0, j = 0;
		
		//获取请求方法 
		while (buf[j] != ' ' && i < sizeof(method) - 1){
    
    
			method[i++] = buf[j++];
		}
		method[i] = '\0';
		if (debug) printf("request method: %s\n", method);
		
		if (strncasecmp(method, "GET", i) == 0){
    
      //get请求 
			while (buf[j++] != ' ');
			
			i = 0;
			
			//获取请求url 
			while (buf[j] != ' ' && i < sizeof(url) - 1){
    
    
				url[i++] = buf[j++];
			}
			url[i] = '\0';
			if (debug) printf("request url: %s\n", url);

			//TODO:获取请求参数
			
			//去掉请求参数 
			char *last = strchr(url, '?');
			if (last) *last = '\0';
			
			if (debug) printf("real request url: %s\n", url);
			
			//获取请求文件、路径 
			sprintf(path, "%s%s", parent_path, url); 
			if (debug) printf("request path: %s\n", path);
			
			//判断文件是否存在
			if (stat(path, &st) == -1){
    
    
				fprintf(stderr, "stat %s find failed. reason: %s\n", path, strerror(errno));
			}else {
    
    
				if (S_ISDIR(st.st_mode)){
    
      //如果是目录,获取对应目录的index.html文件
					strcat(path, "index.html");
				}
			}
		}else{
    
      //其他请求 (暂不处理)
			fprintf(stderr, "other request [%s]\n", method);
		}
		
		//打印数据
		if (debug) printf("request body:\n%s\n", buf);
		
		do{
    
    
			len = get_http_line(clnt_sock, buf, sizeof(buf));
			if (debug) printf("%s\n", buf);
		}while (len > 0);
		
	}else{
    
      //请求格式有问题,出错处理 
		fprintf(stderr, "request format error\n");
	} 
}

编译并运行程序,输出如下:
在这里插入图片描述

实现Http响应

HTTP 状态码
当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含 HTTP 状态码的信息头(server header)用以响应浏览器的请求。

HTTP 状态码的英文为 HTTP Status Code。

下面是常见的 HTTP 状态码:

200 - 请求成功
301 - 资源(网页等)被永久转移到其它URL
404 - 请求的资源(网页等)不存在
500 - 内部服务器错误
HTTP 状态码分类
HTTP 状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型。响应分为五类:信息响应(100–199),成功响应(200–299),重定向(300–399),客户端错误(400–499)和服务器错误 (500–599):
在这里插入图片描述
HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。
在这里插入图片描述
这里我只实现了200,400,404,500,501的状态码
200 OK 请求成功。一般用于GET与POST请求
400 Bad Request 客户端请求的语法错误,服务器无法理解
404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
500 Internal Server Error 服务器内部错误,无法完成请求
501 Not Implemented 服务器不支持请求的功能,无法完成请求

定义HTTP响应状态行全局变量

char z00[] = "HTTP/1.0 200 OK\r\n";
char f00[] = "HTTP/1.0 400 BAD REQUEST\r\n";
char f0f[] = "HTTP/1.0 404 NOT FOUND\r\n";
char b00[] = "HTTP/1.0 500 Internal Sever Error\r\n";
char b0l[] = "HTTP/1.0 501 Method Not Implemented\r\n";

定义一个do_http_response(int clnt_sock, const char *path)的函数,实现Http响应

void do_http_response(int clnt_sock, const char *path){
    
    
	FILE *resourse = NULL;
	resourse = fopen(path, "r");
	
	if (resourse == NULL){
    
    
		fprintf(stderr, "stat %s open failed. reason: %s\n", path, strerror(errno));
		not_found(clnt_sock);
		return;
	}
	
	//定义HTTP响应状态行
	char *header = z00;
	if (strncasecmp(path, "./error.html", 12) == 0){
    
    
		header = f0f;
	}else if (strncasecmp(path, "./inner.html", 12) == 0){
    
    
		header = b00;
	}else if (strncasecmp(path, "./unimplemented.html", 18) == 0){
    
    
		header = b0l;
	}else if (strncasecmp(path, "./bad_request.html", 16) == 0){
    
    
		header = f00;
	}
	
	//发送头部
	if (headers(clnt_sock, resourse, header) == 0){
    
    
		//发送内容
		tailers(clnt_sock, resourse);
	}
	
	fclose(resourse);
}

定义not_found、inner_error、unimplemented、bad_request函数来实现响应404、500、501、400状态。

void not_found(int clnt_sock){
    
    
	do_http_response(clnt_sock, "./error.html");
}

void inner_error(int clnt_sock){
    
    
	do_http_response(clnt_sock, "./inner.html");
}

void unimplemented(int clnt_sock){
    
    
	do_http_response(clnt_sock, "./unimplemented.html");
}
void bad_request(int clnt_sock){
    
    
	do_http_response(clnt_sock, "./bad_request.html");
}

在do_http_request函数这个位置调用它们
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在do_http_request函数这个位置调用do_http_response函数在这里插入图片描述
发送头部函数headers(int clnt_sock, FILE *resourse, const char *header),返回-1发送失败(文件已经打开但是访问文件出错),返回0发送成功

int headers(int clnt_sock, FILE *resourse, const char *header){
    
    
	struct stat st;
	int fileId = 0;
	fileId = fileno(resourse);  //获取已经打开的文件的文件描述符
	if (fstat(fileId, &st) == -1){
    
      //文件已经打开但是访问文件出错
		fprintf(stderr, "inner error. reason: %s\n", strerror(errno));
		inner_error(clnt_sock);
		return -1;
	}
	
	char buf[1024] = {
    
    0};
	char temp[64];
	//状态行
	strcpy(buf, header);
	//消息报头
	strcat(buf, "Server: Martin Server\r\n");
	strcat(buf, "Content-Type: text/html\r\n");
	strcat(buf, "Connection: Close\r\n");
	
	int wc = st.st_size;  //正文大小
	sprintf(temp, "Content-Length: %d\r\n\r\n", wc);
	strcat(buf, temp);
	
	if (debug) printf("write:\n%s\n", buf);
	
	if(send(clnt_sock, buf, strlen(buf), 0) < 0){
    
    
		fprintf(stderr, "failed to send.reason:%s.buf:%s\n", strerror(errno), buf);
		return -1;
	}
	return 0;
}

发送内容函数tailers(int clnt_sock, FILE *resourse)

void tailers(int clnt_sock, FILE *resourse){
    
    
	char buf[1024];
	do{
    
      //边读边写
		fgets(buf, sizeof(buf), resourse);
	 	int len = write(clnt_sock, buf, strlen(buf));
	 	
	 	if (len < 0){
    
    
	 		fprintf(stderr, "send error, reason: %s\n", strerror(errno));
	 		break;
		}
	 	
	 	if (debug) printf("%s", buf);
	}while (!feof(resourse));
	if (debug) printf("\n");
}

创建html文件,并放在存放html文件的主目录里面
在这里插入图片描述
index.html

<!DOCTYPE html>
<html style="font-size: 16px;">
  <head>
<meta charset="UTF-8">
    <title>index</title>
  </head>
  <body>
        <h1><b>Hello world</b></h1>
        <p>Hi world</p>
  </body>
</html>

error.html

<!DOCTYPE html>
<html style="font-size: 16px;">
  <head>
<meta charset="UTF-8">
    <title>404</title>
  </head>
  <body>
        <h1><b>404 Not Found</b></h1>
        <p>未找到该页面</p>
  </body>
</html>

inner.html

<!DOCTYPE html>
<html style="font-size: 16px;">
  <head>
<meta charset="UTF-8">
    <title>500</title>
  </head>
  <body>
        <h1><b>500 Inner Error</b></h1>
        <p>服务器内部错误</p>
  </body>
</html>

unimplemented.html

<!DOCTYPE html>
<html style="font-size: 16px;">
  <head>
<meta charset="UTF-8">
    <title>501</title>
  </head>
  <body>
        <h1><b>501 Method Not Implemented</b></h1>
        <p>服务器不支持的请求方法</p>
  </body>
</html>

bad_request.html

<!DOCTYPE html>
<html style="font-size: 16px;">
  <head>
<meta charset="UTF-8">
    <title>400</title>
  </head>
  <body>
        <h1><b>400 Bad Request</b></h1>
        <p>错误的请求</p>
  </body>
</html>

编译并运行程序,输出如下:

  • 默认请求./index.html
    在这里插入图片描述
    在这里插入图片描述

  • 请求a.html
    在这里插入图片描述
    在这里插入图片描述

  • 请求空行

    在终端输入:

    telnet ip port
    

    再按一次回车键
    在这里插入图片描述
    在这里插入图片描述

  • POST请求
    在终端输入:

    telnet ip port
    POST index.html http/1.0
    

    在这里插入图片描述
    在这里插入图片描述

多线程实现并发

串行缺点:客户端排成一队,一个客户端完成请求后服务器才能接受下一个客户端的请求,客户端等待时间长。
在终端输入:

telnet ip port

然后在浏览器输入

http://ip:port

在这里插入图片描述
就会发现浏览器一直在等待服务器的响应,因为在浏览器前面服务器在处理telnet的请求,然而telnet不发送请求,就会造成死锁的情况。
在这里插入图片描述

解决死锁情况方法:

  1. 设定一个limit_time时间,服务器与客户端http连接后如果超过这个limit_time时间就断开连接。
  2. 使用多线程,并行执行http请求。

我用多线程来实现。
pthread_create函数
创建一个新线程,并行的执行任务。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
返回值:成功:0; 失败:错误号。
参数:
pthread_t:当前Linux中可理解为:typedef unsigned long int pthread_t;
参数1:传出参数,保存系统为我们分配好的线程ID
参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
参数4:线程主函数执行期间所使用的参数。
在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。start_routine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数的类型为void *,这个指针按什么类型解释由调用者自己定义。start_routine的返回值类型也是void *,这个指针的含义同样由调用者自己定义。start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值,以后再详细介绍pthread_join。
pthread_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。
attr参数表示线程属性,本节不深入讨论线程属性,所有代码例子都传NULL给attr参数,表示线程属性取缺省值。

包含<pthread.h>头文件

#include <pthread.h>

将原来的void do_http_request(int clnt_sock)修改成void * do_http_request(void * pclnt_sock),因为pthread_create传的是void *的函数指针和void *的参数。

void * do_http_request(void * pclnt_sock){
    
    
	int len = 0;
	char buf[256];
	char method[16];
	char url[256];
	char path[260];
	int clnt_sock = *(int *)pclnt_sock;  //新增
	len = get_http_line(clnt_sock, buf, sizeof(buf));
	if (len > 0){
    
    
		int i = 0, j = 0;
		
		//获取请求方法 
		while (buf[j] != ' ' && i < sizeof(method) - 1){
    
    
			method[i++] = buf[j++];
		}
		method[i] = '\0';
		if (debug) printf("request method: %s\n", method);
		
		if (strncasecmp(method, "GET", i) == 0){
    
      //get请求 
			while (buf[j++] != ' ');
			
			i = 0;
			
			//获取请求url 
			while (buf[j] != ' ' && i < sizeof(url) - 1){
    
    
				url[i++] = buf[j++];
			}
			url[i] = '\0';
			if (debug) printf("request url: %s\n", url);
			
			//去掉请求参数 
			char *last = strchr(url, '?');
			if (last) *last = '\0';
			
			if (debug) printf("real request url: %s\n", url);
			
			//获取请求文件、路径 
			sprintf(path, "%s%s", parent_path, url); 
			if (debug) printf("request path: %s\n", path);
			
			//判断文件是否存在
			if (stat(path, &st) == -1){
    
    
				fprintf(stderr, "stat %s find failed. reason: %s\n", path, strerror(errno));
				not_found(clnt_sock);
			}else {
    
    
				if (S_ISDIR(st.st_mode)){
    
    
					strcat(path, "index.html");
				}
				do_http_response(clnt_sock, path);
			}
		}else{
    
      //其他请求 (暂不处理)
			fprintf(stderr, "other request [%s]\n", method);
			unimplemented(clnt_sock);
		}
		
		if (debug) printf("request body:\n%s\n", buf);
		
		do{
    
    
			len = get_http_line(clnt_sock, buf, sizeof(buf));
			if (debug) printf("%s\n", buf);
		}while (len > 0);
		
	}else{
    
      //请求格式有问题,出错处理 
		fprintf(stderr, "request format error\n");
		bad_request(clnt_sock);
	} 
	
	//关闭套接字
	close(clnt_sock);
	//释放指针 
	if (pclnt_sock) free(pclnt_sock);
	pclnt_sock = 0;
	return NULL;
}

在main函数增加如下代码:

pthread_t p_id; 
int * pclnt_sock = NULL;
pclnt_sock = (int *)malloc (sizeof(int));
*pclnt_sock = clnt_sock;
//开启线程
pthread_create(&p_id, NULL, do_http_request, (void *)pclnt_sock);

在这里插入图片描述
编译代码并运行

gcc server.c -o server -lpthread
./server

在这里插入图片描述

完整代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h> 

#define SERVER_PORT 1234

struct stat st;
static int debug = 1;
static char parent_path[] = ".";
char z00[] = "HTTP/1.0 200 OK\r\n";
char f00[] = "HTTP/1.0 400 BAD REQUEST\r\n";
char f0f[] = "HTTP/1.0 404 NOT FOUND\r\n";
char b00[] = "HTTP/1.0 500 Internal Sever Error\r\n";
char b0l[] = "HTTP/1.0 501 Method Not Implemented\r\n";


int get_http_line(int clnt_sock, char * buf, int size);
int headers(int clnt_sock, FILE *resourse, const char *header);
void tailers(int clnt_sock, FILE *resourse); 
void not_found(int clnt_sock);
void inner_error(int clnt_sock);
void unimplemented(int clnt_sock);
void bad_request(int clnt_sock);
void do_http_response(int clnt_sock, const char *path);
//void do_http_request(int clnt_sock);
void * do_http_request(void * pclnt_sock); 

int main(){
    
    
    //创建套接字
    int serv_sock = socket(AF_INET, SOCK_STREAM, 0);
    
    //将套接字和IP、端口绑定
    struct sockaddr_in serv_addr;
    
    //每个字节都用0填充
    bzero(&serv_addr, sizeof(serv_addr));
    
    
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //具体的IP地址
    serv_addr.sin_port = htons(SERVER_PORT);  //端口
    
    
    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    
    
    //进入监听状态,等待用户发起请求
    listen(serv_sock, 20);
    
    printf("wait client connect...\n");
    
    int done = 1;
    
    while (done){
    
    
	    //接收客户端请求
	    struct sockaddr_in clnt_addr;
	    socklen_t clnt_addr_size = sizeof(clnt_addr);
	    int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
    	
    	char client_ip[64];
    	char buf[256];
    	
    	pthread_t p_id; 
    	int * pclnt_sock = NULL;
    	pclnt_sock = (int *)malloc (sizeof(int));
    	*pclnt_sock = clnt_sock;
    	
    	//打印客户端ip地址和端口号 
    	printf("client ip: %s\t port: %d\n", 
			inet_ntop(AF_INET, &clnt_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)), 
			ntohs(clnt_addr.sin_port));
    	
    	//获取客户端http请求数据 
//    	do_http_request(clnt_sock);
   
	    //关闭套接字
//	    close(clnt_sock);

		//开启线程
		pthread_create(&p_id, NULL, do_http_request, (void *)pclnt_sock);
	}
	
	//关闭套接字
	close(serv_sock);
    return 0;
}

//void do_http_request(int clnt_sock){
    
    
void * do_http_request(void * pclnt_sock){
    
    
	int len = 0;
	char buf[256];
	char method[16];
	char url[256];
	char path[260];
	int clnt_sock = *(int *)pclnt_sock;
	len = get_http_line(clnt_sock, buf, sizeof(buf));
	if (len > 0){
    
    
		int i = 0, j = 0;
		
		//获取请求方法 
		while (buf[j] != ' ' && i < sizeof(method) - 1){
    
    
			method[i++] = buf[j++];
		}
		method[i] = '\0';
		if (debug) printf("request method: %s\n", method);
		
		if (strncasecmp(method, "GET", i) == 0){
    
      //get请求 
			while (buf[j++] != ' ');
			
			i = 0;
			
			//获取请求url 
			while (buf[j] != ' ' && i < sizeof(url) - 1){
    
    
				url[i++] = buf[j++];
			}
			url[i] = '\0';
			if (debug) printf("request url: %s\n", url);
			
			//去掉请求参数 
			char *last = strchr(url, '?');
			if (last) *last = '\0';
			
			if (debug) printf("real request url: %s\n", url);
			
			//获取请求文件、路径 
			sprintf(path, "%s%s", parent_path, url); 
			if (debug) printf("request path: %s\n", path);
			
			//判断文件是否存在
			if (stat(path, &st) == -1){
    
    
				fprintf(stderr, "stat %s find failed. reason: %s\n", path, strerror(errno));
				not_found(clnt_sock);
			}else {
    
    
				if (S_ISDIR(st.st_mode)){
    
    
					strcat(path, "index.html");
				}
				do_http_response(clnt_sock, path);
			}
		}else{
    
      //其他请求 (暂不处理)
			fprintf(stderr, "other request [%s]\n", method);
			unimplemented(clnt_sock);
		}
		
		if (debug) printf("request body:\n%s\n", buf);
		
		do{
    
    
			len = get_http_line(clnt_sock, buf, sizeof(buf));
			if (debug) printf("%s\n", buf);
		}while (len > 0);
		
	}else{
    
      //请求格式有问题,出错处理 
		fprintf(stderr, "request format error\n");
		bad_request(clnt_sock);
	} 
	
	//关闭套接字
	close(clnt_sock);
	//释放指针 
	if (pclnt_sock) free(pclnt_sock);
	pclnt_sock = 0;
	return NULL;
}


void do_http_response(int clnt_sock, const char *path){
    
    
	FILE *resourse = NULL;
	resourse = fopen(path, "r");
	
	if (resourse == NULL){
    
    
		fprintf(stderr, "stat %s open failed. reason: %s\n", path, strerror(errno));
		not_found(clnt_sock);
		return;
	}
	
	//定义HTTP响应状态行
	char *header = z00;
	if (strncasecmp(path, "./error.html", 12) == 0){
    
    
		header = f0f;
	}else if (strncasecmp(path, "./inner.html", 12) == 0){
    
    
		header = b00;
	}else if (strncasecmp(path, "./unimplemented.html", 18) == 0){
    
    
		header = b0l;
	}else if (strncasecmp(path, "./bad_request.html", 16) == 0){
    
    
		header = f00;
	}
	
	//发送头部
	if (headers(clnt_sock, resourse, header) == 0){
    
    
		//发送内容
		tailers(clnt_sock, resourse);
	}
	
	fclose(resourse);
}

int get_http_line(int clnt_sock, char * buf, int size){
    
    
	int cnt = 0;
	char ch = '\0';
	int len = 0;
	
	while (cnt < size - 1 && ch != '\n'){
    
    
		len = read(clnt_sock, &ch, 1);
		if (len == 1){
    
    
			if (ch == '\r'){
    
    
				continue;
			}else if (ch == '\n'){
    
    
				break;
			}
		}else if (len == -1){
    
    
			fprintf(stderr, "read failed\n");
			cnt = -1;
			break;
		}else {
    
    
			fprintf(stderr, "client close\n");
			cnt = -1;
			break;
		}
		buf[cnt++] = ch;
	}
	
	if (cnt >= 0){
    
    
		buf[cnt] = '\0';
	}
	
	return cnt;
}

void not_found(int clnt_sock){
    
    
	do_http_response(clnt_sock, "./error.html");
}

void inner_error(int clnt_sock){
    
    
	do_http_response(clnt_sock, "./inner.html");
}

void unimplemented(int clnt_sock){
    
    
	do_http_response(clnt_sock, "./unimplemented.html");
}
void bad_request(int clnt_sock){
    
    
	do_http_response(clnt_sock, "./bad_request.html");
}

int headers(int clnt_sock, FILE *resourse, const char *header){
    
    
	struct stat st;
	int fileId = 0;
	fileId = fileno(resourse);  //获取已经打开的文件的文件描述符
	if (fstat(fileId, &st) == -1){
    
      //文件已经打开但是访问文件出错
		fprintf(stderr, "inner error. reason: %s\n", strerror(errno));
		inner_error(clnt_sock);
		return -1;
	}
	
	char buf[1024] = {
    
    0};
	char temp[64];
	//状态行
	strcpy(buf, header);
	//消息报头
	strcat(buf, "Server: Martin Server\r\n");
	strcat(buf, "Content-Type: text/html\r\n");
	strcat(buf, "Connection: Close\r\n");
	
	int wc = st.st_size;  //正文大小
	sprintf(temp, "Content-Length: %d\r\n\r\n", wc);
	strcat(buf, temp);
	
	if (debug) printf("write:\n%s\n", buf);
	
	if(send(clnt_sock, buf, strlen(buf), 0) < 0){
    
    
		fprintf(stderr, "failed to send.reason:%s.buf:%s\n", strerror(errno), buf);
		return -1;
	}
	return 0;
}

void tailers(int clnt_sock, FILE *resourse){
    
    
	char buf[1024];
	do{
    
    
		fgets(buf, sizeof(buf), resourse);
	 	int len = write(clnt_sock, buf, strlen(buf));
	 	
	 	if (len < 0){
    
    
	 		fprintf(stderr, "send error, reason: %s\n", strerror(errno));
	 		break;
		}
	 	
	 	if (debug) printf("%s", buf);
	}while (!feof(resourse));
	if (debug) printf("\n");
}

猜你喜欢

转载自blog.csdn.net/ymxyld/article/details/124812731