协议
首先什么是协议?
计算机与网络设备间要互相通信,双方必须基于相同的方法。eg:如何探测到通信目标、由哪一边发起通信、使用什么语言、怎样结束通信等规则都要事先确定。不同的硬件、操作系统之间的通信,所有的这些都需要一种规则,这种规则就称为协议。一句话说:协议就是约定
应用层协议
负责应用程序之间的数据沟通
自定义协议
这里以网络计算器为例:
- 客户端给服务器发送的请求是什么样的?
包含的信息(数字、运算符)
信息组织方式(信息表达) - 服务器给客户端的响应是什么样的?
包含的信息(计算结果)
信息组织方式(字符串)
这里客户端传递数字及运算符给服务端,服务端收到数据进行解析后得到数字、运算符,进行运算后返回给客户端
这里客户端需要将数据对象进行格式组织,再通过网络数据传输给服务端
如:10,20,+
结构体成员变量赋值的过程就是数据对象在内存中进行二进制数据组织的过程
实现:
在之前封装的tcpsocket类中添加结构体的定义
客户端:
#include <iostream>
#include <stdlib.h>
#include <signal.h>
#include "tcpsocket.hpp"
void sigcb(int signo)
{
printf("连接已断开,继续发送数据触发异常SIGPIPE信号\n");
}
int main(int argc,char *argv[])
{
if(argc!=3)
{
printf("em:./cil 192.168.1.11 9000--服务端绑定的地址");
return -1;
}
signal(SIGPIPE,sigcb);
std::string ip=argv[1];
uint16_t port=atoi(argv[2]);
TcpSocket cli_sock;
CHECK_RET(cli_sock.Socket());
CHECK_RET(cli_sock.Connect(ip,port));
int fd=cli_sock.GetFd();
while(1)
{
tmp_t tmp;
tmp.num1=10;
tmp.num2=20;
tmp.op='+';
send(fd,(void*)&tmp,sizeof(tmp_t),0);
sleep(100);
}
cli_sock.Close();
return 0;
}
服务端: `
#include <iostream>
#include <stdlib.h>
#include "tcpsocket.hpp"
int main(int argc,char *argv[])
{
if(argc!=3)
{
printf("em:./srv 192.168.1.11 9000");
return -1;
}
std::string ip=argv[1];
uint16_t port=atoi(argv[2]);
TcpSocket lst_sock;
CHECK_RET(lst_sock.Socket());
CHECK_RET(lst_sock.Bind(ip,port));
CHECK_RET(lst_sock.Listen());
while(1)
{
TcpSocket cli_sock;
bool ret=lst_sock.Accept(&cli_sock);
if(ret==false) continue;
int fd=cli_sock.GetFd();
tmp_t tmp;
recv(fd,(void*)&tmp,sizeof(tmp_t),0);
int sum=tmp.num1+tmp.num2;
printf("%d %c %d = %d\n",tmp.num1,tmp.op,tmp.num2,sum);
}
lst_sock.Close();
return 0;
}
序列化:把数据对象按照指定协议组织成可持久化存储/数据传输的二进制数据串
反序列化:把持久化存储/可传输的二进制数据串按照指定的协议解析出各数据对象
使用结构体来组织数据其实就是一种对数据对象的二进制序列化【常见的序列化方式:json序列化、protobuf序列化】
HTTP协议
超文本传输协议,优点在于http协议给程序员留有一定的自定制空间
网址:URL【统一资源定位符】,定位网络中唯一的资源
在浏览器中输入网址后浏览器首先对其进行解析生成发送给Web服务器的请求信息
提交给服务器的查询字符串中val需要进行url编码【因为url中很多特殊字符有自己的含义,如果用户提交的数据中也有相同的特殊字符就会造成歧义】
url编码:将特殊字符的每一字节都转化为16进制数字的字符(eg:±–>2b)万一用户提交的数据有2b也会造成歧义,因此在对每一字节转换后需要在前面加上%表示后面的两个字符经过了url编码(±–>%2b)
url解码:得到查询字符串后,在val中遇到%就认为后面的两个字符需要进行解码【第一个数字左移4位(或乘16)加后面的数字:eg:2b:2<<4+11=32+11=43】
fiddler工具:浏览器抓包工具
HTTP协议格式:
请求
首行
包含三大信息,以空格间隔,以\r\n结尾
【请求方法 URL 协议版本\r\n】
- 请求方法:不同的请求方法负责不同的功能
GET:请求获取一个资源并要求服务器返回实体资源
HEAD:请求获取一个资源,但并不要求服务器返回实体数据,只是响应头部信息
POST:向服务器提交表单数据
GET/HEAD区别:是否要服务器响应正文
GET/POST区别:GET也可以向服务器提交数据,但提交的数据是在url的查询字符串中(GET没有正文),POST提交的数据在正文中【GET提交的数据是不太安全的且url有长度限制,早期1k现在大多4/8k】- URL:主要信息是请求的资源路径和提交的查询字符串
- 协议版本
HTTP/1.1 0.9/1.0/1.1/2,不同的版本有不同的特性
0.9:仅支持GET请求方法,并且是短连接【发送一请求,得到响应后关闭连接】
1.0:支持GET/HEAD/POST请求方法,支持长连接
1.1:支持了更多请求方法及特性,默认支持长连接,实现管道化传输
2:开始支持服务端向客户端主动推送消息头部信息(header)
含若干行
一个个key:val的键值对,键值对间用\r\n间隔
- Connection:描述当前连接是否长连接
- Content-Length:描述正文长度(告诉对端本次请求应接受多长的数据)
- Connect-Type:描述正文的类型(告诉对端如何处理正文 test/html)
- Accept:告诉对端能接收什么样的数据
- Referer:告诉服务器,请求是从那个网页过来的
- Transfer-Encoding:正文的分块传输,每块在发送前告诉对端数据的长度,常用于服务端自己不确定要相应的数据长度
- Location:http://123.2.7.58.25/ 搭配3xx状态码,通过描述的地址信息告诉客户端重新请求指定的地址
- Coolie/Set-Cookie:服务端为每一个登录的客户端在服务器主机上创建一个session(会话),会话中描述客户端的信息,将session保存在数据库,通过Set-Cookie将session id【身份标识】以及重要信息返回给客户端,客户端将其保存在cookie文件中,下次请求服务端时直接从cookie文件读取信息,通过Cookie传递给服务端
- Cookie与session区别:
session是服务端为每一客户端单独创建的会话,保存在服务端
Cookie是服务端通过Set-Cookie响应给客户端的信息,保存在客户端空行
\r\n
表示头部信息结束请求体/正文(body)
具体的数据
是可选的,可有可没有POST/PUT请求自带body
响应
首行
包含三大信息,空格间隔,\r\n结尾
【协议版本 响应状态码 状态码描述\r\n】
- 版本号
- 状态响应码:向客户端反应本次请求的的处理结果状态,包含五大类:
1xx:一些描述信息
2xx:本次请求正确处理完毕(200:请求成功)
3xx:重定向,本次请求的资源可能移动到其他位置,请客户端重新请求新位置(301-永久,302-临时,303-查看其他)
4xx:客户端错误(403-用户没有权限访问,404-没找到指定资源)
5xx:服务端错误(502-服务端挂了,504-服务器响应超时)- 状态码描述信息
头部信息
空行
body
简单的HTTP服务器:
在传输层使用TCP协议,【3.0可以基于UDP】
#include <iostream>
#include <sstream>
#include <stdlib.h>
#include "tcpsocket.hpp"
int main(int argc, char *argv[])
{
if (argc != 3) {
printf("em:./tcp_srv 192.168.1.11 9000\n");
return -1;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
TcpSocket lst_sock;
CHECK_RET(lst_sock.Socket());
CHECK_RET(lst_sock.Bind(ip, port));
CHECK_RET(lst_sock.Listen());
while(1) {
TcpSocket cli_sock;
bool ret = lst_sock.Accept(&cli_sock);
if (ret == false) {
continue;
}
std::string http_req;
cli_sock.Recv(&http_req);
printf("req:[%s]\n", http_req.c_str());
std::string body = "<html><body><h1>Hello World</h1></body></html>";
std::string blank = "\r\n";
std::stringstream header;
header << "Content-Length: " << body.size() << "\r\n";
header << "Content-Type: text/html\r\n";
header << "Location: http://www.baidu.com/\r\n";
std::string first_line = "HTTP/1.1 500 Internal Server Error\r\n";
cli_sock.Send(first_line);
cli_sock.Send(header.str());
cli_sock.Send(blank);
cli_sock.Send(body);
cli_sock.Close();
}
lst_sock.Close();
return 0;
}
操作时关闭防火墙:
systemctl stop firewalld[暂时关闭]
systemctl disable firewalld[禁用]