第四章 Linux实现高并发http服务器


一、项目需求

实现一个http 服务器项目,服务器启动后监听80端口的tcp 连接,当用户通过任意一款浏览器(IE、火狐和腾讯浏览器等)访问我们的http服务器,http服务器会查找用户访问的html页面是否存在,如果存在则通过http 协议响应客户端的请求,把页面返回给浏览器,浏览器显示html页面;如果页面不存在,则按照http 协议的规定,通知浏览器此页面不存在(404 NOT FOUND)

二、需求分析

1、何为Html页面

html,全称Hypertext Markup Language,也就是“超文本链接标示语言”。HTML文本是由 HTML命令组成的描述性文本,HTML 命令可以说明文字、 图形、动画、声音、表格、链接等。 即平常上网所看到的的网页。

2、html例子

<html lang=\"zh-CN\">
<head>
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">
<title>This is a test</title>
</head>
<body>
<div align=center height=\"500px\" >
<br/><br/><br/>
<h2>大家好,欢迎来到编程世界!</h2><br/><br/>
<form action="commit" method="post">
名字: <input type="text" name="name" />
<br/>年龄: <input type="password" name="age" />
<br/><br/><br/><input type="submit" value="提交" />
<input type="reset" value="重置" />
</form>
</div>
</body>
</html>

3、什么是Http协议

HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。

4、请求格式

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。

服务器响应客户端的HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。

       

响应代号

代号描述

服务器上存在请求的内容,并可以响应给客户端

 200

  OK

客户端的请求有异常,方法有问题

501

Method Not Implemented

服务器收到请求后,因为自生的问题没法响应

500

Internal Server Error

请求的内容不存在

404

NOT FOUND

客户端发送的请求格式有问题等

400

BAD REQUEST

5、Demo示例

浏览器请求:
GET /demo.html HTTP/1.1
Host: 47.100.162.191
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2767.400
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie:cna=BT0+EoVi1FACAXH3Nv5I7h6k;isg=BIOD99I03BNYvZDfE2FJUOsMB0ftUBZcBFi4E7VgYOJZdKOWPcvRinAl6kSfVG8y


服务器响应:
HTTP/1.0 200 OK
Server: Hello Server
Content-Type: text/html
Connection: Close
Content-Length: 526

<html lang="zh-CN">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>This is a test</title>
</head>
<body>
<div align=center height="500px" >
<br/><br/><br/>
<h2>大家好,欢迎来到编程世界!</h2><br/><br/>
<form action="commit" method="post">
名字: <input type="text" name="name" />
<br/>年龄: <input type="password" name="age" />
<br/><br/><br/><input type="submit" value="提交" />
<input type="reset" value="重置" />
</form>
</div>
</body>
</html>

三、Mini型Http服务器的实现

小马同学心猿意马,心想,Echo 服务器我已经学会了,不就是从客户端收信息,服务器再响应信息嘛,现在我又知道了http 协议,html文本自己搞不定就直接用老马老师的demo.html撒,现在我只要把浏览器发送的http 请求按照http 协议的格式进行解析,获取到浏览器想要访问的html 是哪个文本,然后按照http 响应的格式把html 文本响应给客户端。

1、接收Http请求

实现按行读取请求头部

//返回值: -1 表示读取出错, 等于0表示读到一个空行, 大于0 表示成功读取一行
int get_line(int sock, char *buf, int size){
	int count = 0;
	char ch = '\0';
	int len = 0;
	
	
	while( (count<size - 1) && ch!='\n'){
		len = read(sock, &ch, 1);
		
		if(len == 1){
			if(ch == '\r'){
				continue;
			}else if(ch == '\n'){
				//buf[count] = '\0';
				break;
			}
			
			//这里处理一般的字符
			buf[count] = ch;
			count++;
			
		}else if( len == -1 ){//读取出错
			perror("read failed");
			count = -1;
			break;
		}else {// read 返回0,客户端关闭sock 连接.
			fprintf(stderr, "client close.\n");
			count = -1;
			break;
		}
	}
	
	if(count >= 0) buf[count] = '\0';
	
	return count;
}

思考题?

如果客户端发送的请求是: GET /baidu.html HTTP/1.1’\r’Host: 47.100.162.191’\r’’\n’ Connection: keep-alive’\r’’\n’ ,

我们仍然想get_line返回正确的三行,又该怎么写代码呢?

(注意: ‘\r’ 代表 回车符,Ascii码是13 ,’\n’代表的是换行符,Ascii码是14)

如果碰到两个连续的回车换行,即,意味着请求头部结束

2、解析请求

 //1.读取请求行
void do_http_request(int client_sock){
	int len = 0;
	char buf[256];
	char method[64];
	char url[256];
	char path[256];
	
	
	/*读取客户端发送的http 请求*/
	
	
	//1.读取请求行
	len = get_line(client_sock, buf, sizeof(buf));
	
	if(len > 0){//读到了请求行
		int i=0, j=0;
		while(!isspace(buf[j]) && (i<sizeof(method)-1)){
			method[i] = buf[j];
			i++;
			j++;
		}
		
		method[i] = '\0';
		if(debug) printf("request method: %s\n", method);
		
		if(strncasecmp(method, "GET", i)==0){ //只处理get请求
	        if(debug) printf("method = GET\n");
		
		    //获取url
		    while(isspace(buf[j++]));//跳过白空格
		    i = 0;
		
		    while(!isspace(buf[j]) && (i<sizeof(url)-1)){
			    url[i] = buf[j];
			    i++;
			    j++;
		    }
		
		    url[i] = '\0';
		
		    if(debug) printf("url: %s\n", url);
			
			//继续读取http 头部
		    do{
			    len = get_line(client_sock, buf, sizeof(buf));
			    if(debug) printf("read: %s\n", buf);
			
		    }while(len>0);
			
			//***定位服务器本地的html文件***
			
			//处理url 中的?
			{
				char *pos = strchr(url, '?');
				if(pos){
					*pos = '\0';
					printf("real url: %s\n", url);
				}
			}
			
			sprintf(path, "./html_docs/%s", url);
			if(debug) printf("path: %s\n", path);
			
			//执行http 响应
	
	    }else {
//非get请求, 读取http 头部,并响应客户端 501 	Method Not Implemented
			fprintf(stderr, "warning! other request [%s]\n", method);
			do{
			    len = get_line(client_sock, buf, sizeof(buf));
			    if(debug) printf("read: %s\n", buf);
			
		    }while(len>0);
			
			//unimplemented(client_sock);   //在响应时再实现
			
		}
			
		
	}else {//请求格式有问题,出错处理
		//bad_request(client_sock);   //在响应时再实现
	}
	
}

3、响应http请求

 void do_http_request(int client_sock){
        int  len=0;
        char buf[256];
        char method[16];
        char url[256];

    /*读取客户端发送的http请求*/

    //1.读取请求行
    len = get_line(client_sock, buf, sizeof(buf));

    if(len>0){
       int i, j;
       while(!isspace(buf[j]) && ( i< sizeof(method)-1)){
           method[i]=buf[j];
           i++;
           j++;
       }
       method[i]='\0';

       //判断方法是否合法

       if(strncasecmp(method, "GET", i)==0){//GET 方法
           printf("requst = %s\n", method);

           //获取url
       while(isspace(buf[++j]));
       i=-1;
       while(!isspace(buf[j]) && ( i< sizeof(url)-1)){
           url[i]=buf[j];
           i++;
           j++;
       }
       url[i]='\0';

       printf("url: %s\n", url);

       //读取http 头部,不做任何处理
       do{
          len = get_line(client_sock, buf, sizeof(buf));
          printf("read line: %s\n", buf);
       }while(len > 0);

       do_http_response(client_sock);

       }else {
           printf("other requst = %s\n", method);
            //读取http 头部,不做任何处理
           do{
              len = get_line(client_sock, buf, sizeof(buf));
              printf("read line: %s\n", buf);
           }while(len > 0);
       }

    }else {//出错的处理

    }


}



 void do_http_response(int client_sock){
    const char *main_header = "HTTP/1.0 200 OK\r\nServer: Hello Server\r\nContent-Type: text/html\r\nConnection: Close\r\n";


    const char * welcome_content = "\
<html lang=\"zh-CN\">\n\
<head>\n\
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\
<title>This is a test</title>\n\
</head>\n\
<body>\n\
<div align=center height=\"500px\" >\n\
<br/><br/><br/>\n\
<h2>大家好,欢迎来到编程世界!</h2><br/><br/>\n\
<form action=\"commit\" method=\"post\">\n\
名字: <input type=\"text\" name=\"name\" />\n\
<br/>年龄: <input type=\"password\" name=\"age\" />\n\
<br/><br/><br/><input type=\"submit\" value=\"提交\" />\n\
<input type=\"reset\" value=\"重置\" />\n\
</form>\n\
</div>\n\
</body>\n\
</html>";

    char send_buf[64];
    int wc_len = strlen(welcome_content);
    int len = write(client_sock, main_header, strlen(main_header));

    if(debug) fprintf(stdout, "... do_http_response...\n");
    if(debug) fprintf(stdout, "write[%d]: %s", len, main_header);

    len =snprintf(send_buf, 64,"Content-Length: %d\r\n\r\n", wc_len);
    len = write(client_sock, send_buf, len);
    if(debug) fprintf(stdout, "write[%d]: %s", len, send_buf);

    len = write(client_sock, welcome_content, wc_len);
    if(debug) fprintf(stdout, "write[%d]: %s", len, welcome_content);

}

四、完整代码

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>


#define SERVER_PORT 80

static int debug = 1;

int get_line(int sock, char *buf, int size);
void do_http_request(int client_sock);
void do_http_response(int client_sock, const char *path);
int  headers(int client_sock, FILE *resource);
void cat(int client_sock, FILE *resource);

void not_found(int client_sock);
void inner_error(int client_sock);

int main(void){

    int sock;//代表信箱
    struct sockaddr_in server_addr;

    //1.美女创建信箱
    sock = socket(AF_INET, SOCK_STREAM, 0);

    //2.清空标签,写上地址和端口号
    bzero(&server_addr, sizeof(server_addr));

    server_addr.sin_family = AF_INET;//选择协议族IPV4
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//监听本地所有IP地址
    server_addr.sin_port = htons(SERVER_PORT);//绑定端口号

    //实现标签贴到收信得信箱上
    bind(sock, (struct sockaddr *)&server_addr,  sizeof(server_addr));

    //把信箱挂置到传达室,这样,就可以接收信件了
    listen(sock, 128);

    //万事俱备,只等来信
    printf("等待客户端的连接\n");


    int done =1;

    while(done){
        struct sockaddr_in client;
        int client_sock, len, i;
        char client_ip[64];
        char buf[256];

        socklen_t  client_addr_len;
        client_addr_len = sizeof(client);
        client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);

        //打印客服端IP地址和端口号
        printf("client ip: %s\t port : %d\n",
                 inet_ntop(AF_INET, &client.sin_addr.s_addr,client_ip,sizeof(client_ip)),
                 ntohs(client.sin_port));
        /*处理http 请求,读取客户端发送的数据*/
        do_http_request(client_sock);
		close(client_sock);

    }
    close(sock);
    return 0;
}


void do_http_request(int client_sock){
	int len = 0;
	char buf[256];
	char method[64];
	char url[256];
	char path[256];
	
	struct stat  st;
	
	
	/*读取客户端发送的http 请求*/
	
	
	//1.读取请求行
	len = get_line(client_sock, buf, sizeof(buf));
	
	if(len > 0){//读到了请求行
		int i=0, j=0;
		while(!isspace(buf[j]) && (i<sizeof(method)-1)){
			method[i] = buf[j];
			i++;
			j++;
		}
		
		method[i] = '\0';
		if(debug) printf("request method: %s\n", method);
		
		if(strncasecmp(method, "GET", i)==0){ //只处理get请求
	        if(debug) printf("method = GET\n");
		
		    //获取url
		    while(isspace(buf[j++]));//跳过白空格
		    i = 0;
		
		    while(!isspace(buf[j]) && (i<sizeof(url)-1)){
			    url[i] = buf[j];
			    i++;
			    j++;
		    }
		
		    url[i] = '\0';
		
		    if(debug) printf("url: %s\n", url);
			
			//继续读取http 头部
		    do{
			    len = get_line(client_sock, buf, sizeof(buf));
			    if(debug) printf("read: %s\n", buf);
			
		    }while(len>0);
			
			//***定位服务器本地的html文件***
			
			//处理url 中的?
			{
				char *pos = strchr(url, '?');
				if(pos){
					*pos = '\0';
					printf("real url: %s\n", url);
				}
			}
			
			sprintf(path, "./html_docs/%s", url);
			if(debug) printf("path: %s\n", path);
			
			//执行http 响应
			//判断文件是否存在,如果存在就响应200 OK,同时发送相应的html 文件,如果不存在,就响应 404 NOT FOUND.
			if(stat(path, &st)==-1){//文件不存在或是出错
				fprintf(stderr, "stat %s failed. reason: %s\n", path, strerror(errno));
			    not_found(client_sock);
			}else {//文件存在
			
			    if(S_ISDIR(st.st_mode)){
					strcat(path, "/index.html");
				}
				
				do_http_response(client_sock, path);
				
			}
			
			
			
	
	    }else {//非get请求, 读取http 头部,并响应客户端 501 	Method Not Implemented
			fprintf(stderr, "warning! other request [%s]\n", method);
			do{
			    len = get_line(client_sock, buf, sizeof(buf));
			    if(debug) printf("read: %s\n", buf);
			
		    }while(len>0);
			
			//unimplemented(client_sock);   //在响应时再实现
			
		}
		
	}else {//请求格式有问题,出错处理
		//bad_request(client_sock);   //在响应时再实现
	}
}


void do_http_response(int client_sock, const char *path){
	int ret = 0;
	FILE *resource = NULL;
	
	resource = fopen(path, "r");
	
	if(resource == NULL){
		not_found(client_sock);
		return ;
	}
	
	//1.发送http 头部
	ret = headers(client_sock, resource);
	
	//2.发送http body .
	if(!ret){
	    cat(client_sock, resource);
	}
	
	fclose(resource);
}

/****************************
 *返回关于响应文件信息的http 头部
 *输入: 
 *     client_sock - 客服端socket 句柄
 *     resource    - 文件的句柄 
 *返回值: 成功返回0 ,失败返回-1
******************************/
int  headers(int client_sock, FILE *resource){
	struct stat st;
	int fileid = 0;
	char tmp[64];
	char buf[1024]={0};
	strcpy(buf, "HTTP/1.0 200 OK\r\n");
	strcat(buf, "Server: Hello Server\r\n");
	strcat(buf, "Content-Type: text/html\r\n");
	strcat(buf, "Connection: Close\r\n");
	
	fileid = fileno(resource);
	
	if(fstat(fileid, &st)== -1){
		inner_error(client_sock);
		return -1;
	}
	
	snprintf(tmp, 64, "Content-Length: %ld\r\n\r\n", st.st_size);
	strcat(buf, tmp);
	
	if(debug) fprintf(stdout, "header: %s\n", buf);
	
	if(send(client_sock, buf, strlen(buf), 0)<0){
		fprintf(stderr, "send failed. data: %s, reason: %s\n", buf, strerror(errno));
		return -1;
	}
	
	return 0;
}

/****************************
 *说明:实现将html文件的内容按行
        读取并送给客户端
 ****************************/
void cat(int client_sock, FILE *resource){
	char buf[1024];
	
    fgets(buf, sizeof(buf), resource);
	
	while(!feof(resource)){
		int len = write(client_sock, buf, strlen(buf));
		
		if(len<0){//发送body 的过程中出现问题,怎么办?1.重试? 2.
			fprintf(stderr, "send body error. reason: %s\n", strerror(errno));
			break;
		}
		
		if(debug) fprintf(stdout, "%s", buf);
		fgets(buf, sizeof(buf), resource);
		
	}
}


void do_http_response1(int client_sock){
    const char *main_header = "HTTP/1.0 200 OK\r\nServer: Hello Server\r\nContent-Type: text/html\r\nConnection: Close\r\n";


    const char * welcome_content = "\
<html lang=\"zh-CN\">\n\
<head>\n\
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\
<title>This is a test</title>\n\
</head>\n\
<body>\n\
<div align=center height=\"500px\" >\n\
<br/><br/><br/>\n\
<h2>大家好,欢迎来到编程世界!</h2><br/><br/>\n\
<form action=\"commit\" method=\"post\">\n\
尊姓大名: <input type=\"text\" name=\"name\" />\n\
<br/>芳龄几何: <input type=\"password\" name=\"age\" />\n\
<br/><br/><br/><input type=\"submit\" value=\"提交\" />\n\
<input type=\"reset\" value=\"重置\" />\n\
</form>\n\
</div>\n\
</body>\n\
</html>";

	//1. 发送main_header
	int len = write(client_sock, main_header, strlen(main_header));
	
	if(debug) fprintf(stdout, "... do_http_response...\n");
    if(debug) fprintf(stdout, "write[%d]: %s", len, main_header);
	
	//2. 生成Content-Length 
	char send_buf[64];
	int wc_len = strlen(welcome_content);
	len = snprintf(send_buf, 64, "Content-Length: %d\r\n\r\n", wc_len);
	len = write(client_sock, send_buf, len);
	
	if(debug) fprintf(stdout, "write[%d]: %s", len, send_buf);
	
	len = write(client_sock, welcome_content, wc_len);
	if(debug) fprintf(stdout, "write[%d]: %s", len, welcome_content);
}



//返回值: -1 表示读取出错, 等于0表示读到一个空行, 大于0 表示成功读取一行
int get_line(int sock, char *buf, int size){
	int count = 0;
	char ch = '\0';
	int len = 0;
	
	
	while( (count<size - 1) && ch!='\n'){
		len = read(sock, &ch, 1);
		
		if(len == 1){
			if(ch == '\r'){
				continue;
			}else if(ch == '\n'){
				//buf[count] = '\0';
				break;
			}
			
			//这里处理一般的字符
			buf[count] = ch;
			count++;
			
		}else if( len == -1 ){//读取出错
			perror("read failed");
			count = -1;
			break;
		}else {// read 返回0,客户端关闭sock 连接.
			fprintf(stderr, "client close.\n");
			count = -1;
			break;
		}
	}
	
	if(count >= 0) buf[count] = '\0';
	
	return count;
}


void not_found(int client_sock){
	const char * reply = "HTTP/1.0 404 NOT FOUND\r\n\
Content-Type: text/html\r\n\
\r\n\
<HTML lang=\"zh-CN\">\r\n\
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
<HEAD>\r\n\
<TITLE>NOT FOUND</TITLE>\r\n\
</HEAD>\r\n\
<BODY>\r\n\
	<P>文件不存在!\r\n\
    <P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\
</BODY>\r\n\
</HTML>";

	int len = write(client_sock, reply, strlen(reply));
	if(debug) fprintf(stdout, reply);
	
	if(len <=0){
		fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
	}
	
	
}


void inner_error(int client_sock){
	const char * reply = "HTTP/1.0 500 Internal Sever Error\r\n\
Content-Type: text/html\r\n\
\r\n\
<HTML lang=\"zh-CN\">\r\n\
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
<HEAD>\r\n\
<TITLE>Inner Error</TITLE>\r\n\
</HEAD>\r\n\
<BODY>\r\n\
    <P>服务器内部出错.\r\n\
</BODY>\r\n\
</HTML>";

	int len = write(client_sock, reply, strlen(reply));
	if(debug) fprintf(stdout, reply);
	
	if(len <=0){
		fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会飞的鱼-blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值