HTTP服务器
- 一.预备知识
- 二.HTTP的请求和响应
- 三.写一个简单的HTTP服务器
- 四.返回响应
- 五.HTTP方法和状态码
一.预备知识
1.域名
https://www.baidu.com,这是一个域名。在技术角度上,访问一个服务器其实只需要知道它的ip和域名就行了,而域名主要是方便我们进行查看。而域名会经过域名解析器解析为ip。那么端口号呢?其实https的端口号固定是443,http的端口号固定是80,所以在访问时,浏览器会自动加上。
2.url
例如你看到一篇博客很好,分享了链接http://t.csdnimg.cn/2fzXz,像这种链接就被称为url(统一资源定位符),所有网络上的资源都可以通过“字符串”标识并且获取。仔细观察这个url,/是Linux的分隔符,代表它的服务器是Linux,同时也被称为web根目录。
3.特殊字符
我们在浏览器上都是通过关键字进行搜索的,例如:搜索一个helloword。
浏览器通过kv的方式进行匹配。但是在我们的搜索词中如果出现了特殊字符,像 / ? : 等这样的字符, 已经被url当做特殊意义理解了,所以为了避免歧义,浏览器就必须先对特殊字符进行转义。
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式
“+” 被转义成了 “%2B”
二.HTTP的请求和响应
http请求报文由多行构成。分为请求行,请求报头,请求正文(如果不需要发资源可以不写);每个部分之间用‘\n’或者’\r\n’分割。
请求行:
1.Method:常用的GET和POST。
2.url:请求的资源
3.HTTP version:一般为1.0,1.1或者2.0
4.中间以空格(一般为一个)为分隔符。
请求报头
1.由多行构成。
2.内部大多是key:value结构。
3.内部有一个content length用来记录请求正文的字节数。
4.在请求报头和请求正文之间用一空行表示分离。
响应与请求是一样的。
三.写一个简单的HTTP服务器
由于之前写过sock的封装,这里就直接使用了。这里的服务器是最基本的就不详解了。
HttpServer.hpp
#include "Socket.hpp"
struct ThreadData
{
int socket;
};
class HttpServer
{
public:
HttpServer(const uint16_t port=3389):port_(port)
{}
~HttpServer()
{}
bool Start()
{
listensock_.Socket();
listensock_.Bind(port_);
listensock_.Listen();
while(true)
{
std::string clientip;
uint16_t clientport;
int sockfd=listensock_.Accept(&clientip,&clientport);
//创建线程处理任务
pthread_t tid;
ThreadData*td=new ThreadData;
td->socket=sockfd;
pthread_create(&tid,nullptr,ThreadRun,td);
}
}
static void* ThreadRun(void*args)
{
pthread_detach(pthread_self());//执行完毕后,自己释放
ThreadData*td=static_cast<ThreadData*>(args);
char buffer[1024];
ssize_t n=read(td->socket,buffer,sizeof(buffer));
if(n>0)
{
buffer[n]=0;
std::cout<<buffer<<std::endl;
}
close(td->socket);
delete td;
return nullptr;
}
private:
Sock listensock_;
uint16_t port_;
};
HttpServer.cc
#include "HttpServer.hpp"
int main(int argc,char*args[])
{
if(argc>2)
{
std::cout<<"参数传递错误"<<std::endl;
exit(1);
}
HttpServer hp;
hp.Start();
return 0;
}
Socket.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
class Sock
{
public:
Sock()
{
}
~Sock()
{
}
void Socket()
{
socket_ = socket(AF_INET, SOCK_STREAM, 0);
if (socket_ < 0)
{
std::cout << "create socket error!!!" << std::endl;
exit(1);
}
}
void Bind(uint16_t port,std::string ip="0.0.0.0")
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
inet_aton(ip.c_str(), &local.sin_addr);
if (bind(socket_, (struct sockaddr *)&local, sizeof(local)))
{
std::cout << "Bind error!!!" << std::endl;
exit(2);
}
}
void Listen(int backlog = 10)
{
if (listen(socket_, backlog) < 0)
{
std::cout << "Listen error!!!" << std::endl;
exit(3);
}
}
int Accept(std::string *clientip, uint16_t *clientport)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int newfd = accept(socket_, (struct sockaddr *)&client, &len);
if (socket_ < 0)
{
std::cout << "Accept error!!!" << std::endl;
return -1;
}
uint16_t port = ntohs(client.sin_port);
char ip[64];
inet_ntop(AF_INET, &(client.sin_addr), ip, sizeof(ip));
*clientip = ip;
*clientport = port;
return newfd;
}
bool Connect(const std::string &ip, const uint16_t &port)
{
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));
int n = connect(socket_, (struct sockaddr *)&peer, sizeof(peer));
if (n == -1)
{
std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
return false;
}
return true;
}
int Fd()
{
return socket_;
}
void Close()
{
close(socket_);
}
private:
int socket_;
};
makefile
HttpServer:HttpServer.cc
g++ -o $@ $^ -lpthread -std=c++11
.PHONY:clean
clean:
rm -f HttpServer
我们主机收到的请求:
第一行:GET,URL,HTTP版本
HOST:代表的主机。
User-Agent:请求的客户端
中间每一行都是key-value结构
四.返回响应
我们可以根据上面的请求格式构建一个响应。
五.HTTP方法和状态码
1.HTTP方法
在平常的搜索过程中,用户是如何将自己的数据提交给服务器的呢?其实是通过表单,也就是搜索框。如果我们通过get进行提参,那么参数就会自动拼接到url后面。而post方法采用请求正文的方式进行提交。由于get方法会显示在url上,所以post方法更私密。
2.HTTP状态码
重点说一下3开头的,重定向就是由于某些原因,服务器无法为我们完成服务,它返回给我们一个新的地址,这个新地址由Location(报头内的数据)指定。这时浏览器就会二次发起请求,去访问新的地址。
3.报头内常见属性
Content-Type: 数据类型(text/html等)
Content-Length: Body的长度
Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
User-Agent: 声明用户的操作系统和浏览器版本信息;
referer: 当前页面是从哪个页面跳转过来的;
location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
Connection:keep-alive 长连接。
Content-Type:请求类型。
长连接:
这次我们写的http其实是短链接,当进行一次链接后就断开,无疑这样是低效的。所以开发者设计了一种长连接,在进行连接时一次发送多个请求,这样就可以进行多次响应。例如:淘宝的主页就有许多照片,每一张照片都有一个链接,而长连接就可以一次读取多张照片以提高效率。
Cookie
我们在登陆一个APP时,即使关闭了它,在短时间内重新登陆也不需要输入密码,这是因为在浏览器内有一个cookie文件,服务器通过Set Cookie响应,把个人的登陆信息储存在cookie文件内,之后的每一次http请求都会自动携带cookie文件内的内容。