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>