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>
相关推荐
MrSYJ3 小时前
Netty异常传播机制
java·服务器·netty
REDcker3 小时前
Linux Core Dump 配置与分析指南
linux·运维·服务器
IMPYLH3 小时前
Linux 的 chcon 命令
linux·运维·服务器
苦逼IT运维4 小时前
SVN 仓库目录迁移,仓库 “降级” 成子目录实战
linux·运维·ci/cd·svn·运维开发
阿拉斯攀登4 小时前
第 13 篇 输入设备驱动(触摸屏 / 按键)开发详解,Linux input 子系统全解析
android·linux·运维·驱动开发·rk3568·瑞芯微·rk安卓驱动
爱丽_4 小时前
TCP 三次握手与四次挥手
服务器·网络·tcp/ip
智能工业品检测-奇妙智能4 小时前
金属矿山安全智能AI视觉识别
服务器·人工智能·安全·openclaw·奇妙智能
bukeyiwanshui4 小时前
【无标题】
linux·运维·服务器
疯狂吧小飞牛4 小时前
Linux 多网卡同网段配置冲突问题
linux·运维·服务器