一、网络版计算器
约定方案一:
- 客户端发送一个形如"1+1"的字符串;
- 这个字符串中有两个操作数, 都是整形;
- 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
- 数字和运算符之间没有空格
约定方案二:
- 定义结构体来表示我们需要交互的信息;
- 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
- 这个过程叫做 “序列化” 和 “反序列化”
Protocol.hpp
#pragma once
#include <iostream>
#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 serializationRequest(request_t& req)
{
Json::Value root;
root["datax"] = req._x;
root["datay"] = req._y;
root["operator"] = req._op;
Json::FastWriter writer;
std::string json_string = writer.write(root);
return json_string;
}
void DeserializationRequest(const std::string& json_string, request_t& out)
{
Json::Reader reader;
Json::Value root;
reader.parse(json_string, root);
out._x = root["datax"].asInt();
out._y = root["datay"].asInt();
out._op = (char)root["operator"].asUInt();
}
std::string serializationResponse(response_t& resp)
{
Json::Value root;
root["code"] = resp.code;
root["result"] = resp._result;
Json::FastWriter writer;
std::string json_string = writer.write(root);
return json_string;
}
void DeserializationResponse(const 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.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
class Sock
{
public:
static int Socket()
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
std::cerr << "Socket error" << errno << std::endl;
exit(2);
}
return sock;
}
static void Bind(int sock, u_int16_t port)
{
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_port = htons(port);
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
std::cerr << "Bind error" << errno << std::endl;
exit(3);
}
}
static void Listen(int sock)
{
if(listen(sock, 5) < 0)
{
std::cerr << "Listen error" << errno << std::endl;
exit(4);
}
}
static int Accept(int sock)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int fd = accept(sock, (struct sockaddr*)&peer, &len);
if(fd >= 0)
{
return fd;
}
return -1;
}
static void Connect(int sock, std::string server_ip, uint16_t port)
{
struct sockaddr_in peer;
memset(&peer, 0, sizeof(sockaddr_in));
peer.sin_family = AF_INET;
peer.sin_addr.s_addr = inet_addr(server_ip.c_str());
peer.sin_port = htons(port);
if(connect(sock, (struct sockaddr*)&peer, sizeof(peer)) < 0)
{
std::cerr << "connet failed" << errno << std::endl;
exit(5);
}
}
};
cal_client.cc
#include "Protocol.hpp"
#include "Sock.hpp"
#include <unistd.h>
#include <cstring>
void Usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " server_ip server_port" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return 1;
}
std::string ip = argv[1];
uint16_t port = static_cast<uint16_t>(atoi(argv[2]));
int sock = Sock::Socket();
Sock::Connect(sock, ip, port);
std::cout << "connect success!" << std::endl;
request_t req;
std::cout << "please input data one#" << std::endl;
std::cin >> req._x;
std::cout << "please input data two#" << std::endl;
std::cin >> req._y;
std::cout << "please input operator#" << std::endl;
std::cin >> req._op;
std::string json_string = serializationRequest(req);
write(sock, json_string.c_str(), json_string.size());
char buffer[1024];
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0;
response_t resp;
std::string str = buffer;
DeserializationResponse(str, resp);
std::cout << "code[0:success]:" << resp.code << std::endl;
std::cout << "result:" << resp._result << std::endl;
}
return 0;
}
cal_server.cc
#include "Protocol.hpp"
#include "Sock.hpp"
#include <string>
#include <thread>
#include <unistd.h>
void Usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " server_port" << std::endl;
}
void HandlerRequest(int *args)
{
int sock = *args;
delete args;
request_t req;
char buffer[1024];
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if(s > 0)
{
buffer[s] = 0;
std::string str = buffer;
DeserializationRequest(str, req);
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;
}
else
{
resp._result = req._x / req._y;
}
break;
case '%':
if (req._y == 0)
{
resp.code = -2;
}
else
{
resp._result = req._x % req._y;
}
break;
default:
resp.code = -3;
break;
}
std::string json_str = serializationResponse(resp);
write(sock, json_str.c_str(), json_str.size());
std::cout << "服务结束!" << std::endl;
}
close(sock);
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
return 1;
}
u_int16_t port = static_cast<u_int16_t>(atoi(argv[1]));
int listen_sock = Sock::Socket();
Sock::Bind(listen_sock, port);
Sock::Listen(listen_sock);
for (;;)
{
int new_sock = Sock::Accept(listen_sock);
if (new_sock >= 0)
{
int *pram = new int(new_sock);
std::thread t(HandlerRequest, pram);
t.detach();
}
}
return 0;
}
无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是ok的. 这种约定, 就是 应用层协议
二、HTTP协议
虽然我们说, 应用层协议是我们程序猿自己定的. 但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)就是其中之一
2.1 认识URL
平时我们俗称的 “网址” 其实就是说的 URL urlencode和urldecode
像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现. 比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义
转义的规则如下: 将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式 “+” 被转义成了 “%2B” urldecode就是urlencode的逆过程;
urldecode解码工具
2.2 HTTP协议格式
Sock.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
class Sock
{
public:
static int Socket()
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
std::cerr << "Socket error" << errno << std::endl;
exit(2);
}
return sock;
}
static void Bind(int sock, u_int16_t port)
{
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_port = htons(port);
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
std::cerr << "Bind error" << errno << std::endl;
exit(3);
}
}
static void Listen(int sock)
{
if(listen(sock, 5) < 0)
{
std::cerr << "Listen error" << errno << std::endl;
exit(4);
}
}
static int Accept(int sock)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int fd = accept(sock, (struct sockaddr*)&peer, &len);
if(fd >= 0)
{
return fd;
}
return -1;
}
static void Connect(int sock, std::string server_ip, uint16_t port)
{
struct sockaddr_in peer;
memset(&peer, 0, sizeof(sockaddr_in));
peer.sin_family = AF_INET;
peer.sin_addr.s_addr = inet_addr(server_ip.c_str());
peer.sin_port = htons(port);
if(connect(sock, (struct sockaddr*)&peer, sizeof(peer)) < 0)
{
std::cerr << "connet failed" << errno << std::endl;
exit(5);
}
}
};
Http.cc
#include "Sock.hpp"
#include <string>
#include <thread>
#include <unistd.h>
void Usage(std::string proc)
{
std::cout << "Usage\n\t" << proc << " prot" << std::endl;
}
void Handler(int *pSock)
{
int sock = *pSock;
delete pSock;
#define MAX_SIZE 1024 * 10
char buffer[MAX_SIZE];
memset(buffer, 0, sizeof(buffer));
ssize_t s = recv(sock, buffer, sizeof(buffer), 0);
if (s > 0)
{
buffer[s] = 0;
std::cout << buffer;
std::string ret = "http/1.0 200 OK\n";
ret += "Content-Type: text/plain\n";
ret += "\n";
ret += "hello world";
send(sock, ret.c_str(), ret.size(), 0);
}
close(sock);
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
return 1;
}
uint16_t port = static_cast<uint16_t>(atoi(argv[1]));
int listen_sock = Sock::Socket();
Sock::Bind(listen_sock, port);
Sock::Listen(listen_sock);
for (;;)
{
int new_sock = Sock::Accept(listen_sock);
if (new_sock > 0)
{
int *pnew_sock = new int(new_sock);
std::thread t(Handler, pnew_sock);
t.detach();
}
}
return 0;
}
HTTP请求
- 首行: [方法] + [url] + [版本]
- Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
- Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;
HTTP响应
- 首行: [版本号] + [状态码] + [状态码解释]
- Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
- Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中.
2.3 HTTP的方法
其中最常用的就是GET方法和POST方法 Http.cc
#include "Sock.hpp"
#include <string>
#include <thread>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fstream>
#define WWWROOT "./wwwroot/"
#define HOME_PAGE "index.html"
void Usage(std::string proc)
{
std::cout << "Usage\n\t" << proc << " prot" << std::endl;
}
void Handler(int *pSock)
{
int sock = *pSock;
delete pSock;
#define MAX_SIZE 1024 * 10
char buffer[MAX_SIZE];
memset(buffer, 0, sizeof(buffer));
ssize_t s = recv(sock, buffer, sizeof(buffer), 0);
if (s > 0)
{
buffer[s] = 0;
std::cout << buffer;
std::string html_file = WWWROOT;
html_file += HOME_PAGE;
std::string http_response = "http/1.0 200 OK\n";
http_response += "Content-type: text/html; charset=utf-8\n";
http_response += "Content-Lengt: ";
struct stat st;
stat(html_file.c_str(), &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 html_file error" << std::endl;
}
else
{
std::string content;
std::string line;
while(std::getline(in, line))
{
content += line;
}
http_response += content;
in.close();
send(sock, http_response.c_str(), http_response.size(), 0);
}
}
close(sock);
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
return 1;
}
uint16_t port = static_cast<uint16_t>(atoi(argv[1]));
int listen_sock = Sock::Socket();
Sock::Bind(listen_sock, port);
Sock::Listen(listen_sock);
for (;;)
{
int new_sock = Sock::Accept(listen_sock);
if (new_sock > 0)
{
int *pnew_sock = new int(new_sock);
std::thread t(Handler, pnew_sock);
t.detach();
}
}
return 0;
}
2.4 HTTP的状态码
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
- 301:永久重定向
- 302 or 307 临时重定向
重定向是浏览器给我们提供支持的浏览器必须识别301,302,307 server告诉浏览器我应该再去哪里;Location:新的地址
2.5 HTTP常见Header
- Content-Type: 数据类型(text/html等)
- Content-Length: Body的长度
- Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
- User-Agent: 声明用户的操作系统和浏览器版本信息;
- referer: 当前页面是从哪个页面跳转过来的;
- location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
- Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
Connection:keep-alive Cookie Cookie,有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。
Session: 在计算机中,尤其是在网络应用中,称为“会话控制”。Session对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的Web页之间跳转时,存储在Session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web页时,如果该用户还没有会话,则Web服务器将自动创建一个 Session对象。当会话过期或被放弃后,服务器将终止该会话。Session 对象最常见的一个用法就是存储用户的首选项。例如,如果用户指明不喜欢查看图形,就可以将该信息存储在Session对象中。有关使用Session 对象的详细信息,请参阅“ASP应用程序”部分的“管理会话”。注意会话状态仅在支持cookie的浏览器中保留。
三、HTTPS
http理解思路图 假设采取对称加密:
假设采取非对称加密 最终方案:对称加加密+非对称加密
但是这样也会不安全 上面的本质问题是了client无法判断发来的密匙协商报文是不是合法的服务方发来的,所以就会有CA证书机构,来证明这个服务方是合法的。
|