Linux - HTTP高并发服务器开发

Linux - HTTP高并发服务器开发

简述HTTP协议

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

请求格式

1. 客户端请求

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:
请求行(request line)、
请求头部(header)、
空行、
请求数据,
附图:
在这里插入图片描述

2. 服务端响应

服务器响应客户端的HTTP响应也由四个部分组成,分别是:
状态行、
消息报头、
空行、
响应正文,
附图:
在这里插入图片描述

响应代号代号描述
服务器上存在请求的内容,并可以响应给客户端200OK
客户端的请求有异常,方法有问题501Method Not Implemented
服务器收到请求后,因为自生的问题没法响应500Internal Server Error
请求的内容不存在404NOT FOUND
客户端发送的请求格式有问题等400BAD REQUEST

服务器开发

1. 需求分析

  1. 开发思路:客户端(浏览器)发送 http 请求到服务器,服务器按照 http 协议解析,获取客户端想要访问的 html 文本,按照 http 响应的格式把 html 文本响应给客户端

  2. 服务器开发流程:接收 http 请求 >> 解析 http 请求 >> 响应 http 请求

2. 开发实现

2.1. 接收 http 请求
/*****************************************
 *读取客户端发来的一行数据,并返回数据长度
 *参数:
 *	sock - 套接字
 *	*buf - 行数据
 *	size - buf读取上限
 *返回值:
 *	-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')
	{ // buf下标不超出上限,读到的字符不为换行符
		len = read(sock, &ch, 1); // 在 sock套接字流 读取 1 个字符到 ch

		if(len == 1)
		{ // 成功读到字符
			if(ch == '\r')
			{ // 读到回车符,直接下一次循环
				continue;
			}
			else if(ch == '\n')
			{
				break;
			}
			buf[count] = ch;
			count++;
		}
		else if(len == -1)
		{ // 读取出错
			perror("read failed.");
			count = -1;
			break;
		}
		else
		{ // 客户端sock关闭
			fprintf(stderr, "client close.\n");
			count = -1;
			break;
		}

	}

	if(count >= 0)
	{ // 设置字符串结束符
		buf[count] = '\0';
	}

	return count;
}
2.2. 解析 http 请求
/*****************************************
 *读取客户端发来的 http 请求,并解析响应
 *参数:
 *      sock - 套接字
 *返回值:
 *      无
 *****************************************/
void do_http_request(int client_sock)
{
	char buf[256];  // 存储请求内容行
	int len = 0;    // 存储请求内容行长度

	// 1. 读取请求行
	char method[64];// 请求行
	char url[256];	// URL
	char path[256];	// html路径
	
	struct stat st;
	
	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("method = %s\n", method);

		if(strncasecmp(method, "GET", i) == 0)
		{ // 处理 GET 请求
			while(isspace(buf[++j]));       // 跳过空格
			i = 0;
			while(!isspace(buf[j]) && (i < sizeof(url) - 1))
			{ // 读取url
				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 line: %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);	// 响应404
			}
			else
			{
				if(S_ISDIR(st.st_mode))
				{ // 判断是否有目录
					strcat(path, "/index.html");
				}
				
				do_http_response(client_sock, path);
			}
		}
		else // 其他请求
		{
			fprintf(stderr, "warning! other request [%s]\n", method);
				// 读取 http 头部,不做任何处理
				do
				{
					len = get_line(client_sock, buf, sizeof(buf));
					printf("read line: %s\n", buf);
				}
			while(len > 0);
			
			// 响应501
			unimplemented(client_sock);
		}
	}
	else
	{ // 读取请求行出错
		// 响应400
		bad_request(client_sock);
	}
}
2.3. 响应 http 请求
/*****************************************
 *响应客户端发来的 http 请求
 *参数:
 *      sock - 套接字
 *返回值:
 *      无
 *****************************************/
void do_http_response(int client_sock, const char *path)
{
	int ret = 0;
	FILE * resource = NULL;
	
	resource = fopen(path, "r");
	
	if(resource == NULL)
	{ // 响应404
		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 - 套接字
 *     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: tofu 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)
	{ // 响应500
		inner_error(client_sock);
		return -1;
	}
	 
	snprintf(tmp, 64, "Content-Length: %ld\r\n\r\n", st.st_size);
	strcat(buf, tmp);
	 
	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 文件的内容按行,读取并送给客户端
 *参数: 
 *     client_sock - 套接字
 *     resource    - 文件句柄 
 *返回值: 
 *	   无
 *****************************************/
 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 的过程中出现问题
			fprintf(stderr, "send body error. reason: %s\n", strerror(errno));
			break;
		}
		
		if(debug) fprintf(stdout, "%s", buf);
		fgets(buf, sizeof(buf), resource);
		
	}
 }
// 异常处理
// 400
void bad_request(int client_sock){
	const char * reply = \
	"HTTP/1.0 400 BAD REQUEST\r\n\
	Content-Type: text/html\r\n\
	\r\n\
	<HTML>\r\n\
	<HEAD>\r\n\
	<TITLE>BAD REQUEST</TITLE>\r\n\
	</HEAD>\r\n\
	<BODY>\r\n\
		<P>Your browser sent a bad request!\r\n\
	</BODY>\r\n\
	</HTML>";

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

// 404
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, "%s", reply);
	
	if(len <= 0){
		fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
	}
	
	
}

// 500
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, "%s", reply);
	
	if(len <= 0){
		fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
	}
}

// 501
void unimplemented(int client_sock){
	const char * reply = \
	"HTTP/1.0 501 Method Not Implemented\r\n\
	Content-Type: text/html\r\n\
	\r\n\
	<HTML>\r\n\
	<HEAD>\r\n\
	<TITLE>Method Not Implemented</TITLE>\r\n\
	</HEAD>\r\n\
	<BODY>\r\n\
		<P>HTTP request method not supported.\r\n\
	</BODY>\r\n\
	</HTML>";

	int len = write(client_sock, reply, strlen(reply));
	if(debug) fprintf(stdout, "%s", reply);
	
	if(len <= 0){
		fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
	}
}
2.4. 完整代码(未实现多线程)
#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>

#define SERVER_PORT 80

// debug
const int debug = 1;
// 读取 http 请求的一行数据
int get_line(int, char *, int);
// 解析 http 请求
void do_http_request(int);
// 响应 http 请求
void do_http_response(int, const char *);
// http 头部
int  headers(int client_sock, FILE *resource);
void cat(int client_sock, FILE *resource);

// 异常处理
void not_found(int client_sock);		// 404
void inner_error(int client_sock);		// 500
void unimplemented(int client_sock);	// 501
void bad_request(int client_sock);		// 400

int main(void)
{
	int sock;
	struct sockaddr_in server_addr;

	// 创建套接字
	sock = socket(AF_INET, SOCK_STREAM, 0);

	// 创建、清空标签,写入监听地址和端口号
	bzero(&server_addr, sizeof(server_addr));

	server_addr.sin_family = AF_INET;		// 协议族
	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));

	// 将套接字在网络上开放,128为可同时接受连接数量
	listen(sock, 128);

	int done = 1;
	while(done)
	{
		int client_sock;		// 客户端套接字
		struct sockaddr_in client_addr;	// 客户端标签
		char client_ip[64];		// 客户端ip

		// 接受客户端连接
		socklen_t client_addr_len;
		client_addr_len = sizeof(client_addr);
		client_sock = accept(sock, (struct sockaddr *)&client_addr, &client_addr_len);

		// 打印客户端IP地址和端口号
		printf("client ip: %s\tport: %d\n",
				inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)),
				ntohs(client_addr.sin_port));

		// 解析响应 http 请求
		do_http_request(client_sock);


		close(client_sock);
	}

	return 0;
}

/*****************************************
 *读取客户端发来的一行数据,并返回数据长度
 *参数:
 *	sock - 套接字
 *	*buf - 行数据
 *	size - buf读取上限
 *返回值:
 *	-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')
	{ // buf下标不超出上限,读到的字符不为换行符
		len = read(sock, &ch, 1); // 在 sock套接字流 读取 1 个字符到 ch

		if(len == 1)
		{ // 成功读到字符
			if(ch == '\r')
			{ // 读到回车符,直接下一次循环
				continue;
			}
			else if(ch == '\n')
			{
				break;
			}
			buf[count] = ch;
			count++;
		}
		else if(len == -1)
		{ // 读取出错
			perror("read failed.");
			count = -1;
			break;
		}
		else
		{ // 客户端sock关闭
			fprintf(stderr, "client close.\n");
			count = -1;
			break;
		}

	}

	if(count >= 0)
	{ // 设置字符串结束符
		buf[count] = '\0';
	}

	return count;
}

/*****************************************
 *读取客户端发来的 http 请求,并解析响应
 *参数:
 *      sock - 套接字
 *返回值:
 *      无
 *****************************************/
void do_http_request(int client_sock)
{
	char buf[256];  // 存储请求内容行
	int len = 0;    // 存储请求内容行长度

	// 1. 读取请求行
	char method[64];// 请求行
	char url[256];	// URL
	char path[256];	// html路径
	
	struct stat st;
	
	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("method = %s\n", method);

		if(strncasecmp(method, "GET", i) == 0)
		{ // 处理 GET 请求
			while(isspace(buf[++j]));       // 跳过空格
			i = 0;
			while(!isspace(buf[j]) && (i < sizeof(url) - 1))
			{ // 读取url
				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 line: %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);	// 响应404
			}
			else
			{
				if(S_ISDIR(st.st_mode))
				{ // 判断是否有目录
					strcat(path, "/index.html");
				}
				
				do_http_response(client_sock, path);
			}
		}
		else // 其他请求
		{
			fprintf(stderr, "warning! other request [%s]\n", method);
				// 读取 http 头部,不做任何处理
				do
				{
					len = get_line(client_sock, buf, sizeof(buf));
					printf("read line: %s\n", buf);
				}
			while(len > 0);
			
			// 响应501
			unimplemented(client_sock);
		}
	}
	else
	{ // 读取请求行出错
		// 响应400
		bad_request(client_sock);
	}
}

/*****************************************
 *响应客户端发来的 http 请求
 *参数:
 *      sock - 套接字
 *返回值:
 *      无
 *****************************************/
void do_http_response(int client_sock, const char *path)
{
	int ret = 0;
	FILE * resource = NULL;
	
	resource = fopen(path, "r");
	
	if(resource == NULL)
	{ // 响应404
		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 - 套接字
 *     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: tofu 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)
	{ // 响应500
		inner_error(client_sock);
		return -1;
	}
	 
	snprintf(tmp, 64, "Content-Length: %ld\r\n\r\n", st.st_size);
	strcat(buf, tmp);
	 
	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 文件的内容按行,读取并送给客户端
 *参数: 
 *     client_sock - 套接字
 *     resource    - 文件句柄 
 *返回值: 
 *	   无
 *****************************************/
 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 的过程中出现问题
			fprintf(stderr, "send body error. reason: %s\n", strerror(errno));
			break;
		}
		
		if(debug) fprintf(stdout, "%s", buf);
		fgets(buf, sizeof(buf), resource);
		
	}
 }
 
// 400
void bad_request(int client_sock){
	const char * reply = \
	"HTTP/1.0 400 BAD REQUEST\r\n\
	Content-Type: text/html\r\n\
	\r\n\
	<HTML>\r\n\
	<HEAD>\r\n\
	<TITLE>BAD REQUEST</TITLE>\r\n\
	</HEAD>\r\n\
	<BODY>\r\n\
		<P>Your browser sent a bad request!\r\n\
	</BODY>\r\n\
	</HTML>";

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

// 404
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, "%s", reply);
	
	if(len <= 0){
		fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
	}
	
	
}

// 500
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, "%s", reply);
	
	if(len <= 0){
		fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
	}
}

// 501
void unimplemented(int client_sock){
	const char * reply = \
	"HTTP/1.0 501 Method Not Implemented\r\n\
	Content-Type: text/html\r\n\
	\r\n\
	<HTML>\r\n\
	<HEAD>\r\n\
	<TITLE>Method Not Implemented</TITLE>\r\n\
	</HEAD>\r\n\
	<BODY>\r\n\
		<P>HTTP request method not supported.\r\n\
	</BODY>\r\n\
	</HTML>";

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

2.5. 使用方式
  1. 编译启动程序(这里把程序放在本地虚拟机,确保源程序目录下有html_docs这个文件夹,里面放入html文件,这里我提供一个简单模板

    <!-- index.html -->
    
    <html lang="zh-CN">
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
    <head>
    <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>
    
    
  2. 打开浏览器,访问这个虚拟机地址,通过ip addrlink/ether 下面的ip就是,程序设置有默认访问 index.html 文件

  3. 浏览器显示 index.html 页面,成功!

高并发(多线程实现)

1. 简述高并发

并发

同时拥有两个或者多个线程,如果程序在单核处理器上运行,多个线程将交替的换入或者换出内存,这些线程是同时 “存在” 的。
每个线程都处于执行过程中的某个状态,如果运行在多核处理器上,此时,程序中的每个线程都将分配到一个处理器核上,因此可以同时运行。

高并发

高并发是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求

2. 线程函数

// pthread_create:创建一个新线程,并行的执行任务。
#include <pthread.h>
	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_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。
    attr参数表示线程属性,我们先不深入讨论线程属性,所有代码例子都传NULL给attr参数,表示线程属性取缺省值。

高并发服务器开发

1. 完整代码(实现多线程)

#include <stdio.h>
#include <stdlib.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 <pthread.h>

#define SERVER_PORT 80

// debug
const int debug = 1;
// 读取 http 请求的一行数据
int get_line(int, char *, int);
// 解析 http 请求
void* do_http_request(void *);
// 响应 http 请求
void do_http_response(int, const char *);
// http 头部
int  headers(int client_sock, FILE *resource);
// http body
void cat(int client_sock, FILE *resource);

// 异常处理
void not_found(int client_sock);		// 404
void inner_error(int client_sock);		// 500
void unimplemented(int client_sock);	// 501
void bad_request(int client_sock);		// 400

int main(void)
{
	int sock;
	struct sockaddr_in server_addr;

	// 创建套接字
	sock = socket(AF_INET, SOCK_STREAM, 0);

	// 创建、清空标签,写入监听地址和端口号
	bzero(&server_addr, sizeof(server_addr));

	server_addr.sin_family = AF_INET;		// 协议族
	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));

	// 将套接字在网络上开放,128为可同时接受连接数量
	listen(sock, 128);

	int done = 1;
	while(done)
	{
		int client_sock;		// 客户端套接字
		struct sockaddr_in client_addr;	// 客户端标签
		char client_ip[64];		// 客户端ip
		
		pthread_t id;
		int* pclient_sock = NULL;

		// 接受客户端连接
		socklen_t client_addr_len;
		client_addr_len = sizeof(client_addr);
		client_sock = accept(sock, (struct sockaddr *)&client_addr, &client_addr_len);

		// 打印客户端IP地址和端口号
		printf("client ip: %s\tport: %d\n",
				inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)),
				ntohs(client_addr.sin_port));

		// 解析响应 http 请求
		//do_http_request(client_sock);

		// 启动线程处理http 请求
		pclient_sock = (int *)malloc(sizeof(int));
		*pclient_sock = client_sock;
		
		pthread_create(&id, NULL, do_http_request, (void *)pclient_sock);

		//close(client_sock);
	}

	return 0;
}

/*****************************************
 *读取客户端发来的一行数据,并返回数据长度
 *参数:
 *	sock - 套接字
 *	*buf - 行数据
 *	size - buf读取上限
 *返回值:
 *	-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')
	{ // buf下标不超出上限,读到的字符不为换行符
		len = read(sock, &ch, 1); // 在 sock套接字流 读取 1 个字符到 ch

		if(len == 1)
		{ // 成功读到字符
			if(ch == '\r')
			{ // 读到回车符,直接下一次循环
				continue;
			}
			else if(ch == '\n')
			{
				break;
			}
			buf[count] = ch;
			count++;
		}
		else if(len == -1)
		{ // 读取出错
			perror("read failed.");
			count = -1;
			break;
		}
		else
		{ // 客户端sock关闭
			fprintf(stderr, "client close.\n");
			count = -1;
			break;
		}

	}

	if(count >= 0)
	{ // 设置字符串结束符
		buf[count] = '\0';
	}

	return count;
}

/*****************************************
 *读取客户端发来的 http 请求,并解析响应
 *参数:
 *      sock - 套接字
 *返回值:
 *      无
 *****************************************/
void* do_http_request(void* pclient_sock)
{
	char buf[256];  // 存储请求内容行
	int len = 0;    // 存储请求内容行长度

	// 1. 读取请求行
	char method[64];// 请求行
	char url[256];	// URL
	char path[256];	// html路径
	
	int client_sock = *(int *)pclient_sock;
	
	struct stat st;
	
	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("method = %s\n", method);

		if(strncasecmp(method, "GET", i) == 0)
		{ // 处理 GET 请求
			while(isspace(buf[++j]));       // 跳过空格
			i = 0;
			while(!isspace(buf[j]) && (i < sizeof(url) - 1))
			{ // 读取url
				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 line: %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);	// 响应404
			}
			else
			{
				if(S_ISDIR(st.st_mode))
				{ // 判断是否有目录
					strcat(path, "/index.html");
				}
				
				do_http_response(client_sock, path);
			}
		}
		else // 其他请求
		{
			fprintf(stderr, "warning! other request [%s]\n", method);
				// 读取 http 头部,不做任何处理
				do
				{
					len = get_line(client_sock, buf, sizeof(buf));
					printf("read line: %s\n", buf);
				}
			while(len > 0);
			
			// 响应501
			unimplemented(client_sock);
		}
	}
	else
	{ // 读取请求行出错
		// 响应400
		bad_request(client_sock);
	}
	
	close(client_sock);
	if(pclient_sock) free(pclient_sock);	// 释放动态分配的内存
	
	return NULL;
}

/*****************************************
 *响应客户端发来的 http 请求
 *参数:
 *      sock - 套接字
 *返回值:
 *      无
 *****************************************/
void do_http_response(int client_sock, const char *path)
{
	int ret = 0;
	FILE * resource = NULL;
	
	resource = fopen(path, "r");
	
	if(resource == NULL)
	{ // 响应404
		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 - 套接字
 *     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: tofu 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)
	{ // 响应500
		inner_error(client_sock);
		return -1;
	}
	 
	snprintf(tmp, 64, "Content-Length: %ld\r\n\r\n", st.st_size);
	strcat(buf, tmp);
	 
	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 文件的内容按行,读取并送给客户端
 *参数: 
 *     client_sock - 套接字
 *     resource    - 文件句柄 
 *返回值: 
 *	   无
 *****************************************/
 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 的过程中出现问题
			fprintf(stderr, "send body error. reason: %s\n", strerror(errno));
			break;
		}
		
		if(debug) fprintf(stdout, "%s", buf);
		fgets(buf, sizeof(buf), resource);
		
	}
 }
 
// 400
void bad_request(int client_sock){
	const char * reply = \
	"HTTP/1.0 400 BAD REQUEST\r\n\
	Content-Type: text/html\r\n\
	\r\n\
	<HTML>\r\n\
	<HEAD>\r\n\
	<TITLE>BAD REQUEST</TITLE>\r\n\
	</HEAD>\r\n\
	<BODY>\r\n\
		<P>Your browser sent a bad request!\r\n\
	</BODY>\r\n\
	</HTML>";

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

// 404
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, "%s", reply);
	
	if(len <= 0){
		fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
	}
	
	
}

// 500
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, "%s", reply);
	
	if(len <= 0){
		fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
	}
}

// 501
void unimplemented(int client_sock){
	const char * reply = \
	"HTTP/1.0 501 Method Not Implemented\r\n\
	Content-Type: text/html\r\n\
	\r\n\
	<HTML>\r\n\
	<HEAD>\r\n\
	<TITLE>Method Not Implemented</TITLE>\r\n\
	</HEAD>\r\n\
	<BODY>\r\n\
		<P>HTTP request method not supported.\r\n\
	</BODY>\r\n\
	</HTML>";

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

2. 使用方式

  • 编译:gcc [file.c] -o [file.c] -lpthread,注意要在后面加上**-lpthread**

  • 代码根据 2.4. 完整代码(未实现多线程) 改造,建议使用代码对比工具,对比其改动内容,配合注释理解。

  • 其他使用方式和未实现多线程版本类似,自行查看

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值