一、Android基础网络编程:socket、HttpClient、HttpURLConnection
1.1 Socket 定义
是一个对TCP/IP协议进行封装的编程调用接口,自己不是一种协议是接口Api!!
成堆出现,一对套接字:包括ip地址和端口号
基于应用层和传输层抽象出来的一个层。App能够经过该层发送、接收数据,并经过Socket将App添加到网络当中
简单来讲就是应用与外部通讯的端口,提供了两端数据的传输的通道
1.2 Socket通讯模型
基于TCP和UDP协议的两种模型
- TCP:采用字节流协议来提供可靠的字节流服务
- UDP:采用数据报文的形势提供数据,打包的形势发送服务
1.3 Socket与Http对比
Android与服务器的通讯方式
(1)Http通讯
基于请求-响应方式;
属于应用层;
解决如何包装数据的问题
(2)Socket通讯
采用服务器主动发送数据的方式,
Socket属于传输层;解决数据如何在网络中传输
1.4 Socket实现
/**
* Tcp 客户端Socket
*/
public void TcpSendMessage(String msg) {
Socket socket = null;
OutputStream outputStream = null;
InputStream inputStream = null;
try {
//一、建立客户端Socket对象,传入目标主机名orId地址和端口号
socket = new Socket("192.168.1.1", 8080);
//二、经过socket获取输出流
outputStream = socket.getOutputStream();
//三、写入输出流操做
outputStream.write(msg.getBytes());
//四、关闭socket操做,msg写入结束 ps:不调用会形成服务器端消息返回的没法获取
socket.shutdownOutput();
//五、msg的IO流读取操做
inputStream = socket.getInputStream();
byte[] b = new byte[1024];
int len = -1;
final StringBuffer sb = new StringBuffer();
while ((len = inputStream.read(b)) != -1) {
sb.append(new String(b, 0, len, Charset.forName("gbk")));
}
//todo 在主线程中更新Ui
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//注意,输出流不须要关闭,由于它不是建立的而是经过Socket中获得输出流对象获取的
if ((socket != null)) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* Tcp 服务器Socket
*/
public void ServerMessage() {
ServerSocket server = null;
Socket socket = null;
try {
//一、建立服务器Socket对象并监听须要的端口号
server = new ServerSocket(8888);
while (true) {
//二、接收客户端发送的请求;ps:若客户端没有发送数据,该线程会停滞,accept中会阻塞
socket = server.accept();
//三、获取输入流
InputStream inputStream = socket.getInputStream();
//四、建立缓存输入流进行数据的读入
BufferedInputStream bis = new BufferedInputStream(inputStream);
byte[] b = new byte[1024];
int len = -1;
while ((len = bis.read()) != -1) {
System.out.println(new String(b, 0, len, "UTF-8"));
}
socket.shutdownInput();
OutputStream outputStream = socket.getOutputStream();
outputStream.write("收到".getBytes());
bis.close();
socket.close();//serverSocket不能被关闭!
socket = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
1.4 HttpClient 和 HttpURLConnection
基本的HttpURLConnection连接 一个简单Get实例:
/**
* HttpURLConnection
*/
public static void readContentFromGet() throws IOException {
//一、拼接get请求字符串
String getURL = "GET_URL" + "?username= " + URLEncoder.encode("fat-man", "UTF-8");
//二、建立URL对象
URL getUrl = new URL(getURL);
//三、代表这个connection只能发送一个请求
HttpURLConnection connection = (HttpURLConnection)
getUrl
.openConnection();
//四、创建连接,这时并无将真正请求发送给服务器端
connection
.connect();
//取得输入流,并使用Reader读取,getInputStream()方法将真正的请求发送给服务器端
BufferedReader reader = new BufferedReader(new InputStreamReader
(
connection
.getInputStream()));
String lines;
while ((lines = reader.readLine()) != null) {
System.out.println(lines);
}
reader
.close();
//断开连接,关闭底层Socket连接
connection
.disconnect();
}
二、了解WebSocket?知道和Socket的区别?OkHttp是如何处理WebSocket的相关问题
2.1 WebSocket
推送-
轮询 是特定的时间间隔,由浏览器对服务器发送Http请求,而后由服务器返回最新的数据给客户端的浏览器。
短轮询
提交表单的形势进行数据传递;
缺陷:在某个时间段Server没有更新数据,但Client端仍然每隔一段时间发送请求来询问,因此这段时间内的询问都是无效的,冗余数据。
长轮询
服务器端接收request请求后不会当即返回数据response给客户端,会检查数据是否有更新。若是有更新了就会返回给客户端数据,若是没有更新则不返回。
缺陷:
- 浪费带宽
- Http Head 过大实际body缺不大
- 消耗服务器CPU占用
WebSocket
WebSocket一旦创建了两端的链接,能够不断的进行通讯,是一种全双通的通讯模式。
2.2 WebSocket 与Http
Http是 懒惰的协议,有接收才有响应
WebSocket是全双向通讯网络协议,server主动向client发送数据
2.3 WebSocket 与Socket
Socket 首先要明白是一种接口 并非一贯协议
WebSocket是同等级的网络协议
二者没有任何关系
- 本质上是一个基于TCP的协议
- 向服务器发起一个HTTP请求 /“Upgrade WebSocket”
- 服务器端解析头信息
2.4 OkHttp是如何处理WebSocket的
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
/**
* WebSocketListener 运行在工做线程的
*/
private final class EchoWebSocketListener extends WebSocketListener {
//WebSocket 和远程 服务器端创建连接
@Override
public void onOpen(WebSocket webSocket, Response response) {
// super.onOpen(webSocket, response);
//OkHttp使用本身的后台发送数据,不用担忧sendMessage1会阻塞当前线程的问题
webSocket.send("xxx");
//发送消息已经完成
webSocket.close(1000, "ss");
}
/**
* onMessage()中与主线程的交互要很是很是当心!!与主线程用handler交互能够
*/
@Override
public void onMessage(WebSocket webSocket, String text) {
// super.onMessage(webSocket, text);
setText("onMessage :" + text);
handler.sendEmptyMessage(0);
}
//远端已经没有数据的状况,准备关闭WebSocket连接可是尚未关闭
@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
// super.onClosing(webSocket, code, reason);
setText("onClosed:" + code + "/" + reason);
}
//这个连接已经彻底被关闭了
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
// super.onClosed(webSocket, code, reason);
setText("onClosed:" + code + "/" + reason);
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
// super.onFailure(webSocket, t, response);
setText("onFailure:" + t + "/" + response);
}
}
三、Http如何处理缓存?OkHttp如何处理缓存相关问题?
(1)Expires 过时时间 —Http1.0
值表示服务器端返回的到期时间;
下一次请求时候,请求的时间 < 服务端返回的到期时间 —》会直接使用缓存数据
缺陷:
到期时间是由服务器端生成的,会与客户端的时间形成偏差
(2)Cache-Control
Cache-control 是由
服务器返回的Response中添加的头信息;
做用是告诉用户是从本地读取缓存仍是从服务端获取消息
Cache-Control的取值:
- private :表示客户端能够取缓存
- public :表示客户端和代理服务器均可以缓存
- max-age:表示缓存内容在多少秒后失效
- no-cache:表示强制缓存的标识没法处理缓存
- no-store:表示不进行缓存
一、首先须要进行比较判断是否可使用缓存
二、服务器会将缓存标识与数据一块儿返回给客户端
流程:
再次请求数据——>
if(有缓存 != null) {
if(是否过时 ?){
没过时直接从缓存读取数据
}else if(
没法判断是否已通过期){
// 进行对比缓存检查
if(判断ETag 标准){
向web服务器请求带If-None-Match—— 二者进行匹配!
资源 有改动返回200,请求响应; 无改动返回304 直接从缓存读取
}else if(ETag != null){
if(Last-Modified == null ){
向服务器请求带If-Modified-Since
有改动返回200 ,请求响应;无改动返回304 直接从缓存读取
}
}
}
}
当前资源是否被改动过,改动过返回200,再去请求响应,没有改动过返回304
ETag / If-None-Match 成对出现
- ETag : 服务器端响应请求时候,告诉浏览器当前资源在服务器的惟一标识
ps:生成规则由服务器端决定惟一标识 与下面的进行匹配
- If-None-Match:再次请求服务器时候,经过此字段通知服务器客户端缓存数据的惟一标识
Last-Modified / If-Modefied-Since 成对出现
- Last-Modified : 服务器在响应请求时,告诉浏览器资源的最后修改时间
- If-Modefied-Since :再次请求服务器时,浏览器通知服务器端上次请求时,服务器返回的资源最后修改时间
四、断点续传的原理?如何实现?OkHttp中如何实现相关原理?
4.1 断点续传
断点续传:从文件已经下载完的地方开始继续下载
实现:客户端发送给浏览器端的请求头报文当中,添加此次下载从什么位置开始的新条件
RANGE:bytes = 200080 -
代表此次从 资源的 200080位置开始下载
在Java中用HttpURLConnection 实现:
public void doBreakDownLoadJava() {
URL url = null;
try {
//一、建立URL对象
//二、经过URL建立 HttpURLConnection,由它完成网络请求
HttpURLConnection httpURLConnection = (HttpURLConnection)
url
.openConnection();
//三、经过setRequestProperty ,建立请求头部信息,设置断点续传的开始位置
httpURLConnection.
setRequestProperty("RANGE", "bytes=2000080”);
InputStream inputStream = httpURLConnection.getInputStream();
//四、获取到流信息保存到文件中,用字节进行指定的读取
RandomAccessFile oSaveFile = new
RandomAccessFile("down.zip", "rw");
long nPos = 2000070;
//五、代表文件读取的位置
oSaveFile
.seek(nPos);
//常规IO流读写操做
byte[] b = new byte[1024];
int nRead;
while ((nRead = inputStream.read(b, 0, 1024)) > 0) {
//六、对文件写入操做
oSaveFile.
write(b, 0, nRead);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
OkHttp中相关实现实例:
/**
* OkHttp断点续传
*/
public void doDownloadWithOkHttp() {
InputStream is = null;
RandomAccessFile savedFie = null;
File file;
//一、首先记录已经下载的文件长度
long downloadLength = 0;
String filename = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
file = new File(directory + filename);
//二、判断下载的文件是否存在 ,存在的话下载长度范围赋值
if (file.exists()) {
downloadLength = file.length();
}
long contentLength = getContentLength(downloadUrl);
//三、建立OkHttpClient对象
OkHttpClient client = new OkHttpClient();
//四、建立Request对象,经过addHeader加头部信息添加到请求里,代表下载的范围
Request request = new Request.Builder()
.addHeader("RANGE", "bytes=" + downloadLength + "-")
.url(downloadUrl)
.build();
//五、开启一个同步请求
try {
Response response = client.newCall(request).execute();
//六、根据Response进行判断
if (request != null) {
is = request.body().byteStream();
savedFie = new RandomAccessFile(file, "rw");
//七、跳过已经下载的字节
savedFie.seek(downloadLength);
byte[] b = new byte[1024];
int total = 0;
int len;
while ((len = is.read()) != -1) {
total += len;
savedFie.write(b, 0, len);
//八、计算已经下载的百分比
int progress = (int) ((total + downloadLength) * 100 / contentLength);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
五、多线程下载原理,OkHttp如何实现?
多线程下载:每一个线程只负责下载文件的一部分,也就是分段加载。
5.1 Java中多线程
在Java中多线程的下载 实例:
/**
* 多线程下载
*/
public void download() throws Exception {
URL url = new URL(path);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
//根据connection的ResponseCode来进行相应的操做
int code = connection.getResponseCode();
if (code == 200) {
//获取资源文件大小
int connectionLength = connection.getContentLength();
//在本地建立一个与资源一样大小的文件来占位
RandomAccessFile randomAccessFile = new RandomAccessFile(
new File(targetfilePath, getFilePath));
//在本地建立一个占位文件
randomAccessFile.setLength(connectionLength);
//计算每个线程加载的数量
int blockSize = connectionLength / threadCount;
//为每个线程分配任务
for (int threadId = 0; threadId < threadCount; threadId++) {
//线程开始/结束下载的位置
int startIndex = threadId * blockSize;
int endIndex = (threadId + 1) * blockSize - 1;
if (threadId == (threadCount - 1)) {
//将全部任务交给endIndex完成
endIndex = connectionLength - 1;
}
//开始正式多线程的实现
new DownloadThread(threadId, startIndex, endIndex).start();
}
randomAccessFile.close();
}
}
/**
* 开始正式多线程的实现
*/
private class DownloadThread extends Thread {
private int threadID, startIndex, endIndex;
public DownloadThread(int threadID, int startIndex, int endIndex) {
this.threadID = threadID;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public void run() {
System.out.println("线程" + threadID + "开始下载");
try {
//一、分段下载也须要分段的获取URL 将文件保存到本地
URL url = new URL(path);
//二、加载下载位置的文件,获取文件大小
File downThreadFile = new File(targetFilePath, "downThread_" + threadID + ".dt");
//三、建立一个新的RandomAccessFile
RandomAccessFile downThreadStream = null;
if (downThreadFile.exists()) {
//四、若是文件不存在
downThreadStream = new RandomAccessFile(downThreadFile, "rwd");
String startIndex_str = downThreadStream.readLine();
if (startIndex_str != unll || !"".equals(startIndex_str)) {
this.startIndex = Integer.parseInt(startIndex_str) - 1;//下载起点
}
} else {
downThreadStream = new RandomAccessFile(downThreadFile, "rwd");
}
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
//五、设置分段下载头信息
connection.setRequestProperty("RANGE", "bytes=" + startIndex + "-" + endIndex);
if (connection.getResponseCode() == 206) {//六、部分资源请求成功
InputStream inputStream = connection.getInputStream();
//七、获取建立的文件
RandomAccessFile randomAccessFile = new RandomAccessFile(
new File(targetFilePath, getFileName(url), "rw")
);
//八、文件写入的计算位置
randomAccessFile.seek(startIndex);
//IO流读写操做
byte[] buffer = new byte[1024];
int length = -1;
int total = 0;//记录本次下载文件的大小
while ((length = inputStream.read(buffer)) > 0) {
randomAccessFile.write(buffer, 0, length);
total += length;
downThreadStream.seek(0);
downThreadStream.write((startIndex + total + "").getBytes("UTF-8"));
}
//九、关闭IO流操做
downThreadStream.close();
inputStream.close();
randomAccessFile.close();
cleanTemp(downThreadFile);//删除建立的占位临时文件
System.out.println("线程:" + threadID + "下载完毕");
} else {
System.out.println("响应码:" + connection.getResponseCode() + "服务器不支持");
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
5.2 OkHttp 多线程下载的实现:
/**
* OkHttp断点续传
*/
public void doDownloadWithOkHttp() {
InputStream is = null;
RandomAccessFile savedFie = null;
File file;
//一、记录已经下载的文件长度
long downloadLength = 0;
String filename = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
file = new File(directory + filename);
if (file.exists()) {
downloadLength = file.length();
}
long contentLength = getContentLength(downloadUrl);
//二、建立OkHttpClient对象
OkHttpClient client = new OkHttpClient();
//三、建立Request对象,经过addHeader加头部信息添加到请求里,代表下载的范围
Request request = new Request.Builder()
.addHeader("RANGE", "bytes=" + downloadLength + "-")
.url(downloadUrl)
.build();
//四、开启一个同步请求
try {
Response response = client.newCall(request).execute();
//五、根据Response进行判断
if (request != null) {
is = request.body().byteStream();
savedFie = new RandomAccessFile(file, "rw");
//六、跳过已经下载的字节
savedFie.seek(downloadLength);
byte[] b = new byte[1024];
int total = 0;
int len;
while ((len = is.read()) != -1) {
total += len;
savedFie.write(b, 0, len);
//七、计算已经下载的百分比
int progress = (int) ((total + downloadLength) * 100 / contentLength);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
六、文件上传如何作?原理?OkHttp如何完成文件上传
6.1 文件上传
Java中的文件上传 :在UrlConnection中使用post方法 而后在请求头时候添加Http之 Content-Type
指定请求和响应的HTTP内容类型 ,好比:
Content-Type :
multipart / form-data;
boundary = ———(分割数据)WebkKitFormBoundaryOGKWPJsJCPWjZP
6.2 OkHttp 文件上传的简单操做
public <T> void upLoadFile( String actionUrl ,HashMap<String,Object> paramsMap){
String requestUrl = String.format (“%s/%s”,”upload”,cationUrl);
//0、经过MultipartBody建立文件上传的body体
MultipartBody.Builder builder = new MultipartBody.Builder()
.addPart(Headers.of(
"Content-Disposition",
"form-data; name=\"mFile\";
filename=\"1.txt\""), fileBody)
.build();
;
builder.setType(MultipartBody.FORM);
for (String key : paramsMap.keySet() ){
Object object =paramsMap.get(key);
if( ! ( object instanceof File) ){
builder.addFormDataPart (key, object.toString() );
} else {
File file = (File)object ;
builder .addFormDataPart(key,file.getName(),
RequestBody.create(MediaType.parse(
“
application/octet-stream
”
)) );
}
}
//一、构建RequestBody
RequestBody body = builder.build();
//二、构建Request
Request request = new Request.Builder().url(“…”).post(body).build();
//三、构建Call
Call call = client . newBuilder().writeTimeout(60,TimeUnit.xxx);
//四、构建异步回调
call.enqueue(new Callback(){
...
});
七、json数据如何解析?OkHttp如何解析json类型数据
7.1 json数据的JAVA 解析
json:文本形式的数据交换格式
一、传统的JSON解析——— JSObject和JSArray
二、GSON————
三、FastJSON——
GSON 的两种解析方式:
/**
** 将json数据解析成list
*/
public void doGson(){
//构建Gson对象
Gson gson = new Gson();
//经过fromJson 实现反序列化
List<T> list =
gson.fromJson(jsonData ,new TypeToken<List<T>>(){}.getType());
}
public void doGson(){
//一、构建JsonParser 解析对象
JsonParser parser = new JsonParser();
//二、经过解析对象 将String类型json数组转化成JsonArray
JsonArray jsonArray = parser .parse (stringjson).
getAsJsonArray():
//三、构建Gson对象 list对象
Gson gson = new Gson();
ArrayList<T> list = new ArrayList<>();
//四、开始一个for循环 循环遍历jsonArray,获取jsonArray的每个元素
for ( JsonElement je : jsonArray ){
T t1 = gson .
fromJson(je , T.class);
list .add ( t1) ;
}
}
7.2 OkHttp中的json解析
a、封装一个工具类HttpUtil
public class HttpUtil {
public static void sendOkHttpRequest (final String address ,final okhttp3.Callback callback){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(address).build();
client.newCall(request).enqueue(callback);
b、在响应中的调用
private void sendRequestWithOkHttp(){
new Thread(new Runnable(){
@Override
public void run(){
//子线程中执行http请求,并将最终的请求结果回调到Callback中
HttpUtil.sendOkHttpRequest( url, new okhttp3.Callback(){
@Override
public void onResponse(Call call, Response response) throws IOException{
String responseData = response.body().string();
//解析json数据
parseJsonWithGson(responseData);
//显示UI界面,通知主线程更新ui
showResponse( responseData.toString() );
}
@Override
public void onFailure(Call call ,IOException e){
}
)};
}
}).start();
}
private void parseJsonWithGson (String jsonData){
//使用轻量级的Gson解析获得json
Gson gson = new Gson();
List<T> list = gson.fromJson(jsonData ,new TypeToken<List<T>>(){}.getType());
}
private void showResponse (final String response) {
//在自线程中通知ui更新
runOnUiThread (new Runnable() {
@Override
public void run(){
//在此进行UI处理操做
text .setText ( response);
}
)};
}
Lambda表达式的操做样式
private void showResponse(
final String response) {
runOnUiThread( () -> {
text.setText (response);
});
}
八、Https协议处理?
Https协议
Https是一种基于SSL/TLS的Http协议,是属于应用层协议;
全部传输的内容都通过加密(对称加密+不对称加密)
对称加密
是指加密和解密使用的密钥匙同一个密钥,二者能够互相推算。
真正传输的数据进行加密
不对称加密
不对称加密和解密使用的密钥
不是同一密钥,对外公开其中一个密钥叫公钥。
该加密是用于握手阶段的!
传送模式:
对称加密所使用的密钥咱们能够经过非对称加密的方式发送出去
实例:
一笔交易流程:
一、客户端生成一个随机对称密钥
二、客户端向服务器端请求一个公共密钥 -不对称加密所须要的公钥,给外界用的
三、服务端返回公钥给客户端
四、客户端接收到公钥后,经过该公钥对本身生成的随机对称密钥进行加密
五、将加密过的对称密钥发送给服务端
六、服务端接收该对称密钥后会用本身的私钥对其进行解密
七、进行传输 使用对称加密进行
Https 握手过程:
一、客户端发起Https连接请求获取不对称加密的公钥(客户端支持的加密规则发送给服务端--不对称加密)
二、服务端接收到请求后,从规则中选出一个不对称加密算法和一个hash算法(验证数据完整性的)
三、服务端将本身的身份信息以证书的形式返回给客户端 -含有不对称加密的公钥
四、客户端生成随机数-对称密钥 须要客户端和服务端双方保存
五、客户端使用不对称加密的公钥对 “随机生成的对称密钥 进行加密”
六、客户端将加密过的密钥发送给服务端
七、服务端经过私钥对获取的加密过的密钥进行解密
八、以后均是: 经过对称密钥加密的密纹通讯