Cookie和Session

Cookie和Session


今天来聊聊Cookie和Session。这俩货平时非常低调,不是很出挑。Session相对来说稍微会好一些,Java程序猿们日常多少会接触点,用Session域存取对象啥的。但这个Cookie,估计大部分Java程序猿,平时不要说用到,连这个单词都很少敲。而且Cookie说起来吧,位置有点特殊。前端的人觉得它和后端沾亲带故,后端的人觉得这玩意就是前端搞的,关我屁事。如此一来,谁都没把它当回事儿。

img

主要内容介绍:

  • 一个问题
  • 什么是会话
  • 认识Cookie
  • Cookie的两种类型
  • 认识Session
  • Session序列化
  • Session钝化与活化
  • 分布式项目的Session共享

一个问题

这是7月份的某一天,一位朋友和我的对话:

img习以为常的知识点,真要仔细深究时,才发现自己其实不懂

想必大家都有这样的经验:

  • 登录京东,选了iphone x放入购物车后关闭浏览器。再次打开时,发现又要重新登录。
  • 登录淘宝,在一个页面逗留了很久,终于决定要买了,却提示你重新登录。

这一切,仿佛是服务器在“监视”着浏览器的行为:

  • 咦?浏览器被关了,应该是不买了。我也把登录状态掐了吧。
  • 这个页面30分钟没动静了,用户可能已经离开。为了防止路过的人使用该账号,我把它的登录状态掐了吧。

但我们知道服务器是不可能知道客户端发生了什么的,它只是被动地响应客户端的请求,不会主动搭理客户端。况且全中国那么多用户,相比起来,京东淘宝那几台服务器在数量上简直微不足道,不可能监控得过来。

既然服务器并不知道浏览器端发生了什么,那上面的两种情形,是如何做到的呢?

答案就是:会话跟踪。

等我们完全了解会话跟踪技术后,再回头解答上面的疑惑。


什么是会话

学习Cookie和Session,最难的并不是如何使用,毕竟底层操作都已经封装好了,调用API即可。最难的还是对于【会话】这个概念的理解。我刚开始学的时候就挺晕。

会话一词,指聚谈、对话。最早出现在欧阳修 《与吴正肃公书》一文中:“前约临行少留会话,终不克遂,至今为恨。”基本可以理解为就是一次对话。

那么在计算机的世界中,会话又是什么?大家可能早就听过这句话:

从打开一个浏览器访问某个站点,到关闭这个浏览器的整个过程,称为一次会话。

这句话固然精辟,但是很明显不是说给初学者听的。对于不清楚底层发生了什么的人,听完之后可能反而冒出更多疑问:

  • 为什么浏览器关闭代表一次会话结束?
  • 关闭时发生了什么操作导致会话结束?
  • 为什么那个操作执行意味着会话结束?

这三个问题,其实归结起来是一个问题:什么是会话。只要知道了什么是会话,一切迎刃而解。

首先,为什么需要会话机制?

会话机制最主要的目的是帮助服务器记住客户端状态(标识用户,跟踪状态)。目前,客户端与服务器的通讯都是通过HTTP协议。而HTTP是一种无状态协议。什么叫无状态?就是客户端第二次来访时,服务器根本不知道这个客户端以前是否来访过,Web服务器本身不能识别出哪些请求是同一个浏览器发出的。即:浏览器的每一次请求都是完全孤立的

imgHTTP无状态协议,每一个请求,在服务器端看来都是各自独立的

为了更好的用户体验(比如实现购物车),就有了会话机制。有了它,服务器可以记住并区分客户端,把同一个客户端的操作归类在一起。否则我用我的电脑在京东买了东西,你用你的电脑打开京东却发现购物车里塞满了商品,你乐意吗?

img有了会话机制,服务器可以区分不同客户端发来的请求

这里再举一个例子:假设我们注册了一个论坛,因为HTTP是无状态的,服务器无法辨认访问者,那么即使是同一个网站的不同页面,服务器都必须强制访问者重新登录一次,以确定你是合法用户…

所以,会话机制显得很有必要。由于某些原因,HTTP必须保持无状态,所以会话跟踪技术就成了对HTTP无状态协议的一种扩展。

其次,如何定义一个会话?

基于上面的分析(会话是为了唯一标识一个用户并记录其状态),既然一个会话可以帮助服务器断定一个客户端,那么反向推导得到的结论就是:当服务器无法断定客户端时,一次会话就结束了!

服务器什么情况下无法断定一个客户端?无非两种情况:

  • 服务器不行了(session失效)
  • 客户端不行了(cookie失效)

又基于上面分析,可以总结出会话的基本原则:

  • 双方共存(cookie与session同在)

什么意思?先举个生活中的例子:我给隔壁老王打电话。这是我和老王之间的会话。因为满足上面基本原则:

  • 我和老王在交互信息(我和老王的之间的会话,双方共存)

什么时候我和老王的会话结束呢?

  • 老王或我的手机没电了(双方不共存)

这是现实的例子,很容易理解,而且会话这个概念本来就来自现实生活。但是客户端和服务器该如何模拟这样的会话呢?


认识Cookie

在Java的世界里,会话跟踪常用的有两种技术:Cookie和Session,并且Session底层依赖于Cookie。

当然啦,如果Cookie被禁用,也可以通过URL重写等手段实现,但这里不涉及。因为只要你真的明白这篇文章所讲的全部内容,URL重写理解起来很简单。

我希望大家在看接下来的文字时,要始终提醒自己会话机制的目的是什么:标识用户,跟踪状态。

先来看一下生活中的一个场景:你约人去下馆子。那个馆子远近闻名,每天都要接待很多顾客,座无虚席。于是你打算早一点去店里预定位置。店门口有一台取号机。稍早些到店里的顾客,店员会用取号机打印一张票据给客人,上面记录了一些信息。此时距离约定就餐时间还有段时间,于是你打算去附近西湖逛逛。当你逛完西湖回来时,把票据交给店员,店员一看票据:bravo1988,18:00,69桌。于是把你领到指定位置就餐。

img

所谓Cookie,可以通俗地理解为上面取号机给的票据。

从编程的角度来讲,Cookie其实是一份小数据,是服务器响应给客户端,并且存储在客户端的一份小数据。**下次客户端访问服务器时,会自动带上这个Cookie。**服务器通过Cookie就可以区分客户端。

服务器端如何将Cookie发给浏览器呢?

img最简单的代码:服务器端new一个cookie,通过response的addCookie方法响应给浏览器

img浏览器访问SendCookieServlet,Servlet通过Response向客户端发送Cookie

response.addCookie()后,实际服务器响应给浏览器时发生了什么?

img

下一步,Tomcat在真正给浏览器发送HTTP响应前,会从Response对象中取出HTTP相关的信息(比如响应体中HTML,响应头等),其中就包括cookie。

HTTP响应头都是以键值对的形式展现,一般来说都是一键一值,比如:

Content-Length: 0
Date: Sun, 09 Sep 2018 10:29:27 GMT
Server: Apache-Coyote/1.1

也有一键多值的,比如Set-Cookie这个响应头:

Set-Cookie: name=bravo1988
Set-Cookie: time=6pm
Set-Cookie: table=69

服务器端(上面编写的Servlet)发给客户端的Cookie最终以HTTP响应形式发送,也就是Set-Cookie响应头。

浏览器接收到这些响应头后,会把它们作为Cookie文件存在客户端。当我们第二次请求同一个服务器,浏览器在发送HTTP请求时,会带上这些Cookie。

img

示意图:

img

因为现在客户端已经有Cookie了,所以往后每次访问该服务器,都会带上这个Cookie。那么,如何在服务器端得到客户端带来的Cookie呢?

img带上访问SendCookieServlet时得到Cookie

img注意,虽然请求头中cookie的内容是name=brava1988;time=6pm;table=69,但是并不需要我们在Servlet中用分号切割字符串。只要调用request.getCookies();即可得到cookie数组(自动切割成数组)

画个简略图就是:

img

但是,上面SendCookieServlet的代码有个问题!

**此种方式传给客户端的Cookie会随着客户端(浏览器)的关闭而消失。**这也就是为什么你登录京东,添加商品进购物车,关闭浏览器重新打开时却要你再次登录的原因。

因为你第一次访问京东登录后,京东服务器给了你的浏览器一个Cookie。当你关闭浏览器时,这个Cookie消失了。所以重新打开京东主页时,服务器找不到证明你登录过的Cookie,就让你重新登录。

img服务器端找不到cookie,不认识请求用户,强制跳转登录页面


Cookie的两种类型

其实Cookie可以分为两种:

  • 会话Cookie (Session Cookie)
  • 持久性Cookie (Persistent Cookie)

上面代码中,服务器向浏览器响应的Cookie就是会话Cookie。会话Cookie被保存在浏览器的内存中,当浏览器关闭时,内存被释放,内存中的Cookie自然也就烟消云散。

这样太麻烦了,关闭浏览器引发Cookie消失,下次还要重新登录。能不能向客户端响应持久性Cookie呢?只要设置Cookie的持久化时间即可!

img

imgSet-Cookie的内容中,多了一个Expires,它代表过期时间,比如现在时间是12:00,你设置MaxAge=10*60,则过期时间就是12:10(注意,显示的时间不是北京时间,我们在东八区)

img

小结一下:

  • 不设置MaxAge,默认响应会话Cookie,存在浏览器内存。Cookie随浏览器关闭而消失
  • 设置MaxAge>0,响应持久性Cookie,会存在电脑硬盘的特定文件夹下(浏览器自定义的)
  • 设置特定Cookie的MaxAge<=0,则会删除已经存在客户端的此Cookie

img响应到客户端后,客户端会删除名字为name的Cookie

至此,其实我们已经可以解释下面的问题:

  • 为什么浏览器关闭代表一次会话结束?
  • 关闭时发生了什么操作导致会话结束?
  • 为什么那个操作执行意味着会话结束?

一般,响应给客户端的Cookie都是会话Cookie(不设置MaxAge),是存在浏览器内存中的。所以关闭浏览器后,内存中Cookie就消失了。Cookie消失,则下次请求服务器时,请求头中不存在代表用户信息的Cookie(唯一标识用户,表示其状态),那么浏览器就无法识别请求的用户。

根据我们上面反向推导的结论:当服务器无法断定客户端时,一次会话就结束了!

img每家浏览器厂商持久性Cookie存储的位置是不同的

而对于每种不同的浏览器,不管是会话Cookie还是持久性Cookie,都是相对独立的。所以,当你用Chrome登录京东,你再用IE打开京东时,仍旧要登录,因为你没有带去标识用户信息的Cookie(不论是会话Cookie还是持久性Cookie,IE都拿不到Chrome的)。

注:这里没有讲Cookie路径,请大家自行了解。
Cookie的路径


认识Session

有了Cookie,似乎已经能解决问题,为什么还需要Session?原因似乎可以列举很多,有可能是出于安全性和传输效率的考虑。首先,Cookie是存在客户端的,如果保存了敏感信息,会被其他用户看到。其次,如果信息太多,可能影响传输效率。但这些都是我由果推因得到的。总之,特么Session会话技术都出来,肯定有它的道理。

相比较Cookie存在客户端,Session则是服务端的东西。其本质上类似于一个大Map,里面的内容,以键值对的形式存储。

这回,我们不再把"name=brava1988;time=6pm;table=69"这样的数据作为Cookie放在请求头/响应头里传来传去了,而是只给客户端传一个JSESSIONID(其实也是一个Cookie)!此时,真正的数据存在服务器端的Session中,Cookie中只是存了Session的id,即JSESSIONID。下次访问该网站时,把JSESSIONID带上,即可在服务器端找到对应的Session,也相当于“带去”了用户信息。

img假设现在服务端有当个Session,根据JSESSIONID获取对应的Session

img同样的,既然返回的JSESSIONID也是一个Cookie,那么也分为会话Cookie和持久性Cookie,可以通过设置MaxAge更改

另外要注意的是,Session有个默认最大不活动时间:30分钟(可在配置文件中修改数值)。也就是说,创建Session并返回JSESSIONID给客户端后,如果30分钟内你没有再次访问,即使你下次再带着JSESSIONID来,服务端也找不到对应ID的Session了,因为它已经被销毁。此时你必须重新登录。

现在,我请大家重新仔细看一下上面设置JSESSIONID到Cookie的代码。

其实,只要你在服务器端创建了Session,即使不写addCookie(“JSESSIONID”, id),JSESSIONID仍会被作为Cookie返回。

img注意,这次我没有addCookie(),只是简单打印,用于和响应信息作对比

img结果服务器默认new一个Cookie,将刚才创建的Session的JSESSIONID返回。默认是会话Cookie,浏览器关闭就消失!


Session序列化

所谓Session序列化,其实是一个默认行为。它存在的意义在于:比如现在有成千上万个用户在线,用户登录信息都在各自的Session中。当服务器不得不重启时,为了不让当前保存在服务器的Session丢失,服务器会将当前内存中的Session序列化到磁盘中,等重启完毕,又重新读取回内存。这些操作浏览器端用户根本感知不到,因为session还在,他不用重新登录。

以Tomcat为例,服务器的Session都会被保存在work目录的对应项目下。比如我的电脑,就是:F:\develop\Tomcat7\work\Catalina\localhost\CookieTest

img

关闭服务器时,当前内存中的session会被序列化在磁盘中,变成一个叫SESSIONS.ser的文件

当服务器重启时,该SESSIONS.ser文件又会被重新读取,该过程称为反序列化。读取后文件会从磁盘消失。

img


Session的钝化和活化

自从改用Session后,由于Session都存在服务器端,当在线用户过多时,会导致Session猛增,无形中加大了服务器的内存负担。于是,服务器有个机制:如果一个Session长时间无人访问,为了减少内存占用,会被钝化到磁盘上。

也就是说,Session序列化不仅仅是服务器关闭时才发生,当一个Session长时间不活动,也是有可能被序列化到磁盘中。当该Session再次被访问时,才会被反序列化。这就是Session的钝化和活化。

可以在Tomcat的conf目录下的context.xml中配置(对所有项目生效)

img

img与服务器关闭时Session的序列化不同的是:1.每个Session单独一个文件,而不是SESSIONS.ser。2.即使Session活化回到内存,磁盘的文件也不消失

还有个问题需要解决:Session被序列化了,存在Session中的值怎么办?比如我之前有这么一步操作:

HttpSession session= request.getSession();
session.setAttribute("user", new User("bravo1988", 26));

此时Session中有一个User对象,那么User对象去哪了?答案是,User从内存中消失,无法随Session一起序列化到磁盘。如果希望Session中的对象也一起序列化到磁盘,该对象必须实现序列化接口。

img

其实也很好理解,有一个大气球(Session),里面有很多小气球(对象),现在大气球要放气,里面的小气球必须也放气。


分布式项目的Session共享

改天补补完:1.为什么需要Session共享 2.画图解释(memcached/Redis、对象序列化)


最后回答开头的问题:

之所以服务器能“知道”浏览器关了,是因为下次再来的时候,并没有带来上次给它的cookie。**你无法证明你还是你。**其实不一定是浏览器关了,也可能是清缓存了。**总之,会话已经结束。**怎么说呢,笼统的讲,也可以说浏览器确实给服务器“发消息了”,毕竟HTTP请求头中没有原来的Cookie,也就相当于告诉它这件事了。

img


参考资料:

1.传智播客崔希凡老师JavaWeb day20(监听器)

2.黑马32期郝强勇老师JavaWeb day16(Cookie&Session)t

3.知乎话题:COOKIE和SESSION有什么区别?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值