Linux HTTP服务器

1.完成对于服务器的基础编写

socket.hpp

套接字模块

cpp 复制代码
#pragma once
#include<iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include<string>
#include<netinet/in.h>
#include <arpa/inet.h>//sockaddr_in 头文件
#include"log.hpp"
using namespace std;
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_ == -1)
        {
            lg(Fatal,"socket error,%s: %d",strerror(errno),errno);
            exit(SocketErr);
        }    
    }
    void Bind(uint16_t port)
    {
        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        local.sin_port=htons(port);
        local.sin_family=AF_INET;
        local.sin_addr.s_addr=INADDR_ANY;
        if(bind(sockfd_,(struct sockaddr*)(&local),sizeof(local)) == -1)
        {
            lg(Fatal,"bind error,%s: %d",strerror(errno),errno);
            exit(BindErr);
        }
    }
    void Listen()
    {
        if(listen(sockfd_,backlog) == -1)
        {
            lg(Fatal,"listen error,%s: %d",strerror(errno),errno);
            exit(ListenErr);
        }
    }
    int Accept(string*clientip,uint16_t*clientport)
    {
        struct sockaddr_in client;
        socklen_t len=sizeof(client);
        int newfd=accept(sockfd_,(struct sockaddr*)(&client),&len);
        if(newfd<0)
        {
            lg(Fatal,"accept error,%s: %d",strerror(errno),errno);
            return -1;
        }
        char ipstr[32];
        inet_ntop(AF_INET,&(client.sin_addr),ipstr,sizeof(ipstr));//字节序转字符串
        *clientip=ipstr;
        *clientport=ntohs(client.sin_port);
        return newfd;
    }
    bool Connect(const string&ip,const uint16_t&port)
    {
        struct sockaddr_in server;
        memset(&server,0,sizeof(server));
        server.sin_family=AF_INET;
        server.sin_port=ntohs(port);
        inet_pton(AF_INET,ip.c_str(),&(server.sin_addr));
        int n=connect(sockfd_,(struct sockaddr*)(&server),sizeof(server));
        if(n==-1)
        {
            cerr<<"connect to"<<ip<<":"<<port<<" error "<<endl;
            return false;
        }
        return true;
    }
    void Close()
    {
        close(sockfd_);
    }
    int Fd()
    {
        return sockfd_;
    }   
private:
    int sockfd_;
};

makefile

cpp 复制代码
httpserver:httpserver.cpp
	g++ -o $@ $^ -std=c++11 -lpthread -g
.PHONY:clean
	rm -f httpserver

log.hpp

日志模块

cpp 复制代码
#pragma once
#include <iostream>
#include <time.h>
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.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"
using namespace std;
class Log
{
public:
    Log()
    {
        printmethod = Screen;
        path = "./log/";
    }
    void Enable(int enable)
    {
        printmethod = enable;
    }
    string Leveltostring(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }
    void printlog(int level, const string &logtxt)
    {
        switch (printmethod)
        {
        case Screen:
            cout << logtxt << endl;
        case Onefile:
        {
            printOnefile(Logfile, logtxt);
            break;
        }
        case Classfile:
        {
            printClassfile(level, logtxt);
            break;
        }
        default:
            break;
        }
    }
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(NULL);
        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\n", leftbuffer, rightbuffer);
        printlog(level, logtxt);
    }
    void printOnefile(const string &logname, const string &logtxt)
    {
        string _logname = path+logname;
        int fd = open(_logname.c_str(), O_WRONLY|O_CREAT|O_APPEND,0666);
        if(fd<0)
        {
            return;
        }
        write(fd,logtxt.c_str(),logtxt.size());
        close(fd);
    }
    void printClassfile(int level, const string &logtxt)
    {
        string filename=Logfile;
        filename+='.';
        filename+=Leveltostring(level);
        printOnefile(filename,logtxt);
    }

private:
    int printmethod;
    string path;
};
Log lg;

httpserver.hpp

对于监听上来的连接,主线程负责监听连接,创建子线程,子线程进行线程分离,打印客户端传来的数据,断开连接。

cpp 复制代码
#pragma once
#include<iostream>
#include<pthread.h>
#include"socket.hpp"
#include"log.hpp"
const uint16_t defaultport=8888;
class ThreadData
{
public:
    int sockfd_;
};
class HttpServer
{
public:
    HttpServer(uint16_t port=defaultport)
    :port_(port)
    {}
    ~HttpServer()
    {}
    bool Start()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
        for(;;)
        {
            string clientip;
            uint16_t port;
            int sockfd=listensock_.Accept(&clientip,&port);
            if(sockfd<0)
            {
                continue;
            }
            lg(Info,"get a new connect,sockfd: %d",sockfd);
            pthread_t tid;
            ThreadData*td=new ThreadData;
            td->sockfd_=sockfd;
            pthread_create(&tid,nullptr,ThreadRun,td);
        }
    }
    static void*ThreadRun(void*args)
    {
        pthread_detach(pthread_self());
        ThreadData*td=static_cast<ThreadData*>(args);
        char buffer[10240];
        ssize_t n=read(td->sockfd_,buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]=0;
            cout<<buffer<<endl;
        }
        close(td->sockfd_);
    }
private:
    sock listensock_;
    uint16_t port_;
};

2.发送数据给客户端

我们完成的是一个http的服务器,也就是说,测试时我们使用浏览器来动作客户端进行访问。

所以首先我们当有浏览器访问时,返回一个"hello world"的字符串给浏览器。

cpp 复制代码
static void*ThreadRun(void*args)
    {
        pthread_detach(pthread_self());
        ThreadData*td=static_cast<ThreadData*>(args);
        char buffer[10240];
        ssize_t n=read(td->sockfd_,buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]=0;
            cout<<buffer<<endl;
            string text="hello world";
            string response="HTTP/1.1 200 OK\r\n";
            response+="\r\n";
            response+=text;
            send(td->sockfd_,response.c_str(),response.size(),0);
        }
        close(td->sockfd_);
    }

编写一个字符串,设置状态行,版本为HTTP/1.1,状态码为200,状态码描述为OK。

正文格式为"hello world"。

服务器接收到请求,打印http请求

cpp 复制代码
Info-2026-3-16-22-5-56-get a new connect,sockfd: 4

GET / HTTP/1.1
Host: 113.46.212.34:8888
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

发送回给浏览器。

通过浏览器的访问ip加端口,果然显示了一个hello world。

html 复制代码
<html>
<head></head>
<body>
<pre style="word-wrap: break-word; white-space: pre-wrap;">hello world
</pre>
</body>
</html>

打开浏览器开发工具,可以看到对应响应的正文信息,因为服务器没有告诉浏览器,发送过来的是什么格式的数据,浏览器默认为是纯文本的信息,使用html进行渲染。

返回html格式的数据

返回html格式的数据给浏览器。

cpp 复制代码
static void*ThreadRun(void*args)
    {
        pthread_detach(pthread_self());
        ThreadData*td=static_cast<ThreadData*>(args);
        char buffer[10240];
        ssize_t n=read(td->sockfd_,buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]=0;
            cout<<buffer<<endl;
            // string text="hello world";
            string text="<html><h1>hello world</h1></html>";
            string response="HTTP/1.1 200 OK\r\n";
            response+="\r\n";
            response+=text;
            send(td->sockfd_,response.c_str(),response.size(),0);
        }
        close(td->sockfd_);
    }

动态加载文件作为 HTTP 响应正文

但是这个样子还不够好,我们期望在服务端将html的响应正文放到文件中,那么每次服务器收到http请求之后,想要构建http响应,那么对于响应正文,每次都打开文件动态的从文件进行加载,这样即使我们要修改文件的内容,那么服务器也不用重启

我们创建一个WWWROOT目录,里面存放需要发送给客户端的html文件。

读取index.html的文件,并发送给浏览器

cpp 复制代码
//读取文件的内容
    static string ReadHTMLContent(const string&htmlpath)
    {
        ifstream in(htmlpath);
        if(!in.is_open())
        {
            return "404";
        }
        string content;
        string line;
        while(getline(in,line))
        {
            content+=line;
        }
        in.close();
        return content;
    }
    static void HandleHttp(int sockfd)
    {
        char buffer[10240];
        ssize_t n=read(sockfd,buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]=0;
            cout<<buffer<<endl;
            // string text="hello world";
            string text=ReadHTMLContent("./WWWROOT/index.html");
            string response="HTTP/1.1 200 OK\r\n";
            response+="\r\n";
            response+=text;
            send(sockfd,response.c_str(),response.size(),0);
        }
        close(sockfd);
    }
    static void*ThreadRun(void*args)
    {
        pthread_detach(pthread_self());
        ThreadData*td=static_cast<ThreadData*>(args);
        HandleHttp(td->sockfd_);
    }

浏览器访问服务器结果:

在不断开连接的情况下面,修改htnl文件,多打印两行hello world

刷新浏览器

可以看到确实多打印了两行hello world,实现动态加载。

解析HTTP请求返回对应的资源

到目前为止,我们真的对 HTTP 请求做解析了吗?其实没有。

我们只是把浏览器发来的 HTTP 请求原样打印出来,没有做任何字符串切割、分析、提取。不管浏览器发什么请求,我们都统一返回一句固定的 hello world 。

但现实中,用户用浏览器访问服务器,不是为了看一句 hello world,而是想访问服务器上的网页、图片、资源。

在 Linux 里,一切皆文件,所谓资源,本质就是磁盘上的文件。不同资源放在不同路径下,用户访问不同路径,我们就返回不同文件。

所以接下来,我们要搭建一个简单的网站根目录 wwwroot,并在里面放不同页面:

WWWROOT/index.html作为首页

WWWROOT/A/hello.html 放第一个网页

WWWROOT/B/world.html 放第二个网页

WWWROOT/index.html

cpp 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>首页</h1>
    <h2>首页</h2>
    <h3>首页</h3>
    <h4>首页</h4>
    <h1>首页</h1>
    <h2>首页</h2>
</body>
</html>

WWWROOT/A/hello.html

cpp 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>hello</h1>
    <h2>hello</h2>
    <h3>hello</h3>
    <h4>hello</h4>
    <h1>hello</h1>
    <h2>hello</h2>
</body>
</html>

WWWROOT/B/world.html

cpp 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>world</h1>
    <h2>world</h2>
    <h3>world</h3>
    <h4>world</h4>
    <h1>world</h1>
    <h2>world</h2>
</body>
</html>

并且我们还要知道,如果用户只是在浏览器里输入服务器的 IP 和端口号,没有写任何路径,那么浏览器默认请求的 URL 就是 /。

而 / 代表的就是我们的 web 根目录。

但是我们打开百度的时候,百度会把它 web 根目录下所有文件、所有资源都发给我们吗?

当然不会。百度服务器只是把百度首页对应的那个 html 文件内容,作为 HTTP 响应的正文返回给我们。所以我们在浏览器上看到的,只是百度首页。只有我们点击、搜索、跳转,才会去访问百度根目录下不同路径、不同资源。

所以,我们今天的服务器也要遵循这个规则:

当用户请求的 URL 是 / 时,我们就返回我们自己的首页。就是我们 web 根目录 WWWROOT 下的 index.html。如果URL目的是WWWROOT下面的A文件夹或者B文件夹,在返回相应的资源给对方。

我们该怎么解析 HTTP 请求?服务器收到的 HTTP 请求,本质上是什么?本质就是一串字符串。

解析 HTTP 请求,本质就是解析字符串。

就需要按照上图中的格式提取出具体访问的地址。

HttpReqest请求处理类

cpp 复制代码
class HttpRequest
{
public:
    void Deserialization(string req)
    {}
    void parse(string url)
    {}
    void DebugPrint()
    {}
private:
    string method_;
    string url_;
    string http_version;
    string filepath_;
};

这个类的目的主要是要从请求的第一行的URL中提取到文件的路径,通过DebugPrint打印出请求行的信息。

成员变量主要是请求行的信息,请求方法,唯一资源定位符,协议版本,访问文件路径。

Deserialization()分离请求消息和正文

这个接口主要在一个完整的http请求中读取并存储请求行和请求报文。

我们也不知道这份 HTTP 请求到底有多少行,所以干脆写个 while(true) 死循环。只要没碰到结束的标志,我们就一直循环读。
每一行都是用 \r\n 结尾的。我们的第一步就是在这段文本里找这个分隔符。

如果找不到:说明这一行还没读完,数据可能被拆分了,或者还没传输完,这时候就先退出循环,等新数据进来再继续。

如果找到了:那就拿到了当前行的结束位置 pos 。
从字符串开头切到 pos 这个位置,就能把当前行的实际内容(不包含 \r\n )单独拿出来存到 temp 里。

如果 temp 是空的就说明"头信息结束了,后面都是正文"。

所以这时候直接跳出循环就行。剩下没处理完的字符串,直接赋值给正文变量 text 即可。

如果 temp 不为空:说明这还是报头信息,把它丢进存报头的数组里。
我们已经处理完这一行了,就应该把它从原始字符串 req 里删掉。就是刚刚取出来的内容长度 temp.size() 加上分隔符 sep 的长度。从开头开始删,这样下次循环就从下一行开始读了。

cpp 复制代码
void Deserialization(string req)
    {
        while(true)
        {
            int pos=req.find(sep);
            if(pos==string::npos)
            {
                break;
            }
            string temp=req.substr(0,pos);
            if(temp.empty())
            {
                break;
            }
            req_header.push_back(temp);
            req.erase(0,temp.size()+sep.size());
        }
        //剩下的就是请求正文
        text=req;
    }

parse()提取请求行

Parse解析的作用是提取数组的第一行,第一行是请求行,那么按照空格进行分隔请求行的字段依次得到请求方法method,url,http版本,根据url分析出用户客户端想要访问的文件路径file_path

我们需要从这一行里把三个关键东西抠出来:

请求方法(比如 GET)

URL(比如 /index.html)

HTTP 版本(比如 HTTP/1.1)

也就是需要分离以空格为间隔的字符串,直接用 C++ 的 stringstream 就可以了

将第一行的字符串丢进 stringstream 里,然后用 >> 去读取。

因为 >> 运算符默认就是以空格为分隔的,所以我们直接顺序读进去:

第一个读到的是 method

第二个读到的是 url

第三个读到的是 http_version

接下来:根据 URL 求出真实的文件路径

我们的服务器根目录叫 WWWROOT,所以所有访问都要基于这个目录。先把 file_path 初始化为根目录,如果只有一个/或者访问的是index/html就说明访问是WWWROOT目录下的index.html文件。

DebugPrint()

那么DebugPrind就是将我们反序列化,解析的字段进行打印即可

cpp 复制代码
void DebugPrint()
    {
        std::cout << "=================================================================" << std::endl;
        for(auto& line : req_header)
            std::cout << line << std::endl;

        std::cout << "=================================================================" << std::endl;
        std::cout << "methond: " << method_ << std::endl;
        std::cout << "url: " << url_ << std::endl;
        std::cout << "version: " << http_version_ << std::endl;
        std::cout << "file_path: " << filepath_ << std::endl;
        std::cout << text << std::endl;
    }

测试:

输入IP地址加端口号:

输入IP地址加端口号加A目录下的hello.html路径

输入IP地址加端口号加B目录下的world.html路径

输入不存在的资源路径

给网页加上跳转功能

index.html

cpp 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>首页</h1>
    <h2>首页</h2>
    <h3>首页</h3>
    <h4>首页</h4>
    <h1>首页</h1>
    <h2>首页</h2>
    <a href="http://113.46.212.34:8888/A/hello.html">点击前往hello网页
    <a href="http://113.46.212.34:8888/B/world.html">点击前往world网页
</body>
</html>

对应网页界面

hello.html

cpp 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>hello</h1>
    <h2>hello</h2>
    <h3>hello</h3>
    <h4>hello</h4>
    <h1>hello</h1>
    <h2>hello</h2>
    <a href="http://113.46.212.34:8888/index.html">点击前往首页
    <a href="http://113.46.212.34:8888/B/world.html">点击前往world网页
</body>
</html>

对应网页界面

world.html

cpp 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>world</h1>
    <h2>world</h2>
    <h3>world</h3>
    <h4>world</h4>
    <h1>world</h1>
    <h2>world</h2>
    <a href="http://113.46.212.34:8888/index.html">点击前往首页
    <a href="http://113.46.212.34:8888/A/hello.html">点击前往hello网页
</body>
</html>

对应网页界面

源代码

httpserver.hpp

cpp 复制代码
#pragma once
#include<iostream>
#include<fstream>
#include<pthread.h>
#include<vector>
#include<sstream>
#include"socket.hpp"
#include"log.hpp"
using namespace std;
const uint16_t defaultport=8888;
const string wwwroot="./WWWROOT";
const string sep="\r\n";
const string homepage="index.html";
class ThreadData
{
public:
    int sockfd_;
};
class HttpRequest
{
public:
    HttpRequest()
    {}
    void Deserialization(string req)
    {
        while(true)
        {
            int pos=req.find(sep);
            if(pos==string::npos)
            {
                break;
            }
            string temp=req.substr(0,pos);
            if(temp.empty())
            {
                break;
            }
            req_header.push_back(temp);
            req.erase(0,temp.size()+sep.size());
        }
        //剩下的就是请求正文
        text=req;
    }
    void parse()
    {
        stringstream ss(req_header[0]);
        ss>>method_>>url_>>http_version_;
        filepath_=wwwroot;
        if(url_=="/"||url_==homepage)
        {
            filepath_+="/";
            filepath_+=homepage;
        }
        else
        {
            filepath_+=url_;
        }
    }
    void DebugPrint()
    {
        std::cout << "=================================================================" << std::endl;
        for(auto& line : req_header)
            std::cout << line << std::endl;

        std::cout << "=================================================================" << std::endl;
        std::cout << "methond: " << method_ << std::endl;
        std::cout << "url: " << url_ << std::endl;
        std::cout << "version: " << http_version_ << std::endl;
        std::cout << "file_path: " << filepath_ << std::endl;
        std::cout << text << std::endl;
    }
public:
    vector<string> req_header;//请求信息
    string text;//请求正文
    string method_;
    string url_;
    string http_version_;
    string filepath_;
};
class HttpServer
{
public:
    HttpServer(uint16_t port=defaultport)
    :port_(port)
    {}
    ~HttpServer()
    {}
    bool Start()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
        for(;;)
        {
            string clientip;
            uint16_t port;
            int sockfd=listensock_.Accept(&clientip,&port);
            if(sockfd<0)
            {
                continue;
            }
            lg(Info,"get a new connect,sockfd: %d",sockfd);
            pthread_t tid;
            ThreadData*td=new ThreadData;
            td->sockfd_=sockfd;
            pthread_create(&tid,nullptr,ThreadRun,td);
        }
    }
    //读取文件的内容
    static string ReadHTMLContent(const string&htmlpath)
    {
        ifstream in(htmlpath);
        if(!in.is_open())
        {
            return "404";
        }
        string content;
        string line;
        while(getline(in,line))
        {
            content+=line;
        }
        in.close();
        return content;
    }
    static void HandleHttp(int sockfd)
    {
        char buffer[10240];
        ssize_t n=read(sockfd,buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]=0;
            cout<<buffer<<endl;
            // string text="hello world";
            HttpRequest req;
            req.Deserialization(buffer);
            req.parse();
            req.DebugPrint();
            string text=ReadHTMLContent(req.filepath_);
            string response="HTTP/1.1 200 OK\r\n";
            response+="\r\n";
            response+=text;
            send(sockfd,response.c_str(),response.size(),0);
        }
        close(sockfd);
    }
    static void*ThreadRun(void*args)
    {
        pthread_detach(pthread_self());
        ThreadData*td=static_cast<ThreadData*>(args);
        HandleHttp(td->sockfd_);
    }
private:
    sock listensock_;
    uint16_t port_;
};

httpserver.cpp

cpp 复制代码
#include<iostream>
#include<memory>
#include"httpserver.hpp"
using namespace std;
int main(int argc,char*argv[])
{
    uint16_t port=stoi(argv[1]); 
    
    HttpServer*svr=new HttpServer(port);
    svr->Start();
    return 0;
}

makefile

cpp 复制代码
httpserver:httpserver.cpp
	g++ -o $@ $^ -std=c++11 -lpthread -g
.PHONY:clean
	rm -f httpserver

socket.hpp

cpp 复制代码
#pragma once
#include<iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include<string>
#include<netinet/in.h>
#include <arpa/inet.h>//sockaddr_in 头文件
#include"log.hpp"
using namespace std;
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_ == -1)
        {
            lg(Fatal,"socket error,%s: %d",strerror(errno),errno);
            exit(SocketErr);
        }
        int opt=1;
        setsockopt(sockfd_,SOL_SOCKET,SO_REUSEPORT,&opt,sizeof(opt));
    }
    void Bind(uint16_t port)
    {
        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        local.sin_port=htons(port);
        local.sin_family=AF_INET;
        local.sin_addr.s_addr=INADDR_ANY;
        if(bind(sockfd_,(struct sockaddr*)(&local),sizeof(local)) == -1)
        {
            lg(Fatal,"bind error,%s: %d",strerror(errno),errno);
            exit(BindErr);
        }
    }
    void Listen()
    {
        if(listen(sockfd_,backlog) == -1)
        {
            lg(Fatal,"listen error,%s: %d",strerror(errno),errno);
            exit(ListenErr);
        }
    }
    int Accept(string*clientip,uint16_t*clientport)
    {
        struct sockaddr_in client;
        socklen_t len=sizeof(client);
        int newfd=accept(sockfd_,(struct sockaddr*)(&client),&len);
        if(newfd<0)
        {
            lg(Fatal,"accept error,%s: %d",strerror(errno),errno);
            return -1;
        }
        char ipstr[32];
        inet_ntop(AF_INET,&(client.sin_addr),ipstr,sizeof(ipstr));//字节序转字符串
        *clientip=ipstr;
        *clientport=ntohs(client.sin_port);
        return newfd;
    }
    bool Connect(const string&ip,const uint16_t&port)
    {
        struct sockaddr_in server;
        memset(&server,0,sizeof(server));
        server.sin_family=AF_INET;
        server.sin_port=ntohs(port);
        inet_pton(AF_INET,ip.c_str(),&(server.sin_addr));
        int n=connect(sockfd_,(struct sockaddr*)(&server),sizeof(server));
        if(n==-1)
        {
            cerr<<"connect to"<<ip<<":"<<port<<" error "<<endl;
            return false;
        }
        return true;
    }
    void Close()
    {
        close(sockfd_);
    }
    int Fd()
    {
        return sockfd_;
    }   
private:
    int sockfd_;
};

WWWROOT

index.html

cpp 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>首页</h1>
    <h2>首页</h2>
    <h3>首页</h3>
    <h4>首页</h4>
    <h1>首页</h1>
    <h2>首页</h2>
    <a href="http://113.46.212.34:8888/A/hello.html">点击前往hello网页
    <a href="http://113.46.212.34:8888/B/world.html">点击前往world网页
</body>
</html>

A/hello.html

cpp 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>hello</h1>
    <h2>hello</h2>
    <h3>hello</h3>
    <h4>hello</h4>
    <h1>hello</h1>
    <h2>hello</h2>
    <a href="http://113.46.212.34:8888/index.html">点击前往首页
    <a href="http://113.46.212.34:8888/B/world.html">点击前往world网页
</body>
</html>

B/world.html

cpp 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>world</h1>
    <h2>world</h2>
    <h3>world</h3>
    <h4>world</h4>
    <h1>world</h1>
    <h2>world</h2>
    <a href="http://113.46.212.34:8888/index.html">点击前往首页
    <a href="http://113.46.212.34:8888/A/hello.html">点击前往hello网页
</body>
</html>
相关推荐
陈希12323 分钟前
Linux 防火墙配置实战:firewalld、iptables 与云安全组
linux·运维·服务器
女神下凡1 小时前
芯参谋(95):嵌入式文件系统_开发指南
linux·服务器·arm开发·驱动开发·设计规范
@兽血沸腾2 小时前
Windows系统安装Jenkins部署应用至远程Windows服务器操作指南
运维·服务器·jenkins
且从容.5 小时前
Ubuntu安装flume
linux·ubuntu·flume
承渊政道5 小时前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
讯捷蓝达7 小时前
戴尔R720xd服务器卡死在 “Configuring Memory...Done“,换了内存还是卡?问题根本不在内存
运维·服务器
且从容.8 小时前
ubuntu安装flink
linux·ubuntu·flink
数字新视界9 小时前
动环监控信创适配在智能机房管理中的应用价值与未来展望
大数据·服务器·数据库·动环监控系统·动力与环境监控系统
爱和冰阔落9 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨9 小时前
WSL2的安装与使用
linux·wsl