网络计算器
简介
一个传输的信息,如果需要双方都达成共识才能进行,这样的东西就叫做协议,例如我发了6 8给另一台机器,另一台机器能够识别,然后我再传入我想要的运算符号,另外一台主机对此将三个信息进行处理,如果我传入“hello world”那么另外的主机无法进行识别,也就是先前没有定制好统一的协议,导致另一台主机无法识别就会报错。
约定方案一:
客户端发送一个形如"1+1"的字符串;
这个字符串中有两个操作数, 都是整形;
两个数字之间会有一个字符是运算符, 运算符只能是 + ;
数字和运算符之间没有空格;
约定方案二:
定义结构体来表示我们需要交互的信息;
发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转
化回结构体;
这个过程叫做 “序列化” 和 “反序列化”
序列化和反序列化
其实就是一种协议,当我发送4 5 +的时候传输的过程中有可能是一串字符串,也就是说将两个整数和运算符号序列化成一个字符串传给另一台主机,之后另一台主机通过解析当前的序列进行识别和运算,也就是反序列化。
计算器实现
无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是ok的. 这种约定, 就是 应用层协议
这里当我们的客户端发送了 6 5 +给服务器,服务器通过接受resquest 然后运算后将结果存储到response中回给客户端,就形成了完整的运算。
类序列化程序
#include <iostream>
typedef struct request{
int x;
int y;
char op;
}request_t;
typedef struct response{
int code;
int result;
}response_t;
服务器类
class server{
private:
int port;
int lsock;
public:
server(int _p):port(_p),lsock(-1)
{}
void initServer()
{
lsock = socket(AF_INET, SOCK_STREAM, 0);
if( lsock < 0 )
{
cerr << "socket error " << endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if(bind(lsock, (struct sockaddr*)&local, sizeof(local)) < 0){
cerr << "bind error!" << endl;
exit(3);
}
if(listen(lsock, 5) < 0){
cerr << "listen error!\n" << endl;
}
}
void cal(int sock)
{
request_t rq;
response_t rsp={4, 0};
ssize_t s = recv(sock, &rq, sizeof(rq), 0);
if(s > 0){
rsp.code = 0;
switch(rq.op){
case '+':
rsp.result = rq.x + rq.y;
break;
case '-':
rsp.result = rq.x - rq.y;
break;
case '*':
rsp.result = rq.x * rq.y;
break;
case '/':
if(rq.y != 0){
rsp.result = rq.x / rq.y;
}
else{
rsp.code = 1;
}
break;
case '%':
if(rq.y != 0){
rsp.result = rq.x + rq.y;
}
else{
rsp.code = 2;
}
break;
default:
rsp.code = 3;
break;
}
}
send(sock, &rsp, sizeof(rsp), 0);
close(sock);
}
void start()
{
struct sockaddr_in peer;
for(;;){
socklen_t len = sizeof(peer);
int sock = accept(lsock, (struct sockaddr*)&peer,&len);
if( sock < 0 ){
cerr << "accept error!" << endl;
continue;
}
if(fork() == 0){
if(fork() > 0){
exit(0);
}
close(lsock);
cal(sock);
exit(0);
}
close(sock);
waitpid(-1, nullptr, 0);
}
}
~server()
{
close(lsock);
}
};
客户端类
class client{
private:
int port;
string ip;
int sock;
public:
client(string _ip, int _p)
:ip(_ip), port(_p),sock(-1)
{}
void initClient()
{
sock = socket(AF_INET, SOCK_STREAM, 0);
if( sock < 0 )
{
cerr << "socket error " << endl;
exit(2);
}
}
void cal(int sock)
{
request_t rq;
response_t rsp={4, 0};
ssize_t s = recv(sock, &rq, sizeof(rq), 0);
if(s > 0){
rsp.code = 0;
switch(rq.op){
case '+':
rsp.result = rq.x + rq.y;
break;
case '-':
rsp.result = rq.x - rq.y;
break;
case '*':
rsp.result = rq.x * rq.y;
break;
case '/':
if(rq.y != 0){
rsp.result = rq.x / rq.y;
}
else{
rsp.code = 1;
}
break;
case '%':
if(rq.y != 0){
rsp.result = rq.x + rq.y;
}
else{
rsp.code = 2;
}
break;
default:
rsp.code = 3;
break;
}
}
send(sock, &rsp, sizeof(rsp), 0);
close(sock);
}
void start()
{
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr(ip.c_str());
if(connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0){
cerr << "connect error!" << endl;
exit(2);
}
response_t rsp;
request_t rq;
cout << "data1# ";
cin >> rq.x;
cout << "data2# ";
cin >> rq.y;
cout << "op# ";
cin >> rq.op;
send(sock, &rq, sizeof(rq), 0);
recv(sock, &rsp, sizeof(rsp), 0);
cout << "code : " << rsp.code << endl;
cout << "result: " << rsp.result << endl;
}
~client()
{
close(sock);
}
};
HTTP协议
基本特征:无连接,无状态,简单快捷。
无连接表示:底层是有tcp建立的连接和http无关,http直接向对方发送http request。
无状态表示:http本身是无状态的,并不会去记录任何用户信息,只负责request和response,而记录基本信息的是cookie和session
简单快速:分为短链接和长连接。
URL
URL就是平常我们俗称的网址:
服务器的地址其实就是IP(通过DNS解析得来)
那么服务器如何去知道这些符号都代表什么此时就需要进行urlencode和urldecode
其实就相当于语义的转换,例如输入+号就会被转成%2B因为有些字符表示的是特殊意义如果想要字符本身就需要对其进行转化。
HTTP协议格式
请求:
大概框架就是又请求行,请求报头,和空行,正文组成,每一个部分的分割都是空格来分割,每一部分类似于解引用的方法拿到对应需要的东西,请求头部一般都是由Key:Value的模型来组成的,其中最重要的就是有一个Contet-Length这个主要是用于正文部分读取的大小进行标识。
响应:
响应也是又响应行,响应报头,和空行正文组成。只不过响应行是有一个code和状态码描述。
HTTP的方法和状态码
最常用的就是GET和POST,区别get是可以无正文,而POST需要有正文的引入。
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
这个就是响应行的状态码描述和Code主要用于通知请求的主机。
HTTP常见Header
补充:
Cookie与Session
因为HTTP是无状态的所以当用户使用时,体验会很差,,类似于每次看爱奇艺的视频都要输入一边账号密码,所以就引入了Cookie和Session
客户端发送一个请求给服务器,服务器通过解析并且响应,此时的响应当中就会有Set-cookie这个KV行,此时就为客户端建立Cookie,再当客户端想要访问时,此时服务器就已经记录了cookie就无需再次建立直接识别。cookie本质是浏览器中的一个文件。存放在客户端中。
Session相对于Cookie相对安全
因为每次的请求都包含这你的账号密码,每次抓包都能知道你的账号密码容易被人盗号,盗取资料。
此时就引入了另一个概念叫做Session。
Session是什么?
客户端请求服务端,服务端会为这次请求开辟一块内存空间,这个对象便是 Session 对象,存储结构为ConcurrentHashMap。Session 弥补了 HTTP 无状态特性,服务器可以利用 Session存储客户端在同一个会话期间的一些操作记录。
前期的过程也是一样的发送请求返回响应,但服务器此时的响应中不会去返回你的账号密码,而是一Session对象的一个代号(SID)客户端可以通过sid来对服务器进行访问,相对来说更加安全。而真正的Sesion是放在服务器当中的。
缺点
Session 机制有个缺点,比如 A 服务器存储了 Session,就是做了负载均衡后,假如一段时间内 A 的访问量激增,会转发到 B 进行访问,但是 B 服务器并没有存储 A 的 Session,会导致 Session 的失效。