当前位置: 首页 > news >正文

LINUX网络基础 [五] - HTTP协议

 

目录

HTTP协议

预备知识

认识 URL

认识 urlencode 和 urldecode

HTTP协议格式 

HTTP请求协议格式

HTTP响应协议格式

HTTP的方法

HTTP的状态码

​编辑HTTP常见Header

HTTP实现代码

HttpServer.hpp

HttpServer.cpp

Socket.hpp 

log.hpp 

Makefile 

Web根目录

HTTPS协议了解

明文 与 密文

对称加密 与 非对称加密

数据摘要 && 数据指纹


HTTP协议

预备知识

HTTP(Hyper Text Transfer Protocol)协议又叫做超文本传输协议,是一个简单的请求-响应协议,HTTP通常运行在TCP之上。

在编写网络通信代码时,我们可以自己进行协议的定制,但实际有很多优秀的工程师早就已经写出了许多非常成熟的应用层协议,其中最典型的就是HTTP协议。 

日常生活中,为什么不用ip地址和端口呢,而是直接使用域名呢?

因为域名是很容易被记住,ip地址体验特别差,给你个ip地址你都不知道是什么连接

其实我们平时用的域名,首先会被解析成IP地址的,在进行访问时,网络通信真正用到的就是IP地址

我们在浏览器进行访问的时候,大部分默认使用的协议是http协议。他们的端口号一般是要固定下来

其中的网络服务,这个端口号都要指明出来,不能改,一旦改了,客户端就找不到你了

认识 URL

URL(Uniform Resource Lacator)叫做统一资源定位符,也就是我们通常所说的网址,是因特网的万维网服务程序上用于指定信息位置的表示方法。

图片 音频...统称为资源,所有网络上的资源,都可以用唯一的一个"字符串"标识,并且可以获取到,我们就称之为:统一资源

可是,我找到了这台主机和http服务,但是我想访问的是什么呢???比如我们平时在搜图片的时候,这个图片其实是被存储在该主机上的,而我们知道Linux一切皆文件,所以每个资源在自己的单机上都有自己的所属路径。因此我们还需要有一个带层次的文件路径。来标识我们具体想访问该主机上哪个地方的资源(一般来说可能是从web根目录开始 也有可能是相对路径)!/是文件路径分隔符

我们在上网的时候无非就两种网络行为。1.把别人的东西拿下来比如说下载图片 2.把自己的东西传上去 比如说登录注册

当我们找到了这个资源的路径,那么这个路径可能有很多很多格式各种的资源,那么我们继续需要锁定其中的一个,就需要有查询字符串,一般在查询标示符?的后面,表明该路径下的唯一资源,而要锁定唯一资源,就需要传入一些指定的参数,不同参数之间用&去分隔,让url支持多参数的提交! 而#后面的是片段标示符,也就是说可能我们想访问的是该资源的某一个位置,可以用这个去进行标识! 

我们在网络编程中,还知道要通过端口号来标识这台主机上的唯一一个服务,而该进程自带着网络协议http的解析方法,可是我们普通人使用的时候是没有端口号这个概念的! 那么浏览器怎么知道你这个端口号呢?? 因为浏览器会默认使用http协议,所以他必须知道绑定443号端口,所以默认会在请求里给我们添加端口号!

认识 urlencode 和 urldecode

urldecode(编码)

  • urldecode 函数用于将字符串中的特殊字符转化为 URL 编码格式。在 URL 中,某些字符(如空格、&、=、? 等)具有特殊意义因此不能直接出现在 URL 中urldecode 通过将这些字符转换为百分号编码(也叫做 URL 编码)来避免这些问题。

urldecode(解码)

  • urlencode 函数用于将 URL 编码(百分号编码)的字符串恢复为原始的字符串形式。它将 URL 中的 % 后跟的十六进制数还原回原来的字符

 少量的情况,提交或者获取的数据本身包含和url中特殊的字符冲突的字符,要求BS双方进行编码(encode)和解码的过程(decode),这样可以保证特殊字符不会在url中出现。浏览器会自动执行

为什么要这样呢?

因为它本身就包含这种特殊符号字符的使用,用户本身的特殊符号不会造成影响。这个编码和解码并不是为了加密什么的,而是为了保证用户提交的数据和url不发生冲突

urldecode转义的规则如下:

将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY 格式

urldecode就是urlencode的逆过程:

HTTP协议格式 

介绍

应用层常见的协议有HTTP和HTTPS,传输层常见的协议有TCP,网络层常见的协议是IP,数据链路层对应就是MAC帧了。其中下三层是由操作系统或者驱动帮我们完成的,它们主要负责的是通信细节。如果应用层不考虑下三层,在应用层自己的心目当中,它就可以认为自己是在和对方的应用层在直接进行数据交互。

下三层负责的是通信细节,而应用层负责的是如何使用传输过来的数据,两台主机在进行通信的时候,应用层的数据能够成功交给对端应用层,因为网络协议栈的下三层已经负责完成了这样的通信细节,而如何使用传输过来的数据就需要我们去定制协议,这里最典型的就是HTTP协议。

HTTP是基于请求和响应的应用层服务,作为客户端,你可以向服务器发起request,服务器收到这个request后,会对这个request做数据分析,得出你想要访问什么资源,然后服务器再构建response,完成这一次HTTP的请求。这种基于request&response这样的工作方式,我们称之为cs或bs模式,其中c表示client,s表示server,b表示browser。

由于HTTP是基于请求和响应的应用层访问,因此我们必须要知道HTTP对应的请求格式和响应格式,这就是学习HTTP的重点。

HTTP请求协议格式

Request请求分析图

HTTP请求由以下四部分组成: 

  • 请求行:[请求方法]+[url]+[http版本]
  • 请求报头:请求的属性,这些属性都是以key: value的形式按行陈列的。
  • 空行:遇到空行表示请求报头结束。
  • 请求正文:请求正文允许为空字符串,如果请求正文存在,则在请求报头中会有一个Content-Length属性来标识请求正文的长度。

 其中,前面三部分是一般是HTTP协议自带的,是由HTTP协议自行设置的,而请求正文一般是用户的相关信息或数据,如果用户在请求时没有信息要上传给服务器,此时请求正文就为空字符串。

你怎么确保正文部分能够一字不落地读完呢??
报头属性里面有 标注正文部分的长度的属性,所以他只要根据换行规则去读取整个报文,然后一直读到空行就说明报头读完了,然后再根据 Content-Length (length字段) 直接向后读相应长度的字节 就可以把正文部分也给读完了!

HTTP响应协议格式

Response响应示意图

HTTP响应由以下四部分组成:

状态行:[http版本]+[状态码]+[状态码描述]
响应报头:响应的属性,这些属性都是以key: value的形式按行陈列的。
空行:遇到空行表示响应报头结束。
响应正文:响应正文允许为空字符串,如果响应正文存在,则响应报头中会有一个Content-Length属性来标识响应正文的长度。比如服务器返回了一个html页面,那么这个html页面的内容就是在响应正文当中的。

使用 Telnet 连接远程主机,来模拟客户端进行访问

telnet <主机名> <端口号>

 退出 Telnet 会话

Ctrl+]
telnet> quit

响应结果解析:

fiddle

抓包软件fiddle

 fiddle为什么能抓包呢

原理:以前是浏览器直接发到网络。fiddle启动之后,浏览器发的请求不会直接发到网络中了,而是先交给fiddle,它进行包装下再发给服务器,服务器再把请求响应返回fiddle,它再给我们的浏览器

postman

可以通过它发送具体请求

在Linux系统中,recv 函数用于接收通过套接字传输的数据。它是一个阻塞函数,通常用于从网络连接中接收数据。recv 函数是POSIX标准的一部分,可以用来从连接的套接字(如TCP套接字)中接收数据。

ssize_t recv(int sockfd, void *buf, size_t len, int flags);参数说明
//sockfd:要接收数据的套接字描述符。它通常是由 socket()、accept() 或 connect() 等函数创建的套接字。
//buf:接收数据的缓冲区。
//len:缓冲区的大小,指定最多接收多少字节。
//flags:接收数据的选项,可以是以下之一(或它们的组合):
//MSG_OOB:接收带外数据。
//MSG_PEEK:从队列中查看数据,但不移除它们。
//MSG_WAITALL:等待接收到指定大小的所有数据。返回值
//如果成功,recv 返回接收到的字节数(即读取的数据量)。如果接收到的数据量小于指定的 len,可能是因为连接中没有更多的数据。
//返回值为0,表示对方关闭了连接(TCP连接的正常关闭)。
//返回值为-1,表示出错,可以通过 errno 获取错误码。

测试效果 

我们的网页难道每一次都要通过静态编码写到服务器里面吗?我们的网页应该单独的是一个独立的文件。需要访问哪个就通过http访问就行了

即http响应的正文部分,要放在网页(index.html)里面。让内容不要静态编码,而是动态的去让服务器响应给浏览器

对index文件修改之后保存,就能动态刷新内容了,不需要重启服务器 

我们的文件可能不止一个,包括图片 样式文件 js文件 各种各样的文件在Linux存在的情况下一定会存在个目录结构,这个目录结构肯定整体能被web访问

一个Http协议一定有自己的web根目录,这个目录也可以由自己去指定。但是这样无论我们怎么请求只能返回一个同一个文件,等未来你可能需要不同的文件,该怎么办呢

很明显将来你不可能把所有东西都放在这个文件下,这样是不现实的。所以我们现在要实现由客户端告诉我,它想要什么文件我就给它哪个文件,那该怎么实现呢?

实现的思路就是:我们要提取发来的请求。我们不难发现,Request请求行中的第一行URL就包括了用户想请求哪个目录

代码实现思路:

把指明的路径拼上前缀,此时就能够动态的在服务器上找到在wwwroot目录下指定的文件了。当未来加入图片等内容的时候,就可以在wwwroot目录下,用树状结构组织好去或许对应的内容了

我们可以定义个config配置文件,把web根目录放在config里面。当服务器启动的时候就读取配置文件里面所需的内容了

如何拿到URL

把发来的请求进行反序列化,拿到请求行和请求报头(正文内容还没添加)

在请求行中拿到URL

我们可以发现HTTP协议处理本质上就是文本的处理,跟我们之前讲的自定义协议是一样的

我们可以用一个东西:stringstream 进行分割字符串 用法如下

std::stringstream 在 C++ 中可以用于分割字符串,其基本原理是通过将字符串加载到流中,然后利用流操作符 (>>) 按照空格(或其他分隔符)逐步提取数据。这个过程利用了流的特性,流会根据输入格式将字符串转换为不同的数据类型。

分割字符串的原理

std::stringstream 分割字符串的过程可以简单理解为:

  1. 加载字符串:将需要分割的字符串传递给 std::stringstream 对象,这样就可以使用流操作符读取字符串中的各个部分。
  2. 使用流提取符提取数据:流操作符 (>>) 会逐个提取数据,并且在遇到空格或其他分隔符时停止提取。
  3. 重复提取直到流为空:你可以反复使用流操作符来从流中提取多个数据,直到流的内容全部被提取完。
#include<sstream>
using namespace std;int main()
{stringstream ss("abc def ghi");string s1, s2, s3;ss >> s1 >> s2 >> s3;cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;return 0;
}

提取URL

如果在访问的时候不指定目录,比如说我们要访问(www.baidu.com,那就会默认去访问www.baidu.com/)那就是默认会带一个/,代表着根目录,只会把网站的首页给你。也就是index.html。而不会把根目录下的所有内容给你。

那么我们也要实现这个思路,当你默认访问的时候设置把首页给你,当你指定访问的时候,就去访问对应的目录

const std::string homepage = "index.html";//解析之后做判断
file_path = wwwroot; // ./wwwroot        
if(url == "/" || url == "/index.html") 
{file_path += "/";file_path += homepage; // ./wwwroot/index.html
}
else file_path += url; // /a/b/c/d.html->./wwwroot/a/b/c/d.html

可以看到,当我输入的url是:/a/b/c.html,服务器自动把路径给我填充为 ./wwwroot/a/b/c/d.html

 当我访问的是默认的话,服务器也会自动填充为首页目录

下一步就是我们新建别的目录

测试结果:

在下一步:可是对我们来说,我们也不这样访问呀,如果我们访问京东的时候,一般都是上一页,下一页,从一个链接跳转到另一个链接的去访问。这个时候我们就需要把URL给维护起来了

此时就需要这个代码了

<a href="xxxx">到第二张网页</a>

 让" "里面的内容具有超链接的作用。同样的,也能跳转到自己的地址,到第三张网页... 

重点是理解HTTP的请求,无非就是通过套接字,读到了HTTP的请求,然后按照格式给解析出来,提取URL,给他响应再把响应返回去。HTTP底层就是TCP

HTTP的方法

同学们您们的请求全部都是get方法那么什么叫做GET方法呢?

但是一般来说,最常用的就是 GET 和 POST 

GET方法一般用于获取某种资源信息,而POST方法一般用于将数据上传给服务器。但实际我们上传数据时也有可能使用GET方法,比如百度提交数据时实际使用的就是GET方法。

我们在日常使用某些网站的时候,是如何把数据提交给服务器的呢?日常又是怎么提交的呢?

其实数据都是通过表单提供的

GET 方法的提交方式

POST 方法的提交方式

GET方法和POST方法都可以带参:

  • GET方法是通过url传参的。
  • POST方法是通过正文传参的。

从GET方法和POST方法的传参形式可以看出,POST方法能传递更多的参数,因为url的长度是有限制的,POST方法通过正文传参就可以携带更多的数据。

此外,使用POST方法传参更加私密,因为POST方法不会将你的参数回显到url当中,此时也就不会被别人轻易看到。不能说POST方法比GET方法更安全,因为POST方法和GET方法实际都不安全,要做到安全只能通过加密来完成。

其实提交参数的本质意义就是,fork子进程让他通过进程替换去执行这个程序,然后我们再通过进程间通信的方法将参数传递给他,然后让他完成相应的功能!!

HTTP的状态码

最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

404表示资源不存在,比如客户端请求的资源,服务器打开失败所以产生错误,是客户端的错误(所以一般来说我们除了要写一个网站首页的文件,还需要写一个如果请求失败的一个返回404页面的网页)

模拟下404界面的实现

当收到的text为空时,说明没有找到路径,那就直接去找err.html提示404 Not Found

403是禁止访问,一般就是因为你没有授权然后去访问了服务端不让你访问的信息 

504服务器错误,一般来说就是比如连接不上,或者是服务的线程创建失败

3XX一般是由于我要访问的服务器可能处于某种原因无法为我提供服务,所以他会传一个地址告诉客户端你想要的资源应该去这里查找(通过参数Location),这就是重定向

Redirection详解

重定向分为永久重定向和临时重定向 

讲个故事:

       比如说你的学校东门有一家火锅店非常有名,但是他需要长时间的装修,为了继续生意,他将生意开到了学校的西门,然后在东门这里贴了一张纸条“正在装修,请到西门用餐”,这个时候你和你的同学到东门时看到了这个纸条,于是就到西门去吃了了,过了一礼拜你们还打算去的时候,你知道东门那边只是暂时关闭,所以你还是会先去东门看看,如果还没开的话才会去西门,这个其实就是 临时重定向,因为我知道这个店只是暂时开到西门,随时可能会回来!

      而后来东门开了的时候,为了能够让之前去西门吃饭的同学知道东门开启了,他会在之前西门的地方贴上“东门已经装修完成,以后请都前往东门用餐” 这个时候你们就知道这个店会一直在东门,这其实就是 永久重定向!!

如果某个网站是永久重定向,那么第一次访问该网站时由浏览器帮你进行重定向,但后续再访问该网站时就不需要浏览器再进行重定向了,此时你访问的直接就是重定向后的网站。而如果某个网站是临时重定向,那么每次访问该网站时如果需要进行重定向,都需要浏览器来帮我们完成重定向跳转到目标网站。 所以有的网站时间比较老,做更新的时候需要把域名和网址换了,可是很多老用户并不知道,所以就会给老网站部署永久性定向服务,让用户直接跳转到新网站!

//构建HTTP响应
string status_line = "http/1.1 307 Temporary Redirect\n"; //状态行
string response_header = "Location: https://www.csdn.net/\n"; //响应报头
string blank = "\n"; //空行
string response = status_line + response_header + blank; //响应报文
//响应HTTP请求
send(sock, response.c_str(), response.size(), 0);

当我们用telnet命令登录我们的服务器时,向服务器发起HTTP请求时,此时服务器给我们的响应就是状态码307,响应报头当中是Location字段对应的就是CSDN首页的网址 

telnet命令实际上只是一来一回,如果我们用浏览器访问我们的服务器,当浏览器收到这个HTTP响应后,还会对这个HTTP响应进行分析,当浏览器识别到状态码是307后就会提取出Location后面的网址,然后继续自动对该网站继续发起请求,此时就完成了页面跳转这样的功能。

此时当浏览器访问我们的服务器时,就会立马跳转到CSDN的首页。

HTTP常见Header

以下是常见的Header

  • Content-Type: 数据类型(text/html等)
  • Content-Length: Body的长度
  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
  • User-Agent: 声明用户的操作系统和浏览器版本信息;
  • referer: 当前页面是从哪个页面跳转过来的;
  • location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
  • Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
  • connection:是否支持长连接(是基于服务费和客户端的版本去协商的)

Content-Type

读html的时候他是文本文件,那么就可以按照字符串读,但是如果我们是图片资源(png),那么就得按照二进制文件的方式来读!!因此在读取html之前需要确认一下文件格式,而不同的格式对应的content-type可以到网上搜索对照表。所以读网页之前必须确定一下后缀,然后不同的后缀就根据content-type对照表去找。

常见的Content-Type

Host 

Host字段表明了客户端要访问的服务的IP和端口,比如当浏览器访问我们的服务器时,浏览器发来的HTTP请求当中的Host字段填的就是我们的IP和端口。但客户端不就是要访问服务器吗?为什么客户端还要告诉服务器它要访问的服务对应的IP和端口?

因为有些服务器实际提供的是一种代理服务,也就是代替客户端向其他服务器发起请求,然后将请求得到的结果再返回给客户端。在这种情况下客户端就必须告诉代理服务器它要访问的服务对应的IP和端口,此时Host提供的信息就有效了。

connection

短连接其实就是一次请求响应一个资源,然后就关闭连接

长连接其实就是一次请求连接上之后可以一直服务直到服务结束再关闭连接

为什么要有长连接呢??

因为一个巨大的网页上的元素是很多的,而每一个元素其实就是一个资源,所以我们在发出http请求申请到网页资源的时候,同时也需要把网页上附带的资源(比如图片、音频) 都申请了。比如服务器要获取100个图片的网页,要发起101次HTTP的请求,对我们的服务器来说要一次性创建几十个线程,而HTTP底层是基于TCP的,TCP是面向连接的,是短连接!所以如果用短连接的话显然效率是不够高的!!

长连接和短连接并没有绝对的优劣,只不过应用场景不一样!!

         但是要注意的是,具体采用长连接还是短连接,是要基于双方的HTTP版本协商的,其中HTTP1.0其实就是短连接  而HTTP1.1其实就是长连接   要支持长连接的话必须要求双方的版本都是1.1   这样Connection就会呈现keep-alive表示支持长连接 

Referer

Referer代表的是你当前是从哪一个页面跳转过来的。Referer记录上一个页面的好处一方面是方便回退,另一方面可以知道我们当前页面与上一个页面之间的相关性。

Cookie

HTTP实际上是一种无状态协议,HTTP的每次请求/响应之间是没有任何关系的,但你在使用浏览器的时候发现并不是这样的。

比如当你登录一次CSDN后,就算你把CSDN网站关了甚至是重启电脑,当你再次打开CSDN网站时,CSDN并没有要求你再次输入账号和密码,这实际上是通过cookie技术实现的,点击浏览器当中锁的标志就可以看到对应网站的各种cookie数据。

这些cookie数据实际都是对应的服务器方写的,如果你将对应的某些cookie删除,那么此时可能就需要你重新进行登录认证了,因为你删除的可能正好就是你登录时所设置的cookie信息。

Cookie是什么

因为HTTP是一种无状态协议,如果没有cookie的存在,那么每当我们要进行页面请求时都需要重新输入账号和密码进行认证,这样太麻烦了。 

首先我们要知道,HTTP默认是无状态的,而他之所以能够知道你你处在登录状态,是因为你之前登录的时候,在浏览器里形成了一个cookie文件,这个文件里存储着你在这个网站的认证信息,而当你打开这个网站时,浏览器向对应服务端发送请求的时候会将你的认证信息(就是用户名和密码)放在cookie参数里面带过去直接认证(认证其实就是拿着你的用户名和密码去他后端的数据库做搜索,所以你想使用的前提是必须得注册,才能在他的数据库里留存数据),认证通过之后会直接将你从原先的登录页面重定向到目标页面,这样你就不需要再次登录了!!当然这个保存一般是有时间限制的!!

从第一次登录认证之后,浏览器再向该网站发起的HTTP请求当中就会自动包含一个cookie字段,其中携带的就是我第一次的认证信息,此后对端服务器需要对你进行认证时就会直接提取出HTTP请求当中的cookie字段,而不会重新让你输入账号和密码了。也就是在第一次认证登录后,后续所有的认证都变成了自动认证,这就叫做cookie技术。也叫做HTTP会话保持功能

内存级别&文件级别

cookie就是在浏览器当中的一个小文件,文件里记录的就是用户的私有信息。cookie文件可以分为两种,一种是内存级别的cookie文件,另一种是文件级别的cookie文件。

  • 将浏览器关掉后再打开,访问之前登录过的网站,如果需要你重新输入账号和密码,说明你之前登录时浏览器当中保存的cookie信息是内存级别的。
  • 将浏览器关掉甚至将电脑重启再打开,访问之前登录过的网站,如果不需要你重新输入账户和密码,说明你之前登录时浏览器当中保存的cookie信息是文件级别的。

cookie被盗 

 如果你浏览器当中保存的cookie信息被非法用户盗取了,那么此时这个非法用户就可以用你的cookie信息,以你的身份去访问你曾经访问过的网站,我们将这种现象称为cookie被盗取了。

比如你不小心点了某个链接,这个链接可能就是一个下载程序,当你点击之后它就会通过某种方式把程序下载到你本地,并且自动执行该程序,该程序会扫描你的浏览器当中的cookie目录,把所有的cookie信息通过网络的方式传送给恶意方,当恶意方拿到你的cookie信息后就可以拷贝到它的浏览器对应的cookie目录当中,然后以你的身份访问你曾经访问过的网站。

所以我们会面临两个问题(1)cookie被盗取 (2)个人信息泄露

我们要知道我们小白用户的防范能力基本为0,他们的电脑在黑客的眼里其实就相当于裸奔,比如你不小心点开了一个病毒,你或许可以通过杀毒软件去杀毒,但是也很有可能在你清理这个病毒之前你的信息就已经被窃取了!! 所以显然不能让客户端来维护这个安全问题,必须由更专业的服务端来维护!

SessionID

所以引入了session技术,我们输入用户名密码的时候,他会将这个用户名密码存在服务端,然后生成一个session id(数字指纹)返回给客户端存在cookie文件里,然后服务端会将session id管理起来(redis),这样认证的时候就会用cookie文件里面存储的session id去服务端做对比,只要通过了就可以直接登录了!!

 

当我们第一次登录某个网站输入账号和密码后,服务器认证成功后还会服务端生成一个对应的SessionID,这个SessionID与用户信息是不相关的。系统会将所有登录用户的SessionID值统一维护起来。

此时当认证通过后服务端在对浏览器进行HTTP响应时,就会将这个生成的SessionID值响应给浏览器。浏览器收到响应后会自动提取出SessionID的值,将其保存在浏览器的cookie文件当中。后续访问该服务器时,对应的HTTP请求当中就会自动携带上这个SessionID。而服务器识别到HTTP请求当中包含了SessionID,就会提取出这个SessionID,然后再到对应的集合当中进行对比,对比成功就说明这个用户是曾经登录过的,此时也就自动就认证成功了,然后就会正常处理你发来的请求,这就是我们当前主流的工作方式。使用SessionID本质上是防止了个人信息泄露了。因为你的个人信息已经是在服务器上维护了,而不是在客户端上维护了

安全是相对的

 引入SessionID之后,浏览器当中的cookie文件保存的是SessionID,此时这个cookie文件同样可能被盗取。此时用户的账号和密码虽然不会泄漏了,但用户对应的SessionID是会泄漏的,非法用户仍然可以盗取我的SessionID去访问我曾经访问过的服务器,相当于还是存在刚才的问题。

  • 之前的工作方式就相当于把账号和密码信息在浏览器当中再保存一份,每次请求时都自动将账号和密码的信息携带上,但是账号和密码一直在网当中发送太不安全了。
  • 因此现在的工作方式是,服务器只有在第一次认证的时候需要在网络中传输账号和密码,此后在网络上发送的都是SessionID。

这种方法虽然没有真正解决安全问题,但这种方法是相对安全的。但是跟之前最大的区别就是session id是由服务端统一管理的,也就意味着他具备回收、停用、甄别异常等能力(比如说他发现你的ip地址突然发生了很大的变动,察觉异常就会暂时给你停止,等你发现问题了再次申诉的时候,他就会把你之前的session id清理掉然后重新分配) 这其实就是达到了控制客户端的目的!! 

不过在安全领域有一个准则:如果破解某个信息的成本已经远远大于破解之后获得的收益(说明做这个事是赔本的),那么就可以说这个信息是安全的。

HTTP实现代码

HttpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unordered_map>
#include "Socket.hpp"
#include "Log.hpp"const std::string wwwroot="./wwwroot"; // web 根目录
const std::string sep = "\r\n";
const std::string homepage = "index.html";static const int defaultport = 8082;class HttpServer;class ThreadData
{
public:ThreadData(int fd, HttpServer *s) : sockfd(fd), svr(s){}public:int sockfd;HttpServer *svr;
};class HttpRequest
{
public:void Deserialize(std::string req){while(true){std::size_t pos = req.find(sep);if(pos == std::string::npos) break;std::string temp = req.substr(0, pos);if(temp.empty()) break;req_header.push_back(temp);req.erase(0, pos+sep.size());}text = req;}// .png:image/pngvoid Parse(){std::stringstream ss(req_header[0]);ss >> method >> url >> http_version;file_path = wwwroot; // ./wwwrootif(url == "/" || url == "/index.html") {file_path += "/";file_path += homepage; // ./wwwroot/index.html}else file_path += url; // /a/b/c/d.html->./wwwroot/a/b/c/d.htmlauto pos = file_path.rfind(".");if(pos == std::string::npos) suffix = ".html";else suffix = file_path.substr(pos);}void DebugPrint(){for(auto &line : req_header){std::cout << "--------------------------------" << std::endl;std::cout << line << "\n\n";}std::cout << "method: " << method << std::endl;std::cout << "url: " << url << std::endl;std::cout << "http_version: " << http_version << std::endl;std::cout << "file_path: " << file_path << std::endl;std::cout << text << std::endl;}
public:std::vector<std::string> req_header;std::string text;// 解析之后的结果std::string method;std::string url;std::string http_version;std::string file_path; // ./wwwroot/a/b/c.html 2.pngstd::string suffix;
};class HttpServer
{
public:HttpServer(uint16_t port = defaultport) : _port(port){content_type.insert({".html", "text/html"});content_type.insert({".png", "image/png"});content_type.insert({".jpg", "image/jpeg"});}bool Start(){_listensock.Socket();_listensock.Bind(_port);_listensock.Listen();for (;;){std::string clientip;uint16_t clientport;int sockfd = _listensock.Accept(&clientip, &clientport);if (sockfd < 0)continue;lg(Info, "get a new connect, sockfd: %d", sockfd);pthread_t tid;ThreadData *td = new ThreadData(sockfd, this);pthread_create(&tid, nullptr, ThreadRun, td);}}static std::string ReadHtmlContent(const std::string &htmlpath){// 坑std::ifstream in(htmlpath, std::ios::binary);if(!in.is_open()) return "";in.seekg(0, std::ios_base::end);auto len = in.tellg();in.seekg(0, std::ios_base::beg);std::string content;content.resize(len);in.read((char*)content.c_str(), content.size());//std::string content;//std::string line;//while(std::getline(in, line))//{//    content += line;//}in.close();return content;}std::string SuffixToDesc(const std::string &suffix){auto iter = content_type.find(suffix);if(iter == content_type.end()) return content_type[".html"];else return content_type[suffix];}void HandlerHttp(int sockfd){char buffer[10240];ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0); // bugif (n > 0){buffer[n] = 0;std::cout << buffer << std::endl; // 假设我们读取到的就是一个完整的,独立的http 请求HttpRequest req;req.Deserialize(buffer);req.Parse();req.DebugPrint();//std::string path = wwwroot;//path += url; // wwwroot/a/a/b/index.html// 返回响应的过程std::string text;bool ok = true;text = ReadHtmlContent(req.file_path); // 失败?if(text.empty()){ok = false;std::string err_html = wwwroot;err_html += "/";err_html += "err.html";text = ReadHtmlContent(err_html);}std::string response_line;if(ok)response_line = "HTTP/1.0 200 OK\r\n";elseresponse_line = "HTTP/1.0 404 Not Found\r\n";//response_line = "HTTP/1.0 302 Found\r\n";std::string response_header = "Content-Length: ";response_header += std::to_string(text.size()); // Content-Length: 11response_header += "\r\n";response_header += "Content-Type: ";response_header += SuffixToDesc(req.suffix);response_header += "\r\n";response_header += "Set-Cookie: name=haha&&passwd=12345";response_header += "\r\n";//response_header += "Location: https://www.qq.com\r\n";std::string blank_line = "\r\n"; // \nstd::string response = response_line;response += response_header;response += blank_line;response += text;send(sockfd, response.c_str(), response.size(), 0);}close(sockfd);}static void *ThreadRun(void *args){pthread_detach(pthread_self());ThreadData *td = static_cast<ThreadData *>(args);td->svr->HandlerHttp(td->sockfd);delete td;return nullptr;}~HttpServer(){}private:Sock _listensock;uint16_t _port;std::unordered_map<std::string, std::string> content_type;
};

HttpServer.cpp

#include "HttpServer.hpp"
#include <iostream>
#include <memory>
#include <pthread.h>
#include "Log.hpp"using namespace std;int main(int argc, char *argv[])
{if(argc != 2){exit(1);}uint16_t port = std::stoi(argv[1]);// HttpServer *svr = new HttpServer();// std::unique<HttpServer> svr(new HttpServer());std::unique_ptr<HttpServer> svr(new HttpServer(port));svr->Start();return 0;
}

Socket.hpp 

#pragma once#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"enum
{SocketErr = 2,BindErr,ListenErr,
};// TODO
const int backlog = 10;class Sock
{
public:Sock(){}~Sock(){}public:void Socket(){sockfd_ = socket(AF_INET, SOCK_STREAM, 0);if (sockfd_ < 0){lg(Fatal, "socker error, %s: %d", strerror(errno), errno);exit(SocketErr);}int opt = 1;setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));}void Bind(uint16_t port){struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0){lg(Fatal, "bind error, %s: %d", strerror(errno), errno);exit(BindErr);}}void Listen(){if (listen(sockfd_, backlog) < 0){lg(Fatal, "listen error, %s: %d", strerror(errno), errno);exit(ListenErr);}}int Accept(std::string *clientip, uint16_t *clientport){struct sockaddr_in peer;socklen_t len = sizeof(peer);int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);if(newfd < 0){lg(Warning, "accept error, %s: %d", strerror(errno), errno);return -1;}char ipstr[64];inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));*clientip = ipstr;*clientport = ntohs(peer.sin_port);return newfd;}bool Connect(const std::string &ip, const uint16_t &port){struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));peer.sin_family = AF_INET;peer.sin_port = htons(port);inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));int n = connect(sockfd_, (struct sockaddr*)&peer, sizeof(peer));if(n == -1) {std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;return false;}return true;}void Close(){close(sockfd_);}int Fd(){return sockfd_;}private:int sockfd_;
};

log.hpp 

#pragma once#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile "log.txt"class Log
{
public:Log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}// void logmessage(int level, const char *format, ...)// {//     time_t t = time(nullptr);//     struct tm *ctime = localtime(&t);//     char leftbuffer[SIZE];//     snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),//              ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,//              ctime->tm_hour, ctime->tm_min, ctime->tm_sec);//     // va_list s;//     // va_start(s, format);//     char rightbuffer[SIZE];//     vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);//     // va_end(s);//     // 格式:默认部分+自定义部分//     char logtxt[SIZE * 2];//     snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);//     // printf("%s", logtxt); // 暂时打印//     printLog(level, logtxt);// }void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen:std::cout << logtxt << std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string _logname = path + logname;int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"printOneFile(filename, logtxt);}~Log(){}void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式:默认部分+自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);// printf("%s", logtxt); // 暂时打印printLog(level, logtxt);}
private:int printMethod;std::string path;
};Log lg;// int sum(int n, ...)
// {
//     va_list s; // char*
//     va_start(s, n);//     int sum = 0;
//     while(n)
//     {
//         sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
//         n--;
//     }//     va_end(s); //s = NULL
//     return sum;
// }

Makefile 

HttpServer:HttpServer.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f HttpServer

Web根目录

HTTPS协议了解

早期很多公司刚起步的时候,使用的应用层协议都是HTTP,而HTTP无论是用GET方法还是POST方法传参,都是没有经过任何加密的,因此早期很多的信息都是可以通过抓包工具抓到的。

为了解决这个问题,于是出现了HTTPS协议,HTTPS实际就是在应用层中加了一层加密层(SSL&TLS),这层加密层本身也是属于应用层的,它会对用户的个人信息进行各种程度的加密。HTTPS在交付数据时先把数据交给加密层,由加密层对数据加密后再交给传输层。

当然,通信双方使用的应用层协议必须是一样的,因此对端的应用层也必须使用HTTPS,当对端的传输层收到数据后,会先将数据交给加密层,由加密层对数据进行解密后再将数据交给应用层。

 

此时数据只有在用户层(应用层)是没有被加密的,而在应用层往下以及网络当中都是加密的,这就叫做HTTPS。

明文 与 密文

  • 明文 (Plaintext):指的是未经加密的原始数据或信息。它是人类可以直接读取的内容,比如普通的文本文件、电子邮件内容、文件内容等。例如,“Hello, how are you?” 这个句子就是明文。

  • 密文 (Ciphertext):指的是通过加密算法将明文转化后的形式,目的是保护数据的隐私。密文通常是无法直接理解的,只有通过解密操作才能还原成明文。例如,使用加密算法加密后,原来的文本可能变成类似“Xy2#&9kfj23”这样的内容。

对称加密 与 非对称加密

加密的方式可以分为对称加密和非对称加密:

  • 采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密。
  • 采用公钥和私钥来进行加密和解密,用其中一个密钥进行加密就必须用另一个密钥进行解密,这种加密方法称为非对称加密。

对称加密 

采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密,特征:加密和解密所用的密钥是相同的。
常见对称加密算法(了解):DES、3DES、AES、TDEA、Blowfish、RC2 等
特点:算法公开、计算量⼩、加密速度快、加密效率⾼

对称加密其实就是通过同一个 "密钥" , 把明文加密成密文, 并且也能把密文解密成明文。

一个简单的对称加密, 按位异或:

  • 假设 明文 a = 1234, 密钥 key = 8888
  • 则加密 a ^ key 得到的密文 b 为 9834。
  • 然后针对密文 9834 再次进行运算 b ^ key, 得到的就是原来的明文 1234. (对于字符串的对称加密也是同理, 每一个字符都可以表⽰成一个数字)

非对称加密 

需要两个密钥来进行加密和解密,这两个密钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。
常见非对称加密算法(了解):RSA,DSA,ECDSA
特点:算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快

 • 通过公钥对明文加密, 变成密文

• 通过私钥对密文解密, 变成明文

• 通过私钥对明文加密, 变成密文

• 通过公钥对密文解密, 变成明文

非对称加密的数学原理比较复杂, 涉及到一些 数论 相关的知识. 这里举一个简单的生活上的例子。

A 要给 B 一些重要的文件, 但是 B 可能不在。

于是 A 和 B 提前做出约定: B 说: 我桌子上有个盒子, 然后我给你一把锁, 你把文件放盒子里用锁锁上, 然后我回头拿着钥匙来开锁取文件。

在这个场景中, 这把锁就相当于公钥, 钥匙就是私钥。 公钥给谁都行(不怕泄露), 但是 私钥只有 B 自己持有。持有私钥的人才能解密。

所以要注意的是:公钥不怕泄露,私钥不能泄露

数据摘要 && 数据指纹

数据摘要 && 数据指纹

数据摘要(又被称为数据指纹),其基本原理是利用单向散列函数(Hash函数)对数据进行运算,生成一串固定长度的数字摘要(散列值)。

摘要的特征:数字摘要并不是一种加密机制,数据摘要具有不可逆性,即无法从摘要推导出原始数据。
摘要的应用:数据摘要用于验证数据的完整性,确保数据在传输或存储过程中没有被修改。
摘要的常见算法:有MD5、SHA1、SHA256、SHA512等。

 

数据签名

数据签名是在数据摘要的基础上添加了非对称的加密操作,用于验证数据的完整性和真实性。
数据签名包含了数据摘要、公钥密码学算法和数字证书等技术。发送者使用私钥对摘要进行加密,形成签名,接收者使用发送者的公钥对签名进行解密和验证。
数据签名不仅验证数据的完整性,还验证发送者的身份,确保数据的真实性和不可否认性。

相关文章:

LINUX网络基础 [五] - HTTP协议

目录 HTTP协议 预备知识 认识 URL 认识 urlencode 和 urldecode HTTP协议格式 HTTP请求协议格式 HTTP响应协议格式 HTTP的方法 HTTP的状态码 ​编辑HTTP常见Header HTTP实现代码 HttpServer.hpp HttpServer.cpp Socket.hpp log.hpp Makefile Web根目录 H…...

嵌入式人工智能应用-第6章 人脸检测

嵌入式人工智能应用 人脸检测 嵌入式人工智能应用1 人脸检测1.1 CNN 介绍1.2 人脸检测原理1.3 MTCNN介绍1.4 NCNN介绍2 系统安装2.1 安装依赖库NCNN2.2 运行对应的库3 总结1 人脸检测 1.1 CNN 介绍 卷积神经网络。卷积是什么意思呢?从数学上说,卷积是一种运算。它是我们学习…...

编程考古-Borland历史:《.EXE Interview》对Anders Hejlsberg关于Delphi的采访内容(中)

为了纪念Delphi在2002年2月14日发布的25周年(2020.2.12),这里有一段由.EXE杂志编辑Will Watts于1995年对Delphi首席架构师Anders Hejlsberg进行的采访记录。在这次采访中,Anders讨论了Delphi的设计与发展,以及即将到来的针对Windows 95的32位版本。 Q. 编译器引擎本身是用…...

redis数据类型以及底层数据结构

redis数据类型以及底层数据结构 String&#xff1a;字符串类型&#xff0c;底层就是动态字符串&#xff0c;使用sds数据结构 Map:有两种数据结构&#xff1a;1.压缩列表&#xff1a;当hash结构中存储的元素个数小于了512个。并且元 …...

C运算符 对比a++、++a、b--、 --b

#include<stdio.h> int main() { int a 21;int b 10;int c, d;c a;//先赋值给c,a本身再运算 c 21, a 22;//c a;//a本身先运算&#xff0c;再赋值给c a 22,c 22;printf("c %d, a %d\n",c, a); d --b;//b本身先运算&#xff0c;再赋值给d …...

Java EE 进阶:Spring MVC(2)

cookie和session的关系 两者都是在客户端和服务器中进行存储数据和传递信息的工具 cookie和session的区别 Cookie是客⼾端保存⽤⼾信息的⼀种机制. Session是服务器端保存⽤⼾信息的⼀种机制. Cookie和Session之间主要是通过SessionId关联起来的&#xff0c;SessionId是Co…...

基于Matlab的人脸识别的二维PCA

一、基本原理 传统 PCA 在处理图像数据时&#xff0c;需将二维图像矩阵拉伸为一维向量&#xff0c;这使得数据维度剧增&#xff0c;引发高计算成本与存储压力。与之不同&#xff0c;2DPCA 直接基于二维图像矩阵展开运算。 它着眼于图像矩阵的列向量&#xff0c;构建协方差矩阵…...

Java 深度复制对象:从基础到实战

目录 一、深度复制的概念二、实现深度复制的方法1. 使用序列化2. 手动实现深度复制 三、总结 在 Java 编程中&#xff0c;对象的复制是一个常见的需求。然而&#xff0c;简单的复制操作&#xff08;如直接赋值&#xff09;只会复制对象的引用&#xff0c;而不是创建一个新的对象…...

【Java开发指南 | 第三十五篇】Maven + Tomcat Web应用程序搭建

读者可订阅专栏&#xff1a;Java开发指南 |【CSDN秋说】 文章目录 前言Maven Tomcat Web应用程序搭建1、使用Maven构建新项目2、单击项目&#xff0c;连续按两次shift键&#xff0c;输入"添加"&#xff0c;选择"添加框架支持"3、选择Java Web程序4、点击&…...

TCP三次握手,四次挥手;多进程、多线程实现并发服务器

三次握手&#xff0c;四次挥手 三次握手示意图&#xff1a; SYN、ACK是TCP协议头里面的标志位 同步 SYN&#xff1a;仅在三次握手建立 TCP 连接时有效。当 SYN 1 而 ACK 0 时&#xff0c;表明这是一个连接请求报文段&#xff0c;对方若同意建立连接&#xff0c;则应在相应的…...

Java基础系列:深入理解八大基本数据类型及避坑指南

目录 一、基本数据类型概述 八大类型速查表 二、各类型详解与常见陷阱 1. 整型家族&#xff08;byte/short/int/long&#xff09; 2. 浮点型&#xff08;float/double&#xff09; 3. 字符型&#xff08;char&#xff09; 4. 布尔型&#xff08;boolean&#xff09; 三…...

【Gaussian Model】高斯分布模型

目录 高斯分布模型用于异常检测&#xff08;Gaussian Model for Anomaly Detection&#xff09;1. 高斯分布简介2. 高斯分布模型用于异常检测(1) 训练阶段&#xff1a;估计数据分布(2) 检测阶段&#xff1a;计算概率判断异常点 3. 示例代码4. 高斯分布异常检测的优缺点优点缺点…...

Unity--Cubism Live2D模型使用

了解LIVE2D在unity的使用--前提记录 了解各个组件的作用 Live2D Manuals & Tutorials 这些文件都是重要的控制动画参数的 Cubism Editor是编辑Live2D的工具&#xff0c;而导出的数据的类型&#xff0c;需要满足以上的条件 SDK中包含的Cubism的Importer会自动生成一个Pref…...

Day4 C语言与画面显示练习

文章目录 1. harib01a例程2. harib01b例程3. harib01e例程4. harib01f例程5. harib01h例程 1. harib01a例程 上一章主要是将画面搞成黑屏&#xff0c;如果期望做点什么图案&#xff0c;只需要再VRAM里写点什么就好了&#xff0c;使用nask汇编语言实现一个函数write_mem8&#…...

【redis】全局命令exists、del、expire、ttl(惰性删除和定期删除)

exists——判定 key 是否存在 语法&#xff1a; exists key [key...] # 返回值&#xff1a;key 存在的个数针对多个 key 来说&#xff0c;是非常有用的时间复杂度 O ( 1 ) O(1) O(1) Redis 组织这些 key 就是按照哈希表的方式来组织的。Redis 支持很多数据结构指的是 value …...

VUE3项目的文档结构分析

1. Vue 3 项目的文档结构 Vue 3 项目通常基于 Vue CLI 或 Vite 等工具创建&#xff0c;其文档结构如下&#xff1a; 常见目录结构 my-vue-project/ ├── public/ # 静态资源目录 │ ├── index.html # 入口页面 ├── src/ …...

Linux笔记---自定义shell

目录 前言 1. 程序框架 2. 打印命令行提示符 2.1 获取用户名(GetUserName) 2.2 获取主机名(GetHostName) 2.3 获取工作目录(GetPwd) 3. 获取命令行输入 4. 判断是否有重定向 5. 解析命令行 6. 内建命令 6.1 内建命令的特点 6.2 常见内建命令 6.3 内建命令 vs 外部命…...

步进电机软件细分算法解析与实践指南

1. 步进电机细分技术概述 步进电机是一种将电脉冲信号转换为角位移的执行机构&#xff0c;其基本运动单位为步距角。传统步进电机的步距角通常为 1.8&#xff08;对应 200 步 / 转&#xff09;&#xff0c;但在高精度定位场景下&#xff0c;这种分辨率已无法满足需求。细分技术…...

mapbox开发小技巧

自定义图标 // 1、单个图标 const url ./static/assets/symbols/code24x24/VIDEO.png // 图标路径 map.loadImage(url ,(error, image) > {if (error) throw errormap.addImage(video-icon, image) })// 2、雪碧图利用canvas // json和png图片 function getStyleImage(fil…...

ApoorvCTF Rust语言逆向实战

上周参加了国外的比赛&#xff0c;名称叫&#xff1a;ApoorvCTF 看一下老外的比赛跟我们有什么不同&#xff0c;然后我根据国内比赛对比发现&#xff0c;他们考点还是很有意思的&#xff0c;反正都是逆向&#xff0c;哈哈哈 Rusty Vault 题目描述&#xff1a; In the heart…...

IntersectionObserver接口介绍

IntersectionObserver API 是浏览器提供的一个用于异步观察目标元素与其祖先元素或视口&#xff08;Viewport&#xff09;交叉状态&#xff08;即是否进入或离开视口&#xff09;的接口。在 IntersectionObserver 出现之前&#xff0c;开发者通常需要通过监听 scroll 事件或使用…...

AI壁纸进阶宝典:让创作效率与质量飞速提升的法门

在数字化创意浪潮席卷的当下&#xff0c;AI壁纸以其独特魅力和无限可能&#xff0c;吸引了众多设计爱好者和专业设计师的目光。然而&#xff0c;如何在众多创作者中脱颖而出&#xff0c;打造令人惊艳的AI壁纸呢&#xff1f;本文将从基础到进阶&#xff0c;全方位剖析AI壁纸创作…...

Releases(发布) 和 版本管理 是两个紧密相关的概念

在软件开发和维护中,Releases(发布) 和 版本管理 是两个紧密相关的概念,特别是在开源项目或企业软件开发中。 1. Releases(发布) Release 是指软件的一个正式发布版本,通常经过开发、测试、修复 Bug,并被认为是足够稳定和可用于生产环境的版本。 主要特点 里程碑:通…...

从零开始用react + tailwindcss + express + mongodb实现一个聊天程序(十二) socketio 消息处理

1.后端 在message.controller.js中 在sendMessage方法中 每当我们发送消息 需要socketio把这个消息转发给 接收人 加入转发逻辑 // 把消息发给指定的用户的socket const receiverSocketId getReceiverSocketId(receiverId); if(receiverSocketId) { io.to(receiverSocket…...

【运维篇】KubeSphere-02(经验汇总)

一、使用建议 1.对于数据库、对像存储比较重的要不能丢失&#xff0c;有异地存储备份需求的有状态服务&#xff0c;不建议采用k8s进行部署&#xff0c;会导致运维难度更大。 2.对于中间件如redis、MQ、harbor、seata、nacos、zookeeper可采用k8s部署。 3.对于无状态服务tomc…...

8.大模型微调学习案例:基于 Hugging Face、8位量化与 LoRA 适配器的方案

文章目录 一、引言二、数据预处理与构建数据集2.1 加载 JSON 数据2.2 构造训练样本2.3 构建 Hugging Face 数据集与分词2.4 添加标签字段2.5 划分训练集与验证集 三、模型加载、量化与适配器配置3.1 配置设备与显存使用3.2 配置 8 位量化3.3 加载预训练模型并映射设备3.4 为 8 …...

用分页干掉显存浪费!聊聊VLLM的PagedAttention

不知道你在部署模型的时候&#xff0c;有没有经历过这样的抓狂时刻——跑大模型时显卡显存明明没有占满&#xff0c;程序却报错OOM&#xff08;内存不足&#xff09;&#xff1f;表面看是显存不足&#xff0c;背后其实是KVCache碎片化和重复存储这个"隐形刺客"在作祟…...

Python——地图可视化

分级统计地图&#xff1a;一般使用同一种颜色表示一个区域的属性&#xff0c;常用与选举和人口普查数据的可视化。对于颜色的选择可以使用单色渐变系、双向渐变系、完整色谱变化。 使用这类颜色渐变进行填充存在的问题&#xff1a; 使用这类方法适用于数据密集分布的区域&#…...

3.8【Q】cv

这个draw_line函数的逻辑和功能是什么&#xff1f;代码思路是什么&#xff1f;怎么写的&#xff1f; 这个t是什么&#xff1f;t.v[0]和t.v[1],[2]又是什么&#xff1f; void rst::rasterizer::draw(rst::pos_buf_id pos_buffer, rst::ind_buf_id ind_buffer, rst::Primitive ty…...

LeetCode 热题 100_字符串解码(71_394_中等_C++)(栈)

LeetCode 热题 100_字符串解码&#xff08;71_394&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;栈&#xff09;&#xff1a; 代码实现代码实现&#xff08;栈&#xff09;&#xff1a;以思路一为例进行调…...

基于大模型预测的新型隐球菌脑膜炎综合诊疗研究报告

目录 一、引言 1.1 研究背景与目的 1.2 研究现状综述 二、大模型预测原理及数据基础 2.1 大模型技术简介 2.2 数据收集与预处理 2.3 模型训练与验证 三、术前风险预测与准备 3.1 病情评估指标确定 3.2 手术方案制定 3.3 麻醉方案规划 四、术中监测与应对 4.1 实时…...

SQL Server核心知识总结

SQL Server核心知识总结 &#x1f3af; 本文总结了SQL Server核心知识点,每个主题都提供实际可运行的示例代码。 一、SQL Server基础精要 1. 数据库核心操作 -- 1. 创建数据库&#xff08;核心配置&#xff09; CREATE DATABASE 学生管理系统 ON PRIMARY (NAME 学生管理系统…...

浏览器WEB播放RTSP

注意&#xff1a;浏览器不能直接播放RTSP&#xff0c;必须转换后才能播放。这一点所有的播放都是如此。 参考 https://github.com/kyriesent/node-rtsp-stream GitHub - phoboslab/jsmpeg: MPEG1 Video Decoder in JavaScript 相关文件方便下载 https://download.csdn.net…...

登录固定账号和密码:

接口文档 【apifox】面试宝典 个人中心-保存用户数据信息 - 教学练测项目-面试宝典-鸿蒙 登录固定账号和密码&#xff1a; 账号&#xff1a;hmheima 密码&#xff1a;Hmheima%123 UI设计稿 【腾讯 CoDesign】面试宝典 CoDesign - 腾讯自研设计协作平台 访问密码&#xff1…...

深度学习/强化学习调参技巧

深度调优策略 1. 学习率调整 技巧&#xff1a;学习率是最重要的超参数之一。过大可能导致训练不稳定&#xff0c;过小则收敛速度慢。可以使用学习率衰减&#xff08;Learning Rate Decay&#xff09;或自适应学习率方法&#xff08;如Adam、RMSprop&#xff09;来动态调整学习…...

Java常用正则表达式(身份证号、邮箱、手机号)格式校验

目录 身份证号的正则表达式 代码解释 正则表达式 方法 isValidIDCard 注意事项 校验邮箱的正则表达式 代码解释 正则表达式 方法 isValidEmail 注意事项 手机号的正则表达式 中国大陆手机号校验&#xff08;支持空字符串&#xff09; 代码解释 通用手机号校验&am…...

1.12.信息系统的分类【ES】

专家系统&#xff08;ES&#xff09;技术架构深度解析 一、ES核心定义 &#x1f9e0; 智能决策中枢 由三大核心能力构建的领域专家模拟系统&#xff1a; 存储专家级领域知识&#xff08;10^4规则量级&#xff09;支持不确定性推理&#xff08;置信度>85%&#xff09;动态…...

c语言程序设计--(结构体、共用体)冲刺考研复试中的面试问答,来看看我是怎么回答的吧!

结构体 1、谈谈你对结构体的理解。 答&#xff0c;首先在结构的基础知识上&#xff0c;结构是一些值的集合&#xff0c;这些值称为成员变量结构的每个成员可以是不同类型的变量。结构体其实就是把&#xff08;一些单一类型的数据&#xff09;不同类型的数据组合在一起的做法便…...

闭包:JavaScript 中的隐形大杀器

你可能已经在很多地方听说过闭包这个词&#xff0c;尤其是涉及到 JavaScript 的作用域和异步操作时。闭包是 JavaScript 中非常核心的概念&#xff0c;然而它又非常容易让开发者感到困惑。今天我们就来深入剖析闭包&#xff0c;帮助你真正理解它的工作原理&#xff0c;以及如何…...

分布式锁—7.Curator的分布式锁一

大纲 1.Curator的可重入锁的源码 2.Curator的非可重入锁的源码 3.Curator的可重入读写锁的源码 4.Curator的MultiLock源码 5.Curator的Semaphore源码 1.Curator的可重入锁的源码 (1)InterProcessMutex获取分布式锁 (2)InterProcessMutex的初始化 (3)InterProcessMutex.…...

linux---天气爬虫

代码概述 这段代码实现了一个天气查询系统&#xff0c;支持实时天气、未来天气和历史天气查询。用户可以通过终端菜单选择查询类型&#xff0c;并输入城市名称来获取相应的天气信息。程序通过 TCP 连接发送 HTTP 请求&#xff0c;并解析返回的 JSON 数据来展示天气信息。 #in…...

C++蓝桥杯基础篇(九)

片头 嗨&#xff01;小伙伴们&#xff0c;大家好~ 今天我们将学习蓝桥杯基础篇&#xff08;十&#xff09;&#xff0c;学习函数相关知识&#xff0c;准备好了吗&#xff1f;咱们开始咯&#xff01; 一、函数基础 一个典型的函数定义包括以下部分&#xff1a;返回类型、函数名…...

Java Spring MVC (2)

常见的Request Controller 和 Response Controller 的区别 用餐厅点餐来理解 想象你去一家餐厅吃饭&#xff1a; Request Controller&#xff08;接单员&#xff09;&#xff1a;负责处理你的点餐请求&#xff0c;记录你的口味、桌号等信息。Response Controller&#xff08…...

Elasticsearch:使用 BigQuery 提取数据

作者&#xff1a;来自 Elastic Jeffrey Rengifo 了解如何使用 Python 在 Elasticsearch 中索引和搜索 Google BigQuery 数据。 BigQuery 是 Google 的一个平台&#xff0c;允许你将来自不同来源和服务的数据集中到一个存储库中。它还支持数据分析&#xff0c;并可使用生成式 AI…...

接口-菜品分页查询

业务内容 页面上菜品根据菜品名称、菜品分类、售卖状态三个字段进行分页查询。 在请求参数中携带了菜品名称、菜品分类、售卖状态三个字段参数。 返回PageResult类型的实体。 注意&#xff1a;在返回数据中在records下有个categoryName&#xff0c;这个字段的内容在category…...

springboot3 RestClient、HTTP 客户端区别

1 RestClient使用 RestClient 是 Spring 6.1 M2 中引入的同步 HTTP 客户端&#xff0c;它取代了 RestTemplate。同步 HTTP 客户端以阻塞方式发送和接收 HTTP 请求和响应&#xff0c;这意味着它会等待每个请求完成后才继续下一个请求。本文将带你了解 RestClient 的功能以及它与…...

自我训练模型:通往未来的必经之路?

摘要 在探讨是否唯有通过自我训练模型才能掌握未来的问题时&#xff0c;文章强调了底层技术的重要性。当前&#xff0c;许多人倾向于关注应用层的便捷性&#xff0c;却忽视了支撑这一切的根本——底层技术。将模型简单视为产品是一种短视行为&#xff0c;长远来看&#xff0c;理…...

RuoYi框架添加自己的模块(学生管理系统CRUD)

RuoYi框架添加自己的模块&#xff08;学生管理系统&#xff09; 框架顺利运行 首先肯定要顺利运行框架了&#xff0c;这个我不多说了 设计数据库表 在ry数据库中添加表tb_student 表字段如图所示 如图所示 注意id字段是自增的 注释部分是后面成功后前端要展示的部分 导入…...

linux查看python版本

1.查看Linux是否安装python yum list all | grep python 2.Linux安装python yum install python 3.Linux查看python版本 python -V...

算法题(89):单项链表

审题&#xff1a; 本题需要我们实现一个可以执行三个指令的数据结构来解决这里的问题 思路&#xff1a; 方法一&#xff1a;利用数组模拟链表 由于这里涉及插入删除操作&#xff0c;所以我们不能使用数组结构存储数据&#xff0c;这样子会超时&#xff0c;所以我们就利用数组来…...