
2.7.2 网络协议
国际标准化组织(International Standard Organization,ISO)公布了开放系统互连参考模型(OSI/RM)。OSI/RM是一种分层的体系结构,参考模型共有7层。作为Internet的核心协议,TCP/IP(Transmission Control Protocol/Internet Protocol)是个协议族,包含多种协议。分层的基本想法是每一层都在它的下层所提供的服务基础上提供更高级的增值服务,而最高层提供能运行分布式应用程序的服务。TCP/IP协议与ISO协议的对比如表2-2所示。
表2-2 TCP/IP协议与ISO协议的对比
1.TCP
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接(连接导向)的、基于字节流的、可靠传输层通信协议。TCP将用户数据打包成报文段,并在发送后启动一个定时器,另一端对收到的数据进行确认、对失序的数据重新排序并丢弃重复数据。
TCP的特点如下所示。
·TCP是面向连接的传输层协议。
·每一条TCP连接只能有两个端点,即只能是点对点的。
·TCP提供可靠交付的服务。
·TCP提供全双工通信。数据在两个方向上独立地进行传输。因此,连接的每一端都必须保持每个方向上的传输数据序号。
·面向字节流。TCP是一种流协议,意味着数据是以字节流的形式传递给接收者,它不具备固有的“报文”或“报文边界”的概念。如果应用进程传送到TCP的数据块太长,TCP就可以把它划分短一些,分几次传送,而如果数据块太短,那么也可以一次传送。
(1)TCP头格式
如图2-9所示,TCP标志位(flag)的每个标志长度均为1bit。
·CWR:压缩,TCP标志值为0x80。
·ECE:拥塞,0x40。
·URG:紧急,0x20。URG=1表示报文段中有紧急数据,应尽快传送。
·ACK:确认,0x10。ACK=1代表这是一个确认的TCP包,若取值0则不是确认包。
·PSH:推送,0x08。当发送端PSH=1,接收端会尽快交付给应用进程。
图2-9 TCP头格式
·RST:复位,0x04。RST=1表明TCP连接中出现严重差错,必须再重新建立连接。
·SYN:同步,0x02。在建立连接时用来同步序号。SYN=1、ACK=0表示一个连接请求报文段。SYN=1、ACK=1表示同意建立连接。
·FIN:终止,0x01。FIN=1表明此报文段的发送端的数据已经发送完毕,并要求释放传输连接。
(2)TCP协议中的三次握手和四次挥手
TCP协议中的握手与挥手过程如图2-10所示。
·Seq:发送方当前报文的顺序号码。
·ack:发送方期望对方在下次返回报文中给回的Seq。
1)建立连接需要三次握手。
第一次握手:客户端(client)向服务端(server)发送连接请求包,标志位SYN(同步序号)置为1,顺序号码为X=0。
第二次握手:服务端收到客户端发过来的报文,由SYN=1知道客户端要求建立联机,故而为这次连接分配资源,并向客户端发送一个SYN和ACK都置为1的TCP报文;设置初始顺序号码Y=0,将确认号码(ack)设置为上一次客户端发送过来的顺序号码(Seq)加1,即X+1=0+1=1。
第三次握手:客户端收到服务端发来的包后检查确认号码是否正确,即第一次发送的Seq加1(X+1=1)以及标志位ACK是否为1。若正确,则服务端再次发送确认包,ACK标志位为1,SYN标志位为0。确认号码=Y+1=0+1=1,发送的顺序号码为X+1=1。服务端收到后确认号码值与ACK=1则连接建立成功,可以传送数据了。
2)断开连接需要四次挥手。
中断连接端可以是客户端,也可以是服务端;只要将两角色互换即可。
图2-10 TCP握手与挥手过程
第一次挥手:客户端给服务端发送FIN报文,用来关闭客户端到服务端的数据传送。将标志位FIN和ACK置为1,顺序号码为X=1,确认号码为Z=1。意思是说“我(客户端)没有数据要发给你了,但如果你还有数据未发送完成,则不必急着关闭Socket,可以继续发送数据。所以,你先发送ACK过来。”
第二次挥手:服务端收到FIN后,发回一个ACK(标志位ACK=1),确认号码为收到的顺序号码加1,即X=X+1=2。顺序号码为收到的确认号码=Z。意思是说“你(客户端)的FIN请求我收到了,但是我还没准备好,请你继续等我的消息。”这个时候客户端就会进入FIN_WAIT状态,继续等待服务端的FIN报文。
第三次挥手:当服务端确定数据已发送完成,则会向客户端发送FIN报文,关闭与客户端的连接。标志位FIN和ACK置为1,顺序号码为Y=1,确认号码为X=2。意思是告诉客户端:“好了,我(服务端)这边数据发完了,准备好关闭连接了。”
第四次挥手:客户端收到服务器发送的FIN之后,发回ACK确认(标志位ACK=1),确认号码为收到的顺序号码加1,即Y+1=2。顺序号码为收到的确认号码X=2。意思是“我(客户端)知道可以关闭连接了,但我还是不相信网络,怕你不知道要关闭。”所以,客户端在发送ACK后进入TIME_WAIT状态,如果服务端没有收到ACK则可以重传。客户端等待了2MSL后依然没有收到回复,则证明服务端已正常关闭,于是客户端也可以关闭连接了。
(3)TCP报文抓取工具:Wireshark
在捕获过滤器中填入表达式host www.cnblogs.com and port 80(80等效于http)。当存在多个TCP流时,在显示过滤器中填入表达式tcp.stream eq 0,筛选出第一个TCP流(包含完整的一次TCP连接:三次握手和四次挥手),如图2-11所示。
图2-11 Wireshark抓取报文
2.HTTP
HTTP是HyperText Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(World Wide Web,WWW)服务器传输超文本到本地浏览器的传送协议。图2-12展示了HTTP协议通信流程。
图2-12 HTTP协议通信流程图
HTTP客户端请求报文(如图2-13所示)和服务端响应报文(如图2-14所示)都是由开始行(对于请求消息,开始行就是请求行;对于响应消息,开始行就是状态行)、消息报头(可选)、空行(只有CRLF的行)以及消息正文(可选)组成。
响应报文结构与请求报文结构唯一真正的区别在于第一行中用状态信息代替了请求信息。状态行通过提供一个状态码来说明所请求的资源情况。
关于HTTP客户端请求和服务端响应的使用如下例所示。
【例2-10】客户端请求
GET /hello.txt HTTP/1.1 User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3 Host: www.example.com Accept-Language: en, mi
图2-13 请求报文结构图
图2-14 响应报文结构图
【例2-11】服务端响应
HTTP/1.1 200 OK Date: Mon, 27 Jul 2009 12:28:53 GMT Server: Apache Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT ETag: "34aa387-d-1568eb00" Accept-Ranges: bytes Content-Length: 51 Vary: Accept-Encoding Content-Type: text/plain 输出结果: Hello World! My payload includes a trailing CRLF.
3.Cookie和Session
服务端和客户端的交互仅限于请求/响应过程,结束之后便断开,在下一次请求时,服务端会认为其是新的客户端。为了维护它们之间的连接,让服务端知道这是前一个用户发送的请求,必须在一个地方保存客户端的信息。
·Cookie:通过在客户端记录的信息来确定用户的身份。
·Session:通过在服务端记录的信息来确定用户的身份。
(1)Cookie与Session的区别
Cookie保存在客户端,未设置存储时间的Cookie为会话Cookie,保存在浏览器的进程开辟的内存中,当浏览器关闭后会话的Cookie也会被删除;设置了存储时间的Cookie则保存在用户设备的磁盘中直到过期。
Session保存在服务端,存储于IIS的进程开辟的内存中。
(2)Cookie
如果一个响应中包含了Cookie,那么我们可以利用如下示例中的方法得到该Cookie的值。
【例2-12】Cookie参数的应用
import requests response = requests.get("http://www.baidu.com/") # 返回CookieJar对象 cookiejar = response.cookies # 将CookieJar转为字典 cookiedict = requests.utils.dict_from_cookiejar(cookiejar) print cookiejar print cookiedict 运行结果: <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]> {'BDORZ': '27315'}
(3)Session
在requests里,Session对象是一个常用的对象,这个对象代表一次用户会话:从客户端浏览器连接服务端开始,到客户端浏览器与服务端断开。会话能让我们在跨请求时保持某些参数,比如在同一个Session实例发出的所有请求之间保持Cookie。关于Session的Cookie的使用方式可以参考如下示例中的方法。
【例2-13】实现人人网登录
import requests # 创建session对象,可以保存Cookie值 ssion = requests.session() # 处理 headers headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"} # 需要登录的用户名和密码 data = {"email":"mr_mao_hacker@163.com", "password":"alarmchime"} # 发送附带用户名和密码的请求,并获取登录后的Cookie值,保存在ssion里 ssion.post("http://www.renren.com/PLogin.do", data = data) # ssion包含用户登录后的Cookie值,可以直接访问那些登录后才可以访问的页面 response = ssion.get("http://www.renren.com/410043129/profile") # 打印响应内容 print response.text