Cookie与Session异同
Cookie 和 Session 都为了用来保存状态信息,都是保存客户端状态的机制,它们都是为了解决HTTP无状态的问题而所做的努力。
Cookie机制
简单地说,Cookie 就是浏览器储存在用户电脑上的一小段文本文件。Cookie 是纯文本格式,不包含任何可执行的代码。 一个 Web 页面或服务器告知浏览器按照一定规范来储存这些信息,并在随后的请求中将这些信息发送至服务器,Web 服务器就可以使用这些信息来识别不同的用户。 大多数需要登录的网站在用户验证成功之后都会设置一个 Cookie,只要这个 Cookie 存在并可以,用户就可以自由浏览这个网站的任意页面。
Cookie 会被浏览器自动删除,通常存在以下几种原因:
- 会话 Cooke (Session Cookie) 在会话结束时(浏览器关闭)会被删除
- 持久化 Cookie(Persistent Cookie)在到达失效日期时会被删除
- 如果浏览器中的 Cookie 数量达到限制,那么 Cookie 会被删除为新建的 Cookie 创建空间。
大多数浏览器支持最大为 4096 字节的 Cookie,只能存储ASCII码。由于这限制了 Cookie 的大小,最好用 Cookie 来存储少量数据,或者存储用户 ID 之类的标识符。
用户 ID 随后便可用于标识用户,以及从数据库或其他数据源中读取用户信息。 浏览器还限制站点可以在用户计算机上存储的 Cookie 的数量。
大多数浏览器只允许每个站点存储 20 个 Cookie;如果试图存储更多 Cookie,则最旧的 Cookie 便会被丢弃。有些浏览器还会对它们将接受的来自所有站点的 Cookie 总数作出绝对限制,通常为 300 个。
使用 Cookie 的缺点:
- 不良站点用 Cookie 收集用户隐私信息(划重点,万恶的资本主义);
- Cookie窃取:黑客以可以通过窃取用户的cookie来模拟用户的请求行为。(跨站脚本攻击XSS)
Session 机制
Session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个Session标识(Session id)。
如果已包含一个SessionID 则说明以前已经为此客户端创建过Session,服务器就按照SessionID把这个 Session检索出来使用(如果检索不到,可能会新建一个)。 如果客户端请求不包含SessionID,则为此客户端创建一个Session并且生成一个与此Session相关联的SessionID,SessionID的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个SessionID将被在本次响应中返回给客户端保存。
具体实现方式:
- Cookie方式
服务器给每个Session分配一个唯一的JSESSIONID,并通过存储在Cookie里发送给客户端。
当客户端发起新的请求的时候,将在Cookie头中携带这个JSESSIONID,这样服务器能够找到这个客户端对应的Session。
- URL回写
服务器在发送给浏览器页面的所有链接中都携带JSESSIONID的参数,这样客户端点击任何一个链接都会把JSESSIONID带回服务器。如果直接在浏览器输入服务端资源的url来请求该资源,那么Session是匹配不到的。
Web 缓存:
WEB缓存(cache)位于Web服务器和客户端之间,缓存机制会根据请求保存输出内容的副本,例如html页面,图片,文件,当下一个请求来到的时候:如果是相同的URL,缓存直接使用副本响应访问请求,而不是向源服务器再次发送请求。
主要分三种情况:
- 未找到缓存(黑色线):当没有找到缓存时,说明本地并没有这些数据,这种情况一般发生在我们首次访问网站,或者以前访问过,但是清除过缓存后。
浏览器就会先访问服务器,然后把服务器上的内容取回来,内容取回来以后,就要根据情况来决定是否要保留到缓存中了。
- 缓存未过期(蓝色线):缓存未过期,指的是本地缓存没有过期,不需要访问服务器了,直接就可以拿本地的缓存作为响应在本地使用了。这样节省了不少网络成本,提高了用户体验过。
- 缓存已过期(红色线):当满足过期的条件时,会向服务器发送请求,发送的请求一般都会进行一个验证,目的是虽然缓存文档过期了,但是文档内容不一定会有什么改变,所以服务器返回的也许是一个新的文档,这时候的HTTP状态码是200,或者返回的只是一个最新的时间戳和304状态码。
缓存过期后,有两种方法来判定服务端的文件有没有更新。
第一种在上一次服务端告诉客户端约定的有效期的同时,告诉客户端该文件最后修改的时间,当再次试图从服务端下载该文件的时候,check下该文件有没有更新(对比最后修改时间),如果没有,则读取缓存.
第二种方式是在上一次服务端告诉客户端约定有效期的同时,同时告诉客户端该文件的版本号,当服务端文件更新的时候,改变版本号,再次发送请求的时候check一下版本号是否一致就行了,如一致,则可直接读取缓存。
浏览器是依靠请求和响应中的的头信息来控制缓存的,如下:
- Expires与Cache-Control:服务端用来约定和客户端的有效时间的。
Expires规定了缓存失效时间(Date为当前时间),而Cache-Control
的max-age规定了缓存有效时间(2552s)。
Expires是HTTP1.0的东西,而Cache-Control
是HTTP1.1的,规定如果max-age
和Expires同时存在,前者优先级高于后者。
- Last-Modified/If-Modified-Since缓存过期后,check服务端文件是否更新的第一种方式。
- ETag/If-None-Match:缓存过期时check服务端文件是否更新的第二种方式。
实际上ETag并不是文件的版本号,而是一串可以代表该文件唯一的字符串,当客户端发现和服务器约定的直接读取缓存的时间过了,就在请求中发送If-None-Match选项,值即为上次请求后响应头的ETag值. 该值在服务端和服务端代表该文件唯一的字符串对比(如果服务端该文件改变了,该值就会变),如果相同,则相应HTTP304,客户端直接读取缓存,如果不相同,HTTP200,下载正确的数据,更新ETag值。
当然并不是所有请求都能被缓存。无法被浏览器缓存的请求:
- HTTP信息头中包含
Cache-Control:no-cache,pragma:no-cache(HTTP1.0)
,或Cache-Control:max-age=0
等告诉浏览器不用缓存的请求 - 需要根据Cookie,认证信息等决定输入内容的动态请求是不能被缓存的
- POST请求无法被缓存
浏览器缓存过程还和用户行为有关。譬如先打开一个主页有个jquery的请求(假设访问后会缓存下来)。
接着如果直接在地址栏输入 jquery 地址,然后回车,响应HTTP200(from cache),因为有效期还没过直接读取的缓存;如果ctrl+r
进行刷新,则会相应HTTP304(Not Modified),虽然还是读取的本地缓存,但是多了一次服务端的请求;而如果是ctrl+shift+r强刷,则会直接从服务器下载新的文件,响应HTTP200。
Client如何实现长连接
TCP协议的KeepAlive
机制与HeartBeat
心跳包
- HeartBeat心跳包
很多应用层协议都有HeartBeat机制,通常是客户端每隔一小段时间向服务器发送一个数据包,通知服务器自己仍然在线,并传输一些可能必要的数据。使用心跳包的典型协议是IM,比如QQ/MSN/飞信等协议。
心跳包之所以叫心跳包是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。
在TCP的机制里面,本身是存在有心跳包的机制的,也就是TCP的选项:SO_KEEPALIVE。系统默认是设置的2小时的心跳频率。但是它检查不到机器断电、网线拔出、防火墙这些断线。而且逻辑层处理断线可能也不是那么好处理。一般,如果只是用于保活还是可以的。
心跳包一般来说都是在逻辑层发送空的echo包来实现的。下一个定时器,在一定时间间隔下发送一个空包给客户端,然后客户端反馈一个同样的空包回来,服务器如果在一定时间内收不到客户端发送过来的反馈包,那就只有认定说掉线了。
其实,要判定掉线,只需要send或者recv一下,如果结果为零,则为掉线。但是,在长连接下,有可能很长一段时间都没有数据往来。
理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的。更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉。在这个时候,就需要我们的心跳包了,用于维持长连接,保活。
在获知了断线之后,服务器逻辑可能需要做一些事情,比如断线后的数据清理呀,重新连接呀……当然,这个自然是要由逻辑层根据需求去做了。
总的来说,心跳包主要也就是用于长连接的保活和断线处理。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒。
- TCP协议的
KeepAlive
机制
TCP的IP传输层的两个主要协议是UDP和TCP,其中UDP是无连接的、面向packet的,而TCP协议是有连接、面向流的协议。
TCP的KeepAlive
机制,首先它貌似默认是不打开的,要用setsockopt
将SOL_SOCKET.SO_KEEPALIVE
设置为1才是打开,并且可以设置三个参数tcp_keepalive_time/tcp_keepalive_probes/tcp_keepalive_intvl
,分别表示连接闲置多久开始发keepalive
的ack包、发几个ack包不回复才当对方死了、两个ack包之间间隔多.
在测试的时候用Ubuntu Server 10.04
下面默认值是7200秒(2个小时,要不要这么蛋疼啊!)、9次、75秒。
于是连接就了有一个超时时间窗口,如果连接之间没有通信,这个时间窗口会逐渐减小,当它减小到零的时候,TCP协议会向对方发一个带有ACK标志的空数据包(KeepAlive探针),对方在收到ACK包以后,如果连接一切正常,应该回复一个ACK;如果连接出现错误了(例如对方重启了,连接状态丢失),则应当回复一个RST;如果对方没有回复,服务器每隔intvl的时间再发ACK,如果连续probes个包都被无视了,说明连接被断开了。
在http早期,每个http请求都要求打开一个tpc socket连接,并且使用一次之后就断开这个tcp连接。
使用keep-alive
可以改善这种状态,即在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用keep-alive
机制,可以减少tcp连接建立次数,也意味着可以减少TIME_WAIT
状态连接,以此提高性能和提高httpd服务器的吞吐率(更少的tcp连接意味着更少的系统内核调用,socket的accept()
和close()
调用)。
但是,keep-alive
并不是免费的午餐,长时间的tcp连接容易导致系统资源无效占用。配置不当的keep-alive
,有时比重复利用连接带来的损失还更大。所以,正确地设置keep-alive timeout
时间非常重要。
使用http keep-alvie
,可以减少服务端TIME_WAIT
数量(因为由服务端httpd守护进程主动关闭连接)。道理很简单,相较而言,启用keep-alive
,建立的tcp连接更少了,自然要被关闭的tcp连接也相应更少了。
使用启用keepalive
的不同。另外,http keepalive
是客户端浏览器与服务端httpd守护进程协作的结果,所以,我们另外安排篇幅介绍不同浏览器的各种情况对keep-alive
的利用。