目录
网络分层
1,应用层:如常用的http协议属于应用层,定义了数据的包装与解析规则。
2,传输层:包含TCP与UDP协议,这一层通常使用java封装的Socket类进行数据的发送与接收。
3,网络层:包含常用的IP协议以及路由协议。这一层用于指定数据的IP传输规则,包含传输路线,路由选择等。
4,链路层:包含常见的ARP协议,负责将IP地址解析为Mac地址(硬件地址),从而找到对应的设备
5,物理层:这一层是在物理设备上进行数据的传输,包含有线与无线通信。
在地址栏输入url后的流程?
1,使用DNS(域名系统)解析域名,从而获取url中的IP地址
2,使用IP地址与本地默认的端口(80)建立TCP连接(3次握手)
3,建立TCP连接后发起http请求
4,服务器收到请求并响应HTTP请求,将对应的html文本发送给浏览器
5,浏览器解析htm代码,并请求htm代码中的资源(如js、css图片等)
6,断开TCP连接(4次挥手)
7,浏览器对页面进行渲染呈现给用户
HTTP
1,HTTP(超文本传输协议):是一个基于请求与响应模式的、无状态的、应用层的协议,常基于TCP的连接方式,默认端口号为80。特点如下:
1,简单快速:客户向服务器请求数据时,只需传送请求方法和路径。由于传输内容少,故通信速度相对较快。
2,灵活:HTTP允许传输任意类型的数据对象。传输的类型由Content-Type加以标记
3,无连接:每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。由于使用TCP连接,这种方式比较耗时并且效率不高。
4,无状态:协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。可以使用Cookie来解决无状态的问题,Cookie就相当于一个通行证,第一次访问的时候给客户端发送一个Cookie,当客户端再次来的时候,拿着Cookie(通行证),那么服务器就知道这个是”老用户“。
2,http协议的组成
- 请求报文:请求行/请求头/请求体
请求行:包含请求方法get/post、URI、HTTP版本信息(如:http1.0)
请求头:即header,里面包含很多字段。比如使用Accept-Language可以设置返回数据的语言类型。
请求体:发送的数据。比如get、post请求的参数。
- 响应报文:状态行/响应头/响应体
状态行:包含HTTP版本,状态码,状态码原因短语。通常使用状态码判断结果。
响应头:返回的header
响应体:请求服务器返回的数据。通常是我们需要使用的数据
- 常用的http头部字段
- 通用首部字段(请求报文与响应报文都会使用的首部字段)
Date:创建报文时间
Connection:连接的管理
Cache-Control:缓存的控制
Transfer-Encoding:报文主体的传输编码方式- 请求首部字段(请求报文会使用的首部字段)
Host:请求资源所在服务器
Accept:可处理的媒体类型
Accept-Charset:可接收的字符集
Accept-Encoding:可接受的内容编码
Accept-Language:可接受的自然语言- 响应首部字段(响应报文会使用的首部字段)
Accept-Ranges:可接受的字节范围
Location:令客户端重新定向到的URI
Server:HTTP服务器的安装信息- 实体首部字段(请求报文与响应报文的的实体部分使用的首部字段)
Allow:资源可支持的HTTP方法
Content-Type:实体主类的类型
Content-Encoding:实体主体适用的编码方式
Content-Language:实体主体的自然语言
Content-Length:实体主体的的字节数
Content-Range:实体主体的位置范围,一般用于发出部分请求时使用
3,请求方式:
GET:用于请求访问已经被URI(统一资源标识符)识别的资源,可以通过URL传参给服务器
POST:用于传输信息给服务器,主要功能与GET方法类似,但一般推荐使用POST方式
PUT:传输文件,报文主体中包含文件内容,保存到对应URI位置
HEAD:获得报文首部,与GET方法类似,只是不返回报文主体,一般用于验证URI是否有效
DELETE:删除文件,与PUT方法相反,删除对应URI位置的文件
OPTIONS:查询响应URI支持的HTTP方法
- get重点在从服务器上获取资源,post重点在向服务器发送数据;
- get传输数据是通过URL请求,以field(字段)= value的形式,置于URL后,并用"?"连接,多个请求数据间用"&"连接,如:http://127.0.0.1/Test/login.action?name=admin&password=admin,这个过程用户是可见的;post传输数据通过Http的post机制,将字段与对应值封存在请求实体中发送给服务器,这个过程对用户是不可见的;
- get传输的数据量小,因为受URL长度限制,但效率较高;post可以传输大量数据,所以上传文件时只能用Post方式;
- get是不安全的,因为URL是可见的,可能会泄露私密信息,如密码等;post较get安全性较高;
- get方式只能支持ASCII字符,向服务器传的中文字符可能会乱码。post支持标准字符集,可以正确传递中文字符。
4,常用的响应码(可以查看HttpURLConnection类中定义的常量)
1xx:指示信息--表示请求已接收,继续处理
2xx:成功--表示请求已被成功接收、理解、接受
3xx:重定向--要完成请求必须进行更进一步的操作
4xx:客户端错误--请求有语法错误或请求无法实现
5xx:服务器端错误--服务器未能实现合法的请求
200:请求被正常处理
204:请求被受理但没有资源可以返回
206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行GET方法,相应报文中通过Content-Range指定范围的资源。
301:永久性重定向
302:临时重定向
303:与302状态码有相似功能,只是它希望客户端在请求一个URI的时候,能通过GET方法重定向到另一个URI上
304:发送附带条件的请求时,条件不满足时返回,与重定向无关
307:临时重定向,与302类似,只是强制要求使用POST方法
400:请求报文语法有误,服务器无法识别
401:请求需要认证
403:请求的对应资源禁止被访问
404:服务器无法找到对应资源
500:服务器内部错误
503:服务器正忙
5,缓存机制:主要是使用header中的字段:Cache-control与ETag 来控制
Okhttp中对于网络请求的缓存使用的就是Http自带的的缓存机制
Volley则是自己实现一套缓存策略
Cache-control主要包含以及几个字段:
- private:则只有客户端可以缓存
- public:客户端和代理服务器都可以缓存
- max-age:缓存的过期时间
- no-cache:需要使用对比缓存来验证缓存数据
- no-store:所有内存都不会进行缓存
ETag:即用来进行对比缓存,Etag是服务端资源的一个标识码,当客户端发送第一次请求时服务端会下发当前请求资源的标识码Etag,下次再请求时,客户端则会通过header里的If-None-Match将这个标识码Etag带上,服务端将客户端传来的Etag与最新的资源Etag做对比,如果一样,则表示资源没有更新,返回304。通过Cache-control和Etag的配合来实现Http的缓存机制。
6,版本差异:
http1.1的特点:
- 默认持久连接节省通信量,只要客户端服务端任意一端没有明确提出断开TCP连接,就一直保持连接,可以发送多次HTTP请求(即长连接)
- 管线化,客户端可以同时发出多个HTTP请求,而不用一个个等待响应
- 断点续传,实际上就是利用HTTP消息头使用分块传输编码,将实体主体分块传输。
http2.0特点:
- 二进制格式:http1.x是文本协议,而http2.0是二进制以帧为基本单位,是一个二进制协议,一帧中除了包含数据外同时还包含该帧的标识:Stream Identifier,即标识了该帧属于哪个request,使得网络传输变得十分灵活。
- 多路复用:多个请求共用一个TCP连接,多个请求可以同时在这个TCP连接上并发,一个是解决了建立多个TCP连接的消耗问题,一个也解决了效率的问题。那么是什么原理支撑多个请求可以在一个TCP连接上并发呢?基本原理就是上面的二进制分帧,因为每一帧都有一个身份标识,所以多个请求的不同帧可以并发的无序发送出去,在服务端会根据每一帧的身份标识,将其整理到对应的request中。
- header头部压缩:主要是通过压缩header来减少请求的大小,减少流量消耗,提高效率。
- 支持服务端推送:服务器主动向客户端推送消息。
7,https : 是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP+SSL,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。使用的是非对称加密算法。公钥加密的信息只能用私钥解开,而私钥加密的信息也只能被公钥解开。
http与https的对比:
1,http是超文本传输协议,信息是明文传输;https则是具有安全性的ssl加密传输协议。
2,http和https使用的是完全不同的连接方式,用的端口也不一样。前者直接运行在TCP上,默认端口号为80,后者运行在SSL/TLS之上,SSL/TLS运行在TCP之上,默认端口为443。
TCP与UDP
1,TCP与UDP的区别
- UDP面向无连接,面向报文,数据不安全,速度快,不区分客户端与服务端,最大读取64K
- TCP面向连接,面向字节流,数据安全,速度相应慢,区分客户端与服务端,是HTTP的底层实现机制。
2,TCP的三次握手与四次挥手
- 三次握手:
1,客户端向服务端请求连接,
2,服务端向客户端回复收到了,此时只能证明客户端到服务端畅通
3,客户端收到服务端的回应,再给服务端发通知,我收到了,证明服务端到客户端是畅通。- 四次挥手:
1,客户端发送完成后,发送数据FIN通知服务端,客户端要关闭了
2, 服务端关闭读取数据的功能并回应数据ACK,此时客户端发关闭写的功能
3,服务端写完成发送FIN通知客户端,服务端也要关闭,次数服务端关闭写的功能,
4,客户端收到通知后关闭读的功能并发送ACK回应服务端。
补充小知识:常见的标识符合
SYN 是发起一个连接
ACK 是回复
RST 是重新连接
FIN 是结束连接
3,TCP的滑动窗口协议:是保证TCP的可靠传输的根本,因为发送窗口只有收到确认帧才会向后移动窗口继续发送其他帧。
- 停止-等待协议:每发一帧都要等到确认消息才能发送下一帧,缺点:效率较差。
- 后退N帧协议:采取累计确认的方式,接收方正确的接受到N帧后发一个累计确认消息给发送窗口,确认N帧已正确收到,如果发送方规定时间内未收到确认消息则认为超时或数据丢失,则会重新发送确认帧之后的所有帧。缺点:出错序号后面的PDU已经发送过了,但是还是要重新发送,比较浪费。
- 选择重传协议:若出现差错,只重新传输出现差错涉及需要的PDU,提高了传输效率,减少不必要的重传。
4,TCP的流量控制与拥塞控制:
- 流量控制:是对一条通信路径上的流量进行控制,就是发送方通过获取接收方的回馈来动态调整发送的速率,来达到控制流量的效果,其目的是保证发送者的发送速度不超过接收者的接收速度。
- 拥塞控制:对网络中的路由和链路传输进行速度限制,避免网络过载;包含四个过程:慢启动、拥塞避免、快重传和快恢复
Socket通讯
Socket(套接字):本质上是对TCP/UDP操作进行封装的API,从而实现TCP/UDP的网络通讯。特点:
1,网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
2,通信的两端都有Socket。
3,网络通信其实就是Socket间的通信。
4,数据在两个Socket间通过IO流传输。
5,Socket在应用程序中创建,通过一种绑定机制与驱动程序建立关系,告诉自己所对应的IP和port。
- UDP通讯常用的类:
DatagramSocket:此类表示用来发送和接收数据报包的套接字。
DatagramPacket:封装的数据报文,包含数据内容,ip,端口号。- TCP通讯中使用的类:
Socket:用于客户端创建套接字进行封装目标ip和端口号
ServerSocket:用于服务端指定监听端口。通过accept()方法获取Scoket对象,然后进行数据读写。
//UDP发送数据(在子线程的run方法中执行)
public void run() {
DatagramSocket scoket = null;
try {
scoket = new DatagramSocket();
Log.d(TAG, "run : ip1 = " + scoket.getLocalAddress().getHostName());
InetAddress inetAddress = InetAddress.getByName(mIp);
DatagramPacket datagramPacket = new DatagramPacket(mSendData.getBytes(), mSendData.getBytes().length, inetAddress, UDP_PORT);
for (int i = 0; i < 3; i++) {
Thread.sleep(100);
Log.d(TAG, "run 11");
scoket.send(datagramPacket);
}
} catch (SocketException e) {
Log.d(TAG, "run : 111" + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
Log.d(TAG, "run : 211" + e.getMessage());
e.printStackTrace();
} catch (InterruptedException e) {
Log.d(TAG, "run : 311" + e.getMessage());
e.printStackTrace();
} finally {
closeConnection(scoket);
}
}
//UDO接收数据
private void getUdpBack() {
while (true) {
DatagramSocket scoket = null;
try {
Thread.sleep(100);
scoket = new DatagramSocket(UDP_PORT);
byte[] tem = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(tem, tem.length);
scoket.receive(datagramPacket);
String hostName = datagramPacket.getAddress().getHostName();
int port = datagramPacket.getPort();
Log.d(TAG, "run : receive : ip = " + hostName + " ; port = " + port);
int length = datagramPacket.getLength();
byte[] data = new byte[length];
System.arraycopy(datagramPacket.getData(), 0, data, 0, length);
mUdpResult = new String(data);
mHandler.sendEmptyMessage(UDP_BACK);
} catch (SocketException e) {
Log.d(TAG, "run 422: " + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
Log.d(TAG, "run 522: " + e.getMessage());
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
closeConnection(scoket);
}
}
}
//关闭数据流的方法
private void closeConnection(Closeable... closeables) {
if (closeables == null || closeables.length == 0) {
return;
}
for (int i = 0; i < closeables.length; i++) {
Closeable closeable = closeables[i];
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//TCP客户端发送数据 (这里会出现Socket无法创建的情况,可以修改端口号进行尝试)
public void run() {
Socket socket = null;
InputStream inputStream = null;
OutputStream outputStream = null;
try {
//1,创建Socket对象,指定目标ip与端口号
socket = new Socket(mIp, TCP_PORT);
inputStream = socket.getInputStream();
String readData = readData(inputStream);
Log.d(TAG, "run: 客户端读 = " + readData);
outputStream = socket.getOutputStream();
//2,发送数据
outputStream.write(mSendData.getBytes());
outputStream.flush();
Log.d(TAG, "run: 客户端读 : 11");
} catch (IOException e) {
Log.d(TAG, "run: 客户端读 : 22 : " + e.getMessage());
e.printStackTrace();
} finally {
//关闭流
closeConnection(inputStream, outputStream, socket);
}
}
/**
* 读取数据流
* @param inputStream
* @return
*/
private String readData(InputStream inputStream) throws IOException {
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = inputStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
closeConnection(outSteam);
return outSteam.toString();
}
//TCP服务端接收数据
private void getTcpBack() {
ServerSocket serverSocket = null;
try {
//1,构造ServerSocket实例,指定端口监听客户端的连接请求
serverSocket = new ServerSocket(TCP_PORT);
while (true) {
try {
Thread.sleep(100);
Socket accept = null;
InputStream inputStream = null;
OutputStream outputStream = null;
try {
//2,建立跟客户端的连接
accept = serverSocket.accept();
inputStream = accept.getInputStream();
outputStream = accept.getOutputStream();
Log.d(TAG, "getTcpBack: 11");
//3,读取接收的内容
mTcpResult = readData(inputStream);
Log.d(TAG, "getTcpBack: 22 : " + mTcpResult);
mHandler.sendEmptyMessage(TCP_BACK);
} catch (IOException e) {
Log.d(TAG, "getTcpBack: 33 : " + e.getMessage());
e.printStackTrace();
} finally {
//关闭流
closeConnection(accept, inputStream, outputStream);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
closeConnection(serverSocket);
}
}
参考与摘录:
https://www.cnblogs.com/sunny-sl/p/6529830.html
http://blog.chinaunix.net/uid-26275986-id-4109679.html
https://juejin.im/post/5b49f9fbf265da0f563dc9d8#heading-3