HTTP请求消息
请求消息分为四部分内容
- 请求行(请求类型,访问资源,http的版本)
请求类型:GET和POST(绝大多数都是,还有很多如head,put,可以自己了解了解) - 请求头 (附加信息)
- 空行 (不能省略 \r\n)
- 请求数据(特定的请求内容)
*** 请求行 ***
*** 请求头 ***
HTTP响应消息
响应消息也分为四部分
- 状态行,(http版本号,状态码,状态信息)
- 消息报头
- 空行
- 响应正文
搭建服务器
既然前面讲了epoll的用法与好处(epoll搭建tcp服务器),这里就用epoll去搭http服务器
根据上面的http的请求和响应消息的了解,很清楚我们搭建服务器的目的:来应对由浏览器发出的请求消息,请求消息由浏览器去完成。我们从请求消息中获取我们需要的信息,再将这些信息变成响应消息发送回浏览器。
- 那么我们首先就该获取请求行,把请求类型,访问资源,http的版本都拿出来。
(那怎么能从那么一长段的请求行和请求头中,成功的筛选出来那个是请求行,哪个是请求头,大家一定都知道粘包,在http中每一行中也结尾也会使用\r和\n的手法来进行拆分)所以我们需要自己写个拆分的函数Readline()这里不再多讲。
//获取请求行
char linebuf[1024] = {0};
Readline(fd, linebuf, 1024);
char method[10], strPath[256], proto[10];
sscanf(linebuf, "%[^ ] %[^ ] %[^ \r\n]", method, strPath, proto);
- 获取请求头(方法一致)
//获取请求头
while(Readline(fd, linebuf, 1024) > 0) {
printf("%s", linebuf);
}
- 获取完请求信息,我们就该根据这些信息发送响应信息,首相我不能无中生有,我得判段我是否有这个资源(这里统一成get方法,也可使用post方法)
struct stat sb; //stat 获取文件信息
char path[256] = {0};
sprintf(path, ".%s",strPath); //将文件的相对地址写入path中
//判断出没有
if(stat(path, &sb) < 0) {
perror("faield to stat");
return -1;
}
- 如果有,我就该给对方发了,这里我们先写两个函数,一个用来发送状态行和消息报头,一个用来发送响应正文。还记得我们说的响应消息的状态行和消息包头由什么构成吗这里我们就需要用到了(状态码,状态信息,content-Type,Content-Length,Content-Length);
//如果是发送为普通文件(文件夹之类的我们暂时不考虑)
if(S_ISREG(sb.st_mode) ) {
//发送状态行和消息报头
send_header(fd, 200, "OK", get_mime_type(path), sb.st_size);
//消息正文
send_file(path, fd);
}
- send_header()函数
send_header(int fd, int code, char *msg, char *fileType, int length)
{
//buf首先发送状态行
char buf[1024] = {0};
int ret = sprintf(buf, "HTTP/1.1 %d %s\r\n", code , msg);
send(fd, buf, ret, 0);
//发送完清空,讲究人
memset(buf, 0x00, sizeof(buf));
//发送Content-Type
ret = sprintf(buf, "Content-Type:%s\r\n", fileType);
send(fd, buf, ret, 0);
memset(buf, 0x00, sizeof(buf));
//发送长度
if( length > 0) {
ret = sprintf(buf, "Content-Length:%d\r\n", length);
send(fd, buf, ret, 0);
memset(buf, 0x00, sizeof(buf));
}
//发送空行
send(fd, "\r\n", 2, 0);
return 0;
}
- send_file()函数
send_file(char *filename, int fd)
{
//打开发送的文件
int srcfd = open(filename, O_RDONLY);
if(srcfd < 0 ) {
perror("Failed to open");
return -1;
}
char buf[256] = {0};
//发送文件
while(1) {
int ret = read(srcfd, buf, sizeof(buf));
if(ret > 0) {
send(fd, buf, ret, 0);
} else {
break;
}
}
close(srcfd);
return 0;
}
6.至此就完成了一个浏览器请求,客户端发送响应的全部过程。下面附上全部代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <errno.h>
#include <fcntl.h>
#include <strings.h>
#include <sys/stat.h>
int send_header(int fd, int code, char *msg, char *fileType, int length)
{
//buf首先发送状态行
char buf[1024] = {0};
int ret = sprintf(buf, "HTTP/1.1 %d %s\r\n", code , msg);
send(fd, buf, ret, 0);
//发送完清空,讲究人
memset(buf, 0x00, sizeof(buf));
//发送Content-Type
ret = sprintf(buf, "Content-Type:%s\r\n", fileType);
send(fd, buf, ret, 0);
memset(buf, 0x00, sizeof(buf));
//发送长度
if( length > 0) {
ret = sprintf(buf, "Content-Length:%d\r\n", length);
send(fd, buf, ret, 0);
memset(buf, 0x00, sizeof(buf));
}
//发送空行
send(fd, "\r\n", 2, 0);
return 0;
}
int send_file(char *filename, int fd)
{
//打开发送的文件
int srcfd = open(filename, O_RDONLY);
if(srcfd < 0 ) {
perror("Failed to open");
return -1;
}
char buf[256] = {0};
//发送文件
while(1) {
int ret = read(srcfd, buf, sizeof(buf));
if(ret > 0) {
send(fd, buf, ret, 0);
} else {
break;
}
}
close(srcfd);
return 0;
}
int http_request(int fd)
{
// 获取请求行
char linebuf[1024] = {0};
int ret = Readline(fd, linebuf, 1024);
if(ret < 0) {
perror("failed to read");
return -1;
}
printf("%s", linebuf);
char method[10], strPath[256], proto[10];
sscanf(linebuf, "%[^ ] %[^ ] %[^ \r\n]", method, strPath, proto);
printf("method[%s],strpath[%s],proto[%s]\n", method, strPath, proto);
//获取请求头
while(Readline(fd, linebuf, 1024) > 0) {
printf("%s", linebuf);
}
//判断是否有
if(strcasecmp("get", method) == 0 ) {
struct stat sb;
char path[256] = {0};
sprintf(path, ".%s",strPath);
if(stat(path, &sb) < 0) {
perror("faield to stat");
return -1;
}
//如果有
if(S_ISREG(sb.st_mode) ) {
send_header(fd, 200, "OK", get_mime_type(path), sb.st_size);
send_file(path, fd);
}
}
printf("request end\n");
return 0;
}
#define WORK_DIR "webpath"
int main()
{
//获取工作目录
char workdir[256] = {0};
sprintf(workdir, "%s/%s", getenv("HOME") , WORK_DIR);
chdir(workdir);
int lfd = Socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in svr, cli;
socklen_t clilen = sizeof(cli);
memset(&svr, 0x00, sizeof(svr));
svr.sin_family = AF_INET;
svr.sin_port = htons(8888);
svr.sin_addr.s_addr = 0;
int opt = 1;
setsockopt(lfd , SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
Bind(lfd, (struct sockaddr*)&svr, sizeof(svr));
Listen(lfd, 128);
int epfd = epoll_create(1);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
struct epoll_event evs[1024];
while(1) {
int nready = epoll_wait(epfd, evs, 1024, -1);
printf("nready ===== %d\n", nready);
for(int i = 0;i < nready; i ++) {
if(evs[i].events & EPOLLIN) {
if(lfd == evs[i].data.fd) {
int cfd = Accept(lfd, NULL, NULL);
printf("cfd ==== %d\n", cfd);
if(cfd > 0) {
ev.data.fd = cfd;
ev.events = EPOLLIN | EPOLLET;
fcntl(cfd, F_SETFL, O_NONBLOCK);
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
continue;
}
} else {
//服务器响应
if(http_request(evs[i].data.fd) < 0) {
ev.data.fd = evs[i].data.fd;
epoll_ctl(epfd, EPOLL_CTL_DEL, ev.data.fd, &ev);
close(evs[i].data.fd);
}
}
}
}
}
close(lfd);
return 0;
}