HTTP
一次http网络请求的过程
浏览器发起请求-> 解析域名得到ip进行TCP连接 ->浏览器发送HTTP请求和头信息发送->服务器对浏览器进行应答,响应头信息和浏览器所需的内容-> 关闭TCP连接或保持-> 浏览器得到数据数据进行操作。
先找到对方ip地址,然后用指定的传输协议传送到指定的端口。
HTTP分层
- Application Layer 应用层:HTTP、FTP、DNS
- Transport Layer 传输层(通讯规则、传输协议):TCP、UDP(负责传输,找到端口)
- Internet Layer 网络层:IP(负责连接,找到IP)
- Link Layer 数据链路层:以太网、Wi-Fi(以太网:网线,为网络提供现实世界(物理设备的支持))
为什么要分层?
因为网络的不稳定性,所以要url分块传输
常见通讯规则、传输协议:TCP/UDP
-
UDP(面向无连接)-->聊天、网络视频会议、步话机
DatagramSocket
将数据及源和目的封装成数据包中,不需要建立连接
每个数据包的大小限制在64k内
因无连接,是不可靠的协议
不需要建立连接,速度快 -
TCP(面向连接)-->下载,打电话
ServiceSocket
建立连接,形成传输数据的通道
在连接中进行大数据量的传输
通过三次握手完成连接,是可靠的协议
必须建立连接,效率会稍低
什么是iP地址
Internet上的主机有两种方式表示地址: 域名:www.baidu.com, IP 地址:202.108.35.210 ,域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,这样才能和主机建立连接。
什么是端口
用于标识进程的逻辑地址,不同进程的标识,有效地址065535,其中01024系统使用或保留端口。
HTTP协议版本
HTTP/1.0
- 链接后,只能获取一个web资源。
- 链接后,发送请求,服务器做出响应,链接立即断开。
HTTP/1.1
- 链接后,可以获取多个web资源。
- 链接后,发送请求,服务器做出响应,链接不会立即断开。再次发送请求,直接有一段时间没操作,自动断开。(服务端可以配置)
三次握手
SYN,SYN/ACK,ACK
syn 客户端发送syn包给服务器,进入服务状态
syn-ack 服务器收到,客户端确实并发送给syn.这时进去接受状态
ack 客户端收到服务端的syn-ack,并向服务器确认,此时链接成功!
第一次本方发送请求,第二次对方确认连接,第三次本方再次发送确认信息告诉对方,这样双方就都知道了,从而才能建立连接。
三次握手的目的,两次行不行?
为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误的问题,网络不好的话不知道双方的状态。
四次挥手
fin 发送请求连接
ack 同意断开连接
fin+ack 服务端断开连接
ack 同意断开
四次关闭,我要把你忘掉
1.我不要消息了
2.我知道了
3.我没有消息给你发了
4.我知道了
为什么 TCP 连接在断开时是四次挥手而不是三次?
因为在客户端停止向服务器发送消息时,也许服务器还有消息需要向客户端发送,因此在它对客户端的「Fin」(即「我不再给你发送消息」)消息进行回应时,不需要立即附加上「我也不再向你发送消息」。在稍后服务器的消息发送完毕之后,才需要向客户端发送通知。
Keep-Alive
从HTTP/1.1起,默认都开启了Keep-Alive,保持连接特性,简单地说,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。虽然这里使用TCP连接保持了一段时间,但是这个时间是有限范围的,到了时间点依然是会关闭的,所以我们还把其看做是每次连接完成后就会关闭。
Socket
Socket就是为网络服务提供的一种机制,通讯的两端都必须有Socket(套接字,就是接口的意思),网络通讯其实就是Socket间的通讯,数据在两个Socket间通过IO传输,IP 地址标识 Internet 上的计算机,端口号标识正在计算机上运行的进程(程序)。 端口号与IP地址的组合得出一个网络套接字。
创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。
Socket则是对TCP/IP协议的封装和应用
1)创建客户端对象: Socket(String host,int port),指定要接收的IP地址和端口号
2)创建服务端对象:ServerSocket(int port):指定接收的客户端的端口
3)Socket accept():侦听并接受到此套接字的连接,服务器用于接收客户端socket对象的方法
主要通过S.getOutputstream和S.getInputSteram
注:服务器没有socket流,也就没有读写操作的流,服务器是通过获取到客户端的socket流(accept)然后获取到其中的读写方法,对数据进行操作的,也正是因为这样服务器与客户端的数据操作才不会错乱
HTTP的缺点
通信使用明文,内容可能被窃听
不验证通信方身份,因此有可能遭遇伪装
http缓存
Pragma和Cache-control共存时,Pragma的优先级是比Cache-Control高的。
only-if-cached表示不进行网络请求,完全只使用缓存,若缓存不命中,则返回503错误
- max-age:告知缓存多长时间,在没有超过缓存时间的情况下,请求会返回缓存内的数据,在超出max-age的情况下向服务端发起新的请求,请求失败的情况下返回缓存数据(测试中已验证),否则向服务端重新发起请求。
- max-stale:指示客户机可以接收超出max-age时间的响应消息,max-stale在请求设置中有效,在响应设置中无效。因此max-age和max-stale在请求中同时使用的情况下,缓存的时间可以为max-age和max-stale的和。
OKHTTP一般控制缓存有两种方式
1、在request里面去设置cacheControl()策略
2、在header里面去添加cache-control
Android okhttp缓存真正正确的实现方式
有网时不缓存,没网时设置缓存,通过Cache-Control和max-age
public class HttpCacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!NetWorkHelper.isNetConnected(MainApplication.getContext())) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
Response response = chain.proceed(request);
if (NetWorkHelper.isNetConnected(MainApplication.getContext())) {
int maxAge = 60 * 60; // 如果想要不缓存,直接时间设置为0,但是需要保存下来吧
response.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
} else {
int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
response.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build();
}
return response;
}}
//设置缓存100M
Cache cache = new Cache(new File(MainApplication.getContext().getCacheDir(),"httpCache"),1024 * 1024 * 100);
return new OkHttpClient.Builder()
.cache(cache)
.addNetworkInterceptor(new HttpCacheInterceptor())
.build();
HTTPS
- HTTPS并不是一个单独的协议,对于一些隐秘性比较高的数据可以用https协议。( 它是在TCP层与http层之间加了个SSl/TLS。TLS是SSL v3.0的升级版,而SSL协议,是一种安全传输协议。)
- SSL/TLS层负责客户端和服务器之间的加解密算法协商、密钥交换、通信连接的建立。
主要用到对称加密、非对称加密、证书,等技术进行客户端与服务器的数据加密传输,最终达到保证整个通信的安全性。 - 说白了就是加密通信的 HTTP
SSL/TLS防止的安全风险:
- 窃听风险
- 篡改风险
- 冒充风险
SSL/TLS防止的安全风险原理:
- 加密:非对称加密+对称加密,主要解决的是窃听风险
- 校验:数字签名,主要解决的是篡改风险
- 证书:数字证书,主要解决的是冒充风险
非对称加密
- 对称加密的问题:秘钥如何保存和传输(非对称加密算法)
- “非对称加密”加密算法,特点是私钥加密后的密文,只要是公钥,都可以解密,但是公钥加密后的密文,只有私钥可以解密。私钥只有一个人有,而公钥可以发给所有的人。
- 非对称加密一般不会单独拿来使用,他并不是为了取代对称加密而出现的,非对称加密速度比对称加密慢 很多,极端情况下会慢 1000 倍,所以一般不会用来加密大量数据,通常我们经常会将对称加密和非对称 加密两种技术联合起来使用,例如用非对称加密来给称加密里的秘钥进行加密(即秘钥交换)。
数字签名
数字签名技术结合Hash算法和加密算法,来防止消息被篡改和进行身份认证。
数字签名就是:发送方使用自己的私钥对信息摘要加密产生
- 发送方使用Hash算法对原文产生信息摘要,原文不变则信息摘要不变。
- 发送方使用自己的私钥对信息摘要加密产生数字签名,并和加密的原文一起发送。
- 接收方使用发送方的公钥解密获取数字签名
- 接收方使用Hash算法对原文计算信息摘要与解密的信息摘要比对,成功表示未篡改、未成功表示篡改。
数字证书
数字签名一般不单独使用,基本都是用在数字证书里实现 SSL 通信协议。
第三步:请求网址后返回证书的公钥和数字证书,客户端验证数字证书的有效性,是ca的,怎么验证的?数字签名,都有标准的,x509
第四步:通过后,使用公钥加密内容,非对称加密加密分别对公钥和内容信息摘要(hash,数字签名),发送
第六步:非对称加密解密对称加密的key,然后通过对称加密的key解密内容
在 Android 中使用 HTTPS
正常情况:直接使用
okhttp中就能验证自己的签名,就是为了让己的签名通过验证。
如果是ca机构的证书,OKHTTP不需要配置直接就可以访问,如果是自定义的证书,OKHTTP就不行了,但是可以信任指定证书或者所有证书来访问。
需要自⼰写证书验证过程的场景
- 用的是自签名证书(例如只用于内网的 https)
- 信息不全,缺乏中间证书机构(可能性不大)
- 手机操作系统较旧,没有安装最新加入的根证书
其他
get和post的区别
get提交:提交的信息都显示在地址栏中,对于敏感数据不安全,传送的数据量较小,不能大于2KB,因为地址栏存储体积有限。将信息封装到了请求的请求行中。
post提交:提交的信息不显示在地址栏中,对于敏感数据安全,可以提交大体积数据。将信息封装到了请求体中
Cookie
Session和Cookie是一种记录客户端状态的机制的话是由服务器发给客户端特殊信息,而这些信息以文本文件的方式存放在客户端,然后客户端每次向服务器发送请求的时候都会在headers里带上这些特殊的信息
Cookie 的作⽤
会话管理:登录状态、购物⻋
个性化:⽤户偏好、主题
Tracking:分析⽤户⾏为
Session
不同于Cookie保存在客户端中,他是保存在服务器上。
session 的工作原理
1.第一步创建Session
2.在创建了Session的同时,服务器会为该Session生成唯一的Session id
3.在Session被创建之后,就可以调用Session相关的方法往Session中增加内容
4.当客户端再次发送请求的时候,会将这个Session id带上,服务器接受到请求之后就会依据Session id找到相应的Session
cookie和session 的区别
1.存放位置不同
2.存取方式的不同
3.安全性(隐私策略)的不同
4.有效期上的不同
5.对服务器造成的压力不同
URI和 URL
统一资源标识符(URI)用于标识某一互联网资源,而统一资源定位符(URL)表示资源的地点(互联网上所处的位置)。所以 URL 是 URI 的子集。URI:http:,https:,ftp:,本地文件系统(file:),和Jar文件(jar:)。
如何上传
用post,通过表单,不过结构单一,比较繁琐,还可以通过将数据转换成jsonstring、文件、string(base64)来上传,如果是多个图片可以写个循环上传。在上传的时候可以在head添加content-type告诉服务器这是什么数据
多线程断点下载
单线程的话从输入流的第0个字节读取
原理:服务器CPU分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源。所以要用多线程去读取。
请求网络时首先获取资源长度设置被进度条,然后除以要开启的线程数,计算出每个线程应该下载多少字节。然后每个线程去请求网络读取数据。
//设置本次http请求所请求的数据的区间
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
//请求部分数据,相应码是206
if(conn.getResponseCode() == 206){
//流里此时只有1/3原文件的数据
InputStream is = conn.getInputStream();
int length = conn.getContentLength();
特别注意: 对于移动端来说,如果不是比较大的文件,不建议使用这种方式上传,因为断点续传是通过分片上传实现的,上传单个文件需要进行多次网络请求,效率不高。
WWW
HTTP协议同时具备极强的扩展性,虽然浏览器请求的是http://www.sina.com.cn/的首页,但是新浪在HTML中可以链入其他服务器的资源,比如,从而将请求压力分散到各个服务器上,并且,一个站点可以链接到其他站点,无数个站点互相链接起来,就形成了World Wide Web,简称WWW。
常见状态码
200 :请求成功处理,一切OK
302 :请求重定向
304 :服务器端资源没有改动,通知客户端查找本地缓存
404 :客户端访问资源不存在
500 :服务器内部出错
乱码的处理
乱码的出现是因为服务器和客户端码表不一致导致
//手动指定码表
text = new String(bos.toByteArray(), "UTF-8");
什么是内网外网
私有网段地址, 内网IP有3种:第一种10.0.0.0~10.255.255.255,第二种172.16.0.0~172.31.255.255,第三种192.168.0.0~192.168.255.255
运营商给你的100.64.* . * 也是私有地址,以前大家大家共享一个地址池,有随机的公网地址,现在公网地址更加紧张,运营商只给客户分配私网地址,然后nat后大家共享一个公网地址
172.31.40.210内网
192.168.17.207外网
TCP和UDP
UDP传输的流程
创建 UDPSocket发送服务对象:DatagramSocket(),可不指定端口
创建 UDPSocket接收服务对象:DatagramSocket(int port),
发送:void send(DatagramPacket p)
接收:void receive(DatagramPacket p)
TCP传输流程
Socket(客户端)和ServiceSocket服务端()
建立客户端和服务端
建立连接后,通过socket中的IO流进行数据的传输
关闭socket
1)创建客户端对象: Socket(String host,int port),指定要接收的IP地址和端口号
2)创建服务端对象:ServerSocket(int port):指定接收的客户端的端口
3)Socket accept():侦听并接受到此套接字的连接,服务器用于接收客户端socket对象的方法
主要通过S.getOutputstream和S.getInputSteram
注:服务器没有socket流,也就没有读写操作的流,服务器是通过获取到客户端的socket流(accept)然后获取到其中的读写方法,对数据进行操作的,也正是因为这样服务器与客户端的数据操作才不会错乱
定义tcp的服务端
建立服务端的socket服务,ServerSocket,并监听一个端口
获取连接过来的客服端对象,通过ServerSokcet的 accept方法。没有连接就会等,所以这个方法阻塞式的。
客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流来读取发过来的数据。
关闭服务端(可选)
public class TCPServerDemo {
public static void main(String[] args) throws IOException {
// 建立服务端的socket服务,并监听一个端口
ServerSocket ss = new ServerSocket(10003);
// 通过accept方法获取连接过来的客户端对象
Socket socket = ss.accept();
String ip = socket.getInetAddress().getHostAddress();
System.out.println(ip + "......connected");
// 获取客户端发过来的数据,那么要使用客户端对象的读取流来获取数据
InputStream in = socket.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf, 0, len));
socket.close();// 关闭客户端
ss.close();// 关闭服务端,可选的操作
}
}
tcp分为客户端和服务端,客服端对象的对象是socket,服务端对应的是serversoceket
客户端,在建立的时候就可以去连接指定的主机
因为tcp是面向连接的,所以在建立socket时就需要有服务端存在
并连接成功,形成通路,才能在该通道上传输数据
public class TCPClientDemo {
public static void main(String[] args) throws Exception {
//1、创建客户端的socket服务,指定目的主机和端口
Socket socket = new Socket("192.168.229.1", 10003);
//2、获取socket的输出流,用于发送数据
OutputStream out = socket.getOutputStream();
//3、发送数据
out.write("tcp client send message come on".getBytes());
socket.close();
}
}
在Android上发送HTTP请求的方式
原生有两种,HttpURLConnection和HttpClient,两种方式都支持HTTPS协议、以流的形式进行上传和下载、配置超时时间、IPv6、以及连接池等功能。HttpURLConnection的API提供的比较简单,可以更加容易地去使用和扩展它,而且速度快、还能节省电量。
HttpURLConnection用法:
发送GET请求
URL url = new URL(path);
//获取连接对象
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置连接属性
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
//建立连接,获取响应吗
if(conn.getResponseCode() == 200){
//获取服务器响应头中的流,流里的数据就是客户端请求的数据
InputStream is = conn.getInputStream()
}
获取服务器返回的流,从流中把html源码读取出来
流转换成string的方法第一种:
byte[] b = new byte[1024];
int len = 0;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while((len = is.read(b)) != -1){
//把读到的字节先写入字节数组输出流中存起来
bos.write(b, 0, len);
}
//把字节数组输出流中的内容转换成字符串
//默认使用utf-8
text = new String(bos.toByteArray());
//第二种:
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder builder = new StringBuilder();
String line;
while ((line=reader.readLine())!=null) {
builder.append(line);
}
String text=builder.toString();
//第三种
Reader reader = null;
reader = new InputStreamReader(stream, "UTF-8");
char[] buffer = new char[len];
reader.read(buffer);
return new String(buffer);
//图片的流
InputStream is = null;
...
Bitmap bitmap = BitmapFactory.decodeStream(is);
ImageView imageView = (ImageView) findViewById(R.id.image_view);
imageView.setImageBitmap(bitmap);
post
connection.setRequestMethod("POST");
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.writeBytes("username=admin&password=123456");
简单封装
public class HttpUtil {
public static void sendHttpRequest(final String address,
final HttpCallbackListener listener) {
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
try {
URL url = new URL(address);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
connection.setDoInput(true);
connection.setDoOutput(true);
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
if (listener != null) {
// 回调onFinish()方法
listener.onFinish(response.toString());
}
} catch (Exception e) {
if (listener != null) {
// 回调onError()方法
listener.onError(e);
}
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}).start();
}
}