计算机网络---应用层(http协议)
Posted Moua
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了计算机网络---应用层(http协议)相关的知识,希望对你有一定的参考价值。
目录
在tcp/ip五层协议模型中,应用层是处于最高层的,主要为应用程序提供服务,我们程序员编写的程序大多数都是运行在应用层的。应用层有很多的协议,主要包括HTTP、DNS、URI、FTP等。
一、简单理解序列化和反序列化
1、什么是序列化、反序列化
协议是一种约定,在socket API中,读写数据都是以字符串的形式进行的,那么要传输一些结构化数据该如何传输呢?
例如:传输一个结构体
- 序列化:在传输时,将结构化数据以字符串的形式传输过去。
- 反序列化:将接收到的字符串在转换成结构化数据。
2、网络版计算器程序
客户端以字符串的形式将要计算的表达式发送给服务器端,服务器端对表达式进行处理将运算结构返回给客户端,同时如果客户发送的数据错误要有相应的提示。
- 客户端发送一个形如"1+1"的字符串;
- 这个字符串中有两个操作数, 都是整形;
- 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
- 数字和运算符之间没有空格;
- ...
- 定义结构体来表示我们需要交互的信息;
- 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转
- 化回结构体;
- 这个过程叫做 "序列化" 和 "反序列化"
代码实现:
//calClient.hpp
#pragma once
#include<iostream>
#include"protocol.hpp"
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/wait.h>
class calServer
{
private:
int sock;//套接字
int port;//端口号
public:
//构造函数
calServer(int _port)
:port(_port)
{}
//初始化
void initServer()
{
//创建套接字
sock = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in local;//本地地址
local.sin_family = AF_INET;
local.sin_port = htons(port);//端口号(主机号转网络号)
local.sin_addr.s_addr = INADDR_ANY;//IP地址,任意IP
//绑定
if(bind(sock,(struct sockaddr *)&local,sizeof(local)) != 0)
{
std::cerr<<"service bind error..."<<std::endl;
exit(1);
}
//监听
if(listen(sock,5) < 0)
{
std::cerr<<"service listen error"<<std::endl;
exit(1);
}
}
void service(int sock)
{
struct result r;
//接收客户端消息
struct expersion rq;
int size = recv(sock,&rq,sizeof(rq),0);
if(size > 0)
{
std::cout<<"recv..."<<std::endl;
r.code = 0;
r.ret = 0;
switch(rq.option)
{
case '+':
r.ret = rq.x+rq.y;
break;
case '-':
r.ret = rq.x-rq.y;
break;
case '*':
r.ret = rq.x*rq.y;
break;
case '/':
if(rq.y == 0)
{
r.code = 1;
}
else
r.ret = rq.x/rq.y;
break;
default:
r.code = 2;
break;
}
//发送消息
if(send(sock,&r,sizeof(r),0) <= 0)
{
std::cerr<<"send error..."<<std::endl;
}
}
}
//开始运行
void start()
{
while(1)
{
struct sockaddr_in clientAddr;
socklen_t clientAddrLen;
//接受链接
int sockClient = accept(sock,(struct sockaddr*)&clientAddr,&clientAddrLen);
if(sockClient < 0)
{
std::cerr<<"service accept error..."<<std::endl;
exit(1);
}
std::cout<<"get a new connection..."<<std::endl;
//创建进程
if(fork() == 0)
{
//子进程
if(fork() == 0)
{
close(sock);
//孙子进程
service(sockClient);
exit(1);
}
exit(0);
}
//进程等待
waitpid(-1,nullptr,WNOHANG);
close(sockClient);
}
}
//析构函数
~calServer()
{
close(sock);
}
};
//calServer.hpp
#pragma once
#include<iostream>
#include<string>
#include"protocol.hpp"
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/wait.h>
class calClient
{
private:
int sock;//套接字
std::string ip;//IP地址
int port;//端口号
public:
//构造函数
calClient(std::string _ip,int _port)
W> :port(_port),ip(_ip)
{}
//初始化
void initClient()
{
//创建套接字
sock = socket(AF_INET,SOCK_STREAM,0);
}
void service(int sock)
{
struct result r;
//接收客户端消息
struct expersion rq;
int size = recv(sock,&rq,sizeof(rq),0);
if(size > 0)
{
r.code = 0;
r.ret = 0;
switch(rq.option)
{
case '+':
r.ret = rq.x+rq.y;
break;
case '-':
r.ret = rq.x-rq.y;
break;
case '*':
r.ret = rq.x*rq.y;
break;
case '/':
if(rq.y == 0)
{
r.code = 1;
}
else
r.ret = rq.x/rq.y;
break;
default:
r.code = 2;
break;
}
//发送消息
if(send(sock,&r,sizeof(r),0) <= 0)
{
std::cerr<<"send error..."<<std::endl;
}
}
}
void cal(struct expersion* exp)
{
//发送
if(send(sock,exp,sizeof(*exp),0) <= 0)
{
std::cout<<"send error"<<std::endl;
}
//接收数据
struct result rt;
recv(sock,&rt,sizeof(rt),0);
//输出结果
if(rt.code == 0)
{
std::cout<<"ret = "<<rt.ret<<std::endl;
}
else if(rt.code == 1)
{
std::cout<<"除0错误"<<std::endl;
}
else
{
std::cout<<"出错"<<std::endl;
}
}
//开始运行
void start()
{
//链接
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
serverAddr.sin_addr.s_addr = inet_addr(ip.c_str());
if(connect(sock,(struct sockaddr*)&serverAddr,sizeof(serverAddr)) == -1)
{
std::cerr<<"client connect error..."<<std::endl;
exit(1);
}
//输入数据
struct expersion exp;
std::cout<<"client#";
std::cin>>exp.x>>exp.option>>exp.y;
//计算
cal(&exp);
}
//析构函数
~calClient()
{
close(sock);
}
};
//protocol.hpp
#ifndef __PROTOCOL_H__
#define __PROTOCOL_H__
struct expersion
{
int x;//左操作数
int y;//右操作数
char option;//运算符
};
/*规定
*code == 0,表示正确
*code == 1,表示除0错误
*code == 2,表示运算符不存在
*code == 3,操作数错误
* */
struct result
{
int code;//错误码
int ret;;//结果
};
#endif
二、HTTP协议
http是典型的应用层协议,它是超文本传输协议。生活中无处不见http,包括现在使用校多的https也是在http的基础上对数据进行了加密处理,提高了安全性。
1、认识URL
生活中,我们访问一个网站需要知道网址才能进行访问,网址就是我们这里说的URL(统一资源定位符)。URL可以分割成如下部分:
需要注意的是:
- 这里的服务器地址也是服务器域名,是通过地址进行解析得到的。
- ?/:等属于URL中的特殊字符,具有特殊含义。如果真正需要使用这些字符的原意,则需要进行转义。
- urlencode:对URL中的特殊字符进行转义。
- urldecode:对转义字符进行转换。
- 转义规则(了解):将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。
2、http协议格式
1)http请求协议格式
http请求协议由请求行、请求报头、空行和请求征文四部分构成。四部分具体结构和内容如下:
思考1:数据在网络中要么以字节流进行传输,要么以数据报进行传输。对于发送的请求协议,如何区分请求行、请求报头和请求正文?
首先,请求行包含了请求方法、URL和协议版本,这些信息单独占一行(通常以\\n结束),当第一次读到\\n时就将请求行读完了。
请求行结束之后是请求报头,请求报头和请求正文之间用一个空行来区分,也就是说当读到空行时请求报头就读完了。当读完空行,就开始读请求正文部分的数据。
思考2:如果一次接收到了多个请求协议,如何区分每一个协议?
已经可以区分一个http请求协议的请求行、请求报头和请求正文,但是由于不知道请求正文的大小,而无法区分下一个请求协议的开始位置。那么该如何区分呢?
在http协议的请求报头中,存储的全部是key:value的字段,其中有一个字段content-lenght标识了请求正文的字节数,当读取完这些字节后就开始下一个请求协议了。
下面是一个http请求协议的实例:
注意:这个示例中使用的是get方法,没有请求正文。
2)http响应协议格式
http响应协议和请求协议类似,整体结构相同,主要是响应行和请求行的信息有所不同。
注意:http响应协议和http请求协议的协议内容和协议之间的区分方法是相同的。
http响应协议实例:
3、http方法及http状态码
1)http方法
这里最常用的就是get和post方法了,这两个方法最主要的区别在于get方法的数据全部展示在URL中,而post通过request body传递数据相对安全。
2)http中基本的状态码和含义
http状态码很多,这里记住最常用的(200、404、403、302、504)几个就可以了。
4、http的特征
- 无连接:http协议应用在传输层的tcp、udp之上,tcp建立连接和http无关,http直接向对方发送http请求即可。
- 无状态:http本身是无状态的,即不会记录用户的任何信息,而记录用户信息的是cookie+session奇数
- 简单快速:短链接进行文本传输。
注意:http协议的报头部分connection字段如果是keep-alive表示长链接,如果是close表示短链接。
1)如何理解http协议是无连接的?
http协议是传输层的上层协议,传输层的tcp协议是面向链接的,但是对于上层协议来说它并不关注底层协议是否是需要链接的,它只关注如何去使用底层协议。而对于http协议本身来说,它并没有直接建立连接。
2)如何理解http是无状态的?
HTTP协议是无状态的,指的是协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。也就是说,打开一个服务器上的网页和上一次打开这个服务器上的网页之间没有任何联系。
3)如何理解长链接和短链接
在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个html或其他类型的Web页中包含有其他的Web资源(如javascript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。而从HTTP/1.1起,默认使用长连接,用以保持连接特性。
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。
4)cookie和session
http协议是无状态的,不会记录用户的任何数据信息。在日常生活中,我们访问一个网站这次登录过下次就不需要登录,甚至当我们打开浏览器访问一个网站后关掉电脑下次打开浏览器还可以继续访问上次访问的位置,实现这一功能的技术就是cookie和session技术。
cookie:本质上是保存在硬盘上的一个文件
- 当第一次访问网站时,使用用户名和密码进行登录,浏览器会自动将用户名和密码保存到本地的cookie文件中
- 当再次访问相同的文件时,浏览器自动使用cookie中的用户名和密码字段进行登录。
- 记录其他内容也是通过cookie进行保存的。
session
- 第一次访问时,用户需要通过用户名和密码进行登录。
- 登录成功后服务器会给该用户生成一个在该服务器内唯一的sessionid保存在cookie中
- 再次访问该网站时,服务器自动使用cookie中的sessionid进行登录。
- session相比单独的cookie要安全,单独使用cookie时用户名和密码会直接保存在cookie文件中,如果被恶意用户拿到cookie文件则该用户可以任意访问该用户的信息甚至修改密码使该用户无法访问。而使用session时,即使被恶意用户拿到cookie文件,直到的也只是sessionid,无法修改密码,账户拥有者可以通过申诉、修改密码等方法防止恶意用户再次访问。
- cookie保存在客户端本地,而session保存在服务器。
三、简单的http协议程序
#pragma once
#include<iostream>
#include<stdlib.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>
#include<string>
class HttpServer
{
private:
int port;
int lsock;
public:
HttpServer(int p):port(p),lsock(-1)
{}
void InitHtppServer()
{
signal(SIGCHLD,SIG_IGN);
//创建套接字
lsock = socket(AF_INET,SOCK_STREAM,0);
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)
{
//绑定失败
std::cerr<<"bind error..."<<std::endl;
exit(1); }
//监听
if(listen(lsock,5) < 0)
{
std::cerr<<"listen error..."<<std::endl;
exit(2);
}
}
void service(int sock)
{
//接收
char buf[1024];
int len = recv(sock,buf,sizeof(buf),0);
buf[len] = 0;
std::cout<<buf<<std::endl;
//拼接响应报格式
std::string buffer = "";
buffer += "http1.0 200 OK\\r\\n";//响应行
buffer += "context-length:1024\\r\\n";
buffer += "connection:keep-alive\\r\\n";
buffer += "\\r\\n";
buffer += "<!DOCTYPE html>\\
<html>\\
<head>\\
<title>hhhhhh</title>\\
</head>\\
<body>\\
<h1>我的第一个标题</h1>\\
<p>我的第一个段落。</p>\\
</body>\\
</html>";
}
void start()
{
//accept
struct sockaddr_in client;
socklen_t len;
while(1)
{
int sock = accept(lsock,(struct sockaddr*)&client,&len);
if(sock < 0)
{
//accept失败
std::cerr<<"accept error..."<<std::endl;
continue;
}
//创建子进程
if(fork())
{
close(lsock);
service(sock);
exit(0);
}
close(sock);
}
}
~HttpServer()
{
if(lsock >= 0)
close(lsock);
}
};
以上是关于计算机网络---应用层(http协议)的主要内容,如果未能解决你的问题,请参考以下文章
Flutter 报错 DioError [DioErrorType.DEFAULT]: Bad state: Insecure HTTP is not allowed by platform(代码片段