三尺非寒 发表于 2024-8-2 10:54:31

基于http协议的服务器代码编写(可以访问指定路径的资源版+添加跳转网页功能

目次
可以访问指定路径资源的服务端
编写
引入
介绍
代码
Serialization.hpp
404_err.html
示例
增长跳转网页功能
href
介绍 
代码 
root.html
page1.html
page2.html
示例
添加重定向功能
引入
介绍
代码
示例
添加显示图片功能
引入
介绍
代码
http_server.hpp
Serialization.hpp
root_page.html
示例 
修改为多线程版(短毗连) 
介绍
代码

可以访问指定路径资源的服务端

编写

   引入

如果我们要访问客户端指定路径的资源,就得把请求拆分出来


[*]url里记录了要访问资源的路径
[*]拆分的过程着实就是反序列化
    介绍

那么题目就酿成了 -- 怎样序列化/反序列化 


[*]因为我们只用编写服务端(因为客户端有浏览器主动帮我们进行处理惩罚),所以只涉及请求的反序列化+响应的序列化
[*]着实响应的序列化我们早就在上一个版本里做过了,我们把它再做个处理惩罚并且拆分出来
请求怎样序列化(格式化数据 -> 字符串)呢?


[*]把响应报头放在vector<string>里
[*]响应正文单独存放在string里
[*]在拼接的时候,利用某个分隔符分开他们(比如http协议里规定的\r\n,固然这个分隔符只适用于报头部分)
响应怎样反序列化(字符串-> 格式化数据)呢?


[*]就是把上述过程反过来
[*]找到分隔符,将得到的数据赋值给结构体对应的字段
[*]详细来说就是:
[*]以\r\n为分隔符,找出报头的每一行,将每一行push到容器里,同时删除掉源串里的内容+"/r/n"
[*]如果读到空行(内容为空)时,就已经将报头读完了
[*] 然后探求报头里的content-length字段,找到后拿到正文长度,就可以继承把正文也拆出来了
怎样得到我们的目标url呢?


[*]也就是要继承细化
[*]url在请求行里,也就是报头的第一行=vector里的第一个元素
[*]把它以空格为分隔符,拆出三大部分,即可得到纯净的url
[*]我们可以手动分割
[*]也可以利用ss流,它默认以空格为分隔符主动分割,只必要用变量给他接着就行(就像水倒出来,用杯子接着它哈哈哈)
得到url后,我们必要将它和我们自定义的web根目次的位置拼接起来


[*]也就是在上一个版本那边介绍的那样,拼接起来得到[要访问的资源在linux中的现实路径]:
[*]https://img-blog.csdnimg.cn/direct/1295b749424540e5983d060ceaf5f370.png
但是这里我们要增长一个特殊处理惩罚:


[*]当访问根目次时,必要手动让它访问我们设定的首页文件
[*]并且当访问不存在的页面时,必要手动让它访问我们设定好的错误页面

   代码

#pragma once

#include <signal.h>
#include <unistd.h>
#include <cstring>
#include <functional>

#include "socket.hpp"
#include "Serialization.hpp"

static MY_SOCKET my_socket;

#define buff_size 1024 * 30
#define root_path "./root_page"
#define def_file "root.html"

class http_server
{
public:
    http_server(const uint16_t port, const std::string &ip = "0.0.0.0")
      : port_(port), ip_(ip) {}
    ~http_server() {}
    void run()
    {
      init();
      while (true)
      {
            uint16_t client_port;
            std::string client_ip;
            lg(DEBUG, "accepting ...");
            int sockfd = my_socket.Accept(client_ip, client_port);
            if (sockfd == -1)
            {
                continue;
            }
            lg(INFO, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, client_ip.c_str(), client_port);

            int ret = fork();
            if (ret == 0)
            {
                my_socket.Close();
                char buffer;
                std::string in_buffer;

                while (true)
                {
                  memset(buffer, 0, sizeof(buffer));
                  int n = read(sockfd, buffer, sizeof(buffer)); //"size"\n"a op b"\n
                  if (n > 0)
                  {
                        buffer = 0;
                        lg(INFO, "get request");
                        in_buffer += buffer; // 连续读取
                        lg(INFO, "%s", in_buffer.c_str());
                        request req;
                        req.deserialize(in_buffer);

                        // 构建访问资源的路径
                        std::string def_page = root_path;
                        if (req.url_ == "/")
                        {
                            def_page += "/";
                            def_page += def_file;
                        }
                        else
                        {
                            def_page += req.url_;
                        }

                        // 构建响应
                        response res;
                        res.version_ = "HTTP/1.1";
                        std::string text = get_page(def_page);
                        if (text.empty())
                        {
                           res.code_ = 404;
                           res.desc_ = "Not Found";
                           def_page = root_path;
                           def_page += "/";
                           def_page += "404_err.html";

                           res.text_ = get_page(def_page);
                              
                        }
                        else
                        {
                            res.code_ = 200;
                            res.desc_ = "OK";
                            res.text_ = text;
                        }

                        std::string cl = "Content-Length: ";
                        cl += std::to_string((res.text_).size());
                        cl += protocol_sep;
                        (res.title_).push_back(cl);

                        std::string content;
                        res.serialize(content);

                        write(sockfd, content.c_str(), content.size());
                  }
                  else if (n == 0)
                  {
                        lg(INFO, "%s quit", client_ip.c_str());
                        break;
                  }
                  else // 读出错误
                  {
                        break;
                  }
                }
                // lg(INFO, "fork quit");
                exit(0);
                close(sockfd);
            }
      }
    }

private:
    void init()
    {
      signal(SIGPIPE, SIG_IGN);
      signal(SIGCHLD, SIG_IGN);

      my_socket.Socket();
      my_socket.Bind(port_);
      my_socket.Listen();
      lg(INFO, "server init done");
    }
    std::string get_page(std::string path)
    {
      std::ifstream in(path.c_str());
      if (!in.is_open())
      {
            return "";
      }
      std::string content, tmp;
      while (std::getline(in, tmp))
      {
            content += tmp;
      }
      return content;
    }

private:
    uint16_t port_;
    std::string ip_;
};
Serialization.hpp

#pragma once

#include <string>
#include <vector>
#include <sstream>
#include <fstream>
#include <iostream>

#define protocol_sep "\r\n"
#define blank_sep ' '

class request
{
public:
    request()
      : type_(""), url_(""), version_(""), text_("") {}
    bool deserialize(std::string &content)
    {
      std::string tmp;
      size_t left = 0;
      while (true)
      {
            size_t pos = content.find(protocol_sep, left);
            if (pos == std::string::npos)
            {
                return false;
            }

            tmp = content.substr(left, pos - left); // 左闭右开
            left = pos + 2;
            if (tmp.empty()) // 读到空行
            {
                break;
            }
            else
            {
                title_.push_back(tmp);
            }
      }

      // 细分请求行
      std::string request_line = title_;
      std::stringstream ss(request_line);
      ss >> type_ >> url_ >> version_;

      // 如果有正文的话
      std::string comp = "Content-Length: ";
      bool is_find = false;
      int size = 0;
      for (auto &it : title_)
      {
            size_t pos = it.find(comp);
            if (pos != std::string::npos)
            {
                ssize_t right = it.find(protocol_sep);
                std::string s_size = it.substr(pos + comp.size(), right - pos - comp.size());
                size = stoi(s_size);
                is_find = true;
                break;
            }
      }
      if (!is_find)
      { // 没有Content-Length字段
            content.erase(0, left + 2);
      }
      else
      {
            text_ = content.substr(left + 2, size);
            content.erase(0, left + 2 + size);
      }
      return true;
    }

public:
    std::string type_;
    std::string url_;
    std::string version_;
    std::vector<std::string> title_;
    std::string text_;
};

class response
{
public:
    response()
      : version_(""), code_(0), desc_(""), text_("") {}
    void serialize(std::string &content)
    {
      // 响应行
      std::string header = version_;
      header += blank_sep;
      header += std::to_string(code_);
      header += blank_sep;
      header += desc_;
      header += protocol_sep;

      // 响应报头
      std::string attribute;
      for (auto &it : title_)
      {
            attribute += it;
            attribute += protocol_sep;
      }

      content = header + attribute + protocol_sep + text_;
    }

public:
    std::string version_;
    int code_;
    std::string desc_;
    std::vector<std::string> title_;
    std::string text_;
};404_err.html

这是我上网任意搜的一个(毕竟咱也不是做前端的,没必要本身写)
<!DOCTYPE html>
<html>

<head>
    <title>404-对不起!您访问的页面不存在</title>
    <meta charset="UTF-8" http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <style>
      body {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            color: #0e356c;
            display: table;
            font-weight: 100;
            font-family: 'Lato';
      }

      .container {
            text-align: center;
            display: table-cell;
            vertical-align: middle
      }

      .content {
            text-align: center;
            display: inline-block;
      }

      .title {
            font-size: 42px;
            margin-bottom: 40px;
      }
    </style>

</head>

<body>
    <div class="container">
      <div class="content">
            <div class="title">404-对不起!您访问的页面不存在</div>
      </div>
    </div>
</body>

</html>

   示例

这是我们创建的多个html文件结构:


[*]https://img-blog.csdnimg.cn/direct/83d44fa96849458598e26662ca7ae71a.png
访问指定资源:


[*]https://img-blog.csdnimg.cn/direct/47ff1529b40b436eb0ea333e8dd52fcc.png
[*]https://img-blog.csdnimg.cn/direct/6c2c676cc20a42a99d9b759514736e0d.png
如果访问的资源不存在:


[*]https://img-blog.csdnimg.cn/direct/4326158a1af547409d7c3e23a8808460.png

增长跳转网页功能

   href

是 HTML 元素中常用的属性,用于指定链接的目标地址


[*]https://img-blog.csdnimg.cn/direct/d7ab9dade42d4e41a2a8fd531e42d28c.png
跳转网页时不一定都发送了请求,因为浏览器会缓存一些网页
    介绍 

路径可以是绝对路径,也可以是相对路径


[*]所以可以实现跳转外部/内部网页
    代码 

root.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>my title</title>
</head>

<body>
    <h1>hello.</h1>
    <h2>hello..</h2>
    <h3>hello...</h3>
    <p>hello world!</p>
    <h2>跳转至第一个页面</h2>
    <p><a href="a/page1.html">page1</a></p>
    <h2>跳转至第二个页面</h2>
    <p><a href="a/b/page2.html">page2</a></p>

</body>

</html>page1.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>my title</title>
</head>

<body>
    <h1>第一个页面</h1>
    <p>hello first</p>
    <h2>跳转至第二个页面</h2>
    <p><a href="b/page2.html">page2</a></p>
    <h2>跳转至首页</h2>
    <p><a href="../root.html">root</a></p>

</body>

</html>page2.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>my title</title>
</head>

<body>
    <h1>第二个页面</h1>
    <p>hello second</p>
    <h2>跳转至第一个页面</h2>
    <p><a href="../page1.html">page1</a></p>
    <h2>跳转至首页</h2>
    <p><a href="../../root.html">root</a></p>

</body>

</html>    示例

当我们起首访问page2:


[*]https://img-blog.csdnimg.cn/direct/8c96d74ebb1e46ef9666ebfe2e882c4a.png
点击跳转首页后:


[*]https://img-blog.csdnimg.cn/direct/b7c57e457d5144c1ba007e8971830a74.png
点击跳转page1后:


[*]https://img-blog.csdnimg.cn/direct/d9e0e73e7c1a46c480ed65626f1d402d.png

添加重定向功能

   引入

我们在上一篇博客中介绍过重定向的概念 -- 域名介绍,url的介绍+原理+特殊字符的处理惩罚,网络行为,http协议请求/响应的格式+结构,状态码介绍,临时/永世重定向,http报头常见字段,fiddler-CSDN博客
    介绍

我们这里假定当客户端访问/302时,就触发重定向功能


[*]也就是让响应的状态码为302
[*]浏览器会帮我们根据http协议 -- 当状态码为302时,主动跳转到location字段定义的资源(这里我们设置为百度主页,都行的啦)
[*]因为只是一个测试嘛,起到一个看看重定向结果的作用
    代码

只必要修改动态构建响应部分即可:
    void run()
    {
      init();
      while (true)
      {
            uint16_t client_port;
            std::string client_ip;
            lg(DEBUG, "accepting ...");
            int sockfd = my_socket.Accept(client_ip, client_port);
            if (sockfd == -1)
            {
                continue;
            }
            lg(INFO, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, client_ip.c_str(), client_port);

            int ret = fork();
            if (ret == 0)
            {
                my_socket.Close();
                char buffer;
                std::string in_buffer;

                while (true)
                {
                  memset(buffer, 0, sizeof(buffer));
                  int n = read(sockfd, buffer, sizeof(buffer)); //"size"\n"a op b"\n
                  if (n > 0)
                  {
                        bool code_302 = false;
                        buffer = 0;
                        lg(INFO, "get request");
                        in_buffer += buffer; // 连续读取
                        lg(INFO, "%s", in_buffer.c_str());
                        request req;
                        req.deserialize(in_buffer);

                        // 构建访问资源的路径
                        std::string def_page = root_path;
                        if (req.url_ == "/")
                        {
                            def_page += "/";
                            def_page += def_file;
                        }
                        else if (req.url_ == "/302")
                        {
                            code_302 = true;
                        }
                        else
                        {
                            def_page += req.url_;
                        }

                        // 构建响应
                        response res;
                        res.version_ = "HTTP/1.1";
                        if (code_302)
                        {
                            res.code_ = 302;
                            res.desc_ = "Found";

                            std::string cl = "Location: ";
                            cl += "https://www.baidu.com";
                            cl += protocol_sep;
                            (res.title_).push_back(cl);
                        }
                        else
                        {
                            std::string text = get_page(def_page);
                            if (text.empty())
                            {
                              res.code_ = 404;
                              res.desc_ = "Not Found";
                              def_page = root_path;
                              def_page += "/";
                              def_page += "404_err.html";

                              res.text_ = get_page(def_page);
                            }
                            else
                            {
                              res.code_ = 200;
                              res.desc_ = "OK";
                              res.text_ = text;
                            }

                            std::string cl = "Content-Length: ";
                            cl += std::to_string((res.text_).size());
                            cl += protocol_sep;
                            (res.title_).push_back(cl);
                        }

                        std::string content;
                        res.serialize(content);

                        write(sockfd, content.c_str(), content.size());
                  }
                  else if (n == 0)
                  {
                        lg(INFO, "%s quit", client_ip.c_str());
                        break;
                  }
                  else // 读出错误
                  {
                        break;
                  }
                }
                // lg(INFO, "fork quit");
                exit(0);
                close(sockfd);
            }
      }
    }    示例

服务端收到的请求:


[*]https://img-blog.csdnimg.cn/direct/edb649850efe40e2a106c7031b37e0f8.png
 成功跳转到我们设定好的百度主页


[*]偶然候大概会一直转圈圈,多切换下网络

添加显示图片功能

   引入

网页一样平常都会有图片


[*]图片也属于网络资源,也必要浏览器向服务器请求这个资源
[*]所以图片资源也会放在web根目次下
这也就意味着:


[*]当我们请求一个页面后,浏览器不仅必要请求网页,还必要根据页面标签定义的图片资源挨个请求
这也就是为什么新版本的http要支持长毗连:


[*]如果还采用短毗连,一个毗连=一个资源,那万一网页有几百张图片资源,那不得毗连死
    介绍

插入图片资源的html语法: -- HTML 图像


[*]https://img-blog.csdnimg.cn/direct/3f3f895ca9fd4e2393e7abd7ca9c232d.png
图片资源着实就是二进制文件,它必要被上层以某种格式解释,才气呈现出我们想要看到的样子


[*]所以就必要添加conten-type字段,来指定该资源的类型
[*]我们之前都没有利用过这个字段,所以他默认以html格式解释
[*]如果是图片的话,就得是image/jpeg / image/png
但是,那么多资源,怎样识别哪个资源是那个类型呢?


[*]我们就必要继承细分url了
[*]url不是记录了客户端要访问的资源路径吗,就肯定会带上资源的文件名
[*]如果文件名带有.jpg / .png后缀,则为图片资源
[*]如果带有.html后缀,则是我们平常的网页资源
[*]如果是其他的,就默认以html格式解释(这里就简朴一点处理惩罚啦)
那么,有了后缀,我们还必要将后缀与对应的conten-type的添补值相关联


[*]所以我们必要定义一个键值对容器
既然图片是二进制,那如果还利用[读取文本文件的一行一行读]的方式就不可了


[*]必要增长一个读取二进制的函数,用来区分两者
    代码

除了增长了读取图片资源的功能,我还把代码结构修改了很多
http_server.hpp

#pragma once

#include <signal.h>
#include <unistd.h>
#include <cstring>
#include <functional>
#include <unordered_map>

#include "socket.hpp"
#include "Serialization.hpp"

static MY_SOCKET my_socket;

#define buff_size 1024 * 30

class http_server
{
public:
    http_server(const uint16_t port, const std::string &ip = "0.0.0.0")
      : port_(port), ip_(ip)
    {
      content_type_[".html"] = "text/html";
      content_type_[".png"] = "image/png";
      content_type_[".jpg"] = "image/jpeg";
      content_type_[".jpeg"] = "image/jpeg";
    }
    ~http_server() {}
    void run()
    {
      init();
      while (true)
      {
            uint16_t client_port;
            std::string client_ip;
            lg(DEBUG, "accepting ...");
            int sockfd = my_socket.Accept(client_ip, client_port);
            if (sockfd == -1)
            {
                continue;
            }
            lg(INFO, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, client_ip.c_str(), client_port);

            int ret = fork();
            if (ret == 0)
            {
                my_socket.Close();
                char buffer;
                std::string in_buffer;

                while (true)
                {
                  memset(buffer, 0, sizeof(buffer));
                  int n = read(sockfd, buffer, sizeof(buffer)); //"size"\n"a op b"\n
                  if (n > 0)
                  {
                        buffer = 0;
                        in_buffer += buffer; // 连续读取
                        lg(INFO, "get request: \n%s", in_buffer.c_str());

                        // 构建请求
                        request req;
                        req.deserialize(in_buffer);

                        //lg(DEBUG, "path: %s ,url: %s ", (req.path_).c_str(), (req.url_).c_str());

                        // 构建响应
                        response res;
                        handle_response(res, req);

                        // 响应序列化
                        std::string content;
                        res.serialize(content);

                        write(sockfd, content.c_str(), content.size());
                  }
                  else if (n == 0)
                  {
                        lg(INFO, "%s quit", client_ip.c_str());
                        break;
                  }
                  else // 读出错误
                  {
                        break;
                  }
                }
                exit(0);
                close(sockfd);
            }
      }
    }

private:
    void init()
    {
      signal(SIGPIPE, SIG_IGN);
      signal(SIGCHLD, SIG_IGN);

      my_socket.Socket();
      my_socket.Bind(port_);
      my_socket.Listen();
      lg(INFO, "server init done");
    }
    void handle_response(response &res, request &req)
    {
      int code = req.code_;
      std::string path = req.path_;
      std::string content_type_data = content_type_;
      //lg(DEBUG, "content_type_data: %s", content_type_data.c_str());

      res.version_ = "HTTP/1.1";
      if (code == 302)
      {
            res.code_ = 302;
            res.desc_ = "Found";

            std::string cl = "Location: ";
            cl += "https://www.qq.com";
            (res.title_).push_back(cl);
            return ;
      }

      if (code == 404)
      {
            res.code_ = 404;
            res.desc_ = "Not Found";
      }
      else
      {
            res.code_ = 200;
            res.desc_ = "OK";
      }

      // 将读取网页和图片资源的方式分开
      if (req.suffix_ == ".html")
      {
            res.text_ = get_page(path);
            //lg(DEBUG, "text: %s", (res.text_).c_str());
      }
      else
      {
            res.text_ = b_get_page(path);
      }

      //构建响应报头
      std::string cl = "Content-Length: ";
      cl += std::to_string((res.text_).size());
      //lg(DEBUG, "text_size: %d", (res.text_).size());
      (res.title_).push_back(cl);

      cl = "Content-Type: ";
      cl += content_type_data;
      (res.title_).push_back(cl);
    }

private:
    uint16_t port_;
    std::string ip_;

    std::unordered_map<std::string, std::string> content_type_;
};
Serialization.hpp

#pragma once

#include <string>
#include <vector>
#include <sstream>
#include <fstream>
#include <iostream>

#define protocol_sep "\r\n"
#define blank_sep ' '
#define root_path "./root_page"
#define def_file "root.html"

std::string get_page(std::string path)
{
    std::string content, tmp;
    std::ifstream in(path.c_str());
    if (!in.is_open())
    {
      return "";
    }
    while (std::getline(in, tmp))
    {
      content += tmp;
    }
    in.close();

   // printf("content : %s", content.c_str());
    return content;
}
std::string b_get_page(std::string path)
{
    std::ifstream in(path.c_str(), std::ios_base::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());
    in.close();

    return content;
}

class request
{
public:
    request()
      : type_(""), url_(""), version_(""), text_(""), path_(""), code_(0), suffix_("") {}
    bool deserialize(std::string &content)
    {
      std::string tmp;
      size_t left = 0;
      while (true)
      {
            size_t pos = content.find(protocol_sep, left);
            if (pos == std::string::npos)
            {
                return false;
            }

            tmp = content.substr(left, pos - left); // 左闭右开
            left = pos + 2;
            if (tmp.empty()) // 读到空行
            {
                break;
            }
            else
            {
                title_.push_back(tmp);
            }
      }

      // 判断是否有正文
      std::string comp = "Content-Length: ";
      bool is_find = false;
      int size = 0;
      for (auto &it : title_)
      {
            size_t pos = it.find(comp);
            if (pos != std::string::npos)
            {
                ssize_t right = it.find(protocol_sep);
                std::string s_size = it.substr(pos + comp.size(), right - pos - comp.size());
                size = stoi(s_size);
                is_find = true;
                break;
            }
      }
      if (!is_find)
      { // 没有Content-Length字段
            content.erase(0, left + 2);
      }
      else
      {
            text_ = content.substr(left + 2, size);
            content.erase(0, left + 2 + size);
      }

      handle_path();

      return true;
    }

private:
    void handle_path() // 构建访问资源的路径
    {
      // 细分请求行
      std::string request_line = title_;
      std::stringstream ss(request_line);
      ss >> type_ >> url_ >> version_;

      // 构建路径
      path_ = root_path;
      if (url_ == "/")
      {
            path_ += "/";
            path_ += def_file;
      }
      else if (url_ == "/302")
      {
            code_ = 302; // 这里设置为重定向到百度页面,所以不能走本地读取
            return;
      }
      else
      {
            path_ += url_;
      }

      // 判断该资源是否存在
      if (get_page(path_).empty())
      {
            code_ = 404; // 需要告诉响应,这里发生了404错误
            path_ = root_path;
            path_ += "/";
            path_ += "404_err.html";
      }

      // 拿到资源后缀
      size_t pos = path_.rfind(".");
      if (pos == std::string::npos)
      {
            suffix_ = ".html";
      }
      else
      {
            suffix_ = path_.substr(pos);
      }
    }

public:
    std::string type_;
    std::string url_;
    std::string path_;
    int code_;
    std::string suffix_;
    std::string version_;
    std::vector<std::string> title_;
    std::string text_;
};

class response
{
public:
    response()
      : version_(""), code_(0), desc_(""), text_("") {}
    void serialize(std::string &content)
    {
      // 响应行
      std::string header = version_;
      header += blank_sep;
      header += std::to_string(code_);
      header += blank_sep;
      header += desc_;
      header += protocol_sep;

      // 响应报头
      std::string attribute;
      for (auto &it : title_)
      {
            attribute += it;
            attribute += protocol_sep;
      }

      content = header + attribute + protocol_sep + text_;
    }

public:
    std::string version_;
    int code_;
    std::string desc_;
    std::vector<std::string> title_;
    std::string text_;
};root_page.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>my title</title>
</head>

<body>
    <h1>hello.</h1>
    <h2>hello..</h2>
    <h3>hello...</h3>
    <p>hello world!</p>

    <form action="a/page1.html" method="get">
      name:<input type="text" name="name"><br>
      password:<input type="password" name="password"><br>
      <input type="submit" name="submit">
    </form>

    <img src="1.jpeg" alt="猫猫" width="500" height="400">

    <h2>跳转至第一个页面</h2>
    <p><a href="a/page1.html">page1</a></p>
    <h2>跳转至第二个页面</h2>
    <p><a href="a/b/page2.html">page2</a></p>

</body>

</html>    示例 

当我们在首页放上我们的图片资源时


[*]访问首页后,浏览器就会根据页面上的html标签继承申请其他资源
[*]https://img-blog.csdnimg.cn/direct/58942a01b1854a558a5e51761041ae7d.png
[*]这是网页的样式(可以看到图片成功被加载出来了):
[*]https://img-blog.csdnimg.cn/direct/dbf60c35db7c493c880574f0f382ee42.png

修改为多线程版(短毗连) 

   介绍

服务器收到一个毗连后,就创建一个线程去处理惩罚请求


[*]并且添加了重连功能
留意:


[*]我们这里是短毗连版本,一次毗连=处理惩罚一次请求
[*]但纵然是短毗连,也得保证读取到的数据是一份完备的请求
[*]所以我在线程外部定义了一个缓冲区,用于保存处理惩罚过程中剩下的数据(因为它有大概是其他毗连的一部分)
[*]而线程内部在反序列化时,将完备请求从缓冲区中剥离
    代码

#pragma once

#include <signal.h>
#include <unistd.h>
#include <cstring>
#include <functional>
#include <pthread.h>
#include <unordered_map>

#include "socket.hpp"
#include "Serialization.hpp"

static MY_SOCKET my_socket;

#define buff_size 1024 * 30

class http_server;
struct thread_data
{
    int sockfd_;
    std::string ip_;
    std::string &in_buffer_;
    http_server *this_;
};

class http_server
{
public:
    http_server(const uint16_t port, const std::string &ip = "0.0.0.0")
      : port_(port), ip_(ip)
    {
      content_type_[".html"] = "text/html";
      content_type_[".png"] = "image/png";
      content_type_[".jpg"] = "image/jpeg";
      content_type_[".jpeg"] = "image/jpeg";
    }
    ~http_server() {}
    void run()
    {
      init();
      while (true)
      {
            uint16_t client_port;
            std::string client_ip;

            // 一个线程处理一次请求(短连接)
            pthread_t pid;
            std::string in_buffer;

            int sockfd = 0;
            int count = 5;
            do
            {
                lg(DEBUG, "accepting ...");
                sockfd = my_socket.Accept(client_ip, client_port);
                if (sockfd != -1 || --count == 0)
                {
                  break;
                }
            } while (true);
            if (sockfd == -1)
            {
                lg(ERROR, "accepting error");
            }

            lg(INFO, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, client_ip.c_str(), client_port);

            thread_data *td = new thread_data{sockfd, client_ip, in_buffer, this};
            lg(DEBUG, "create pthread");
            pthread_create(&pid, nullptr, entry, reinterpret_cast<void *>(td));

            // 一个进程服务一个客户端

            // lg(DEBUG, "accepting ...");
            // int sockfd = my_socket.Accept(client_ip, client_port);
            // if (sockfd == -1)
            // {
            //   continue;
            // }
            // lg(INFO, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, client_ip.c_str(), client_port);
            //int ret = fork();
            //if (ret == 0)
            //{
            //      my_socket.Close();
            //char buffer;
            //std::string in_buffer;

            // while (true)
            // {
            //   memset(buffer, 0, sizeof(buffer));
            //   int n = read(sockfd, buffer, sizeof(buffer)); //"size"\n"a op b"\n
            //   if (n > 0)
            //   {
            //         buffer = 0;
            //         in_buffer += buffer; // 连续读取
            //         lg(INFO, "get request: \n%s", in_buffer.c_str());

            //         // 构建请求
            //         request req;
            //         req.deserialize(in_buffer);

            //         // lg(DEBUG, "path: %s ,url: %s ", (req.path_).c_str(), (req.url_).c_str());

            //         // 构建响应
            //         response res;
            //         handle_response(res, req);

            //         // 响应序列化
            //         std::string content;
            //         res.serialize(content);

            //         write(sockfd, content.c_str(), content.size());
            //   }
            //   else if (n == 0)
            //   {
            //         lg(INFO, "%s quit", client_ip.c_str());
            //         break;
            //   }
            //   else // 读出错误
            //   {
            //         break;
            //   }
            // }
            //   exit(0);
            //   close(sockfd);
            // }
      }
    }

private:
    void init()
    {
      signal(SIGPIPE, SIG_IGN);
      signal(SIGCHLD, SIG_IGN);

      my_socket.Socket();
      my_socket.Bind(port_);
      my_socket.Listen();
      lg(INFO, "server init done");
    }
    void handle_response(response &res, request &req)
    {
      int code = req.code_;
      std::string path = req.path_;
      std::string content_type_data = content_type_;
      // lg(DEBUG, "content_type_data: %s", content_type_data.c_str());

      res.version_ = "HTTP/1.1";
      if (code == 302)
      {
            res.code_ = 302;
            res.desc_ = "Found";

            std::string cl = "Location: ";
            cl += "https://www.qq.com";
            (res.title_).push_back(cl);
            return;
      }

      if (code == 404)
      {
            res.code_ = 404;
            res.desc_ = "Not Found";
      }
      else
      {
            res.code_ = 200;
            res.desc_ = "OK";
      }

      // 将读取网页和图片资源的方式分开
      if (req.suffix_ == ".html")
      {
            res.text_ = get_page(path);
            // lg(DEBUG, "text: %s", (res.text_).c_str());
      }
      else
      {
            res.text_ = b_get_page(path);
      }

      //构建响应报头
      std::string cl = "Content-Length: ";
      cl += std::to_string((res.text_).size());
      // lg(DEBUG, "text_size: %d", (res.text_).size());
      (res.title_).push_back(cl);

      cl = "Content-Type: ";
      cl += content_type_data;
      (res.title_).push_back(cl);
    }
    static void *entry(void *args)
    {
      pthread_detach(pthread_self());

      thread_data *td = reinterpret_cast<thread_data *>(args);
      int sockfd = td->sockfd_;
      std::string ip = td->ip_;
      std::string in_buffer = td->in_buffer_;
      http_server *it = td->this_;

      // 读取请求
      char buffer;
      bool flag = true;
      request req;
      while (true) // 虽说是短连接,但也得确保读出来的内容是一个完整的请求
      {
            memset(buffer, 0, sizeof(buffer));
            int n = read(sockfd, buffer, sizeof(buffer));
            if (n > 0)
            {
                buffer = 0;
                in_buffer += buffer; // 连续读取
                lg(INFO, "get request: \n%s", in_buffer.c_str());

                // 构建请求
                flag = req.deserialize(in_buffer);
                if (flag == false)
                {
                  continue;
                }
                else
                {
                  break;
                }
            }
            else if (n == 0)
            {
                lg(INFO, "%s quit", ip.c_str());
                return nullptr;
            }
            else
            {
                lg(ERROR, "%s read error", ip.c_str());
                return nullptr;
            }
      }

      // lg(DEBUG, "path: %s ,url: %s ", (req.path_).c_str(), (req.url_).c_str());

      // 构建响应
      response res;
      it->handle_response(res, req);

      // 响应序列化
      std::string content;
      res.serialize(content);

      write(sockfd, content.c_str(), content.size());

      //销毁资源
      delete td;
      close(sockfd);

      return nullptr;
    }

private:
    uint16_t port_;
    std::string ip_;

    std::unordered_map<std::string, std::string> content_type_;
};


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 基于http协议的服务器代码编写(可以访问指定路径的资源版+添加跳转网页功能