数据协议ptl.hpp
:#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
namespace ns_protocol
{
struct request//客户按照这样的约定发送数据
{
int x;
int y;
char op;
request():x(0),y(0),op('+'){}
};
struct respone//处理数据的返回结果
{
int code;
int result;
respone():code(0),result(0){}
};
}
sv.hpp
#pragma once
#include "ptl.hpp"
#include
namespace ns_server
{
class Server
{
private:
uint16_t port;//端口
int listen_sock;
public:
Server(uint16_t _po):port(_po),listen_sock(-1){}
void IninSe()
{
listen_sock = socket(AF_INET,SOCK_STREAM,0);//创建
if(listen_sock<0)
{
cerr<<"socket error " <<endl;
exit(2);
}
struct sockaddr_in local;
bzero(&local,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) < 0)//绑定
{
cerr<<"bind error"<<endl;
exit(3);
}
if(listen(listen_sock,5)<0)//监听
{
cerr<<"listen error"<<endl;
exit(4);
}
}
static void* calc(void* args)//线程处理函数
{
int sock = *(int*)args;
delete (int*)args;
while(true)
{
ns_protocol::request req;
ssize_t s = recv(sock,&req,sizeof(req),0);//接受数据,存储 处理
if(s>0)
{
ns_protocol::respone rsp;
switch(req.op)
{
case '+':
rsp.result = req.x + req.y;
break;
case '-':
rsp.result = req.x - req.y;
break;
case '*':
rsp.result = req.x * req.y;
break;
case '/':
if(req.y!=0)
{
rsp.result = req.x / req.y;
}
else{
rsp.code = 1;
}
break;
case '%':
if(req.y!=0)
{
rsp.result = req.x % req.y;
}
else
{
rsp.code = 2;
}
break;
default:
rsp.code = 3;
break;
}
send(sock,&rsp,sizeof(rsp),0);//发送数据
}
else if (s==0)
{
cout<< "cl quit---"<<endl;
break;
}
else
{
cerr<<"cl quit---,me to0"<<endl;
break;
}
}
close(sock);
return nullptr;
}
void Loop()//循环获取连接,处理数据
{
while(true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(listen_sock,(struct sockaddr*)&peer,&len);//获取客户端的连接
if(sock<0)
{
cerr << "accept error" << endl;
exit(5);
}
pthread_t tid;
int* p = new int(sock);
pthread_create(&tid,nullptr,calc,p);//创建线程
}
}
~Server()
{
if(listen_sock>=0)
{
close(listen_sock);
}
}
};
}
---//sv.cc
#include "sv.hpp"
int main()
{
uint16_t port = 8081;
ns_server::Server svr(port);
svr.IninSe();
cout<<"初始化完成"<<endl;
svr.Loop();
return 0;
}
cl.hpp
#pragma once
#include "ptl.hpp"
#include
namespace ns_client
{
class client
{
private:
string svr_ip;
uint16_t svr_port;
int sock;
public:
client(const string& _ip,uint16_t _port)
:svr_ip(_ip)
,svr_port(_port)
{}
void InitClient()
{
sock = socket(AF_INET,SOCK_STREAM,0);//创建
if(sock<0)
{
cerr<<"socket error"<<endl;
exit(2);
}
}
void Run()
{
struct sockaddr_in peer;
bzero(&peer,sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(svr_port);
peer.sin_addr.s_addr = inet_addr(svr_ip.c_str());
int ret = connect(sock,(struct sockaddr*)&peer,sizeof(peer));//发起连接
if(ret<0)
{
cerr<<"connect error"<<endl;
exit(3);
}
while(true)
{
ns_protocol::request req;
cout<<"cin one number# "<<endl;
cin>>req.x;
cout<<"cin onr char# "<<endl;
cin>>req.op;
cout<<"cin one number# "<<endl;
cin>>req.y;
send(sock,&req,sizeof(req),0);//发送
ns_protocol::respone rp;
ssize_t s = recv(sock,&rp,sizeof(rp),0);//接收
if(s>0)
{
cout<< "code : " <<rp.code<<endl;
cout<<"answer: "<<rp.result<<endl;
}
}
}
~client()
{
if(sock>=0)
{
close(sock);
}
}
};
}
---//cl.cc
#include "cl.hpp"
int main()
{
string ip ="127.0.0.1";
uint16_t port = 8081;
ns_client::client cl(ip,port);
cl.InitClient();
cl.Run();
return 0;
}
---//makefile
.PHONY:all
all:cl sv
cl:cl.cc
g++ -o $@ $^ -std=c++11
sv:sv.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f cl sv
HTTP(超文本传输协议)是应用层最重要的协议。有些场景需要自己定义协议。目前所有的上网行为,本质无非两种情况:一,把服务器上的数据拉取到本地;二,把自己的数据提交到服务器上(注册/登录)。程序员角度,IP=域名;用户角度,域名为了用户方便使用。
序列化、反序列化:将结构化的数据,序列化成可以在网络上传输的字符串流。序列化和反序列化的算法:json、XML。
ftp File Transfer protocol
http Hypertext Transfer Protocol
gopher The Gopher protocol
mailto Electronic mail address
news USENET news
nntp USENET news using NNTP access
telnet Reference to interactive sessions
wais Wide Area Information Servers
file Host-specific file names
prospero Prospero Directory Service
: @ /
进行编码。host
:域名。网络主机名由合法的 域名 、或者 IP 表示,其中合法的域名中一系列域标签由点号分隔,每个域标签以字母、数字、字符、开头和结尾,中间可能还包含 - 字符,最右边的域标签永远不会以、数字、开头。
port
: 可选的端口号。大多数协议具有默认端口号,所以有时可以省略。端口号以十进制为单位,与主机用冒号分隔,如果省略了端口,冒号也是可以省略的。
path
,要获取(访问)的应用资源的路径,即资源的存储位置,一般会使用/
来分级描述。路径也是可选的,缺省访问默认资源。query
:以 ? 开头,查询参数。格式为 key=value
,多个参数使用 &
分隔;参数也是可选的。frag
,一个网页上的某个资源在不调整大网页的情况下,一直在刷新,只需要调整这一小部分。#
开始到最后,表示定位到页面某个位置,/ ? :
等这样的字符,已经被url当做特殊意义理解了,因此这些字符不能随意出现。 static void *HandlerRequest(void *args)
{
pthread_detach(pthread_self());
int sock = *(int*)args;
delete (int*)args;
//debug
//get request
std::cout << "######################################## begin: " << sock << " #####" << std::endl;
char buffer[4096];
ssize_t s = recv(sock, buffer, sizeof(buffer), 0);
if(s > 0){
std::cout << buffer;
}
std::cout << "######################################## end: " << sock << " #####" << std::endl;
close(sock); //基于短链接
return nullptr;
}
######################################## begin: 5 #####
GET / HTTP/1.1
Host: 106.55.60.193:8081
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/104.0.5112.102 Safari/537.36 Edg/104.0.1293.63
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/\*;q=0.8,application/signed-exchange;v=b3;q=0.9
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
dnt: 1
sec-gpc: 1
######################################## end: 5 #####
格式组成解析解析:
GET
:请求指定的页面信息,并返回实体主体。POST
:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。Host
:指定要请求的资源所在的主机和端口User-Agent
作用:告诉服务器,客户端使用的操作系统、浏览器版本和名称。Accept:
:请求什么资源。Accept-Encoding
:可以接受的数据编码格式;Accept-Language
:表示浏览器所支持的语言类型Connection
:允许客户端和服务器指定与请求/响应连接有关的选项,例如这是为Keep-Alive则表示保持连接。只进行一次通信的叫短连接。Content-Length
:有效载荷的内容长度。Cache-Control
:对缓存进行控制,一个请求希望响应的内容是否在在客户端缓存。Referer
:表示这是请求是从哪个URL进来的。比如想在网上购物,但是不知道选择哪家电商平台。你就去问度娘,说哪家电商的东西便宜啊?然后一堆东西弹出在你面前,第一给就是某宝,当你从这里进入某宝的时候,这个请求报文的Referer就是www.baidu.com。Cookie
:用于在客户端存储少量信息,通常用于实现会话(session)功能。HTTP的解包和分用
响应报文组成:
Content-Type
:发送给接收者的实体正文的媒体类型。Content-Lenght
:实体正文的长度Content-Language
:描述资源所用的自然语言,没有设置则该选项则认为实体内容将提供给所有的语言阅读。Date
:表示消息产生的日期和时间Server
:包含可服务器用来处理请求的系统信息,与User-Agent请求报头是相对应的。Transfer-Encoding
:告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式。服务端核心代码简易的模拟构建响应:
static void *HandlerRequest(void *args)
{
pthread_detach(pthread_self());
int sock = *(int*)args;
delete (int*)args;
//make response
std::string body = "hello world
欢迎来的我的测试服务器
";
std::string response = HTTP_VERSION;
response += " 200 OK\n";
response += "Content-Type: text/html\n";
response += ("Content-Length: " + std::to_string(body.size()) + "\n");
response += "\n"; //传说中的空行,用来区分我们的报头和有效载荷
response += body;
send(sock, response.c_str(), response.size(), 0);
close(sock); //基于短链接
return nullptr;
}
#define HTTP_VERSION "HTTP/1.0"
#define HOME_PAGE "wwwroot/index.html"
static void *HandlerRequest(void *args)
{
pthread_detach(pthread_self());
int sock = *(int*)args;
delete (int*)args;
std::string response;
std::string body;
std::ifstream in(HOME_PAGE, std::ios::in | std::ios::binary);
if(!in.is_open()){
}
else{//成功
std::string line;
while(std::getline(in, line)){
body+=line;
}
response = HTTP_VERSION;
response += " 200 OK\n";
response += "Content-Type: text/html\n";
response += ("Content-Length: " + std::to_string(body.size()) + "\n");
response += "\n"; //传说中的空行,用来区分我们的报头和有效载荷
response += body;
}
in.close();
send(sock, response.c_str(), response.size(), 0);
close(sock); //基于短链接
return nullptr;
}
---//简易的前端html框架文件:/wwwroot/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>你好,这里是云服务器!</h1>
<p>此时所看到的页面,是在进程所在路径的同级某个目录,该目录作为根目录。</p>
<p>根目录下的一个html文件作为默认主页,底层通过tcp协议传输,再通过浏览器解析成所看到的网页样式</p>
<p>test for http response</p>
<p>方法: 写一个网页的html文件</p>
<p>将文件读到一个缓冲区,作为有效数据。按照响应格式,字符串+="....\n" </p>
<p>最后 send 发送 </p>
<p>通过html实现动态响应,将前端和后端解耦。</p>
<p>差不多就是这个意思。。。帖子们</p>
</body>
</html>
获取响应报文的方法:postman、浏览器、telnet
使用 postman 进行GET方法获取响应:
可以通过浏览器 ip 和 端口的方式获取响应。
通过 telnet
语法操作获取响应。
主要介绍 GET 和 POST 方法。
GET方法
POST方法
一般用于将数据上传给服务器。
是通过正文传参的,通过正文传参能传递更多的参数。
提交更具有私密性。
两者比较:两种方法都不安全,POST方法传参可以被截取,要做到安全只能通过加密来完成
HTML表单:网页可以实现账户密码登录的功能
GET 与 POST 现象验证:
GET:
POST:
每个网站的根目录都会有一个 index.html
作为网站的主页面。这里的根目录可以是服务器后端任意一个路径的一个目录。
4xx系列:404为例:
---//wwwroot/404.html
<title>test for 404</title>
</head>
<body>
<h1>404</h1>
<p>Not Found ,您请求的网页飞走了.....</p>
</body>
3xx重定向系列:301、307
重定向演示:需要用到响应报文中的 Location 报头。
---//重定向验证
string status_line = HTTP_VERSION;//状态行
status_line += (" " + to_string(301) + " Moved Permanently\n");
string resp_header = "Content-Type: text/html;charset=uft8\n";
resp_header += "location: https://cn.bing.com/?mkt=zh-CN\n";
resp_header += "\n";
send(sock,status_line.c_str(),status_line.size(),0);
send(sock,resp_header.c_str(),resp_header.size(),0);
什么是Http无状态?Session、Cookie、Token三者之间的区别
cookie 和 session 相互配合,依然存在信息被盗取的风险。区别在于,用户个人私密信息是保存在服务器端 的,客户端只保留了 session ID。服务器端有各位网络安全工程师通过各种复杂机制来确保安全。比如:cookie 和 session 有时间限制的。有黑就有白,安全永远是处于 相对安全的状态。
---//响应报文伪代码
response += "HTTP/1.0 200 OK\n";
response += "Content-Type: text/html\n";
response += ("Content-Length: " + std::to_string(body.size()) + "\n");
response += "Set-Cookie: sessionid=123456\n"; //添加Set-Cookie字段
response += "\n";
response += body;
send(sock, response.c_str(), response.size(), 0);