【Linux网络编程】自定义协议+HTTP协议

news2024/11/23 7:53:00

【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地址即可?因为服务器不会轻易改变端口号,端口号相当于众所周知的,浏览器会自动为我们添加端口号

特定的众所周知服务,端口号必须是确定的!常见协议对应的端口号如下:

协议名称对应端口号
HTTP80
HTTPS443
SSH22
  • 注意:用户自己写的网络服务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

  1. GET通过url传参
  2. POST通过正文传参
  3. GET方法传参不私密(因为GET会把用户输入的有效信息用户名,密码等回显到浏览器)
  4. POST方法因为通过正文传参,所以,相对比较私密一些(因为一些小白一般不会抓包看正文,所以相对私密)
  5. GET通过url传参,POST通过正文传参,所以一般一些比较大的内容都是通过post方式传参的
  6. HTTP GET请求提交参数有长度限制;HTTP POST请求提交参数没有长度限制。

解释6:Http Get方法提交的数据大小长度并没有限制,HTTP协议规范没有对URL长度进行限制。但是特定的浏览器及服务器对URL有限制,所以还是有限制的; 因为POST方法通过正文传参,理论上讲,POST是没有大小限制的。HTTP协议规范也没有进行大小限制,起限制作用的是服务器的处理程序的处理能力,而并非限制。

HTTP的状态码

状态码也就是字符串版本的描述

类型原因短语
1XXInformational(信息性状态码)接受的请求正在处理
2XXSuccess(成功状态码)请求正常处理完毕
3XXRedirection(重定向状态码)需要进行附加操作以完成请求
4XXClient Error(客户端错误状态码)服务器无法处理请求
5XXServer Error(服务器错误状态码)服务器处理请求出错

最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

  • 4XX:客户端错误——>客户请求了不存在的资源,即客户提出了无理的要求,是客户的错。
  • 5XX:服务器错误——>服务器代码中的内容错误,例如fork错误,就会返回5XX

HTTP状态码列表

100Continue继续。客户端应继续其请求
101Switching Protocols切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议
200OK请求成功。一般用于GET与POST请求
201Created已创建。成功请求并创建了新的资源
202Accepted已接受。已经接受请求,但未处理完成
203Non-Authoritative Information非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本
204No Content无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
205Reset Content重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域
206Partial Content部分内容。服务器成功处理了部分GET请求
300Multiple Choices多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
301Moved Permanently永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302Found临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303See Other查看其它地址。与301类似。使用GET和POST请求查看
304Not Modified未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
305Use Proxy使用代理。所请求的资源必须通过代理访问
306Unused已经被废弃的HTTP状态码
307Temporary Redirect临时重定向。与302类似。使用GET请求重定向
400Bad Request客户端请求的语法错误,服务器无法理解
401Unauthorized请求要求用户的身份认证
402Payment Required保留,将来使用
403Forbidden服务器理解请求客户端的请求,但是拒绝执行此请求
404Not Found服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
405Method Not Allowed客户端请求中的方法被禁止
406Not Acceptable服务器无法根据客户端请求的内容特性完成请求
407Proxy Authentication Required请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权
408Request Time-out服务器等待客户端发送的请求时间过长,超时
409Conflict服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突
410Gone客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置
411Length Required服务器无法处理客户端发送的不带Content-Length的请求信息
412Precondition Failed客户端请求信息的先决条件错误
413Request Entity Too Large由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息
414Request-URI Too Large请求的URI过长(URI通常为网址),服务器无法处理
415Unsupported Media Type服务器无法处理请求附带的媒体格式
416Requested range not satisfiable客户端请求的范围无效
417Expectation Failed(预期失败)服务器无法满足请求头中 Expect 字段指定的预期行为。
418I’m a teapot状态码 418 实际上是一个愚人节玩笑。它在 RFC 2324 中定义,该 RFC 是一个关于超文本咖啡壶控制协议(HTCPCP)的笑话文件。在这个笑话中,418 状态码是作为一个玩笑加入到 HTTP 协议中的。
500Internal Server Error服务器内部错误,无法完成请求
501Not Implemented服务器不支持请求的功能,无法完成请求
502Bad Gateway作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
503Service Unavailable由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
504Gateway Time-out充当网关或代理的服务器,未及时从远端服务器获取请求
505HTTP 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.xlsapplication/vnd.ms-excel
2010 Excel.xlsxapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet
文本文件.txttext/plain
图片.png/.jpg/.gifimage/*
页面.htm/.htmltext/html
视频.avi/ .mpg/ .mpeg/ .mp4video/*
音频.mp3/ .wav/audio/*
PDF.pdfapplication/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;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1646931.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一般实现分布式锁都有哪些方式?使用 Redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?

目录 1.Redis 分布式锁 &#xff08;1&#xff09;Redis 最普通的分布式锁 &#xff08;2&#xff09;RedLock 算法 2.zk 分布式锁 3.redis 分布式锁和zk分布式锁的对比 1.Redis 分布式锁 官方叫做 RedLock 算法&#xff0c;是 Redis 官方支持的分布式锁算法。 这个分布式…

[qnx] 通过zcu104 SD卡更新qnx镜像的步骤

0. 概述 本文演示如果给Xlinx zcu104开发板刷入自定义的qnx镜像 1.将拨码开关设置为SD卡启动 如下图所示&#xff0c;将1拨到On,2,3,4拨到Off&#xff0c;即为通过SD启动。 2.准备SD卡中的内容 首先需要将SD格式化为FAT32的&#xff08;如果已经是FAT32格式&#xff0c;则…

力扣每日一题114:二叉树展开为链表

题目 中等 提示 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同…

sass-loader和node-sass与node版本的依赖问题

sass-loader和node-sass与node版本的依赖问题 没有人会陪你走到最后&#xff0c;碰到了便是有缘&#xff0c;即使到了要下车的时候&#xff0c;也要心存感激地告别&#xff0c;在心里留下空白的一隅之地&#xff0c;多年后想起时依然心存甘味。——林清玄 报错截图 报错信息 np…

linux/windows安装Tomcat

安装tomcat 注意版本一致问题 Tomcat 10.1.0-M15(alpha)Tomcat 10.0.x-10.0.21Tomcat 9.0.x-9.0.63Tomcat 8.5.x-8.0.53规范版本 Servlet 6.0,JSP 3.1, EL 5.0 WebSocket 2.1&#xff0c;JASPIC 3.0 Servlet 5.0,JSP 3.0, EL 4.0 WebSocket 2.0&#xff0c;JASPIC 2.0 Serv…

20240507最新 ubuntu20.04安装ros noetic

ubuntu20.04安装ros 主要参考博客 【ROS】在 Ubuntu 20.04 安装 ROS 的详细教程_ubuntu20.04安装ros-CSDN博客 出现问题 1.ubuntu20.04 更换清华源报错 ubuntu20.04 更换清华源报错_gvfs metadata is not supported. fallback to teplme-CSDN博客 &#xff1f;&#xff1f…

CNN-BiLSTM-Attention(12种算法优化CNN-BiLSTM-Attention多输入单输出)

12种算法优化CNN-BiLSTM-Attention模型预测的代码。其中Attention模型可以改为单头或者多头&#xff0c;在代码中就是改个数字而已。代码注释已写好如何更改。 12种算法优化CNN-BiLSTM-Attention多特征输入单步预测代码获取戳此处代码获取戳此处代码获取戳此处 主要功能为:采用…

数据库SQL语言实战(七)

前言 这次的有一点点难~~~~~我也写了好久 练习题 题目一 在学生表pub.student中统计名字&#xff08;姓名的第一位是姓氏&#xff0c;其余为名字&#xff0c;不考虑复姓&#xff09;的使用的频率&#xff0c;将统计结果放入表test5_01中 create table test5_01(First_name…

【一步一步了解Java系列】:探索Java基本类型转换的秘密

看到这句话的时候证明&#xff1a;此刻你我都在努力~ 加油陌生人~ 个人主页&#xff1a; Gu Gu Study ​​ 专栏&#xff1a;一步一步了解Java 喜欢的一句话&#xff1a; 常常会回顾努力的自己&#xff0c;所以要为自己的努力留下足迹。 如果喜欢能否点个赞支持一下&#…

ComfyUI 基础教程(十四):ComfyUI中4种实现局部重绘方法

在ComfyUI中有多种方式可以实现局部重绘,简单的方式是使用VAE内补编码器进行局部重绘,也可以用Fooocus inpaint进行局部重绘,还可以用controlNet的inpaint模型进行局部重绘,以及使用Clip seg蒙版插件! 本篇介绍使用VAE內补编码器进行局部重绘的方法。 1、VAE内补编码器 局…

【docker】docker compose 搭建私服

安装 Docker Registry 创建目录 mkdir -pv /usr/local/docker/registrymkdir -pv /usr/local/docker/data 创建 docker-compose.yml文件 进入目录创建docker-compose.yml cd /usr/local/docker/registrytouch docker-compose.yml 编辑docker-compose.yml vim docker-compo…

ASRPRO

https://gitee.com/gitee-128/ASR-PRO-U8G2/tree/main 不下载模型 语音就是天问51唤醒我 u8g2的移植过程 第一步 下载u8g2的源代码 第二步 修改 delay and 函数 第三步 添加头文件 显示 显示 动画 SPI I2C(SOFT SPI ;SOFT I2C U8G2 移植过程&#xff08;移植过程参考…

JUC基础概念

文章目录 JUC的基础概念什么是JUC进程与线程并行与并发线程的五种状态JUC的架构 JUC的基础概念 什么是JUC JUC 是 Java.utils.concurrent 包内的类&#xff0c;是为了开发者可以在多线程的情况下减少竞争条件和防止死锁而出现的。 进程与线程 进程&#xff1a;一个进程包含…

【LeetCode算法】389. 找不同

提示&#xff1a;此文章仅作为本人记录日常学习使用&#xff0c;若有存在错误或者不严谨得地方欢迎指正。 文章目录 一、题目二、思路三、解决方案 一、题目 给定两个字符串 s 和 t &#xff0c;它们只包含小写字母。字符串 t 由字符串 s 随机重排&#xff0c;然后在随机位置添…

大模型_基于医疗领域用lora微调ChatDoctor模型

文章目录 ChatDoctor目标方法结果结论收集和准备医患对话数据集创建外部知识数据库具有知识大脑的自主聊天医生的开发模型培训结果数据和模型&#xff1a; 微调推理 ChatDoctor 目标 这项研究的主要目的是通过创建一个在医学建议中具有更高准确性的专业语言模型&#xff0c;来…

[Flutter]创建一个私有包并使用

在Flutter中创建一个自己的私有组件&#xff08;通常称为包或库&#xff09;&#xff0c;并通过Dart的包管理工具pub进行使用。 一、创建一个新的Flutter包 1.使用命令行创建 使用Flutter命令行工具来创建一个新的包&#xff1a; $ flutter create --templatepackage my_pri…

ECS弹性云服务器居然这么好用。

引言 在过去的十年里&#xff0c;云计算从一个前沿概念发展为企业和开发者的必备工具。传统的计算模型通常局限于单一的、物理的位置和有限的资源&#xff0c;而云计算则通过分布式的资源和服务&#xff0c;为计算能力带来了前所未有的"弹性"。 云弹性服务器——为什…

N9048B PXE EMI 测试接收机,1 Hz 至 44 GHz

​ _EMI_ N9048B EMI 测试接收机 1 Hz 至 44 GHz Keysight N9048B PXE 是一款符合标准的 EMI 测试接收机&#xff0c;配有射频预选器和 LNA 设计。其实时扫描&#xff08;RTS&#xff09;功能有助于您缩短总体测试时间&#xff0c;轻松执行无间隙的信号捕获和分析。 特点 …

【人工智能基础】GAN与WGAN实验

一、GAN网络概述 GAN&#xff1a;生成对抗网络。GAN网络中存在两个网络&#xff1a;G&#xff08;Generator&#xff0c;生成网络&#xff09;和D&#xff08;Discriminator&#xff0c;判别网络&#xff09;。 Generator接收一个随机的噪声z&#xff0c;通过这个噪声生成图片…

视频改字祝福 豪车装X系统源码uniapp前端小程序源码

视频改字祝福 豪车装X系统源码uniapp前端小程序源码&#xff0c;创意无限&#xff01;AI视频改字祝福&#xff0c;豪车装X系统源码开源&#xff0c;打造个性化祝 福视频不再难&#xff01; 想要为你的朋友或家人送上一份特别的祝福&#xff0c;让他们感受到你的真诚与关怀吗&am…