Linux网络基础 — 应用层

目录

应用层

 再谈 "协议"

 网络版计算器

HTTP协议

 认识URL

urlencode和urldecode

HTTP协议格式

HTTP请求 

HTTP响应

HTTP的方法

HTTP的状态码

HTTP常见Header

拓展知识(了解)

长链接

http周边会话保持

基本工具(http)


Linux网络基础 — 应用层_第1张图片

应用层

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

 再谈 "协议"

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

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

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

Linux网络基础 — 应用层_第2张图片

 网络版计算器

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

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

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

calServer.hpp

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  

#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 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 

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 tsvr(new calServer(port));
    tsvr->initServer();
    tsvr->start(cal);

    return 0;
}

Protocol.hpp

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 

#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* 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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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: " <= 0) close(_sock);//可写,可不写
        }
    private:
        int _sock;
        string _serverip;
        uint16_t _serverport;
    };

}//namespace Client end

calClient.cc

#include "calClient.hpp"
#include 

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 tpcl(new calClient(serverip, serverport));
    tpcl->ininClinet();
    tpcl->start();

    return 0;
}

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

操作演示:

Linux网络基础 — 应用层_第3张图片

Linux网络基础 — 应用层_第4张图片Linux网络基础 — 应用层_第5张图片只要能够保证, 客户端发送时构造的数据, 在服务端能够正确的进行解析, 就是ok的。 这种约定, 就是 应用层协议。


HTTP协议

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

 认识URL

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

Linux网络基础 — 应用层_第6张图片

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

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

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

urlencode和urldecode

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

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

Linux网络基础 — 应用层_第7张图片

 urldecode就是urlencode的逆过程;
Linux网络基础 — 应用层_第8张图片

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

HTTP协议格式

Linux网络基础 — 应用层_第9张图片

HTTP请求 

Linux网络基础 — 应用层_第10张图片

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

HTTP响应

Linux网络基础 — 应用层_第11张图片

  • 首行: [版本号] + [状态码] + [状态码解释]
  • 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。

Linux网络基础 — 应用层_第12张图片

HTTP的状态码

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

Linux网络基础 — 应用层_第13张图片

 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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 




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;
   
    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 

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 = "for test

hello world

你好呀

"; 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 httpsvr(new HttpServer(Get,port)); httpsvr->initServer(); httpsvr->start(); return 0; }

Protocol.hpp 

#pragma once 

#include 
#include 

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" 

Linux网络基础 — 应用层_第14张图片Linux网络基础 — 应用层_第15张图片

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

拓展知识(了解)

长链接

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

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

http周边会话保持

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

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

基本工具(http)

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

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

你可能感兴趣的:(Linux网络编程笔记,服务器,运维,linux,网络)