Linux网络基础 — 应用层

news2024/12/27 11:17:56

目录

应用层

 再谈 "协议"

 网络版计算器

HTTP协议

 认识URL

urlencode和urldecode

HTTP协议格式

HTTP请求 

HTTP响应

HTTP的方法

HTTP的状态码

HTTP常见Header

拓展知识(了解)

长链接

http周边会话保持

基本工具(http)


应用层

程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层

 再谈 "协议"

        协议是一种 "约定". socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢?

        这个"结构化的数据"是什么呢?比如我们在用聊天软件聊天时,发送的消息只有文字吗?当然不是,我们发送的数据中会包含:头像,昵称,时间和内容等,它们是一个整体。

        "结构化的数据"在发送到网络中的时候,先序列化在发送,收到的数据一定是序列字节流,要先进行反序列化,然后才能使用。

 网络版计算器

实现一个服务器版的计算器. 我们需要客户端把要计算的两个数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端。

在原有的tcp网络通信的基础上,添加了几个组件:

  • 处理计算任务
  • 读取一个完整的请求。因为tcp是面向字节流的,所以怎样读取一个完整的请求也需要我们自己处理;
  • 自定义协议报头,定制接口用于添加和删除报头;
  • 将结构化的数据进行序列化和反序列化;(自己实现和使用工具)

calServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h> 

#include "log.hpp"
#include "Protocol.hpp"



namespace Server
{
    // using namespace std;

    static const uint16_t gport = 8080;
    static const int gbacklog = 5;

    // const Request& req 输入性参数 | Response& resp 输出型参数
    typedef function<bool(const Request& req,Response& resp)> func_t;

    void handlerEnter(int sock,func_t func)
    {
        string inbuffer;
        while(true)
        {
            // 1.读取数据 "content_len"\r\n"x op y"\r\n
            // 1.1保证读到的是完整的请求
            string req_text,req_str;

            // 1.2 到这 req_text里面一定是一个完整的请求:"content_len"\r\n"x op y"\r\n
            if(!recvPackage(sock,inbuffer,&req_text))   //读取一个完整的请求
                return;
            // cout << "带报头的请求:\n" << req_text << endl;
            if(!deLength(req_text,&req_str))    //去掉报头
                return;
            // cout << "去掉报头的正文:" << req_str << endl;
                
            // 2.反序列化
            // 2.1得到一个结构化的对象
            Request req;
            if(!req.deserialize(req_str))  
                return;

            // 3.进行处理
            // 3.1得到一个结构化的响应
            Response resp;
            func(req,resp);  //req的处理结果,全部放到了resp中

            // 4.对响应的Response 进行序列化
            // 4.1得到一个  "字符串"
            string resp_str;
            resp.serialize(&resp_str);
            // cout << "计算完成,对响应进行序列化:" << resp_str << endl;
            // 5.发送响应
            // 5.1 构建一个完整的报文
            string send_string = enLength(resp_str);
            // cout << "构建完整的报文:\n" << send_string << endl;
            send(sock,send_string.c_str(),send_string.size(),0);    //发送
        }
    }

    class calServer
    {
    public:
        calServer(const uint16_t& port = gport)
            :_listensock(-1),_port(port)
        {}
        void initServer()
        {
            //1.创建socket文件套接字对象
            _listensock = socket(AF_INET,SOCK_STREAM,0);
            if(_listensock < 0)
            {
                logMessage(FATAL,"create socket error");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL,"create socket success: %d",_listensock);

            // 2.bind 绑定自己的网络信息
            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(_listensock,(struct sockaddr*)&local,sizeof(local)) < 0)
            {
                logMessage(FATAL,"bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL,"bind socket success");

            //3. 设置socket为监听状态
            if(listen(_listensock,gbacklog) < 0)
            {
                logMessage(FATAL,"listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL,"listen socket success");


        }
        void start(func_t func)
        {

            for( ; ; )  //死循环
            {
                //4. Server获取新链接
                //sock 是用于和client进行通信的
                struct sockaddr_in peer;
                socklen_t peer_len = sizeof(peer);
                int sock = accept(_listensock,(struct sockaddr*)&peer,&peer_len);
                if(sock < 0)
                {
                    logMessage(ERROR,"accept error,next");
                    continue;
                }
                logMessage(NORMAL,"accept a new link success sock:%d",sock);
            
                //5. 未来通信就用这个sock,面向字节流的,后面全是文件操作                
                // //version 2.1 多进程版
                pid_t id = fork();
                if(id == 0) //子进程
                {
                    close(_listensock);
                    if(fork() > 0) exit(0); //让孙子进程执行代码,子进程退出被父进程回收,孙子进程会变成孤儿进程

                    // serviceIO(sock);
                    handlerEnter(sock,func);
                    close(sock);
                    exit(0);
                }
                //父进程
                pid_t ret = waitpid(id,nullptr,0);
                close(sock);
                if(ret > 0)
                {
                    logMessage(NORMAL,"wait child success");
                }
                // //------------------------------------------------------------------------------------
                // //version 2.2 多进程版 信号方式
                // signal(SIGCHLD,SIG_IGN);    //信号忽略,忽略对子进程的管理

                // pid_t id = fork();
                // if(id == 0) //子进程
                // {
                //     close(_listensock);

                //     serviceIO(sock);
                //     close(sock);
                //     exit(0);
                // }
                // close(sock);

            }
        }
           
        ~calServer()
        {}
    private:
        int _listensock;    //不是用来数据通信的,它是监听链接是否到来的,用于获取新链接的
        uint16_t _port;
    } ;
    
}//namespace end Server 

calServer.cc

#include "calServer.hpp"


#include <memory>

using namespace Server;
// 提示 运行格式
static void Usage(string proc)
{
    cout<<"Usage:\n\t" << proc <<" local_port\n\n";
}
// 计算客户端发来的任务
// req:里面是一个处理好的完整的请求对象
// resp:根据req进行业务处理,填充resp,不用管理任何读取和写入,序列化和反序列化等细节,在这之前已经处理好了
bool cal(const Request& req,Response& resp)
{
    // req 里已经有结构化的数据了,直接使用即可
    resp._exitcode = OK;
    resp._result = OK;
    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._exitcode = DEV_ZERO;
            else 
                resp._result = req._x / req._y;
        }
            break;    
        case '%':
        {
            if(req._y == 0) 
                resp._exitcode = MOD_ZERO;
            else 
                resp._result = req._x % req._y;
        }
            break;  
        default:
            resp._exitcode = OP_ERROR;
            break;                      
    }
    return true;
}

// ./calServer local_port
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);

    unique_ptr<calServer> tsvr(new calServer(port));
    tsvr->initServer();
    tsvr->start(cal);

    return 0;
}

Protocol.hpp

#pragma once

#include <iostream>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <jsoncpp/json/json.h>

#define SEP " "
#define SEP_LEN strlen(SEP)             //不能使用sizeof
#define LINE_SEP "\r\n"
#define LINE_SEP_LIN strlen(LINE_SEP)   //不能使用sizeof

enum{ OK = 0,DEV_ZERO,MOD_ZERO,OP_ERROR};

//定制协议,添加报头
// "x op y" -> "content_len"\r\n"x op y"\r\n
string enLength(const string& text) 
{
    string send_str = to_string(text.size());
    send_str += LINE_SEP;
    send_str += text;
    send_str += LINE_SEP;

    return send_str;
}
//定制协议,去掉报头
// "content_len"\r\n"exitcode result"\r\n -> "exitcode result"
bool deLength(const string& package,string* text)
{
    auto pos = package.find(LINE_SEP);
    if(pos == string::npos)
        return false;

    string text_len_str = package.substr(0,pos);
    int text_len = stoi(text_len_str);
    *text = package.substr(pos+LINE_SEP_LIN,text_len);

    return true;    
}
// 读取一个完整的请求
// "content_len"\r\n"x op y"\r\n
bool recvPackage(int sock,string& inbuffer,string* text)
{

    char buffer[1024];
    while(true)
    {
        ssize_t n = recv(sock,buffer,sizeof(buffer)-1,0);
        if(n > 0)
        {
            buffer[n] = 0;
            inbuffer += buffer;
            // 分析处理
            auto pos = inbuffer.find(LINE_SEP);
            if(pos == string::npos) //一个请求都不完整,继续读取
                continue;
            string text_len_str = inbuffer.substr(0,pos);
            int text_len = stoi(text_len_str);

            // text_len_str.size() + 2*LINE_SEP_LIN +text_len <= inbuffer.size() 证明至少有一个完整的请求 
            int total_len = text_len_str.size() + 2*LINE_SEP_LIN +text_len;
            if(inbuffer.size() < total_len)//一个请求都不完整,继续读取
                continue;
            // cout << "inbuffer处理前: \n" << inbuffer << endl;
            // 至少有一个完整的请求 
            *text = inbuffer.substr(0,total_len);
            inbuffer.erase(0,total_len);

            // cout << "inbuffer处理后: \n" << inbuffer << endl;
            break;
        }
        else return false;
    }
    return true;
}
//由于是阻塞式读取,所以这种方法暂时用不了
// bool recvRequestAll(int sock,vector<string>* out)
// {
//     string line;
//     while(recvRequest(sock,&line))
//         out->push_back(line);
// }

class Request
{
public:
    Request()
        :_x(0),_y(0),_op(0)
    {} 
    Request(int x,int y,char op)
        :_x(x),_y(y),_op(op)
    {} 
    //序列化,通过条件编译可以随意切换,是用自己写的,还是使用工具
    bool serialize(string* out)
    {
#ifdef MYSELF 
        //结构化数据 -> "x op y"
        *out = "";
        string x_str = to_string(_x);
        string y_str = to_string(_y);

        *out = x_str;   
        *out += SEP;
        *out += _op;    
        *out += SEP;
        *out += y_str;  
#else   //使用Json工具
        Json::Value root;
        root["first"] = _x;
        root["second"] = _y;
        root["oper"] = _op;

        Json::FastWriter writer;
        *out = writer.write(root);

#endif
        return true;
    }
    //反序列化
    bool deserialize(const string& in)
    {
        //"x op y" -> 结构化数据
#ifdef MYSELF 
        auto left = in.find(SEP);
        auto right = in.rfind(SEP);

        if(left == string::npos || right == string:: npos || left == right)
            return false;
        if(right- (left + SEP_LEN) != 1) 
            return false;
        string x_str = in.substr(0,left);
        string y_str = in.substr(right+SEP_LEN);
        if(x_str.empty() || y_str.empty())
            return false;

        _x = stoi(x_str);
        _y = stoi(y_str);
        _op = in[left+SEP_LEN];
#else
        Json::Value root;
        Json::Reader reader;
        reader.parse(in,root);
        _x = root["first"].asInt();
        _y = root["second"].asInt();
        _op = root["oper"].asInt();

#endif
        return true;
    }
public:
    // "x op y"
    int _x;
    int _y;
    char _op;
};

class Response
{
public:
    Response()
        :_exitcode(0),_result(0)
    {}
    Response(const int exitcode,int result)
        :_exitcode(exitcode),_result(result)
    {}
    //自己写
    //序列化
    bool serialize(string* out)
    {
#ifdef MYSELF 
        *out = "";
        string ec_str = to_string(_exitcode);
        string re_str = to_string(_result);

        *out = ec_str;
        *out += SEP;
        *out += re_str;
#else
        Json::Value root;
        root["exitcode"] = _exitcode;
        root["result"] = _result;
        Json::FastWriter writer;
        *out = writer.write(root);

#endif
        return true;
    }
    //反序列化
    bool deserialize(const string& in)
    {
#ifdef MYSELF
        // "exitcode result"
        auto pos = in.find(SEP);
        if(pos == string::npos)
            return false;
        
        string ec_str = in.substr(0,pos);
        string re_str = in.substr(pos + SEP_LEN);
        if(ec_str.empty() || re_str.empty())
            return false;
        
        _exitcode = stoi(ec_str);
        _result = stoi(re_str);
#else
        Json::Value root;
        Json::Reader reader;
        reader.parse(in,root);
        _exitcode = root["exitcode"].asInt();
        _result = root["result"].asInt();


#endif
        return true;
    }
public:
    int _exitcode;   // 0:表示计算成功 !0:表示计算失败,具体的是多少会有一个标准
    int _result;     //计算结果
};

calClient.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#include "log.hpp"  //日志信息
#include "Protocol.hpp"

#define NUM 1024

namespace Client
{
    // using namespace std;
    class calClient
    {
    public:
        calClient(const string& serverip,const uint16_t serverport)
            :_sock(-1),_serverip(serverip),_serverport(serverport)
        {}
        void ininClinet()
        {
            //1.创建socket
            _sock = socket(AF_INET,SOCK_STREAM,0);
            if(_sock < 0)
            {
                logMessage(FATAL,"socket create error");//错误日志信息
                exit(2);
            }
            //2.tcp客户端也要bind,但是不用我们显示的bind,OS会帮我们bind的

        }
        void start()
        {

            struct sockaddr_in server;
            memset(&server,0,sizeof(server));
            server.sin_family = AF_INET;
            server.sin_port = htons(_serverport);
            server.sin_addr.s_addr = inet_addr(_serverip.c_str());
            //向服务器发起链接请求
            if(connect(_sock,(struct sockaddr*)&server,sizeof(server)) != 0)
            {
                logMessage(ERROR,"socket connect error");
            }
            else
            {
                string msg;
                string inbuffer;
                while(true)
                {
                    cout << "Mycal>> ";
                    getline(cin,msg);   //cin>> 1+1

                    //准备计算任务,
                    Request req = ParseLine(msg);   //计算任务结构化
                    string content;
                    if(!req.serialize(&content)) continue;   //"1 + 1" 序列化
                    string send_string = enLength(content); //"content_len"\r\n"x op y"\r\n 添加报头

                    // 发送计算任务
                    send(_sock,send_string.c_str(),send_string.size(),0);

                    // 获取计算结果
                    string package,text;
                    if(!recvPackage(_sock,inbuffer,&package)) // 获取一个完整的响应"content_len"\r\n"exitcode result"\r\n
                        continue;
                    if(!deLength(package,&text))    //"exitcode result" 去报头
                        continue;
                    Response resp;
                    resp.deserialize(text);         //反序列化

                    // 输出计算结果
                    cout << "exitcode: " <<resp._exitcode << endl;
                    cout << "result: " <<resp._result << endl << endl;

                }
            }
        }
        // 简易版本的状态机,用于拆分输入的计算字符串
        Request ParseLine(const string& line)
        {
            // "1+1" "10*10" "1234/0"
            int status = 0;
            int i = 0,cnt = line.size();
            string x,y;
            char op;
            while(i < cnt)
            {
                switch(status)
                {
                    case 0:
                    {
                        if(!isdigit(line[i]))
                        {
                            op = line[i];
                            status = 1;
                        }
                        else 
                            x.push_back(line[i++]);
                    }
                        break;
                    case 1:
                    {
                        i++;
                        status = 2;
                    }
                        break;
                    case 2:
                        y.push_back(line[i++]);
                        break; 
                }
            }
            return Request(stoi(x),stoi(y),op);
        }
        ~calClient()
        {
            if(_sock >= 0) close(_sock);//可写,可不写
        }
    private:
        int _sock;
        string _serverip;
        uint16_t _serverport;
    };

}//namespace Client end

calClient.cc

#include "calClient.hpp"
#include <memory>

using namespace Client;

// 提示 运行格式
static void Usage(string proc)
{
    cout<<"Usage:\n\t" << proc <<" server_ip server_port\n\n";
}
// ./calClient server_ip_ip server_port
int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    unique_ptr<calClient> tpcl(new calClient(serverip, serverport));
    tpcl->ininClinet();
    tpcl->start();

    return 0;
}

还有一些日志信息和执行指令请看完整代码:lesson13/1_ · 晚风不及你的笑/MyCodeStorehouse - 码云 - 开源中国 (gitee.com)

操作演示:

只要能够保证, 客户端发送时构造的数据, 在服务端能够正确的进行解析, 就是ok的。 这种约定, 就是 应用层协议。


HTTP协议

虽然我们说, 应用层协议是我们程序猿自己定的。但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用:HTTP(超文本传输协议)就是其中之一。
 

 认识URL

平时我们俗称的 "网址" 其实就是说的 URL。

我们的上网行为无非两种:1.把网络上的资源拉到本地做显示或使用,2.把本地资源上传或提交到网络中。

http的作用就是找到服务器IP地址、通过端口号和文件路径、把对应的资源返回给客户端。

 说白了http就是一个文件传输的协议,它能从服务器上拿到对应的 “资源”,像日常上网中,你看到的图片、视频、文字、音频等这些都属于对应的资源,一切你在网络中看到的都是资源,我们把这些资源看作是资源文件,它存储在服务器的磁盘上。有了对应的路径结构我们就能在网络中从服务器上拿到该资源,又因为文件资源种类特别多,http都能搞定,所以http叫超文本传输协议。

urlencode和urldecode

        像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义。

转义的规则如下:
        将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式

 urldecode就是urlencode的逆过程;

编码解码工具:url解码在线,在线url编码解码工具,urldecode在线解码-站长工具 (senlt.cn) 

HTTP协议格式

HTTP请求 

  • 首行: [方法] + [url] + [版本]
  • Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
  • Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;

HTTP响应

  • 首行: [版本号] + [状态码] + [状态码解释]
  • Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
  • Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中.

这里有两个问题需要回答:

1. 怎么保证应用层读取的是一个完整的请求或响应呢?

首先不管是请求还是响应,空行之前的内容都是以行为单位的,可以设计接口读取完整的一行,再循环读取请求行和请求报头,一直读取到空行才结束。那么正文怎么读呢?总不能还按行读取吧。我们只要能保证把报头读完,在报头中有一个属性Content-Length标识正文的长度,通过解析出来的长度,在读取正文即可,这样整个求情或响应就读取完毕了。

2. 请求和响应是怎么做到序列化和反序列化的?

是http自己实现的,第一行的内容+请求/响应报头,只需要按照\r\n的方式将字符串由第一行到第n行即可。正文不需要处理。

HTTP的方法

        其中最常用的就是GET方法和POST方法。GET通过url传递参数,POST通过http请求的正文提交参数,POST方法通过正文提交参数,所以一般用户看不到,私密性会更好,但是这里私密性不代表安全性。无论GET和POST这两种方法都不安全,要谈安全必须加密,要安全就得用https。

HTTP的状态码

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

 HTTP常见Header

  • Content-Type: 数据类型(text/html等)
  • Content-Length: Body的长度
  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
  • User-Agent: 声明用户的操作系统和浏览器版本信息;
  • referer: 当前页面是从哪个页面跳转过来的;
  • Location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
  • Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;

最简单的HTTP服务器

实现一个最简单的HTTP服务器, 只在网页上输出 "hello world"; 只要我们按照HTTP协议的要求构造数据, 就很容易能做到;
 

HttpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>




namespace Server
{
    // using namespace std;

    enum { USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,LISTEN_ERR};

    static const uint16_t gport = 8080;
    static const int gbacklog = 5;

    using func_t = function<bool(const HttpRequest&, HttpResponse& )>;
   
    class HttpServer
    {
    public:
        HttpServer(func_t func,const uint16_t& port = gport)
            :_func(func),_listensock(-1),_port(port)
        {}
        void initServer()
        {
            //1.创建socket文件套接字对象
            _listensock = socket(AF_INET,SOCK_STREAM,0);
            if(_listensock < 0)
            {
                exit(SOCKET_ERR);
            }

            // 2.bind 绑定自己的网络信息
            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(_listensock,(struct sockaddr*)&local,sizeof(local)) < 0)
            {
                exit(BIND_ERR);
            }

            //3. 设置socket为监听状态
            if(listen(_listensock,gbacklog) < 0)
            {
                exit(LISTEN_ERR);
            }


        }
        void handlerHttp(int sock)
        {
            // 1. 读取一个完整的http请求
            // 2. 序列化
            // 3. 回调
            // 4. resp 序列化
            // 5. 发送

            char buffer[4096];
            HttpRequest req;
            HttpResponse resp; 
            ssize_t n = recv(sock,buffer,sizeof(buffer)-1,0);   //大概率能直接读到一个完整的请求
            if(n > 0)
            {
                buffer[n] = 0;
                req.inbuffer = buffer;

                _func(req,resp);    //req -> resp

                send(sock,resp.outbuffer.c_str(),resp.outbuffer.size(),0);
            }

        }
        void start()
        {

            for( ; ; )  //死循环
            {
                //4. Server获取新链接
                //sock 是用于和client进行通信的
                struct sockaddr_in peer;
                socklen_t peer_len = sizeof(peer);
                int sock = accept(_listensock,(struct sockaddr*)&peer,&peer_len);
                if(sock < 0)
                {
                    continue;
                }

            
                //5. 未来通信就用这个sock,面向字节流的,后面全是文件操作                
                // //version 2.1 多进程版
                pid_t id = fork();
                if(id == 0) //子进程
                {
                    close(_listensock);
                    if(fork() > 0) exit(0); //让孙子进程执行代码,子进程退出被父进程回收,孙子进程会变成孤儿进程
                    handlerHttp(sock);

                    close(sock);
                    exit(0);
                }
                close(sock);
                //父进程
                waitpid(id,nullptr,0);
                
            }
        }
           
        ~HttpServer()
        {}
    private:
        int _listensock;    //不是用来数据通信的,它是监听链接是否到来的,用于获取新链接的
        uint16_t _port;
        func_t _func;
    } ;
    
}//namespace end Server 

HttpServer.cc


#include "HttpServer.hpp"

#include <memory>

using namespace Server;

static void Usage(string argv)
{
    cerr << "Usage: \n\t" << argv <<" port \n\n" << endl;
}
bool Get(const HttpRequest& req, HttpResponse& resp)
{
    cout<< "----------------------http start------------------------------------" << endl;
    cout << req.inbuffer << endl;//请求

    cout<< "----------------------http end------------------------------------" << endl;
     //响应
    string respline = "Http/1.1 200 OK\r\n";
    string respheader = "Content-Type:text/html\r\n";
    string respblank = "\r\n";
    string body = "<html lang=\"en\"><head><meta charset=\"UTF-8\"><title>for test</title><h1>hello world</h1></head><body><p>你好呀</p></body></html>";

    resp.outbuffer += respline;
    resp.outbuffer += respheader;
    resp.outbuffer += respblank;
    resp.outbuffer += body;

    return true;
}
// ./httpserver 8080
int main(int argc,char* argv[])
{

    if(argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t port = atoi(argv[1]);

    unique_ptr<HttpServer> httpsvr(new HttpServer(Get,port));
    httpsvr->initServer();
    httpsvr->start();


    return 0;
}

Protocol.hpp 

#pragma once 

#include <iostream>
#include <string>

const string sep = "\r\n";


using namespace std;

class HttpRequest
{
public:
    HttpRequest(){}
    ~HttpRequest(){}

public:
    string inbuffer;

};


class HttpResponse
{
public:
    string outbuffer;
};

编译, 启动服务. 在浏览器中输入 [ip]:[port], 就能看到显示的结果 "Hello World" 

 此处我们使用 8080 端口号启动了HTTP服务器. 虽然HTTP服务器一般使用80端口,但这只是一个通用的习惯. 并不是说HTTP服务器就不能使用其他的端口号.

拓展知识(了解)

长链接

我们看到的网页,实际上可能由多种元素组成,文字、图片、视频等,那么一张网页就需要多次http请求。

http网页中可能包含多个元素,由于http是基于tcp,tcp是面向连接的,如果频繁发起http请求,就会出现频繁创建链接的问题。解决这个问题需要客户端和服务器都支持长连接,建立好一条链接,获取大份资源的时候,通过类似串行化的方式在一条链接上完成。

http周边会话保持

会话保持严格意义来讲不是http天然具备的,是后面使用的时候发现需要的。比如,某个视频网页上登陆自己的账号,多开几个网页,或者关闭网页后重新开启,我们发现网页自动帮我们登录了账号。

http协议是无状态的,也就是说同一个图片资源,我们多次刷新,http每一次都会提交一个请求,这样会导致访问变慢。因为用户查看新的网页是常规操作,如果发生网页跳转,新的页面也就无法识别用户了,但是为了让用户一次登陆,多次访问不受限制,有一个会话保持功能,是浏览器实现的,也就是它会将我们输入的账号密码保存起来,往后我们只要访问同一个网站,浏览器会自动推送历史保留信息。这种我们称为cookie,cookie分为文件级别和内存级别。

基本工具(http)

piostman 不是抓包工具,主要是用来模拟客户端(浏览器)行为的,相当于自己就是一个客户端。

fiddler 是一个抓包工具,是用来抓http的,且只能是抓本地的。主要用于调试,他相当于一个中间代理,它抓到客户端的请求,经过处理再次发给服务器,服务器也会把内容先返回给它,再由它返回给客户端。

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

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

相关文章

MOS管电源开关电路的缓启动功能是怎么实现的

先看一个电路&#xff1a; 其主要设计思路是使用MOS管来做一个开关&#xff0c;控制电源输出&#xff1b; 为什么选用MOS管&#xff1f; 这就涉及到MOS管的两个重要特性&#xff1a; 1.MOS管的导通电流大&#xff1b; 2.MOS管导通时内阻小&#xff0c;内部功耗低&#xff1b…

Probit模型、Logit模型、IV-Probit模型、IV-Probit模型

概述 Y β 1 X 1 β 2 X 2 ϵ i Y\beta_1X_1\beta_2X_2\epsilon_i Yβ1​X1​β2​X2​ϵi​ 边际效应&#xff1a;就是系数&#xff0c;即 β 1 \beta_1 β1​ 、 β 2 \beta_2 β2​ 解释&#xff1a;如&#xff0c;在控制其他变量&#xff08;条件&#xff09;不变的情况…

常用设计模式之单例模式

文章目录 饿汉式和懒汉式多线程中的懒汉式单例模式内存释放问题单例模式优缺点单例应用场景测试代码 饿汉式和懒汉式 单例模式是指在任何时候都保证只有一个类实例&#xff0c;并提供一个访问它的全局访问节点。 单例模式结构图&#xff1a; 解释&#xff1a;单例模式就是一…

罗湖区田心村旧改确认实施主体的公示,华润集团开发

深圳市罗湖区城市更新和土地整备局发布关于罗湖区笋岗街道田心村改造项目一期子项目2&#xff08;1-14、1-16、1-17地块&#xff09;确认实施主体的公示。 田心村改造项目位于罗湖区笋岗街道田心村&#xff0c;2012年4月&#xff0c;深圳市城市规划委员会建筑与环境艺术委员会2…

关于ElementPlus中的表单验证

关于ElementPlus中表单的校验规则&#xff0c;官网文档已经给出了&#xff0c;但是没有说明性文字&#xff0c;所以我想来记录一下&#xff0c;给出一些文字说明 ElementPlus中的简单校验 ElementPlus的表单的一般结构是&#xff1a; <el-form><el-form-item>&l…

(一)CSharp-Net框架

.NET框架由三部分组成&#xff1a; 1.编程工具。 2.基类库(BCL). 3.公共语言运行库(CLR) CLR 在运行时管理程序的执行&#xff0c;包括以下内容&#xff1a; 内存管理和垃圾收集。代码安全验证。代码执行、线程管理及异常处理。 NET 框架的特点以及其带来的好处&#xff1a…

web测试工程师的工作职责(合集)

web测试工程师的工作职责1 职责: 1、 负责数据平台产品的测试工作&#xff0c;参与产品需求分析&#xff0c;负责方案制定,并能预先评估项目风险&#xff0c;确保测试活动的顺利开展; 2、 深入理解系统内部的设计原理&#xff0c;并能从测试的角度提供优化意见; 3、 根据产品需…

【Linux】多线程概念初讲

线程大章节第一篇文章 文章目录 前言一、linux线程基本概念二、线程与进程的对比 1.线程控制的接口总结 前言 什么是线程呢&#xff1f; 在一个程序里的一个执行路线就叫做线程&#xff08; thread &#xff09;。更准确的定义是&#xff1a;线程是 “ 一个进程内部的控制…

Nginx+Tomcat负载均衡、动静分离,4层代理,7层代理

一&#xff1a;7层反向代理 Nginx 服务器&#xff1a;192.168.52.200:80 Tomcat服务器1&#xff1a;192.168.52.201:80 Tomcat服务器2&#xff1a;192.168.52.108:8080 192.168.52.108:8081 一.部署Nginx 负载均衡器 1.关闭防火墙 注意&#xff1a;所有的虚拟机都要注意关闭防…

硬件入门什么是之电阻

第1章 硬件入门什么是之电阻 文章目录 第1章 硬件入门什么是之电阻[TOC] 一、电阻基本概念二、电路设计实际应用1.限流( 恒流)2.分压 { 采样&#xff1a;电阻精度至少为1%}3.上拉和下拉4.分担功耗5.通信芯片的阻抗匹配 总结 一、电阻基本概念 对电流有阻碍作用的导体叫做&…

NFS(Network File System)服务搭建

NFS基础服务搭建 环境介绍:服务拓扑:nfs serverclient1临时挂载永久挂载client2临时挂载永久挂载注意事项:环境介绍: 系统全为centos7系统 使用本地yum源 服务拓扑: nfs server yum -ty install nfs-utilst rpcbind setenforce 0 mkdir /var/{world,cloud} echo -e “/va…

【Python XML】零基础也能轻松掌握的学习路线与参考资料

Python是一种广泛使用的编程语言&#xff0c;可以用来处理各种数据类型&#xff0c;并且具有广泛的应用&#xff0c;从Web开发到人工智能都能够胜任。在这样的大环境下&#xff0c;XML&#xff08;扩展标记语言&#xff09;成为了一项非常重要的数据交换格式&#xff0c;它提供…

JAVA对象头的指针压缩

JAVA对象头的指针压缩 文章目录 JAVA对象头的指针压缩对象在JVM中的内存布局对象的访问定位压缩实验实验步骤压缩策略组合压缩内容压缩后的影响指针压缩的实现 JVM内存关键大小 对象在JVM中的内存布局 在 Hotspot 虚拟机中,对象的内存布局主要由 3 部分组成&#xff1a; 对象头…

OpenMMLab AI 实战营笔记4——MMPreTrain算法库:构建高效、灵活、可扩展的深度学习模型

文章目录 摘要一、工具箱介绍二、丰富的模型三、推理API四、环境搭建——OpenMMLab软件栈五、OpenMMLab重要概念——配置文件六、代码框架七、配置及运作方式经典主干网络残差网络Vison Transformer(VIT)注意力机制自监督学习常见类型SimCLRMAE自监督学习 多模态CLIPBLIPOthers…

jvm 命令和工具

目录 堆内存分析工具 MAT ZProfiler - 线上的mat EagleEye-MProf - 命令行 命令行 线程池排查 jstack jmap -dump jmap -heap jstat 堆内存分析工具 MAT eclipse官方推出的本地内存分析工具&#xff0c;运行需要大量内存&#xff0c;从使用角度来讲&#xff0c;并不…

Mysql查询优化

Mysql查询优化器 在多种情况下,可能会导致查询结果从缓存中清除,例如:. 数据可能已被修改 您可能运行了一条语句,其文本与缓存的语句略有不同(小写/大写,换行符,...) 缓存可能已达到其大小限制之一(内存,查询计数,块等),并决定逐出您的特定查询 高速缓存碎片过多…

DatenLord前沿技术分享 No.26

达坦科技专注于打造新一代开源跨云存储平台DatenLord&#xff0c;通过软硬件深度融合的方式打通云云壁垒&#xff0c;致力于解决多云架构、多数据中心场景下异构存储、数据统一管理需求等问题&#xff0c;以满足不同行业客户对海量数据跨云、跨数据中心高性能访问的需求。达坦科…

SpringBoot+MyBatisplus搭建校园新闻平台——已开源

概述 开发背景 校园新闻平台是以新闻宣传机构的在线信息发布需求为基础&#xff0c;随着数字化和信息化的快速发展&#xff0c;校园新闻在校园内的传播和沟通中变得越来越重要。学校需要一个有效的管理系统来整合、发布和传播校园新闻&#xff0c;以满足师生、校友和其他利益…

我对测试行业发展和自我价值诉求的思考

测试圈子生态的思考 其实测试的生态&#xff0c;说起来蛮简单的&#xff0c;一个词语概括就是两极分化。有个梗&#xff1a;hand hands&#xff0c;load loads&#xff0c;太贴切了。 两极分化这个词&#xff0c;可以从下面三个维度来看&#xff1a; 薪资 我认识的测试也算不少…

搜索插入位置 力扣 Python

题目描述&#xff1a; 解题代码&#xff1a; class Solution:def searchInsert(self, nums: List[int], target: int) -> int:if target in nums:return nums.index(target)else:nums.append(target)nums.sort()return nums.index(target)题目分析&#xff1a; 时间复杂度…