文章目录
一、什么是http?
HTTP 协议是 Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网服务器传输超文本到本地浏览器的传送协议。HTTP 是一个基于TCP/IP 通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。
二、工作原理
HTTP 协议工作于客户端-服务端架构,浏览器作为 HTTP 客户端通过 URL 向 HTTP 服务端 即 WEB 服务器发送所有请求。
Web 服务器根据接收到的请求后,向客户端发送响应信息。
三、客户端请求消息
客户端发送一个 HTTP 请求到服务器的请求消息包括以下格式:请求行(request line)、请求
头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。
例如:
GET /hello.txt HTTP/1.1
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
Host: www.example.com
Accept-Language: en, mi
四、过程分析
1.ntyreactor_run 不断循环
2.accept_cb ----> 建立http连接
3.recv_cb -------> 接收客户端http请求
4.send_cb ------> 发送http响应
五、过程实现
- (1)www.baidu.com --> 翻译为ip地址。 (DNS)
- (2)tcp连接这个ip地址:端口
- (3)发送http协议
六、结构和函数介绍
(1) struct hostent
struct hostent
{
char *h_name; //正式主机名
char **h_aliases; //主机别名
int h_addrtype; //主机IP地址类型:IPV4-AF_INET
int h_length; //主机IP地址字节长度,对于IPv4是四字节,即32位
char **h_addr_list; //主机的IP地址列表
};
#define h_addr h_addr_list[0] //保存的是IP地址
(1) gethostbyname()
操作系统提供的库函数 : gethostbyname() 函数主要作用:用域名或者主机名获取地址.。
struct hostent *host_entry = gethostbyname(hostname); //DNS
(2) inet_ntoa()
将网络字节序表示的IP地址转换成我们比较常见的点分十进制表示的IP地址
inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list);
(3) select()
select()函数监视一系列文件描述符,特别是
- readfds 读
- writefds 写
- exceptfds 出错
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int numfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
宏操作
-
FD_ZERO(fd_set *set) – 清除一个文件描述符集合
-
FD_SET(int fd, fd_set *set) - 添加fd到集合
-
FD_CLR(int fd, fd_set *set) – 从集合中移去fd
-
FD_ISSET(int fd, fd_set *set) – 测试fd是否在集合中
七、基于select解析 http
(1) www.baidu.com --> 翻译为ip地址。
- gethostbyname() 得到 网络字节序表示的IP地址
- inet_ntoa() 将网络字节序表示的IP地址转换成我们比较常见的点分十进制表示的IP地址
char *host_to_ip(const char *hostname) {
struct hostent *host_entry = gethostbyname(hostname); //dns
// 14.215.177.39 -->
//inet_ntoa ( unsigned int --> char *
// 0x12121212 --> "18.18.18.18"
if (host_entry) {
return inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list);
}
return NULL;
}
(2) 建立tcp连接的socket
关于socket的相关接口可见:Socket 学习记录
int http_create_socket(char *ip) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建一个socket
struct sockaddr_in sin = {0};//服务器 IP
sin.sin_family = AF_INET; //TCP
sin.sin_port = htons(80); //端口
sin.sin_addr.s_addr = inet_addr(ip);//IP地址-->网络字节序
//连接服务器
if (0 != connect(sockfd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) {
return -1;
}
fcntl(sockfd, F_SETFL, O_NONBLOCK);//设置为非阻塞
return sockfd;
}
(3) 客户端向服务器发送http协议请求
char * http_send_request(const char *hostname, const char *resource) {
char *ip = host_to_ip(hostname); //DNS
int sockfd = http_create_socket(ip);
char buffer[BUFFER_SIZE] = {0};
sprintf(buffer,
"GET %s %s\r\n\
Host: %s\r\n\
%s\r\n\
\r\n",
resource, HTTP_VERSION,
hostname,
CONNETION_TYPE
);
send(sockfd, buffer, strlen(buffer), 0);
//select监测IO里有没有可以读取的数据
fd_set fdread;
FD_ZERO(&fdread);
FD_SET(sockfd, &fdread);
struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;
char *result = malloc(sizeof(int));
memset(result, 0, sizeof(int));
while (1) {
int selection = select(sockfd+1, &fdread, NULL, NULL, &tv);
if (!selection || !FD_ISSET(sockfd, &fdread)) {
break;
} else {
memset(buffer, 0, BUFFER_SIZE);
int len = recv(sockfd, buffer, BUFFER_SIZE, 0);
if (len == 0) { // disconnect
break;
}
result = realloc(result, (strlen(result) + len + 1) * sizeof(char));
strncat(result, buffer, len);
}
}
return result;
}
(4)写main函数
int main(int argc, char *argv[]) {
if (argc < 3) return -1;
char *response = http_send_request(argv[1], argv[2]);
printf("response : %s\n", response);
free(response);
}
八、Linux中编译和运行
(1) 编译
gcc -o httprequest httprequest.c
(2) 运行
请求百度首页的资源
./httprequest www.baidu.com /
请求成功
九、基于reactor实现http服务器
1. ntyevent 结构体
struct ntyevent {
int fd;
int events;
void *arg;
int (*callback)(int fd, int events, void *arg);
int status;
char buffer[BUFFER_LENGTH];
int length;
long last_active;
// http param
int method; // get / post
char resource[BUFFER_LENGTH]; //资源位。url的长度
int ret_code;
};
2. readline
int readline(char *allbuf, int idx, char *linebuf) {
int len = strlen(allbuf); // buf的总长度
for(;idx < len;idx ++) {
if (allbuf[idx] == '\r' && allbuf[idx+1] == '\n') {
return idx+2;// \r\n\r\n ,是idx+4
} else {
*(linebuf++) = allbuf[idx];
}
}
return -1;
}
3.http_request
int http_request(struct ntyevent *ev) {
// GET, POST
char linebuf[1024] = {0};
int idx = readline(ev->buffer, 0, linebuf);
if (strstr(linebuf, "GET")) {
ev->method = HTTP_METHOD_GET; //判断message
//uri
int i = 0;
while (linebuf[sizeof("GET ") + i] != ' ') i++;
linebuf[sizeof("GET ")+i] = '\0';
sprintf(ev->resource, "./%s/%s", HTTP_WEBSERVER_HTML_ROOT, linebuf+sizeof("GET "));
} else if (strstr(linebuf, "POST")) {
}
}
http_request 的调用
int recv_cb(int fd, int events, void *arg) {
struct ntyreactor *reactor = (struct ntyreactor*)arg;
struct ntyevent *ev = ntyreactor_idx(reactor, fd);
int len = recv(fd, ev->buffer, BUFFER_LENGTH, 0); //
if (len > 0) {
ev->length = len;
ev->buffer[len] = '\0';
printf("C[%d]:%s\n", fd, ev->buffer); //http
//----------------------------------------------------
//GET / uri
http_request(ev);
//----------------------------------------------------
//send();
nty_event_del(reactor->epfd, ev);
nty_event_set(ev, fd, send_cb, reactor);
nty_event_add(reactor->epfd, EPOLLOUT, ev);
} else if (len == 0) {
nty_event_del(reactor->epfd, ev);
close(ev->fd);
//printf("[fd=%d] pos[%ld], closed\n", fd, ev-reactor->events);
} else {
nty_event_del(reactor->epfd, ev);
close(ev->fd);
printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
}
return len;
}
4.http_response
int http_response(struct ntyevent *ev) {
if (ev == NULL) return -1;
memset(ev->buffer, 0, BUFFER_LENGTH);
#if 0
const char *html = "<html><head><title>hello http</title></head><body><H1>King</H1></body></html>\r\n\r\n";
ev->length = sprintf(ev->buffer,
"HTTP/1.1 200 OK\r\n\
Date: Thu, 11 Nov 2021 12:28:52 GMT\r\n\
Content-Type: text/html;charset=ISO-8859-1\r\n\
Content-Length: 83\r\n\r\n%s",
html);
#else
printf("resource: %s\n", ev->resource);
int filefd = open(ev->resource, O_RDONLY);
if (filefd == -1) { // return 404
ev->ret_code = 404;
ev->length = sprintf(ev->buffer,
"HTTP/1.1 404 Not Found\r\n"
"Date: Thu, 11 Nov 2021 12:28:52 GMT\r\n"
"Content-Type: text/html;charset=ISO-8859-1\r\n"
"Content-Length: 85\r\n\r\n"
"<html><head><title>404 Not Found</title></head><body><H1>404</H1></body></html>\r\n\r\n" );
} else {
struct stat stat_buf;
fstat(filefd, &stat_buf);//获取文件属性
close(filefd);
if (S_ISDIR(stat_buf.st_mode)) {
ev->ret_code = 404;
ev->length = sprintf(ev->buffer,
"HTTP/1.1 404 Not Found\r\n"
"Date: Thu, 11 Nov 2021 12:28:52 GMT\r\n"
"Content-Type: text/html;charset=ISO-8859-1\r\n"
"Content-Length: 85\r\n\r\n"
"<html><head><title>404 Not Found</title></head><body><H1>404</H1></body></html>\r\n\r\n" );
} else if (S_ISREG(stat_buf.st_mode)) {
ev->ret_code = 200;
ev->length = sprintf(ev->buffer,
"HTTP/1.1 200 OK\r\n"
"Date: Thu, 11 Nov 2021 12:28:52 GMT\r\n"
"Content-Type: text/html;charset=ISO-8859-1\r\n"
"Content-Length: %ld\r\n\r\n",
stat_buf.st_size );
}
}
#endif
return ev->length;
}
http_response的调用
int send_cb(int fd, int events, void *arg) {
struct ntyreactor *reactor = (struct ntyreactor*)arg;
struct ntyevent *ev = ntyreactor_idx(reactor, fd);
//-----------------------------------------------------
http_response(ev);
//-----------------------------------------------------
int len = send(fd, ev->buffer, ev->length, 0);
if (len > 0) {
printf("send[fd=%d], [%d]%s\n", fd, len, ev->buffer);
if (ev->ret_code == 200) {
int filefd = open(ev->resource, O_RDONLY);
struct stat stat_buf;
fstat(filefd, &stat_buf);
sendfile(fd, filefd, NULL, stat_buf.st_size);//mmap原理 --> 零拷贝 ,不需要cpu参加就能拷贝
close(filefd);
}
nty_event_del(reactor->epfd, ev);
nty_event_set(ev, fd, recv_cb, reactor);
nty_event_add(reactor->epfd, EPOLLIN, ev);
} else {
close(ev->fd);
nty_event_del(reactor->epfd, ev);
printf("send[fd=%d] error %s\n", fd, strerror(errno));
}
return len;
}