文章目录
Linux - HTTP高并发服务器开发
简述HTTP协议
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web)服务器传输超文本到本地浏览器的传送协议。
请求格式
1. 客户端请求
客户端发送一个HTTP请求到服务器的请求消息包括以下格式:
请求行(request line)、
请求头部(header)、
空行、
请求数据,
附图:
2. 服务端响应
服务器响应客户端的HTTP响应也由四个部分组成,分别是:
状态行、
消息报头、
空行、
响应正文,
附图:
响应代号 | 代号描述 | |
---|---|---|
服务器上存在请求的内容,并可以响应给客户端 | 200 | OK |
客户端的请求有异常,方法有问题 | 501 | Method Not Implemented |
服务器收到请求后,因为自生的问题没法响应 | 500 | Internal Server Error |
请求的内容不存在 | 404 | NOT FOUND |
客户端发送的请求格式有问题等 | 400 | BAD REQUEST |
服务器开发
1. 需求分析
-
开发思路:客户端(浏览器)发送 http 请求到服务器,服务器按照 http 协议解析,获取客户端想要访问的 html 文本,按照 http 响应的格式把 html 文本响应给客户端。
-
服务器开发流程:接收 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. 使用方式
-
编译启动程序(这里把程序放在本地虚拟机,确保源程序目录下有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>
-
打开浏览器,访问这个虚拟机地址,通过
ip addr
的 link/ether 下面的ip就是,程序设置有默认访问 index.html 文件 -
浏览器显示 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. 完整代码(未实现多线程) 改造,建议使用代码对比工具,对比其改动内容,配合注释理解。
-
其他使用方式和未实现多线程版本类似,自行查看