【Linux网络编程】自定义协议+HTTP协议
目录
- 【Linux网络编程】自定义协议+HTTP协议
- 协议定制,序列化和反序列化
- 应用层中的HTTP
- 认识URL(网址)
- urlencode和urldecode
- HTTP协议格式
- 使用telnet获取百度的根目录资源
- HTTP的方法
- 表单
- HTTP的状态码
- HTTP常见Header
- 简单的HTTP服务器代码
作者:爱写代码的刚子
时间:2024.4.28
前言:本篇博客将会介绍自定义协议和应用层的HTTP协议
TCP也叫传输控制协议,什么时候发,发多少,出错了怎么办,完全是由TCP协议自主决定。上层调用write接口也仅仅是将用户应用层的数据拷贝到TCP的发送缓冲区中(交给操作系统)
协议定制,序列化和反序列化
协议概念:
- “协议”本质就是一种约定,通信双方只要曾经做过某种约定,之后就可以使用这种约定来完成某种事情。而网络协议是通信计算机双方必须共同遵从的一组约定,因此我们一定要将这种约定用计算机语言表达出来,此时双方计算机才能识别约定的相关内容
- 为了使数据在网络上能够从源到达目的,网络通信的参与方必须遵循相同的规则,我们将这套规则称为协议(protocol),而协议最终都需要通过计算机语言的方式表示出来。只有通信计算机双方都遵守相同的协议,计算机之间才能互相通信交流。
结构化数据的传输
理论上我们可以直接将结构体对象以二进制形式发送到网络中,但是不同的编译器对结构体的处理不同,粘包等问题,可能会对结构体在网络中的传输产生影响。
所以需要我们使用协议来解决(将结构体转化为特定结构的字符串)
自定义协议的实现代码:
- Protocol.hpp:
#pragma once
#include <iostream>
#include <string>
//我们可以为我们的协议添加报头,协议版本、长度等。
const std::string blank_space_sep = " ";
const std::string protocol_sep = "\n";
std::string Encode(std::string &content)
{
std::string package= std::to_string(content.size());
package+= protocol_sep;
package += content;
package+= protocol_sep;
return package;
}
//"len"\n"x op y"\n
bool Decode(std::string &package,std::string *content)
{
std::size_t pos = package.find(protocol_sep);
if(pos == std::string::npos)return false;
std::string len_str = package.substr(0,pos);
std::size_t len = std::stoi(len_str);
//package = len_str + content_str + 2(两个\n)
std::size_t total_len = len_str.size() + len + 2;
if(package.size() < total_len) return false;
*content = package.substr(pos+1,len);
package.erase(0,total_len);
return true;
}
class Request
{
public:
Request(int data1,int data2,char oper):x(data1),y(data2),op(oper)
{}
Request(){}
public:
bool Serialize(std::string *out)
{
//构建报文的有效载荷
//struct => string, "x op y"
std::string s = std::to_string(x);
s+=blank_space_sep;
s+=op;
s+=blank_space_sep;
s+=std::to_string(y);
*out = s;
return true;
}
bool Deserialize(const std::string &in)//"x op y"
{
std::size_t left= in.find(blank_space_sep);
if(left == std::string::npos) return false;
std::string part_x = in.substr(0,left);
std::size_t right = in.rfind(blank_space_sep);
if(right == std::string::npos) return false;
std::string part_y = in.substr(right+1);
if(left + 2!=right) return false;
op = in[left+1];
x = std::stoi(part_x);
y = std::stoi(part_y);
return true;
}
void DebugPrint()
{
std::cout << "新请求构建完成: " << x << op << y << "=?" << std::endl;
}
public:
int x;
int y;
char op;
};
class Response
{
public:
Response(int res,int c):result(res),code(c)
{}
Response(){}
public:
bool Serialize(std::string *out)
{
//"len"\n"result code"
//构建报文的有效载荷
std::string s = std::to_string(result);
s += blank_space_sep;
s +=std::to_string(code);
*out = s;
return true;
}
bool Deserialize(const std::string &in)//"result code"
{
std::size_t pos = in.find(blank_space_sep);
if(pos == std::string::npos)
{
return false;
}
std::string part_left = in.substr(0,pos);
std::string part_right = in.substr(pos+1);
result = std::stoi(part_left);
code = std::stoi(part_right);
return true;
}
void DebugPrint()
{
std::cout << "结果响应完成, result: " << result << ", code: "<< code << std::endl;
}
public:
int result;
int code;//0, 可信,否则!0具体是几,表明对应的错误原因
};
- ClientCal.cc:
#include "Socket.hpp"
#include <string>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include "Protocol.hpp"
static void Usage(const std::string &proc)
{
std::cout << "\nUsage: " << proc <<" serverip serverport\n"<<std::endl;
}
// ./client ip port
int main(int argc,char *argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(0);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
Sock sockfd;
sockfd.Socket();
bool ret=sockfd.Connect(serverip,serverport);
if(!ret)return 1;
srand(time(nullptr) ^ getpid());
int cnt = 1;
const std::string opers = "+-*/%=-=&^";
std::string inbuffer_stream;
while(cnt <= 10)
{
std::cout << "===============第" << cnt << "次测试....., " << "===============" << std::endl;
int x = rand() % 100 + 1;
usleep(1234);
int y = rand() % 100;
usleep(4321);
char oper = opers[rand()%opers.size()];
Request req(x, y, oper);
req.DebugPrint();
std::string package;
req.Serialize(&package);
package = Encode(package);
int num = write(sockfd.Fd(), package.c_str(), package.size());
// std::cout << "这是最新的发出去的请求: " << num << "\n" << package;
// num = write(sockfd.Fd(), package.c_str(), package.size());
// std::cout << "这是最新的发出去的请求: \n" << num << "\n" << package;
// num = write(sockfd.Fd(), package.c_str(), package.size());
// std::cout << "这是最新的发出去的请求: \n" << num << "\n" << package;
// num = write(sockfd.Fd(), package.c_str(), package.size());
// std::cout << "这是最新的发出去的请求: \n" << num << "\n" << package;
char buffer[128];
ssize_t n = read(sockfd.Fd(), buffer, sizeof(buffer)); // 我们也无法保证我们能读到一个完整的报文
if(n > 0)
{
buffer[n] = 0;
inbuffer_stream += buffer; // "len"\n"result code"\n
std::cout << inbuffer_stream << std::endl;
std::string content;
bool r = Decode(inbuffer_stream, &content); // "result code"
assert(r);
Response resp;
r = resp.Deserialize(content);
assert(r);
resp.DebugPrint();
}
std::cout << "=================================================" << std::endl;
sleep(1);
cnt++;
}
sockfd.Close();
return 0;
}
- Log.hpp:
#pragma once
#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#define SIZE 1024
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
#define Screen 1
#define Onefile 2
#define Classfile 3
#define LogFile "log.txt"
class Log
{
public:
Log()
{
printMethod = Screen;
path = "./log/";
}
void Enable(int method)
{
printMethod = method;
}
std::string levelToString(int level)
{
switch (level)
{
case Info:
return "Info";
case Debug:
return "Debug";
case Warning:
return "Warning";
case Error:
return "Error";
case Fatal:
return "Fatal";
default:
return "None";
}
}
void printLog(int level, const std::string &logtxt)
{
switch (printMethod)
{
case Screen:
std::cout << logtxt << std::endl;
break;
case Onefile:
printOneFile(LogFile, logtxt);
break;
case Classfile:
printClassFile(level, logtxt);
break;
default:
break;
}
}
void printOneFile(const std::string &logname, const std::string &logtxt)
{
std::string _logname = path + logname;
int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
if (fd < 0)
return;
write(fd, logtxt.c_str(), logtxt.size());
close(fd);
}
void printClassFile(int level, const std::string &logtxt)
{
std::string filename = LogFile;
filename += ".";
filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
printOneFile(filename, logtxt);
}
~Log()
{
}
void operator()(int level, const char *format, ...)
{
time_t t = time(nullptr);
struct tm *ctime = localtime(&t);
char leftbuffer[SIZE];
snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
va_list s;
va_start(s, format);
char rightbuffer[SIZE];
vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
va_end(s);
// 格式:默认部分+自定义部分
char logtxt[SIZE * 2];
snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);
// printf("%s", logtxt); // 暂时打印
printLog(level, logtxt);
}
private:
int printMethod;
std::string path;
};
- ServerCal.cc:
#include "TcpServer.hpp"
#include "ServerCal.hpp"
#include "Protocol.hpp"
#include "Socket.hpp"
#include <unistd.h>
#include "Log.hpp"
static void Usage(const std::string &proc)
{
std::cout<< "\nUsage: "<<proc<< " port\n"<<std::endl;
}
//./servercal 8080
int main(int argc,char *argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(0);
}
uint16_t port = std::stoi(argv[1]);
ServerCal cal;
TcpServer *tsvp = new TcpServer(port,std::bind(&ServerCal::Calculator,&cal,std::placeholders::_1));
tsvp->InitServer();
//Daemon
//如果参数nochdir为0,则将守护进程的工作目录该为根目录,否则不做处理。
//如果参数noclose为0,则将守护进程的标准输入、标准输出以及标准错误重定向到/dev/null,否则不做处理。
//std::cout<<"test1"<<std::endl;
//daemon(0,0);
//std::cout<<"test2"<<std::endl;
tsvp->Start();
return 0;
}
- ServerCal.hpp:
#pragma once
#include <iostream>
#include "Protocol.hpp"
enum
{
Div_Zero = 1,
Mod_Zero,
Other_Oper
};
class ServerCal
{
public:
ServerCal()
{}
Response CalculatorHelper(const Request &req)
{
Response resp(0, 0);
switch (req.op)
{
case '+':
resp.result = req.x + req.y;
break;
case '-':
resp.result = req.x - req.y;
break;
case '*':
resp.result = req.x * req.y;
break;
case '/':
{
if (req.y == 0)
resp.code = Div_Zero;
else
resp.result = req.x / req.y;
}
break;
case '%':
{
if (req.y == 0)
resp.code = Mod_Zero;
else
resp.result = req.x % req.y;
}
break;
default:
resp.code = Other_Oper;
break;
}
return resp;
}
std::string Calculator(std::string &package)
{
std::string content;
bool r = Decode(package,&content);
std::cout<< r <<std::endl;
if(!r)return "";
Request req;
r = req.Deserialize(content);//转换成对象
std::cout<< r <<std::endl;
if(!r)return "";
content = "";
Response resp = CalculatorHelper(req);
resp.Serialize(&content);
content = Encode(content);
return content;
}
};
- Socket.hpp:
#pragma once
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
Log lg;
enum
{
SocketErr =2,
BindErr,
ListenErr
};
const int backlog = 10;
class Sock{
public:
Sock()
{}
~Sock()
{}
public:
void Socket()
{
sockfd_ = socket(AF_INET,SOCK_STREAM,0);
if(sockfd_<0)
{
lg(Fatal,"socker error, %s: %d",strerror(errno),errno);
exit(SocketErr);
}
}
void Bind(uint16_t port)
{
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if(bind(sockfd_,(struct sockaddr *)&local,sizeof(local))<0)
{
lg(Fatal,"bind error, %s: %d",strerror(errno),errno);
exit(BindErr);
}
}
void Listen()
{
if(listen(sockfd_,backlog)<0)
{
lg(Fatal,"listen error, %s: %d",strerror(errno),errno);
exit(ListenErr);
}
}
int Accept(std::string *clientip,uint16_t *clientport)
{
struct sockaddr_in peer;//获取客户端信息
socklen_t len = sizeof(peer);
int newfd = accept(sockfd_,(struct sockaddr*)&peer,&len);
if(newfd < 0)
{
lg(Warning, "accept error, %s: %d", strerror(errno), errno);
return -1;
}
char ipstr[64];
inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
*clientip = ipstr;
*clientport = ntohs(peer.sin_port);
return newfd;
}
bool Connect(const std::string &ip,const uint16_t &port)
{
struct sockaddr_in peer;
memset(&peer,0,sizeof(peer));
socklen_t len = 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(sockfd_, (struct sockaddr*)&peer, sizeof(peer));
if(n == -1)
{
std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
return false;
}
return true;
}
void Close()
{
close(sockfd_);
}
int Fd()
{
return sockfd_;
}
private:
int sockfd_;
};
- TcpServer.hpp:
#pragma once
#include "Socket.hpp"
#include <string>
#include <signal.h>
#include "Log.hpp"
#include <functional>
using func_t = std::function<std::string(std::string &package)>;
class TcpServer
{
public:
TcpServer(uint16_t port, func_t callback) : port_(port), callback_(callback)
{}
bool InitServer()
{
listensock_.Socket();
listensock_.Bind(port_);
listensock_.Listen();
lg(Info,"init server .... done");
return true;
}
void Start()
{
signal(SIGCHLD,SIG_IGN);
signal(SIGPIPE,SIG_IGN);
while(true)
{
std::string clientip;
uint16_t clientport;
int sockfd = listensock_.Accept(&clientip,&clientport);//获取客户端信息
if(sockfd < 0)
continue;
lg(Info,"accept a new link, sockfd: %d,clentip: %s,clientport: %d",sockfd,clientip.c_str());
if(fork() == 0)
{
listensock_.Close();
std::string inbuffer_stream;
// 数据计算
while(true)
{
char buffer[1280];
ssize_t n = read(sockfd,buffer,sizeof(buffer));
if(n>0)
{
buffer[n] = 0;
inbuffer_stream += buffer;
lg(Debug, "debug:\n%s",inbuffer_stream.c_str());
while(true)
{
std::string info = callback_(inbuffer_stream);
if(info.empty())
break;
lg(Debug,"debug response:\n%s",info.c_str());
lg(Debug,"debug: \n%s",inbuffer_stream.c_str());
write(sockfd,info.c_str(),info.size());
}
}else if(n==0)
{
break;
}
else
{
break;
}
}
exit(0);
}
close(sockfd);
}
}
~TcpServer()
{}
private:
uint16_t port_;
Sock listensock_;
func_t callback_;
};
使用JSON代替我们的自定义协议(sudo yum install -y jsoncpp-devel):
测试demo:
#include <iostream>
#include <jsoncpp/json/json.h>
int main()
{
Json::Value part1;
part1["haha"] = "haha";
part1["hehe"] = "hehe";
Json::Value root;
root["x"] = 100;
root["y"] = 200;
root["op"] = '+';
root["desc"] = "this is a + oper";
root["test"] = part1;
//Json::FastWriter w;
Json::StyledWriter w;
std::string res = w.write(root);
std::cout<< res <<std::endl;
Json::Value v;
Json::Reader r;
r.parse(res,v);
int x = v["x"].asInt();
int y = v["y"].asInt();
char op = v["op"].asInt();
std::string desc = v["desc"].asString();
Json::Value test2 = v["test"];
std::string test3 = test2["haha"].asString();
std::cout<< x <<std::endl;
std::cout<< y <<std::endl;
std::cout<< op <<std::endl;
std::cout<< desc <<std::endl;
std::cout<< test3 <<std::endl;
return 0;
}
- 注意编译时带上-ljsoncpp选项
将jsoncpp运用到我们的协议中:
Protocol.hpp:
#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
// #define MySelf 1
//我们可以为我们的协议添加报头,协议版本、长度等。
const std::string blank_space_sep = " ";
const std::string protocol_sep = "\n";
std::string Encode(std::string &content)
{
std::string package= std::to_string(content.size());
package+= protocol_sep;
package += content;
package+= protocol_sep;
return package;
}
//"len"\n"x op y"\n
bool Decode(std::string &package,std::string *content)
{
std::size_t pos = package.find(protocol_sep);
if(pos == std::string::npos)return false;
std::string len_str = package.substr(0,pos);
std::size_t len = std::stoi(len_str);
//package = len_str + content_str + 2(两个\n)
std::size_t total_len = len_str.size() + len + 2;
if(package.size() < total_len) return false;
*content = package.substr(pos+1,len);
package.erase(0,total_len);
return true;
}
class Request
{
public:
Request(int data1,int data2,char oper):x(data1),y(data2),op(oper)
{}
Request(){}
public:
bool Serialize(std::string *out)
{
#ifdef MySelf
//构建报文的有效载荷
//struct => string, "x op y"
std::string s = std::to_string(x);
s+=blank_space_sep;
s+=op;
s+=blank_space_sep;
s+=std::to_string(y);
*out = s;
return true;
#else
Json::Value root;
root["x"] = x;
root["y"] = y;
root["op"] = op;
// Json::FastWriter w;
Json::StyledWriter w;
*out = w.write(root);
return true;
#endif
}
bool Deserialize(const std::string &in)//"x op y"
{
#ifdef MySelf
std::size_t left= in.find(blank_space_sep);
if(left == std::string::npos) return false;
std::string part_x = in.substr(0,left);
std::size_t right = in.rfind(blank_space_sep);
if(right == std::string::npos) return false;
std::string part_y = in.substr(right+1);
if(left + 2!=right) return false;
op = in[left+1];
x = std::stoi(part_x);
y = std::stoi(part_y);
return true;
#else
Json::Value root;
Json::Reader r;
r.parse(in, root);
x = root["x"].asInt();
y = root["y"].asInt();
op = root["op"].asInt();
return true;
#endif
}
void DebugPrint()
{
std::cout << "新请求构建完成: " << x << op << y << "=?" << std::endl;
}
public:
int x;
int y;
char op;
};
class Response
{
public:
Response(int res,int c):result(res),code(c)
{}
Response(){}
public:
bool Serialize(std::string *out)
{
#ifdef MySelf
//"len"\n"result code"
//构建报文的有效载荷
std::string s = std::to_string(result);
s += blank_space_sep;
s +=std::to_string(code);
*out = s;
return true;
#else
Json::Value root;
root["result"] = result;
root["code"] = code;
// Json::FastWriter w;
Json::StyledWriter w;
*out = w.write(root);
return true;
#endif
}
bool Deserialize(const std::string &in)//"result code"
{
#ifdef MySelf
std::size_t pos = in.find(blank_space_sep);
if(pos == std::string::npos)
{
return false;
}
std::string part_left = in.substr(0,pos);
std::string part_right = in.substr(pos+1);
result = std::stoi(part_left);
code = std::stoi(part_right);
return true;
#else
Json::Value root;
Json::Reader r;
r.parse(in, root);
result = root["result"].asInt();
code = root["code"].asInt();
return true;
#endif
}
void DebugPrint()
{
std::cout << "结果响应完成, result: " << result << ", code: "<< code << std::endl;
}
public:
int result;
int code;//0, 可信,否则!0具体是几,表明对应的错误原因
};
Makefile:
.PHONY:all
all:servercal clientcal
Flag=-DMySelf=1
Lib=-ljsoncpp
servercal:ServerCal.cc
g++ -o $@ $^ -std=c++11 $(Lib) #$(Flag)
clientcal:ClientCal.cc
g++ -o $@ $^ -std=c++11 -g $(Lib) #$(Flag)
.PHONY:clean
clean:
rm -f servercal clientcal
- 七层协议:
应用层中的HTTP
认识URL(网址)
URL(Uniform Resource Lacator)叫做统一资源定位符,也就是我们通常所说的网址,是因特网的万维网服务程序上用于指定信息位置的表示方法。用于在互联网中定位某种资源。
协议方案名:
- http:// 表示的是协议名称,表示请求时需要使用的协议,通常使用的是HTTP协议或安全协议HTTPS。HTTPS是以安全为目标的HTTP通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性。
登陆信息:(可以省略)
- usr:pass表示的是登录认证信息,包括登录用户的用户名和密码。虽然登录认证信息可以在URL中体现出来,但绝大多数URL的这个字段都是被省略的,因为登录信息可以通过其他方案交付给服务器。
服务器地址:
- www.example.jp表示的是服务器地址,也叫做域名,比如www.alibaba.com,www.qq.com,www.baidu.com,通过域名解析变成IP地址。
- 需要注意的是,我们用IP地址标识公网内的一台主机,但IP地址本身并不适合给用户看。比如说我们可以通过ping命令,分别获得www.baidu.com和www.qq.com这两个域名解析后的IP地址 。
服务器端口号:为什么我们在浏览器上只需要输入域名/IP地址即可?因为服务器不会轻易改变端口号,端口号相当于众所周知的,浏览器会自动为我们添加端口号
特定的众所周知服务,端口号必须是确定的!常见协议对应的端口号如下:
协议名称 | 对应端口号 |
---|---|
HTTP | 80 |
HTTPS | 443 |
SSH | 22 |
-
注意:用户自己写的网络服务bind端口的范围一定是1024之后的:[1024, n];因为前1023个是给这些httpserver服务的。
-
它们特定的服务与特定的端口之间的关系就比如:110与警察;120与救护车;119与火警
域名标识互联网上唯一一台主机(区分不同的主机),协议和端口号可以表示该主机上唯一的服务(哪些资源由哪些进程访问),后面的资源路径表示可以让我们找到对应的网络资源。
**统一资源定位符:**所有网络上的资源,都可以用唯一的一个“字符串”标识,并且可以获取到
urlencode和urldecode
像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下: 将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY 格式
HTTP协议格式
HTTP请求(Request)
HTTP请求由如下四部分构成:(每行以\r\n结尾)
-
请求行:[请求方法] + [url(一般省略了域名和端口,只有路径)] + [http版本]。ps:http协议请求时大小写是忽略的,例如请求行的 GET / HTTP/1.1 和get / http/1.1都一样
-
请求报头:请求的属性,这些属性都是以 key: value的形式按行陈列的(: 和value中间有空格)
-
空行:因为只包含了一个 \r\n,用于做分隔符,把报头和有效载荷分离
-
请求正文:请求正文允许为空字符串,如果请求正文存在,则在请求报头中会有一个Content-Length属性来标识请求正文的长度
其中,前面三部分是一般是HTTP协议自带的,是由HTTP协议自行设置的,而请求正文一般是用户的相关信息或数据,如果用户在请求时没有信息要上传给服务器,此时请求正文就为空字符串。
【问题】:如何将HTTP请求的报头与有效载荷进行分离?
- 当应用层收到一个HTTP请求时,它必须想办法将HTTP的报头与有效载荷进行分离。对于HTTP请求来讲,这里的请求行和请求报头就是HTTP的报头信息,而这里的请求正文实际就是HTTP的有效载荷。
- 如果将HTTP请求想象成一个大的线性结构,此时每行的内容都是用 \r\n 隔开的,因此在读取过程中,如果连续读取到了两个 \r\n ,就说明已经将报头读取完毕了,后面剩下的就是有效载荷了
首行: [方法] + [url] + [版本]
Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个 Content-Length属性来标识Body的长度;
HTTP响应(Response)
HTTP响应由以下四部分组成:(也是每行以 \r\n 结尾)
- 状态行:[http版本 (http/1.1) ]+[状态码 (例如404报错,200代表OK) ]+[状态码描述 (例如404对应的"Not Found"描述) ]
- 响应报头:响应的属性,这些属性都是以key: value的形式按行陈列的。(注意:和value中间有空格)比如 Content-Type: text/html; charset=utf-8 用于表示正文是 text/html文档类型,字符集为utf-8
- 空行:因为只包含了一个 \r\n ,用与做分隔符,把报头和有效载荷分离
- 响应正文:响应正文允许为空字符串,如果响应正文存在,则响应报头中会有一个Content-Length属性来标识响应正文的长度。比如服务器返回了一个html页面,那么这个html页面的内容就是在响应正文当中的。
- 首行: [版本号] + [状态码] + [状态码解释]
- Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
- Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个 Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中
使用telnet获取百度的根目录资源
虽然请求是不合法的,但是响应已经有了
正确的请求:
GET / HTTP/1.1
响应:
HTTP的方法
方法 | 说明 | 支持的HTTP协议版本 |
---|---|---|
GET | 获取资源 | 1.0、1.1 |
POST | 传输实体文件 | 1.0、1.1 |
PUT | 传输文件 | 1.0、1.1 |
HEAD | 获得报文首部 | 1.0、1.1 |
DELETE | 删除文件 | 1.0、1.1 |
OPTIONS | 询问支持的方法 | 1.1 |
TRACE | 追踪路径 | 1.1 |
CONNECT | 要求用隧道协议连接代理 | 1.1 |
LINK | 建立和资源之间的联系 | 1.0 |
UNLINE | 断开连接关系 | 1.0 |
- 其中最常用的就是GET方法和POST方法
- HEAD请求是没有响应体的,仅传输状态行和标题部分
- DELETE方法用来删除指定的资源,它会删除URI给出的目标资源的所有当前内容
- PUT方法用于将数据发送到服务器以创建或更新资源,它可以用上传的内容替换目标资源中的所有当前内容
其中最常用的就是GET方法和POST方法
表单
我们的网络行为无非有两种:
- 我们想把远端的资源拿到你的本地: GET /index.html http/1.1
- 我们想把我们的属性字段,提交到远端
提交到远端有两种方法(GET | POST)
- 使用GET方法提交表单:
- 使用POST方法提交表单:
GET vs POST
- GET通过url传参
- POST通过正文传参
- GET方法传参不私密(因为GET会把用户输入的有效信息用户名,密码等回显到浏览器)
- POST方法因为通过正文传参,所以,相对比较私密一些(因为一些小白一般不会抓包看正文,所以相对私密)
- GET通过url传参,POST通过正文传参,所以一般一些比较大的内容都是通过post方式传参的
- HTTP GET请求提交参数有长度限制;HTTP POST请求提交参数没有长度限制。
解释6:Http Get方法提交的数据大小长度并没有限制,HTTP协议规范没有对URL长度进行限制。但是特定的浏览器及服务器对URL有限制,所以还是有限制的; 因为POST方法通过正文传参,理论上讲,POST是没有大小限制的。HTTP协议规范也没有进行大小限制,起限制作用的是服务器的处理程序的处理能力,而并非限制。
HTTP的状态码
状态码也就是字符串版本的描述
类型 | 原因短语 | |
---|---|---|
1XX | Informational(信息性状态码) | 接受的请求正在处理 |
2XX | Success(成功状态码) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
- 4XX:客户端错误——>客户请求了不存在的资源,即客户提出了无理的要求,是客户的错。
- 5XX:服务器错误——>服务器代码中的内容错误,例如fork错误,就会返回5XX
HTTP状态码列表
100 | Continue | 继续。客户端应继续其请求 |
---|---|---|
101 | Switching Protocols | 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议 |
200 | OK | 请求成功。一般用于GET与POST请求 |
201 | Created | 已创建。成功请求并创建了新的资源 |
202 | Accepted | 已接受。已经接受请求,但未处理完成 |
203 | Non-Authoritative Information | 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本 |
204 | No Content | 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 |
205 | Reset Content | 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 |
206 | Partial Content | 部分内容。服务器成功处理了部分GET请求 |
300 | Multiple Choices | 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 |
301 | Moved Permanently | 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 |
302 | Found | 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI |
303 | See Other | 查看其它地址。与301类似。使用GET和POST请求查看 |
304 | Not Modified | 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 |
305 | Use Proxy | 使用代理。所请求的资源必须通过代理访问 |
306 | Unused | 已经被废弃的HTTP状态码 |
307 | Temporary Redirect | 临时重定向。与302类似。使用GET请求重定向 |
400 | Bad Request | 客户端请求的语法错误,服务器无法理解 |
401 | Unauthorized | 请求要求用户的身份认证 |
402 | Payment Required | 保留,将来使用 |
403 | Forbidden | 服务器理解请求客户端的请求,但是拒绝执行此请求 |
404 | Not Found | 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 |
405 | Method Not Allowed | 客户端请求中的方法被禁止 |
406 | Not Acceptable | 服务器无法根据客户端请求的内容特性完成请求 |
407 | Proxy Authentication Required | 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 |
408 | Request Time-out | 服务器等待客户端发送的请求时间过长,超时 |
409 | Conflict | 服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突 |
410 | Gone | 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 |
411 | Length Required | 服务器无法处理客户端发送的不带Content-Length的请求信息 |
412 | Precondition Failed | 客户端请求信息的先决条件错误 |
413 | Request Entity Too Large | 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息 |
414 | Request-URI Too Large | 请求的URI过长(URI通常为网址),服务器无法处理 |
415 | Unsupported Media Type | 服务器无法处理请求附带的媒体格式 |
416 | Requested range not satisfiable | 客户端请求的范围无效 |
417 | Expectation Failed(预期失败) | 服务器无法满足请求头中 Expect 字段指定的预期行为。 |
418 | I’m a teapot | 状态码 418 实际上是一个愚人节玩笑。它在 RFC 2324 中定义,该 RFC 是一个关于超文本咖啡壶控制协议(HTCPCP)的笑话文件。在这个笑话中,418 状态码是作为一个玩笑加入到 HTTP 协议中的。 |
500 | Internal Server Error | 服务器内部错误,无法完成请求 |
501 | Not Implemented | 服务器不支持请求的功能,无法完成请求 |
502 | Bad Gateway | 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应 |
503 | Service Unavailable | 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 |
504 | Gateway Time-out | 充当网关或代理的服务器,未及时从远端服务器获取请求 |
505 | HTTP Version not supported | 服务器不支持请求的HTTP协议的版本,无法完成处理 |
重定向状态码
重定向就是通过各种方法将各种网络请求重新定个方向转到其它位置,此时这个服务器相当于提供了一个引路的服务。且返回的响应response报头里会携带http的响应属性Location:new url。此时就会重定向到新的url网址。
- 重定向又可分为临时重定向和永久重定向,其中状态码301表示的就是永久重定向,而状态码302和307表示的是临时重定向。
临时重定向:
进行临时重定向时需要用到Location字段,Location字段是HTTP报头当中的一个属性信息,该字段表明了你所要重定向到的目标网站。
- 验证网页重定向:
当我们访问网页时,跳转为了qq的网页
(让服务器指导浏览器,让浏览器访问新的地址)
临时重定向常用于登陆页面的跳转
永久重定向:
只需要将HTTP响应当中的状态码改为301,然后跟上对应的状态码描述即可:
临时重定向 VS 永久重定向
- 一个网站1如果临时不想被访问就用 302 暂时重定向 重定向到网站2;一个网站1如果永久不想被访问就用 301 永久重定向 重定向到网站2;
种类 | 文件扩展名 | Content-Type(Mime-Type) |
---|---|---|
2003 Excel | .xls | application/vnd.ms-excel |
2010 Excel | .xlsx | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
文本文件 | .txt | text/plain |
图片 | .png/.jpg/.gif | image/* |
页面 | .htm/.html | text/html |
视频 | .avi/ .mpg/ .mpeg/ .mp4 | video/* |
音频 | .mp3/ .wav/ | audio/* |
application/pdf |
HTTP常见Header
- Content-Type: 数据类型(text/html等),比如 Content-Type: text/html; charset=utf-8 用于表示正文是 text/html文档类型,字符集为utf-8,不区分大小写 charset=UTF-8 也可以)
- Content-Language:用于表示用户希望采用的语言或语言组合,比如 Content-Language: de-DE 表示该文件为说德语的人提供,但是要注意者不代表文件内容就是德语的。这里理解 Content-Type 和 Content-Language 区别: Content-Language更多表示上层语言的表示, 而Content-Type用于底层数据编码的表示
- Content-Length: Body(正文)的长度
- Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;User-Agent: 声明用户的操作系统和浏览器版本信息;
- User-Agent: 声明用户的操作系统和浏览器版本信息;
- referer: 当前页面是从哪个页面跳转过来的;
- location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
- Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
cookie
HTTP实际上是一种无状态协议,HTTP的每次请求/响应之间是没有任何关系的,但你在使用浏览器的时候发现并不是这样的。
-
比如当你登录一次B站后,就算你把B站关了甚至是重启电脑,当你再次打开B站时,B站并没有要求你再次输入账号和密码,这实际上是通过cookie技术实现的,点击浏览器当中锁的标志就可以看到对应网站的各种cookie数据。
-
这些cookie数据实际都是对应的服务器方写的,如果你将对应的某些cookie删除,那么此时可能就需要你重新进行登录认证了,因为你删除的可能正好就是你登录时所设置的cookie信息。
-
cookie是一种保存在客户端的小型文本文件,用于保存服务器通过Set-Cookie字段返回的数据,在下次请求服务器时通过Cookie字段将内容发送给服务器。是HTTP进行客户端状态维护的一种方式。而Set-Cookie以及Cookie字段可以包含有多条信息,也可以由多个Cookie及-Set-Cookie字段进行传输多条信息,并且cookie有生命周期,在超过生命周期后cookie将失效,对应的cookie文件将被删除。
cookie的失效时间:
- Cookie的Expires属性指定了cookie的生存期,默认情况下coolie是暂时存在的,他们存储的值只在浏览器会话期间存在,当用户退出浏览器后这些值也会丢失,如果想让cookie存在一段时间,就要为expires属性设置为未来的一个过期日期。现在已经被max-age属性所取代,max-age用秒来设置cookie的生存期。当没有设定过期时间时,则退出当前会话时cookie失效
如果没有为Cookie指定失效时间,则设置的Cookie将在何时失效?
- 如果不设置过期时间,则表示这个cookie生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览会话期的cookie被称为会话cookie。会话cookie一般不保存在硬盘上而是保存在内存里。
- 如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie依然有效直到超过设定的过期时间。
内存级别 vs 文件级别:
cookie就是在浏览器当中的一个小文件,文件里记录的就是用户的私有信息。cookie文件可以分为两种,一种是内存级别的cookie文件,另一种是文件级别的cookie文件。
- 将浏览器关掉后再打开,访问之前登录过的网站,如果需要你重新输入账号和密码,说明你之前登录时浏览器当中保存的cookie信息是内存级别的。
- 将浏览器关掉甚至将电脑重启再打开,访问之前登录过的网站,如果不需要你重新输入账户和密码,说明你之前登录时浏览器当中保存的cookie信息是文件级别的。
cookie的登陆策略:
因为HTTP是一种无状态协议,如果没有cookie的存在,那么每当我们要进行页面请求时都需要重新输入账号和密码进行认证,这样太麻烦了。
- 就比如你是爱奇艺的VIP会员,你每次点击一个VIP视频都要重新进行VIP身份认证。而HTTP不支持记录用户状态,那么我们就需要有一种独立技术来帮我们支持,这种技术目前现在已经内置到HTTP协议当中了,叫做cookie。
cookie的登陆策略如下:
- 当第一次登陆某网站时,客户端向服务器输入我们的用户名和密码
- 服务器把 cookie用户名&&密码返回给客户端(在HTTP请求中的Cookie是明文传递的)
- 客户端下次登录 自动携带浏览器访问该网站对应的cookie文件中的内容,这样就能保持登录
总的来说就是第一次登录时,服务器就会进行Set-Cookie的设置(Set-Cookie也是HTTP报头当中的一种属性信息)。当认证通过并在服务端进行Set-Cookie设置后,服务器在对浏览器进行HTTP响应时就会将这个Set-Cookie响应给浏览器。而浏览器收到响应后会自动提取出Set-Cookie的值,将其保存在浏览器的cookie文件当中,此时就相当于我的账号和密码信息保存在本地浏览器的cookie文件当中。
- 也可以携带多个cookie
cookie的风险:
仅仅是cookie的登陆策略会存在安全隐患:
- 当你不小心被迫下载了木马病毒,该病毒会扫描你的浏览器当中的cookie目录,它就会把所有的cookie信息通过网络的方式传给黑客,你的cookie用户名密码可能会被盗取,黑客会拿着你的cookie去登录,更严重的是黑客会修改你的cookie密码对账号产生威胁。
- 因此单纯的使用cookie是非常不安全的,因为此时cookie文件当中就保存的是你的私密信息,一旦cookie文件泄漏你的隐私信息也就泄漏。
解决办法就是使用cookie + session的登陆策略。
cookie + session
session介绍:
- session服务器为了保存用户状态而创建的临时会话,或者说一个特殊的对象,保存在服务器中,将会话ID通过cookie进行传输即可,就算会话ID被获取利用,但是session中的数据并不会被恶意程序获取,这一点相对cookie来说就安全了一些,但是session也存在一些缺陷,需要建立专门的session集群服务器,并且占据大量的存储空间(要保存每个客户端信息)
cookie + session的登陆策略:
- 当前主流的服务器还引入了session_id这样的概念,当我们第一次登录某个网站输入账号和密码后,服务器认证成功后还会服务端生成一个对应的session文件(文件名具备唯一性),用户的临时私密信息,会保存在这个文件中。
- 此时当认证通过后服务端在对浏览器进行HTTP响应时,仅仅会将生成的session_id值响应给浏览器。浏览器收到响应后会自动提取出session_id的值,将其保存在浏览器的cookie文件当中。后续访问该服务器时,对应的HTTP请求当中就会自动携带上这个session_id。
- 而服务器识别到HTTP请求当中包含了session_id,就会提取出这个session_id,然后再到对应的集合当中进行对比,对比成功就说明这个用户是曾经登录过的,此时也就自动就认证成功了,然后就会正常处理你发来的请求,这就是我们当前主流的工作方式。
session是相对安全的:
引入session_id之后,浏览器当中的cookie文件保存的是session_id,此时这个cookie文件同样可能被盗取。此时用户的账号和密码虽然不会泄漏了,但用户对应的session_id是会泄漏的,非法用户仍然可以盗取我的session_id去访问我曾经访问过的服务器,相当于还是存在刚才的问题。
- 之前的工作方式就相当于把账号和密码信息在浏览器当中再保存一份,每次请求时都自动将账号和密码的信息携带上,但是账号和密码一直在网当中发送太不安全了。
- 因此现在的工作方式是,服务器只有在第一次认证的时候需要在网络中传输账号和密码,此后在网络上发送的都是session_id。
这种方法虽然没有真正解决安全问题,但这种方法是相对安全的。互联网上是不存在绝对安全这样的概念的,任何安全都是相对的,就算你将发送到网络当中的信息进行加密,也有可能被别人破解。
Connection
Connection: closed —— 短链接(http/1.0)
- 短链接一次只能处理一条http请求
- 用户所看到的完整的网页内容——背后可能是无数次http请求,每个图片就是一个文件,就需要一次请求
- http底层主流采用的就是tcp协议,每处理一次请求就会进行一次 三次握手与四次挥手链接;一个网页有上百次http请求,就要进行上百次的 三次握手与四次挥手。则短链接不再适用。
Connection: keep-aliye —— 长链接(http/1.1)
- 双方都同意采用长链接方案时,请求和响应中都携带了 Connection: keep-aliye ,客户端建立一个tcp链接,这一个tcp链接发送多次http请求,服务器接收后通过这个链接返回给客户端多次响应,当所有响应全部返回,此链接才断开。不用再向短链接那样重复建立链接了,大大提高了效率。
http协议无连接解释:
- HTTP定义:超文本传输协议,是一个 无连接,无状态的应用层协议。
- http协议底层是tcp,tcp是面向连接的,http只是使用了tcp的连接能力,但是http本身是无链接的。
简单的HTTP服务器代码
HttpServer.cc
#include "HttpServer.hpp"
#include <iostream>
#include <memory>
using namespace std;
int main(int argc,char *argv[])
{
if(argc != 2)
{
exit(1);
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<HttpServer> svr(new HttpServer(port));
svr->Start();
return 0;
}
HttpServer.hpp
#pragma once
#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/socket.h>
#include <sys/types.h>
#include "Socket.hpp"
#include "Log.hpp"
#include <pthread.h>
#include <unordered_map>
const std::string wwwroot="./wwwroot";
const std::string sep = "\r\n";
const std::string homepage = "index.html";
static const int defaultport = 8080;
class HttpServer;
class ThreadData
{
public:
ThreadData(int fd, HttpServer *s) : sockfd(fd), svr(s)
{}
public:
int sockfd;
HttpServer *svr;
};
class HttpRequest{
public:
void Deserialize(std::string req)
{
while(true)
{
std::size_t pos = req.find(sep);
if(pos==std::string::npos)break;
std::string temp = req.substr(0,pos);
if(temp.empty())break;
req_header.push_back(temp);
req.erase(0,pos+sep.size());
}
text = req;
}
void Parse()
{
std::stringstream ss(req_header[0]);
ss>> method >> url >> http_version;
file_path = wwwroot;//./wwwroot
if(url == "/"|| url == "/index.html")
{
file_path += "/";
file_path += homepage;
}else
{
///a/b/c.html -> ./wwwroot/a/b/c.html
file_path += url;
}
auto pos = file_path.rfind(".");
if(pos == std::string::npos) suffix = ".html";
else suffix = file_path.substr(pos);
}
void DebugPrint()
{
for(auto &line : req_header)
{
std::cout << "------------------------------"<<std::endl;
std::cout << line << "\n\n";
}
std::cout<< "method: " <<method<<std::endl;
std::cout<< "url: " <<url<<std::endl;
std::cout<< "file_path: " <<file_path<<std::endl;
std::cout<< "http_version: " <<http_version<<std::endl;
std::cout << text <<std::endl;
}
public:
std::vector<std::string> req_header;
std::string text;
//解析后的结果
std::string method;
std::string url;
std::string http_version;
std::string file_path;
std::string suffix;
};
class HttpServer{
public:
HttpServer(uint16_t port = defaultport):port_(port){
content_type.insert({".html","text/html"});
content_type.insert({".png","image/png"});
}
bool Start()
{
listensock_.Socket();
listensock_.Bind(port_);
listensock_.Listen();
for(;;)
{
std::string clientip;
uint16_t clientport;
int sockfd = listensock_.Accept(&clientip,&clientport);
if(sockfd<0)
{
continue;
}
lg(Info, "get a new connect, sockfd: %d", sockfd);
pthread_t tid;
ThreadData *td = new ThreadData(sockfd,this);
pthread_create(&tid,nullptr,ThreadRun,td);
}
}
static std::string ReadHtmlContent(const std::string &htmlpath)
{
std::ifstream in(htmlpath,std::ios::binary);//读图片要以二进制的方式
if(!in.is_open())return "";
in.seekg(0,std::ios_base::end);
auto len = in.tellg();
in.seekg(0,std::ios_base::beg);
std::string content;
content.resize(len);
in.read((char*)content.c_str(),content.size());//content.c_str()是字符串指针
// std::string content;
// std::string line;
// while(std::getline(in,line))
// {
// content+=line;
// }
in.close();
return content;
}
std::string SuffixToDesc(const std::string &suffix)
{
auto iter = content_type.find(suffix);
if(iter == content_type.end())return content_type[".html"];
else return content_type[suffix];
}
void HandlerHttp(int sockfd){
char buffer[10240];
ssize_t n = recv(sockfd,buffer,sizeof(buffer)-1,0);
if(n >0)
{
buffer[n] = 0;
std::cout << buffer;
//假设读过来的buffer是完整的
HttpRequest req;
req.Deserialize(buffer);
req.Parse();
req.DebugPrint();
//返回响应的过程
std::string text;
bool ok = true;
text = ReadHtmlContent(req.file_path);
if(text.empty())
{
ok = false;
std::string err_html = wwwroot;
err_html += "/";
err_html += "err.html";
text = ReadHtmlContent(err_html);
}
std::string response_line;
if(ok)
{
response_line = "HTTP/1.0 200 OK\r\n";
}else{
response_line = "HTTP/1.0 404 Not Found\r\n";
}
//response_line = "HTTP/1.0 302 Found\r\n";
std::string response_header = "Content-Length: ";
response_header += std::to_string(text.size());
response_header +="\r\n";
response_header +="Content-Type: ";
response_header +=SuffixToDesc(req.suffix);
response_header +="\r\n";
response_header += "Set-Cookie: name=haha";
response_header +="\r\n";
response_header += "Set-Cookie: passwd=123123";
response_header +="\r\n";
//response_header +="Location: https://www.qq.com\r\n";
std::string blank_line = "\r\n"; // \n
std::string response = response_line;
response += response_header;
response += blank_line;
response += text;
send(sockfd,response.c_str(),response.size(),0);
}
close(sockfd);
}
static void *ThreadRun(void *args)//C++类中的线程处理函数必须为static
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData*>(args);
td->svr->HandlerHttp(td->sockfd);
delete td;//这一步不能忘!!!,记得要释放资源
return nullptr;
}
~HttpServer(){}
private:
Sock listensock_;
uint16_t port_;
std::unordered_map<std::string,std::string> content_type;
};
Socket.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
Log lg;
enum
{
SocketErr =2,
BindErr,
ListenErr
};
const int backlog = 10;
class Sock{
public:
Sock()
{}
~Sock()
{}
public:
void Socket()
{
sockfd_ = socket(AF_INET,SOCK_STREAM,0);
if(sockfd_<0)
{
lg(Fatal,"socker error, %s: %d",strerror(errno),errno);
exit(SocketErr);
}
int opt = 1;
setsockopt(sockfd_,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
}
void Bind(uint16_t port)
{
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if(bind(sockfd_,(struct sockaddr *)&local,sizeof(local))<0)
{
lg(Fatal,"bind error, %s: %d",strerror(errno),errno);
exit(BindErr);
}
}
void Listen()
{
if(listen(sockfd_,backlog)<0)
{
lg(Fatal,"listen error, %s: %d",strerror(errno),errno);
exit(ListenErr);
}
}
int Accept(std::string *clientip,uint16_t *clientport)
{
struct sockaddr_in peer;//获取客户端信息
socklen_t len = sizeof(peer);
int newfd = accept(sockfd_,(struct sockaddr*)&peer,&len);
if(newfd < 0)
{
lg(Warning, "accept error, %s: %d", strerror(errno), errno);
return -1;
}
char ipstr[64];
inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
*clientip = ipstr;
*clientport = ntohs(peer.sin_port);
return newfd;
}
bool Connect(const std::string &ip,const uint16_t &port)
{
struct sockaddr_in peer;
memset(&peer,0,sizeof(peer));
socklen_t len = 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(sockfd_, (struct sockaddr*)&peer, sizeof(peer));
if(n == -1)
{
std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
return false;
}
return true;
}
void Close()
{
close(sockfd_);
}
int Fd()
{
return sockfd_;
}
private:
int sockfd_;
};
Log.hpp
#pragma once
#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#define SIZE 1024
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
#define Screen 1
#define Onefile 2
#define Classfile 3
#define LogFile "log.txt"
class Log
{
public:
Log()
{
printMethod = Screen;
path = "./log/";
}
void Enable(int method)
{
printMethod = method;
}
std::string levelToString(int level)
{
switch (level)
{
case Info:
return "Info";
case Debug:
return "Debug";
case Warning:
return "Warning";
case Error:
return "Error";
case Fatal:
return "Fatal";
default:
return "None";
}
}
void printLog(int level, const std::string &logtxt)
{
switch (printMethod)
{
case Screen:
std::cout << logtxt << std::endl;
break;
case Onefile:
printOneFile(LogFile, logtxt);
break;
case Classfile:
printClassFile(level, logtxt);
break;
default:
break;
}
}
void printOneFile(const std::string &logname, const std::string &logtxt)
{
std::string _logname = path + logname;
int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
if (fd < 0)
return;
write(fd, logtxt.c_str(), logtxt.size());
close(fd);
}
void printClassFile(int level, const std::string &logtxt)
{
std::string filename = LogFile;
filename += ".";
filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
printOneFile(filename, logtxt);
}
~Log()
{
}
void operator()(int level, const char *format, ...)
{
time_t t = time(nullptr);
struct tm *ctime = localtime(&t);
char leftbuffer[SIZE];
snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
va_list s;
va_start(s, format);
char rightbuffer[SIZE];
vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
va_end(s);
// 格式:默认部分+自定义部分
char logtxt[SIZE * 2];
snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);
// printf("%s", logtxt); // 暂时打印
printLog(level, logtxt);
}
private:
int printMethod;
std::string path;
};