网络编程

网络层

传输层

  1. 端口的作用体现在传输层,0-1023为系统端口,1024-49151为登记端口号,用于没有系统端口号的应用程序
  2. UDP的特性(我所能理解的几条):
(1)UDP是无连接的,发送数据之前不需要建立连接
(2)UDP尽最大努力交付,不保证交付可靠性
(3)UDP是面向报文的,只对IP数据报做简单的封装,首部开销小
  1. UDP抓包(待学习)
  2. TCP的特性(我所能理解的几条):
(1)TCP提供可靠的数据传输服务,是面向连接的
(2)TCP连接是点对点的,一条TCP连接只能连接两个端点
(3)TCP提供的传输是按顺序的
  1. TCP连接的建立和释放:
(1)建立:三次握手,客户端发出请求,服务端发送确认,客户端发送确认,客户端进入已连接状态
(2)释放:三次握手,客户端发送结束,服务端发送确认,客户端发送确认,等待状态结束后(4分钟)断开连接
  1. TCP抓包(待学习)
  2. 关于TCP协议是如何识别的,在IP首部其实就有一个protocol number,标6的即说明传输层首部使用的是TCP协议,接收端会将接下去的部分交由TCP程序来进行解析
  3. 这里的意思貌似是socket就是一个文件啊,是从socket上读取的(socket的概念是TCP/IP协议栈之上的)

注解

在示例代码中有两个关键点。一是第10行的 sock.connect((‘example.com’, 80)),该调用的作用是向example.com主机的80端口发起网络连接请求。 二是第14行、第18行的sock.recv(4096),该调用的作用是从socket上读取4K字节数据。

  1. TCP连接当中的flags
(1)F : FIN - 结束,结束会话
(2)S : SYN - 同步,表示开始会话请求
(3)R : RST - 复位,中断一个连接
(4)P : PUSH - 推送,数据包立即发送
(5)A : ACK - 应答,也用.表示
(6)U : URG - 紧急
(7)E : ECE - 显式拥塞提醒回应
(8)W : CWR - 拥塞窗口减少
  1. TCP连接的过程
13:17:00.540191 IP localhost.58010 > localhost.irdmi: Flags [SEW], seq 2636799043, win 65535, options [mss 16344,nop,wscale 5,nop,nop,TS val 388506438 ecr 0,sackOK,eol], length 0
13:17:00.540281 IP localhost.irdmi > localhost.58010: Flags [S.], seq 1109273373, ack 2636799044, win 65535, options [mss 16344,nop,wscale 5,nop,nop,TS val 388506438 ecr 388506438,sackOK,eol], length 0
13:17:00.540296 IP localhost.58010 > localhost.irdmi: Flags [.], ack 1, win 12759, options [nop,nop,TS val 388506438 ecr 388506438], length 0
13:17:00.540304 IP localhost.irdmi > localhost.58010: Flags [.], ack 1, win 12759, options [nop,nop,TS val 388506438 ecr 388506438], length 0
  1. socket是传输层的东西,不要将其和HTTP的概念绑定到一起去

应用层

问题汇总

  1. DNS协议具体是如何工作的呢?一个指向某个域名的HTTP请求的发出过程当中,DNS协议如何起作用

基础知识

  1. DNS协议:基于UDP协议,其提供将域名解析为ip地址的功能,通过host命令可以进行DNS查询,hosts文件其实就是一个本地的DNS服务器(原来如此!)
域名解析的优先顺序:先向查询DNS缓存,再查询本地的hosts文件,还是没有才向DNS服务器发送DNS查询报文
  1. FTP协议:基于TCP协议,提供文件传输服务,在进行文件传输时同时建立两个TCP连接,在21号端口建立控制连接,在20号端口建立数据连接
  2. HTTP协议:基于TCP协议,使用端口80或8080,点击一个链接即发起TCP,浏览器就通过HTTP协议将网页信息从服务器提取再显示出来,其报文有一个状态码,1xx表示通知信息,2xx表示成功接收,3xx表示重定向,4xx表示客户的差错,5xx表示服务器的差错
  3. Telnet协议:基于TCP协议,使用23端口号,相当于在本地shell运行一个telnet程序,用户在其中输入的命令可以传递到服务端上去,可以用来测试目标(远程或者本地都可以)及其的TCP端口有没有开放,比如 telnet IP 3389
  4. SMTP协议:基于TCP协议,用来控制信件的中转方式,其存在两个端:在发信人邮件服务器上执行的客户端和在收信人邮件服务器上的服务端(要注意都是在邮件服务器上执行的,并不是在本地)
  5. POP3协议:基于TCP协议,用来支持使用客户端远程管理在服务器上的电子邮件
  6. VPN和代理服务器的区别(有待了解)现有的理解是:
(1)VPN似乎是一种介乎于传输层和应用层的东西,其功能是在目标地址与本机之间建立一条专有的加密通讯线路,让你能够连接到某个虚拟专用网络
(2)代理服务器相当于代购,在你和目标地址之间建立一个中介,而你不需要去访问目标地址就能获得你想要的数据
  1. 邮件传输的过程:发件者在客户端发邮件,邮件通过SMTP协议被发到发件者的邮箱服务器,然后又通过SMTP协议发送到收件者的邮箱服务器,然后进行存储,收件者通过POP3或者其他类似协议,pull邮件到自己的客户端

HTTP协议

  1. URL与URI之间的区别:URL是URI的子集,URL必须是绝对路径,而URI则是在某种规则下可以唯一标志一个资源,所以URL也是URI的一种,URL必定能够提供定位该资源的信息
  2. HTTP方法汇总:
(1)GET:只是获取页面上的信息并返回回来
(2)POST:需要提交一些信息,服务器需要存储这些信息
(3)HEAD:只对头信息感兴趣,相当于是一个不需要实际内容返回的GET请求
(4)PUT:比POST更稳定一些,服务器会对提交的信息进行多次存储(不太理解)
(5)DELETE:移除给定位置的信息
(6)OPTIONS:给客户端一个快速的途径来指出这个URL支持什么方法
  1. session是存储在服务端用来识别用户状态的,其对于用户的身份识别基于本地cookies
  2. 看了一下HTTP的请求,发现HTTP response的header有对返回的数据格式进行定义,就flask而言,默认的是html,如果要传递json形式的数据格式,要在views当中进行jsonify,这样返回的数据格式就是json形式的(jsonify本质上是会返回一个response)
  3. 对于CSRF攻击,其实是其他站点恶意调用某个受信任(登录)站点的资源更新请求,由于本地cookie的存在,导致在用户不知情的情况下获取了授权并完成了资源更新,如何防止呢?归根结底还是在于登录状态是不安全的,必须在登录状态与资源更新之间加一道墙,可以用的方法是每次返回的表单页面都带有一个根据秘钥生成的随机数,表单提交的时候必须带上这个随机数,这样就能有效防止其他站点利用用户的cookies提交表单
  4. 承载在TLS和SSL协议层上的HTTP协议就是HTTPS
  5. 一个完整的HTTP请求/响应包含请求行、请求头、空行和请求体 HTTP 请求头与请求体 - 某熊的全栈之路 - SegmentFault
../_images/4800373F-6F8F-4DB4-8A1A-A02F59222ACE.png ../_images/3EAA5FBA-45F2-465E-B6AD-7B29AB15B73A.png
  1. 一些常用的请求头字段
(1)cookie:使用这个字段可以带上cookie数据
(2)User-Agent:客户端的软件环境
(3)connection:一般是keep-alive
(4)Date:时间
(5)Accept:支持数据类型
(6)content-type:请求体的类型, application/json,application/x-www-form-urlencoded 后者是web表单的默认数据类型
  1. 响应状态码
(1)100-199:表示成功接收请求,要求客户端继续提交下一次请求才能完成整个处理过程
(2)表示成功接收请求并已完成整个处理过程。常用200
(3)为完成请求,客户需进一步细化请求。例如:请求的资源已经移动一个新地址、常用302(意味着你请求我,我让你去找别人),307和304(我不给你这个资源,自己拿缓存)
(4)客户端的请求有错误,常用404(意味着你请求的资源在web服务器中没有)403(服务器拒绝访问,权限不够)405(不允许的请求方法)
(5)服务器端出现错误,常用500
  1. 一些常用的响应头字段
(1)Sever:服务器的类型
(2)Content-Length:响应数据的长度
(3)Content-Type:响应数据的类型,比如text/html
  1. HTTP协议的特性:
(1)无连接:相当于每次tcp连接只处理一次请求响应,后来加了keep-alive之后会等待一段时间才关掉这个tcp连接,至于为什么要提出keep-alive是因为一个请求会附带很多后续的比如说图片请求之类的,因此需要多次开关tcp连接导致了资源损耗(一个小知识点,tcp连接会通过心跳包的方式来保持连接,并通过套接字(五元组)来保证唯一连接)
(2)无状态:相当于每个请求是独立,服务器端处理一个丢弃一个,不会记录哪个请求是从哪个ip发过来的,所以每次发过来一个请求并不知道请求者的身份,自然也不知道请求者的状态。因此只能通过每次都带上一些信息的解决这个问题,也就是cookie和session。两种解决方式是有所不同的,第一种cookie相当于客户端做请求的时候带上这个域名对应的cookie,这些cookie是有过期时间的,根据cookie服务端能读取到用户的身份信息甚至是另外的状态信息,另外一种是session,session的话更加安全,将信息都存储在服务器,然后给一个sessionid到客户端浏览器,但是一般服务端会有一个回收机制

根据刚才说的cookie和session来说一下之前那个CSRF攻击吧,之前的理解貌似完全是错误的,其实每次获得表单页面的时候系统会随机发放一个token,你提交表单的时候要带上这个token才行,不然就会通不过检验,由于token是随机的,且对于每个用户是唯一的,因此外部站点是无法猜到对应用户的这个token的。之前写的其实是另外一种方式,这种方式使得生成的表单能够获取本地的cookie,将其hash之后发往服务端进行验证。这种方法的话好像不太好用,我看很多文章都没有提,而且为什么需要hash呢?这个问题现在已经修正了

这个就是我自己设置的CSRF验证:

<input id="csrf_token" name="csrf_token" type="hidden" value="IjY4NmU3MWY3NjE0MmExNDQ2ZmI4ZjVjMjY5OGNhODBkMzAzNjg2ZGQi.DJI6Xg.5988meY8ZbHVqdXn2fJkCaSXjDQ">
  1. 关于cookie
(1)response头部的set-cookie字段用来设置cookie的,一个字段只能设置一条cookie
(2)set-cookie的格式:
Set-cookie:name=name;expires=date;path=path;domain=domain;secure name=name
(3)看了下flask-login的状态实现方式,直接是用session的,好像要用cookie的话需要自己去搞
(4)flask的所谓session本质上他妈的就是cookie,相当于每次请求的时候拿一下cookie,然后解码,请求结束之后再种回去,怪不得放在请求上下文里面
val = self.get_signing_serializer(app).dumps(dict(session))
# 在response中设置cookie.
response.set_cookie(app.session_cookie_name, val,
                    expires=expires, httponly=httponly,
                    domain=domain, path=path, secure=secure)

注意到set_cookie的源码,直接把dict(session)dumps之后传过去了。。。

  1. 302跳转响应的响应头中会包含一个Location字段,其就是要跳转的地址的url
  2. 关于HTTP API的安全性,这是一篇非常好的文章http://www.jianshu.com/p/54312d645048,checksum其实是为了防止中途数据被修改的,请求中加时间戳主要是为了防止重放攻击的
  3. 重放攻击(Replay Attacks)又称重播攻击、回放攻击,是指攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程,破坏认证的正确性。重放攻击可以由发起者,也可以由拦截并重发该数据的敌方进行。攻击者利用网络监听或者其他方式盗取认证凭据,之后再把它重新发给认证服务器。重放攻击在任何网络通过程中都可能发生,是计算机世界黑客常用的攻击方式之一。
  4. api接口中的checksum是为了防止中途数据被修改的,请求中加时间戳主要是为了防止重放攻击的
  5. 那么短时间的重放攻击怎么办呢?貌似要通过某种访问控制才行。
  6. https使用的是443端口,所以https相对于http来说其实就是一个新协议,这个协议在http的基础上使用了双向加密
  7. https的流程
server 收到请求,并自己生成一对密钥,即 公钥S 和 私钥S
server 把生成的 公钥S 传递给 client (除了 公钥S ,还有很多额外信息)
client 收到 公钥S 后进行判断,若无效,弹出警告,否则生成一串随机数,我们称之为 私钥C ,然后 client 用 server 传过来的 公钥S 对该随机数加密,形成【私钥C】
client 把【私钥C】传递给 server
server 收到【私钥C】,用 私钥S 把【私钥C】解密成 私钥C,然后把需要传递的数据用 私钥C 进行加密
server 把【数据】传递给 client
client 收到【数据】,用 私钥C 解密,完成一波收割

本质上其实是用非对象加密来约定对象加密使用的私钥,然后对称加密来进行数据传递

HTTP服务器

  1. uwsgi的配置当中关于 --socket--http 的区别

注解

I aslo ran into same issue while following some tutorial. The problem was that I set the option socket = 0.0.0.0:8000 instead of http = 0.0.0.0:8000. socket option intended to be used with some third-party router (nginx for instance), while when http option is set uwsgi can accept incoming HTTP requests and route them by itself.

stackoverflow真的是解决问题的一个好去除

WSGI接口

  1. WSGI接口:只要求开发者实现一个函数,就可以响应HTTP请求,这个函数例如廖雪峰python当中的appliction函数,接受两个参数,一个是environ参数,是http请求的信息,另一个参数start_response参数,是对http请求进行response。
其中具体的实现是怎么样的呢?单纯的这样一个函数是没有用的,你需要一个WSGI服务器来对你构造的这个函数进行调用,期间它会将获取到的environ传进来,也会帮助你构造start_response函数来response HTTP Header(真正的请求体则是在app的return当中产生的,调用start_response仅产生响应头),因此这么一个函数其实只是一个外部接口,你只要在其中定义如何处理输入,如何处理输出之间的逻辑就可以了,输入之前和输出之后都已经被服务器处理了。这种接口模式相当于是一种中间接口,一般我们接触到的要么是输入接口,要么是输出接口,中间接口还是很少遇到的,挺新奇的。还有一点,为什么中间接口需要一个函数去做呢?因为你定义的是中间的逻辑,当这段逻辑封装为函数还是挺适合的,于是乎这就是一种高等函数,中间嵌套了三层
make_server的一个参数是一个函数,而application的一个参数又是一个函数,也许这就是高等函数的一种很好的用法,用去中间层接口。
WSGI相当于定义environ(一个包含所有http请求信息的dic)是什么,然后你要start_response()什么http Header之间的逻辑,return的部分是请求体
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [b'<h1>Hello, web!</h1>']
  1. 对于flask来说,层级结构应该是
WSGI服务器(flask自己也带了一个)—— werkzeug工具包 —— flask核心。
几个点:
(1)在第一个连接处WSGI服务器将http请求转化为environ(一个dict),并构造start_response函数,将两个参数传入到处理程序当中(WSGI服务器层以上的所有部分)
(2)werkzeug工具包首先可以通过Response对象对WSGI接口函数进行封装,改造后的WSGI接口函数只需要return一个Response的call就行了,算是对输出层的封装,还可以通过Request对象对输入层进行封装,这两个东西干的就是这个活
(3)对于应用程序(广义,包含实际上的application和中间件),他需要接收environ和回调函数start_response,一般来说的app其实就是这样一个**中间函数**,或者说是**起始函数**,相当于是application暴露给服务器的一个接口,服务器向这个接口传递回调函数,application内部处理的时候调用这个回调函数,然后返回。可以说application所有的部分都可以抽象为这样一个接口函数了。实际上内部有非常复杂的关系,比如对于传递进去的一个environ,需要识别url进行处理,处理完再返回,这个过程可以映射到整个flask app所做的事情上
  1. 对于WSGI的更深一步的解析

首先来看一下官方的说法:

注解

The environ parameter is a dictionary object, containing CGI-style environment variables. This object must be a builtin Python dictionary (not a subclass, UserDict or other dictionary emulation), and the application is allowed to modify the dictionary in any way it desires. The dictionary must also include certain WSGI-required variables (described in a later section), and may also include server-specific extension variables, named according to a convention that will be described below.

这段内容说明environ这个参数要求是一个python内置的字典类型

注解

The start_response parameter is a callable accepting two required positional arguments, and one optional argument. For the sake of illustration, we have named these arguments status, response_headers, and exc_info, but they are not required to have these names, and the application must invoke the start_response callable using positional arguments (e.g. start_response(status, response_headers)).

这段内容说明start_response是一个callable,也就是一个可调用对象,并且接受两个固定参数,一个是status,另外一个是response_headers,可选参数是exc_info

其中status是response码,如"404 Not Found",等同于状态行当中的信息,response_headers是响应头,是一个以(header_name, header_value) 的元组形式组成的list,exc_info用来返回错误信息

application必须返回一个可迭代对象,并且这个可迭代对象要求产生的是bytestring,重点是可迭代,迭代的产生结果是bytestring,对于对象类型没有更多的限制,可以是一个生成器,可以是一个list,也可以是一个实现了iter方法的可迭代类实例,所以是一个字符串当然也没有关系咯

之所以要返回一个可迭代对象是因为要保证大文件的可传输性

更加细节的东西还是等学了socket和更加深层次的底层协议时候再探索吧

  1. 关于CGI、FastCGI
(1)CGI:通用网关接口(Common Gateway Interface),是外部应用程序(CGI程序)与Web服务器之间的接口标准,由于其在遇到连接请求的时候先要创建CGI子进程,因此性能非常低下
(2)FastCGI:从CGI发展改进而来,是一个常驻(long-live)型的CGI, 它可以一直执行着,只要激活后,不会每次都要花费时间去fork一次

具体的还是很不理解,可能要深入网络编程的底层才能明白它们之间究竟是什么关系。解释得比较好的是 这篇文章

上面的回答多少都有些问题吧。 CGI是HTTP Server和一个独立的进程之间的协议,把HTTP Request的Header设置成进程的环境变量,HTTP Request的正文设置成进程的标准输入,而进程的标准输出就是HTTP Response包括Header和正文。 FASTCGI是和HTTP协议类似的概念。无非就是规定了在同一个TCP连接里怎么同时传多个HTTP连接。这实际上导致了个问题,有个HTTP连接传个大文件不肯让出FASTCGI连接,在同一个FASTCGI连接里的其他HTTP连接就傻了。所以Lighttpd? 引入了 X-SENDFILE 。 php-fpm就相当于是Apache+mod_php。无非php-fpm自带了FASTCGI Server,而Apache是HTTP Server。 那个WSGI和这个问题没啥关系吧。WSGI这个只是Python内部的一个接口。无论你前面是FASTCGI,HTTP,SCGI,uWSGI等协议,你的FASTCGI/HTTP/SCGI/uWSGI Server都以相同的参数格式去调用一个函数,这样你用Python写的Web应用并不需要修改代码,就可以运行在不同的Server后面了。无非CGI协议是进程间的,而WSGI是进程内的。

socket

  1. socket可以被看做一个标准的文件描述符,而文件描述符一般指一个文件或某个类似文件的实体,所以socket本身在操作系统当中就像文件一样被处理
  2. socket与文件之间的区别最明显的在于其建立方式,文件通过open()函数打开,而socket通过socket()函数建立,recv()和send()这两个系统调用和read()及write()非常相似
  3. 建立一个socket连接需要两个步骤,第一是建立一个socket对象,第二是将其连接至远程服务器上
  4. 研究一下这个代码
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 8000))
sock.send(b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8000\r\n\r\n')
data = sock.recv(4096)
print(data)
sock.close()

在这里sock.connect调用就是与服务器建立连接,这个连接就是三次握手的过程,而能够建立连接的前提是服务器调用sock.listen

import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8000))
while 1:
  pass
sock.listen(5)
while 1:
  pass

在这段服务端的代码当中,如果只是bind的,那么端口已经启用但未开始监听,此时查看netstat会发现如下情况,客户端connect会一直没反应,用tcpdump看的话可以看到客户端一直在发连接请求

tcp4       0      0  127.0.0.1.8000         *.*                    CLOSED

调用listen,则表示开始监听,此时客户端connect能够建立tcp连接

tcp4       0      0  127.0.0.1.8000         *.*                    LISTEN

  1. socket的数据是通过内核维护的读写缓冲区来获取的
../_images/v2-eb72f0fd73e6e431303cd98fbe31ec09_b.png
  1. 关于unix域socket
当用Unix Domain Socket发起bind操作时,会在文件系统中创建一个条目,socket和路径名为一对一关系。一般来说,Unix Domain Socket只针对在同一主机下应用程序下的网络通信,它还有一个特点是可以使用目录权限来控制socket的访问。(例如我们使用mysql时用到的mysql.sock就是使用unix domain sokcet的载体)
  1. 关于recv的作用机制

(1)recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,

(2)如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,

如果s接收缓冲区中没有数据或者协议正在接收数 据,那么recv就一直等待,直到协议把数据接收完毕。

当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中

  1. 关于socket回调事件的条件
  1. 同一时间只能存在一个特定的套接字,而套接字的组成是由双边ip和双边端口共同构成的,所以端口的限制其实是进程的限制,一个进程用一个端口,但是这个端口可以建立很多的连接,连接数的增多只会导致运行减缓,而不是一个端口只能建立一个输出层连接
  2. sock文件就是UNIX 域套接字(UNIX domain socket),即通过文件系统(而非网络地址)进行寻址和访问的套接字,用于linux内部程序间的交互,内部使用连接什么的用的就是这个套接字。
  3. NIO就是非阻塞同步IO,其实现看了几篇文章其实和我之前写的协程调用socket非常相似,就是一个单线程循环,去查询epoll事件

oauth2

  1. oauth2的作用其实是用于第三方登录,由于第三方app与认证平台互相不信任,因此你要用认证平台的账号登录第三方app,需要先去认证平台拿一个token,然后把token给第三方app再由第三方app去验证
  2. 相关的说明http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
  3. oauth2的案例
(1)登录qq
(2)获取code
(3)将code传给第三方app使第三方app获取token,通过token获取用户信息