网络版计算器
我们用方案二来实现,方案一没有啥意义。需要安装第三方库jsoncpp
sudo yum install -y jsoncpp-devel
#include <pthread.h>
#include "Protocol.hpp"
#include "Sock.hpp"
using namespace std;
#include <unistd.h>
#include <functional>
#include <map>
static void Usage(string proc)
{
cout << "Usage: " << proc << " port" << endl;
exit(1);
}
void *HandlerRequest(void *args)
{
int sock = *(int *)args;
delete (int *)args;
pthread_detach(pthread_self());
// version1 原生方法,没有明显的序列化和反序列化的过程
// 业务逻辑, 做一个短服务
// request -> 分析处理 -> 构建response -> sent(response)->close(sock)
// 1. 读取请求
std::map<char, std::function<std::pair<int, int>(int, int)>> mp;
mp['+'] = [](int a, int b)->std::pair<int,int>{return make_pair(0, a + b); };
mp['-'] = [](int a, int b)->std::pair<int, int>{return make_pair(0, a - b); };
mp['*'] = [](int a, int b)->std::pair<int, int>{return make_pair(0, a * b); };
mp['%'] = [](int a, int b)->std::pair<int, int>{if (b == 0) return make_pair(-1, 0);
else return make_pair(0, a % b);};
mp['/'] = [](int a, int b)->std::pair<int, int>{if (b == 0) return make_pair(-2, 0);
else return make_pair(0, a / b); };
char buffer[1024];
request_t req;
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0;
cout << "get a new request: " << buffer << endl;
std::string str = buffer;
DeserializeRequest(str, req); //反序列化请求
// request_t req;
// ssize_t s = read(sock, &req, sizeof(req));
// if (s == sizeof(req))
// {
//读取到了完整的请求,待定
// req.x , req.y, req.op
// 2. 分析请求 && 3. 计算结果
// 4. 构建响应,并进行返回
response_t resp = {0, 0};
// switch (req.op)
// {
// case '+':
// resp.result = req.x + req.y;
// break;
// case '-':
// resp.result = req.x - req.y;
// break;
// case '*':
// resp.result = req.x * req.y;
// break;
// case '/':
// if (req.y == 0)
// resp.code = -1; //代表除0
// else
// resp.result = req.x / req.y;
// break;
// case '%':
// if (req.y == 0)
// resp.code = -2; //代表模0
// else
// resp.result = req.x % req.y;
// break;
// default:
// resp.code = -3; //代表请求方法异常
// break;
// }
if(mp.find(req.op) != mp.end())
{
resp.result = mp[req.op](req.x,req.y).second;
resp.code = mp[req.op](req.x,req.y).first;
}
else
{
resp.code = -3;
}
cout << "request: " << req.x << req.op << req.y << endl;
// write(sock, &resp, sizeof(resp));
std::string send_string = SerializeResponse(resp); //序列化之后的字符串
write(sock, send_string.c_str(),send_string.size());
cout << "服务结束: " << send_string << endl;
// }
}
// 5. 关闭链接
close(sock);
}
// ./CalServer port
int main(int argc, char *argv[])
{
if (argc != 2)
Usage(argv[0]);
uint16_t port = atoi(argv[1]);
int listen_sock = Sock::Socket();
Sock::Bind(listen_sock, port);
Sock::Listen(listen_sock);
for (;;)
{
int sock = Sock::Accept(listen_sock);
if (sock >= 0)
{
cout << "get a new client..." << endl;
int *pram = new int(sock);
pthread_t tid;
pthread_create(&tid, nullptr, HandlerRequest, pram);
}
}
return 0;
}
NetClient.cc
#include "Protocol.hpp"
#include "Sock.hpp"
using namespace std;
#include<unistd.h>
void Usage(string proc)
{
cout << "Usage: " << proc << " server_ip server_port" << endl;
}
// ./CalClient server_ip server_port
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
int sock = Sock::Socket();
Sock::Connect(sock, argv[1], atoi(argv[2]));
// while (true)
// {
// 业务逻辑
request_t req;
memset(&req, 0, sizeof(req));
cout << "Please Enter Data One# ";
cin >> req.x;
cout << "Please Enter Data Two# ";
cin >> req.y;
cout << "Please Enter operator# ";
cin >> req.op;
std::string json_string = SerializeRequest(req);
// ssize_t s = write(sock, &req, sizeof(req));
ssize_t s = write(sock, json_string.c_str(), json_string.size());
char buffer[1024];
s = read(sock, buffer, sizeof(buffer) - 1);
if (s > 0)
{
response_t resp;
buffer[s] = 0;
std::string str = buffer;
DeserializeResponse(str, resp);
cout << "code[0:success]: " << resp.code << endl;
cout << "result: " << resp.result << std::endl;
}
// response_t resp;
// s = read(sock, &resp, sizeof(resp));
// if (s == sizeof(resp))
// {
// cout << "code[0:success]: " << resp.code << endl;
// cout << "result: " << resp.result << std::endl;
// }
// else{
// break;
// }
// }
return 0;
}
Protocol.hpp
#include<jsoncpp/json/json.h>
typedef struct request
{
int x;
int y;
char op;
}request_t;
typedef struct response
{
int code;
int result;
}response_t;
std::string SerializeRequest(request_t &req) //序列化请求
{
Json::Value root; //可以承载任何对象
root["datax"] = req.x;
root["datay"] = req.y;
root["op"] = req.op;
//序列化请求
Json::FastWriter writer;
std::string json_string = writer.write(root);
return json_string;
}
void DeserializeRequest(std::string &json_string,request_t &out) //反序列化请求
{
Json::Reader reader;
Json::Value root;
reader.parse(json_string,root);
out.op = root["op"].asInt();
out.x = root["datax"].asInt();
out.y = root["datay"].asInt();
}
std::string SerializeResponse(response_t &res) //序列化响应
{
Json::Value root; //可以承载任何对象
root["code"] =res.code;
root["result"] = res.result;
//序列化响应
Json::FastWriter writer;
std::string json_string = writer.write(root);
return json_string;
}
void DeserializeResponse(std::string &json_string,response_t &out) //反序列化响应
{
Json::Reader reader;
Json::Value root;
reader.parse(json_string,root);
out.code = root["code"].asInt();
out.result = root["result"].asInt();
}
Sock.cc
#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
class Sock
{
public:
static int Socket()
{
int listen_sock = socket(AF_INET,SOCK_STREAM,0);
if(listen_sock < 0)
{
std::cerr<<"listen_sock"<<listen_sock<<std::endl;
exit(1);
}
return listen_sock;
}
static void Bind(int sock,uint16_t port)
{
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_addr.s_addr = INADDR_ANY;
local.sin_family = AF_INET;
local.sin_port = htons(port);
if(bind(sock,(struct sockaddr*)(&local),sizeof(local)) < 0)
{
std::cerr<<"bind()"<<std::endl;
}
}
static void Listen(int sock)
{
if(listen(sock,5) < 0)
{
std::cerr<<"listen()"<<std::endl;
}
}
static int Accept(int sock)
{
struct sockaddr_in perr;
memset(&perr,0,sizeof(perr));
socklen_t len = sizeof(perr);
int newsock = accept(sock,(struct sockaddr*)(&perr),&len);
if(newsock >= 0)
return newsock;
return -1;
}
static void Connect(int sock,std::string ip,uint16_t port)
{
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_addr.s_addr = inet_addr(ip.c_str());
server.sin_family = AF_INET;
server.sin_port = htons(port);
if(connect(sock,(struct sockaddr*)(&server),sizeof(server)) == 0)
{
std::cerr<<"connect() is sucesss"<<std::endl;
}
else
{
std::cerr<<"connect() is fail"<<std::endl;
}
}
};
makefile
.PHONY:all
all:NetServer NetClient
NetServer:NetServer.cc
g++ -o $@ $^ -std=c++11 -lpthread -ljsoncpp
NetClient:NetClient.cc
g++ -o $@ $^ -std=c++11 -lpthread -ljsoncpp
.PHONY:clean
clean:
rm -rf NetClient NetServer
HTTP协议
认识URL
平时我们俗称的 "网址" 其实就是说的 URL
我们通常表示互联网上的唯一资源,是ip+路径。
ip通常是以域名的方式呈现的,路径是通过路径 + /确定的
urlencode和urldecode
Http的格式
Http的请求或者响应,基本上都是按行(\n)为单位来进行构建的请求和响应的!无论是请求还是响应都是由3到4部分组成的。
Http的读取的发送都是按照字符串的方式。空行是一个特殊字符:用空行可以将长字符串一切为二。
接下来我们观察一下Http的报文
#include <iostream>
#include "Sock.hpp"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
void Usage(std::string str)
{
std::cout << str << " port " << std::endl;
}
void *HandlerResult(void *args)
{
int scok = *(int *)args;
delete (int *)args;
pthread_detach(pthread_self());
char buf[1024];
ssize_t s = recv(scok, buf, sizeof(buf), 0);
if(s > 0)
{
buf[s] = 0;
std::cout << buf << std::endl;
}
close(scok);
return nullptr;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
Usage(argv[0]);
return 1;
}
int listen_sock = Sock::Socket();
Sock::Bind(listen_sock, atoi(argv[1]));
Sock::Listen(listen_sock);
for (;;)
{
int sock = Sock::Accept(listen_sock);
pthread_t tid;
int *pram = new int(sock);
pthread_create(&tid, nullptr, HandlerResult, pram);
}
}
用电脑连接服务器
用手机连接服务器
现在我们客服端网页没有任何响应,接写来我们响应网页内容
#include <iostream>
#include "Sock.hpp"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
void Usage(std::string str)
{
std::cout << str << " port " << std::endl;
}
void *HandlerResult(void *args)
{
int scok = *(int *)args;
delete (int *)args;
pthread_detach(pthread_self());
char buf[1024];
ssize_t s = recv(scok, buf, sizeof(buf), 0);
if(s > 0)
{
buf[s] = 0;
std::cout << buf;
std::string http_response = "http/1.0 200 OK\n";
http_response += "Content-Type: text/plain\n"; //text/plain,正文是普通的文本
http_response += "\n"; //传说中的空行
http_response += "hello bit, hello 102!";
send(scok, http_response.c_str(), http_response.size(), 0); //ok??
}
close(scok);
return nullptr;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
Usage(argv[0]);
return 1;
}
int listen_sock = Sock::Socket();
Sock::Bind(listen_sock, atoi(argv[1]));
Sock::Listen(listen_sock);
for (;;)
{
int sock = Sock::Accept(listen_sock);
pthread_t tid;
int *pram = new int(sock);
pthread_create(&tid, nullptr, HandlerResult, pram);
}
}
我们也可以手动构建一个请求
下面一堆html文本就是百度首页的网页消息。
HTTP协议格式
一个http请求如何保证报头数据全部读完,当一个http读到空行时,如果Content-Length属性有数据,那么请求正文的长度就是Content-Length的值,Content-Length就是http的有效载荷。
HTTP响应
首行: [版本号] + [状态码] + [状态码解释]
Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个 Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在 body中
HTTP的方法
其中最常用的就是GET方法和POST方法
测试GET和POST
在当前目录下创建一个文件夹wwwroot(wbe根目录)新建文件index.html,HTML基础教程
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h5>hello world</h5>
<h5>hello world</h5>
<h5>hello world</h5>
<h5>hello world</h5>
<h5>hello world</h5>
<h5>hello world</h5>
</body>
</html>
http.cc
#include <iostream>
#include "Sock.hpp"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string>
#include<fstream>
#define html_file "./wwwroot/index.html"
void Usage(std::string str)
{
std::cout << str << " port " << std::endl;
}
void *HandlerResult(void *args)
{
int scok = *(int *)args;
delete (int *)args;
pthread_detach(pthread_self());
char buf[1024];
ssize_t s = recv(scok, buf, sizeof(buf), 0);
if(s > 0)
{
buf[s] = 0;
std::cout << buf;
// std::string http_response = "http/1.0 200 OK\n";
// http_response += "Content-Type: text/plain\n"; //text/plain,正文是普通的文本
// http_response += "\n"; //传说中的空行
// http_response += "hello bit, hello 102!";
// send(scok, http_response.c_str(), http_response.size(), 0); //ok??
std::string http_response = "http/1.0 200 OK\n";
http_response += "Content-Type: text/html;charset=utf8\n";
http_response += "Content-Length: ";
//获取文件大小
struct stat st;
stat(html_file,&st);
http_response += std::to_string(st.st_size);
http_response += "\n";
http_response += "\n";
//读取正文
std::ifstream in(html_file);
if(!in.is_open())
{
std::cerr<<"open is error"<<std::endl;
exit(1);
}
else
{
std::string line;
while(getline(in,line))
{
http_response += line;
}
in.close();
}
send(scok, http_response.c_str(), http_response.size(), 0); //ok??
}
close(scok);
return nullptr;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
Usage(argv[0]);
return 1;
}
int listen_sock = Sock::Socket();
Sock::Bind(listen_sock, atoi(argv[1]));
Sock::Listen(listen_sock);
for (;;)
{
int sock = Sock::Accept(listen_sock);
pthread_t tid;
int *pram = new int(sock);
pthread_create(&tid, nullptr, HandlerResult, pram);
}
}
通过telnet发送请求
通过网页请求
GET方法
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h5>hello 我是首页!</h5>
<h5>hello 我是表单!</h5>
<!-- /a/b/handler_from并不存在,也不处理 -->
<form action="/a/b/handler_from" method="GET">
姓名: <input type="text" name="name"><br/>
密码: <input type="password" name="passwd"><br/>
<input type="submit" value="登陆">
</form>
</body>
</html>
POST方法
将index.html的method改为POST方法
通过Fiddler抓包
可以看出请求的数据在正文当中。
总结:
1.GET是最常用的方法默认一般获取所有网页,都是GET方法,同url拼接从而提交GET参数。POST是提交参数比较常用的方法,一般通过正文提交的
2.GET提交的参数在url中,POST提交的参数在正文中。
3.POST方法更私密(私密不等于安全)。不会回显在url中
4.通过GET传参大小是有限制的(url不可能太长,和具体游览器决定的),而POST传参没有限制。
如果提交的参数比较小且不敏感可以考虑用GET方法。
GET和POST是前后端交互的一个重要方式
HTTP的状态码
其实我觉得状态码没没有任何意思,不过是服务器程序反应给用户层的结果而已,但是还是说一下吧。
4**(是客户端错误,客户端请求,服务端没有这个资源)
#include <iostream>
#include "Sock.hpp"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <fstream>
#define html_file "./wwwroot/index.html.bak" //故意给一个不存在的文件
void Usage(std::string str)
{
std::cout << str << " port " << std::endl;
}
void *HandlerResult(void *args)
{
int scok = *(int *)args;
delete (int *)args;
pthread_detach(pthread_self());
char buf[1024];
ssize_t s = recv(scok, buf, sizeof(buf), 0);
if (s > 0)
{
buf[s] = 0;
std::cout << buf;
// std::string http_response = "http/1.0 200 OK\n";
// http_response += "Content-Type: text/plain\n"; //text/plain,正文是普通的文本
// http_response += "\n"; //传说中的空行
// http_response += "hello bit, hello 102!";
// send(scok, http_response.c_str(), http_response.size(), 0); //ok??
//读取正文
std::ifstream in(html_file);
if (!in.is_open())
{
std::string http_response = "http/1.0 404 NOT FOUND\n";
http_response += "Content-Type: text/html;charset=utf8\n";
http_response += "\n";
http_response += "<html><p>你访问的资源不存在</p></html>";
//std::cerr << "open is error" << std::endl;
// exit(1);
send(scok, http_response.c_str(), http_response.size(), 0); // ok??
}
else
{
//获取文件大小
struct stat st;
stat(html_file, &st);
std::string http_response = "http/1.0 200 OK\n";
http_response += "Content-Type: text/html;charset=utf8\n";
http_response += "Content-Length: ";
http_response += std::to_string(st.st_size);
http_response += "\n";
http_response += "\n";
std::string line;
while (getline(in, line))
{
http_response += line;
}
in.close();
send(scok, http_response.c_str(), http_response.size(), 0); // ok??
}
}
close(scok);
return nullptr;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
Usage(argv[0]);
return 1;
}
int listen_sock = Sock::Socket();
Sock::Bind(listen_sock, atoi(argv[1]));
Sock::Listen(listen_sock);
for (;;)
{
int sock = Sock::Accept(listen_sock);
pthread_t tid;
int *pram = new int(sock);
pthread_create(&tid, nullptr, HandlerResult, pram);
}
}
5**(服务端错误)例如:客服端来了请求服务端创建线程,进程崩溃了,或者文件读取错误,进程异常了...
3**重定性 301永久重定向,302和307是临时重定向需要搭配报头中location属性使用
当我们输入150.158.146.173:8080时会跳转到腾讯官网
#include <iostream>
#include "Sock.hpp"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <fstream>
#define html_file "./wwwroot/index.html"
void Usage(std::string str)
{
std::cout << str << " port " << std::endl;
}
void *HandlerResult(void *args)
{
int scok = *(int *)args;
delete (int *)args;
pthread_detach(pthread_self());
char buf[1024];
ssize_t s = recv(scok, buf, sizeof(buf), 0);
if (s > 0)
{
buf[s] = 0;
std::cout << buf;
std::string http_response = "http/1.1 301 Permanently moved!\n";
http_response += "Location: https://www.qq.com/\n";
http_response += "\n";
send(scok, http_response.c_str(), http_response.size(), 0); // ok??
// std::string http_response = "http/1.0 200 OK\n";
// http_response += "Content-Type: text/plain\n"; //text/plain,正文是普通的文本
// http_response += "\n"; //传说中的空行
// http_response += "hello bit, hello 102!";
// send(scok, http_response.c_str(), http_response.size(), 0); //ok??
//读取正文
// std::ifstream in(html_file);
// if (!in.is_open())
// {
// std::string http_response = "http/1.0 404 NOT FOUND\n";
// http_response += "Content-Type: text/html;charset=utf8\n";
// http_response += "\n";
// http_response += "<html><p>你访问的资源不存在</p></html>";
// //std::cerr << "open is error" << std::endl;
// // exit(1);
// send(scok, http_response.c_str(), http_response.size(), 0); // ok??
// }
// else
// {
// //获取文件大小
// struct stat st;
// stat(html_file, &st);
// std::string http_response = "http/1.0 200 OK\n";
// http_response += "Content-Type: text/html;charset=utf8\n";
// http_response += "Content-Length: ";
// http_response += std::to_string(st.st_size);
// http_response += "\n";
// http_response += "\n";
// std::string line;
// while (getline(in, line))
// {
// http_response += line;
// }
// in.close();
// send(scok, http_response.c_str(), http_response.size(), 0); // ok??
// }
}
close(scok);
return nullptr;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
Usage(argv[0]);
return 1;
}
int listen_sock = Sock::Socket();
Sock::Bind(listen_sock, atoi(argv[1]));
Sock::Listen(listen_sock);
for (;;)
{
int sock = Sock::Accept(listen_sock);
pthread_t tid;
int *pram = new int(sock);
pthread_create(&tid, nullptr, HandlerResult, pram);
}
}
HTTP常见Header
User-Agent里的历史故事
Connection:keep-alive是长链接。没写就是短链接。HTTP/1.0是短链接,HTTP1.1是长链接,长链接解决了频繁建立连接的过程。
cookie和session
http本身是无状态的协议。但是有时候我们在访问有些网站时能够认识用户,比如有些网站需要登陆,关闭掉网页再次打开网页就不需要登录。http协议本身不并不解决这个问题,而是提供技术支持来保证网站具有"会话保持“的功能。
cookie会话管理
1.cookie其实是一个文件,该文件里面保存的是我们用户的私密消息。
2.一旦网站有对应有cookie,在发起任何请求的时候,都会在http协议request中携带该cookie消息。
当我们在请求某个网页的时候在第一次登录过后会在客户端浏览器中生成对应的cookie文件,cookie文件中保存的是用户的私密信息(用户名,密码),在后续的http请求报文中都会携带cookie内容,来保证 网站认识用户。
cookie可能有文件版或者内存版。文件版一般都在浏览器的安装目录下可以找到,如果别人盗取了我们的cookie文件,就可以以我们的身份认证访问某些特定的资源,如果保存的是我们的用户名和密码,那么就是非常糟糕的。
总结:单纯使用cookie是具有一定的安全隐患的。所以现在大多数都是session和cookie配合使用。
session
核心思路就是:将用户的私密信息,保存在服务器中!
客户端请求的时候会在服务端上构建一个session文件,响应给客户端的是保存在服务器上的session文件名,下次客户端请求来的时候,会去查看该客户的cookie文件中的会话id是否在服务端找到。找到就不需要再次认证。当前用户的session文件时唯一的。但是cookie文件时还是会有泄露的危险,目前没有办法解决。
总结:cookie+session本质时提高用户的体验。
HTTPS
宏观认识
加密方式
1.对称加密
发送方数据用密钥(X)加密,接受方也要用密钥(X)解密。
2.非对称加密(RSA)
有一对密钥:公钥和私钥,可以用公钥加密但是只能有私钥解密,或者可以用私钥加密,只能用公钥解密,一般公钥时全世界公开的,私钥只能自己私有保存。
数字签名
发送方在发送一段文本同时会发送该文本的数据签名,接收方在接受到文本内容的时候,通过相同的hash散列算法,形成数据摘要,在将发送方发送的数据签名进行解密形成数据摘要,对比两份数据摘要,就可以观察数据是否被篡改。如下图:
HTTPS形成过程
1.通信双方都用对称加密的方式
2.通信双方采用一对非对称加密
3.通信双方采用两对非对称加密
采用两对非对称加密就可以保证数据安全吗?然而并非如此。依旧有被非法窃取的风险并且非对称算法非常费时间。
4.实际采用的是非对称加对称
但是这种方式任然有攻击的可能,类似于偷梁换柱,server和client交换密钥的时候中间将自己的密钥给clinet,client加密后数据又被截取到,中间人因为给clinet是自己的公钥,那么中间人就可以通过自己的公钥进行解密,让后在把数据发送给server,server和client在中间人眼里就是透明的。现在采用的是server向CA机构申请证书,CA机构会生成证书和数字签名人后在发送给client,中间人就算截取到这个内容也没有办法查看和篡改。只有修改任何一点内容,数字签名就会不被匹配。