项目功能:
(1)能接收客户端的GET请求;
(2)能够解析客户端的请求报文,根据客户端要求找到相应的资源;
(2)能够回复http应答报文;
(3)能够读取服务器中存储的文件,并返回给请求客户端,实现对外发布静态资源;
(4)使用I/O复用来提高处理请求的并发度;
(5)服务器端支持错误处理,如要访问的资源不存在时回复404错误等。
1.http协议
1.客户端请求消息
客户端有get请求和post请求
过程:浏览器->发给->服务器,客户端(浏览器)发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成
步骤:
请求行:说明请求类型,要访问的资源,以及使用的 http 版本
请求头:说明服务器要使用的附加信息,每一行都需要 \r\n 表示某一个属性结束
空 行:必须!,即使没有请求数据,其实就是 \r\n
请求数据:也叫主体,可以添加任意的其他数据,是客户端需要的数据,由服务器发送
注意:在连续读取http请求头部时,如果碰到两个连续的回车换行,即表示请求头部结束
浏览器请求的内容样例
浏览器请求:
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
2.服务器响应消息
服务器获取浏览器的状态消息
过程:服务器->发给->浏览器
步骤:
状态行: 包括 http 协议版本号,状态码,状态信息
消息报头: 说明客户端要使用的一些附加信息
空 行:必须!
响应正文:服务器返回给客户端的文本信息
服务器响应的内容样例
服务器响应:
HTTP/1.0 200 OK
Server: Martin 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>
一些响应代号及代号描述
响应代号 | 代号描述 | |
服务器上存在请求的内容,并可以响应给客户端 | 200 | OK |
客户端的请求有异常,方法有问题 | 501 | Method Not Implemented |
服务器收到请求后,因为自生的问题没法响应 | 500 | Internal Server Error |
请求的内容不存在 | 404 | NOT FOUND |
客户端发送的请求格式有问题等 | 400 | BAD REQUEST |
3.使用到的一些函数及结构体
1.stat和fstat函数
函数原型:int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int stat(const char *pathname, struct stat *statbuf);
作用:获取文件信息
包含在头文件:include <sys/types.h> #include <sys/stat.h> #include <unistd.h>
返回值:成功返回0,失败返回-1
参数解释:
参数pathname:表示文件路径
参数statbuf:struct stat类型的结构体
int fstat(int fd, struct stat *statbuf);
作用:由文件描述符获取文件的状态
包含的头文件:#include <sys/stat.h> #include <unistd.h>
参数解释:
参数fd:表示是已经打开的文件描述符
参数statbuf:是struct stat类型的结构体
返回值:执行成功返回0,失败返回-1
结构体原型:
struct stat
{
dev_t st_dev; /* ID of device containing file */文件使用的设备号
ino_t st_ino; /* inode number */ 索引节点号
mode_t st_mode; /* protection */ 文件对应的模式,文件,目录等
nlink_t st_nlink; /* number of hard links */ 文件的硬连接数
uid_t st_uid; /* user ID of owner */ 所有者用户识别号
gid_t st_gid; /* group ID of owner */ 组识别号
dev_t st_rdev; /* device ID (if special file) */ 设备文件的设备号
off_t st_size; /* total size, in bytes */ 以字节为单位的文件容量
blksize_t st_blksize; /* blocksize for file system I/O */ 包含该文件的磁盘块的大小
blkcnt_t st_blocks; /* number of 512B blocks allocated */ 该文件所占的磁盘块
time_t st_atime; /* time of last access */ 最后一次访问该文件的时间
time_t st_mtime; /* time of last modification */ /最后一次修改该文件的时间
time_t st_ctime; /* time of last status change */ 最后一次改变该文件状态的时间
};
st_mode包含了三部分的信息:
1. 15-12位保存了文件类型
2. 11-9位保存了执行文件时设置的信息
3. 8-0位保存文件访问权限
在c库中定义的S_ISDIR函数:S_ISDIR(statbuf.st_mode)
作用:判断一个路径是否是一个目录
返回值:不是路径返回非零,是路径返回0
2.isspace函数
函数原型:int isspace(int c);
int isspace(int c); 包含在头文件:#include <ctype.h>
作用:判断字符c是否为空白符(空白符指空格、水平制表、垂直制表、换页、回车和换行符。)
返回值:成功返回非0,失败返回0
3.strncasecmp函数
函数原型:int strncasecmp (const char *s1, const char *s2, size_t count);
int strncasecmp (const char *s1, const char *s2, size_t count);
作用:判断字符串指定长度的字符是否相等,忽略大小写
包含的头文件:#include<string.h>
返回值:
小于0:表示s1大于s2
等于0:表示s1等于s2
大于0:表示s1大于s2
4.strchr函数
函数原型:char *strstr( const char *string, const char *strCharSet );
char *strstr( const char *string, const char *strCharSet );
作用:用于判断字符串strCharSet是否是string的子串
返回值:如果是子串,则返回字串strCharSet在字符串string中第一个出现的位置到结尾的字符串,
否则返回NULL,返回值是一个指针,返回的是子串在原字符串中第一个出现的位置,如果
没有找到返回NULL
5.snprintf函数
函数原型:int snprintf(char *str, size_t size, const char *format, ...)
作用:如果格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给其后添加一个字符串结束符('\0');如果格式化后的字符串长度 >= size,则只将其中的(size-1)个字符复制到str中,并给其后添加一个字符串结束符('\0'),返回值为欲写入的字符串长度。
6.fprintf函数
函数原型:intfprintf( FILE *stream, constchar *format, ... );
包含的头文件:#include <stdio.h>
int fprintf( FILE *stream, const char *format, ... );
作用:根据指定的format(格式)发送信息(参数)到由stream(流)指定的文件.
因此fprintf()可以使得信息输出到指定的文件
fprintf()和printf()一样工作.
printf是打印输出到屏幕,fprintf是打印输出到文件。
fprintf()的返回值是输出的字符数,发生错误时返回一个负值.
7.fileno函数
函数原型:int fileno(FILE *stream);
int fileno(FILE *stream); 包含在头文件 <stdio.h>
作用:把文件流指针转换成文件描述符
参数stream:是指定的文件路径
返回值:成功返回的是stream对应的文件描述符id,失败返回-1
8.pthread_create函数
函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start_rtn)(void),
void *restrict arg);
作用:创建一个线程
包含的头文件:<pthread.h>
在linux下编译时,如果程序使用到pthread.h头文件中的函数,需要加 -lpthread 选项
例如:gcc Minihttp.c -o minihttp -lpthread
参数解释:
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数
4.实现一个简单http服务器的步骤
1.创建用于连接的服务器socket套接字
2.处理客户端连接请求的消息,把请求行和请求头部的内容读取出来,并且判断客户端实现的是get请求还是post请求
3.服务器响应客户端的请求,如果客户端申请的是一个网页,那就需要在服务器编写出符合http协议的请求行和请求头,并且发送给客户端,再把客户端请求的内容发送给浏览器;如果客户端申请的不是网页,那就不需要发送请求头和请求行,直接发送内容即可。
5.实现了简单的http服务器的代码
1.使用了epoll实现的http服务器
#include<stdio.h>
#include<unistd.h>
#include<ctype.h>
#include<string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/epoll.h>
#include<arpa/inet.h>
#include<sys/stat.h>
#include<sys/un.h>
#include<stddef.h>
#include<fcntl.h>
int init_listen_fd(int port, int epfd)
{
//创建监听的套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1)
{
perror("socket error\n");
exit(1);
}
struct sockaddr_in srv_addr;
bzero(&srv_addr, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(port);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//端口复用
int opt = -1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
//给lfd绑定结构体
int ret = bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
if (ret == -1)
{
perror("bind error\n");
exit(1);
}
//设置监听上限
ret = listen(lfd, 128);
if (ret == -1)
{
perror("listen error\n");
}
//将lfd 添加到epoll树上
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = lfd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
if (ret == -1)
{
perror("epoll_ctl error\n");
exit(1);
}
return lfd;
}
void do_accept(int lfd, int epfd)
{
struct sockaddr_in clt_addr;
socklen_t clt_addr_len = sizeof(clt_addr);
int cfd = accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len);
if (cfd == -1)
{
perror("accept error\n");
exit(1);
}
//打印客户端IP+PORT
char client_ip[64] = { 0 };
printf("New Client IP:%s, Port:%d, cfd = %d\n",
inet_ntop(AF_INET, &clt_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)), ntohs(clt_addr.sin_port), cfd);
//设置cfd非阻塞
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
//将新节点cfd挂上epoll上
struct epoll_event ev;
ev.data.fd = cfd;
//边缘非阻塞模式
ev.events = EPOLLIN | EPOLLET;
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
if (ret == -1)
{
perror("epoll_ctl add cfd error\n");
exit(1);
}
}
//获取一行\r\n结尾的数据
int get_line(int cfd, char* buf, int size)
{
int i = 0;
char c = '\0';
int n;
while ((i < size - 1) && c != '\n')
{
n = recv(cfd, &c, 1, 0);
if (n > 0)
{
if (c == '\r')
{
n = recv(cfd, &c, 1, MSG_PEEK);
if ((n > 0) && (c == '\n'))
{
recv(cfd, &c, 1, 0);
}
else
{
c = '\n';
}
}
buf[i] = c;
i++;
}
else
{
c = '\n';
}
}
buf[i] = '\0';
if (n == -1)
{
i = n;
}
return i;
}
//断开链接
void disconnect(int cfd, int epfd)
{
int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
if (ret != 0)
{
perror("epoll_ctl error\n");
exit(1);
}
close(cfd);
}
void send_respond(int cfd, //客户端的socket
int no, //错误号
char* disp, //错误描述
char* type, //回发文件类型
int len) //文件长度
{
//拼写出http协议
char buf[1024] = { 0 };
sprintf(buf, "HTTP/1.1 %d %s\r\n", no, disp);
sprintf(buf+strlen(buf), "%s\r\n", type);
sprintf(buf + strlen(buf), "Content_Length:%d\r\n", len);
send(cfd, buf, strlen(buf), 0);
send(cfd, "\r\n", 2, 0);
}
//把本地文件内容发生给浏览器
void send_file(int cfd, const char* file)
{
int n = 0;
char buf[1024];
int fd = open(file, O_RDONLY);//打开的服务器文件
if (fd == -1)
{
//404错误页面
perror("oepn error");
exit(1);
}
while ((n = read(fd, buf, sizeof(buf))) > 0)//获取文件中的数据
{
send(cfd, buf, n, 0);//
}
close(fd);
}
//处理http请求,判断文件是否存在,存在则把内容发给浏览器
void http_request(int cfd, const char* file)
{
struct stat sbuf;
//判断文件是否存在
int ret = stat(file, &sbuf);
if (ret != 0)
{
perror("stat");
exit(1);
}
if (S_ISREG(sbuf.st_mode))//判断是一个普通文件
{
// 回发 http协议应答
send_respond(cfd, 200, "OK", "Content-Type: text/plain; charset=iso-8859-1", sbuf.st_size);
//回发 给客户端请求数据内容
send_file(cfd, file);
}
}
void do_read(int cfd, int epfd)
{
//读取一行http协议,拆分,获取get文件名 协议号
char line[1024] = { 0 };
int len = get_line(cfd, line, sizeof(line));//读取 请求协议首行
if (len == 0)
{
printf("服务器,检查到客户端关闭.....\n");
disconnect(cfd, epfd);
}
else
{
char method[16] = { 0 };
char path[256] = { 0 };
char protocol[16] = { 0 };
sscanf(line, "%[^ ] %[^ ] %[^ ]", method, path, protocol);//分割首行
printf("method=%s, path=%s, protocol=%s\n", method, path, protocol);
while (1)
{
char buf[1024] = { 0 };
len = get_line(cfd, buf, sizeof(buf));//把缓冲区的剩下内容读走
if (len == '\n' || len == -1)
{
break;
}
}
if (strncasecmp(method, "GET", 3) == 0)//比较字符串,参数3表示比较几个
{
char* file = path + 1; //取出 客户端要访问的文件名
http_request(cfd, file);//读出文件内容
}
}
}
void epoll_run(int port)
{
int i = 0;
struct epoll_event all_events[128];
//创建一个epoll监听树根
int epfd = epoll_create(128);
if (epfd == -1)
{
perror("epoll_create error\n");
exit(1);
}
//创建lfd,并添加到监听树根
int lfd = init_listen_fd(port, epfd);
while (1)
{
//监听节点对应事件
int ret = epoll_wait(epfd, all_events, 128, -1);
if (ret == -1)
{
perror("epoll_wait error\n");
exit(1);
}
for (i = 0; i < ret; i++)
{
//只处理读事件,其他事件不处理
struct epoll_event* pev = &all_events[i];
//不是读事件
if (!(pev->events & EPOLLIN))
{
continue;
}
if (pev->data.fd == lfd)
{
//接受链接请求
do_accept(lfd, epfd);
}
else
{
//读事件处理
do_read(pev->data.fd, epfd);
}
}
}
}
int main(int argc, char* argv[])
{
//命令行参数获取 端口 和 server提供的目录
if (argc < 3)
{
printf("./server port path\n");
}
//获取端口号
int port = atoi(argv[1]);
//改变进程工作目录
int ret = chdir(argv[2]);
if (ret != 0)
{
perror("chdir error");
exit(1);
}
//启动epoll监听
epoll_run(port);
return 0;
}
2.使用线程实现的http服务器
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/stat.h>
static int debug = 1;
int get_line(int sock, char* buf, int size);//获取客户端请求的内容
void* do_http_request(void* pclient_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);//给客户端发送html数据
void unimplemented(int client_sock);//请求没有被实现
void not_found(int client_sock);//如果没有找到就执行这个
void inner_error(int client_sock);//服务器内部出错
void bad_request(int client_sock);//请求出错
int main()
{
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(8888);//绑定端口号
//实现标签贴到收信得信箱上
int ret = bind(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
if(ret == -1)
{
perror("bind error");
exit(1);
}
//把信箱挂置到传达室,这样,就可以接收信件了
ret = listen(sock, 128);
if(ret == -1)
{
perror("bind error");
exit(1);
}
//设置端口复用
int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(opt));
//万事俱备,只等来信
printf("等待客户端连接\n");
while (1)
{
struct sockaddr_in client;
bzero(&client, sizeof(client));
int client_sock, len, i;
char client_ip[128];
char buf[256];
pthread_t id;//线程id
int* pclient_sock = NULL;
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 请求,读取客户端发送的数据*/
pclient_sock = (int*)malloc(sizeof(int));//分配空间
*pclient_sock = client_sock;
//使用线程处理请求
pthread_create(&id, NULL, do_http_request, (void*)pclient_sock);//并行
}
close(sock);
return 0;
}
//1.读取请求行
void* do_http_request(void* pclient_sock)
{
int len = 0;
char buf[256];
char method[64];
char url[256];
char path[256];
int client_sock = *(int*)pclient_sock;//解参数的引用
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)) {//S_SIDIR判断一个路径是否是目录
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); //在响应时再实现
}
close(client_sock);//关闭sock
if (pclient_sock) free(pclient_sock);//释放动态分配的内存
return NULL;
}
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: Martin 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) {//给浏览器发送http请求的数据
fprintf(stderr, "send failed. data: %s, reason: %s\n", buf, strerror(errno));
return -1;
}
return 0;
}
//返回值: -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 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, reply);
if (len <= 0)
{
fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
}
}
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 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));
}
}
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 (len <= 0) {
fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
}
}
3.访问的注意事项
1.访问的格式
2.连接状态