axios

1 axios的引入

1.1. axios是什么?

  1. 前端最流行的ajax请求库

  2. react/vue官方都推荐使用axios发ajax请求

  3. 文档: https://github.com/axios/axios

1.2. axios特点

  1. 基于xhr + promise的异步ajax请求库
  2. 浏览器端/node端都可以使用
  3. 支持请求/响应拦截器
  4. 支持请求取消
  5. 请求/响应数据转换
  6. 批量发送多个请求

1.3 axios引入

引入:npm init -y

安装:npm install axios

使用中引入axios

方法一:

  • 安装好以后回生成node_modules文件夹,在当前目录下直接引入./node_modules/axios/dist/axios.min.js

方法二:

  • 在猫云下搜索axios,复制链接引入

2 axios的基本使用

创建站点服务data.json,打开小黑屏

json-server data.json -w -p 8090 -H 127.0.0.1

共同点:都需要传参 都是有请求地址 返回数据

2.1 get获取数据

使用params 将对象转化为字符串,和地址进行拼接

     btns[0].onclick = async function() {// const result = axios.get("http://127.0.0.1/scoreList",{//     params:{//         sex:"男"//     }// })// // result.then(value=>{// //     console.log(value.data)// // })// 或者用解构赋值// result.then(({data})=> {//     console.log(data)// })// 使用awaitconst {data} = await axios.get("http://127.0.0.1/scoreList", {params:{age:12}})console.log(data)}

2.2 post添加数据

闪退更新的问题,新建server,创建express

const express = require("express");
const app = express();
app.use(eapress.static(__dirname))
app.listen(80,function() {console.log("success")
})

**注意:**服务端访问的地址要为localhost下默认地址

当第二个参数是对象时:content-type,application/json 传递的参数是对象

     btns[1].onclick = async function() {const result = await axios.post("http://localhost:3000/scoreList", {userName:"zhangsan"}) console.log(result.data)}

当第二个参数是字符串时:content-type,application/x-www-form-urlencoded 传递的参数是字符串

         btns[1].onclick = async function() {const result = await axios.post("http://localhost:3000/scoreList","age=100")console.log(result.data)}

2.3 put更新数据(完整)

     btns[2].onclick = async function() {const result = await axios.put("http://127.0.0.1:3000/scoreList/8",{userName:"laodai"})console.log(result.data)}

2.4 patch 更新数据(局部)

     btns[3].onclick = async function() {const result = await axios.patch("http://127.0.0.1:3000/scoreList/7",{userName:"laoqian"})console.log(result.data)}

2.5 delete删除数据

     btns[4].onclick = async function() {const result = await axios.delete("http://127.0.0.1:3000/scoreList/11",{userName:"zhangsan"})console.log(result.data)}

2.6 head获取数据(get)

head有priview和response内容的显示

head得不到响应的内容,用于验证请求资源是否存在

2.7 axios 是一个函数

不写method 等于axios.get()

写method 等于axios.post()

测试timeout,json-server --watch data.json --delay 3000 延迟三秒反应

 axios({method:"post",url:"http://127.0.0.1:3000/scoreList",data:"a=1&b=2",//放置到请求体当中params:{c:3},//转化为字符串和地址进行拼接headers:{authorization:"abxfde"},timeout:1000}).then(value=>{console.log(value)}).catch(err=>{console.log(111,err)})

2.8 通过xhr模拟axios(了解)

axios是一个函数 模拟axios() axios.get() axios.post()

function axios(options={}){let {method="get",data="",url="",params={}} = options;return new Promise(function (resolve,reject) {const xhr = new XMLHttpRequest();xhr.responseType="json";if(options.timeout)xhr.timeout = options.timeout;// 增加超时设置// {c:3,d:4}   http://localhost:3000/scoreList// http://localhost:3000/scoreList?c=3&d=4// [c=3,d=4]url=url+(url.includes("?")?"&":"?")+Object.keys(params).map(v=>v+"="+params[v]).join("&")xhr.open(method,url);if(method.toLowerCase() === "post"){if(typeof data === "object"){xhr.setRequestHeader("content-type","application/json");xhr.send(JSON.stringify(data));}else{xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");xhr.send(data);}}else{xhr.send();}xhr.ontimeout = function(){reject("请求超时");}xhr.onload = function () {resolve({// 配置对象config:options,// 响应体的内容,格式 jsondata:xhr.response,// 响应头的内容headers:xhr.getAllResponseHeaders(),// 发起请求的xhr实例request:xhr,// 得到的状态码status:xhr.status,// 得到的状态码说明statusText:xhr.statusText});}})
}
axios.get = function (url,config) {return axios({method:"get",url,params:config.params})
}
axios.post = function (url,data) {return  axios({method:"post",url,data})
}

2.9 axios实例

当项目当中请求接口在不同的服务器时,可以通过create创建axios实例

     const a1 = axios.create({baseURL:"http://localhost:3000",timeout:1000,})const a2 = axios.create({baseURL:"http://127.0.0.1:3000",})a1({method:"post",url:"/scoreList"})a2.get("/scoreList")

3 axios常用语法

3.1 数据的请求与响应的转换

transformRequest:请求时发送的数据,该函数返回的值,才是真正发送的数据,,返回的内容其实是传递给了xhr.send() 将请求的数据进行二次设置

     axios ({method:"post",url:"http://127.0.0.1:3000/scoreList",data:{a:1,b:2},trandformRequest(data,headers) {//post put patch 中实现data = {...data,c:100}headers["Content-Type"]="application/json"return JSON.stringify(data)  //相当于send当中的内容}}).then(value=>{console.log(value)})

transformResponse:处理响应的数据,res是响应体的内容,返回的结果,才是真正得到的数据

         // 处理响应的数据,res是响应体的内容。transformResponse(res){// 返回的结果 ,即是真正得到的数据(data)// const {ok,msg} =  JSON.parse(res);// if(ok === -1){//     alert(msg);// }return JSON.parse(res);}

3.2 默认配置

设置默认项

         axios.defaults.baseURL = "http://localhost:3000";axios.defaults.headers["abcdefg"] = "11111";axios.defaults.timeout= 1000;axios.get("/scoreList")

3.3 拦截器(重要)

① 请求拦截

axios.interceptors.request.use():发送请求之前执行, 拦截函数会被执行,接收的是axios配置对象

         // 请求拦截:发送请求之前执行axios.interceptors.request.use(function(config) {// 拦截函数会被执行,接收的是axio配置对象(config)config.params.sex = "男"//拦截的时候可以进行更改 没有return救护报错return config;//拦下什么也没干})

注意:请求拦截接定义多次,先定义的后执行

② 响应拦截

axios.interceptors.response.use() axios得到响应后,会执行响应拦截。返回的内容就是响应之后得到的数据

         axios.interceptors.response.use(function(res) {console.log(res)  //res的响应内容return res.data;//返回的内容就是响应之后的数据   没有return得到的就是undefined})

注意:响应拦截如果写多个,先定义的先执行

请求拦截和响应拦截也具有异常穿透的功能,因为它是由若干个promise组成的

axios是基于promise通过xhr发送请求

axios.interceptors.request.eject() :取消请求拦截 (通过下标来进行取消)

axios.interceptors.response.eject() :取消响应拦截

3.4 all

用于批量执行多个异步请求

加上拦截器,输出的是一个数组

         axios.interceptors.response.use(function(res) {axios.get("http://127.0.0.1:3000/scoreList",{params:{sex:"男"}}).then(data=>{console.log(data)})axios.get("http://127.0.0.1:3000/scoreList",{params:{sex:"男"}}).then(data=>{console.log(data)})})
 // 将两者合并const a1 = axios.get("http://127.0.0.1:3000/scoreList",{params:{sex:男}})const a1 = axios.get("http://127.0.0.1:3000/scoreList",{params:{sex:女}})axios.all([a1,a2]).then(value=>{console.log(value)})

all原理

         function myAxios(){}myAxios.all = function (aArr) {return Promise.all(aArr);}myAxios.all([a1,a2]).then(value=>{console.log(value);})

3.5 spread

用来指定接收所有成功数据的回调函数的方法

const a1 = axios.get("http://localhost:3000/scoreList",{params:{sex:"男"}});
const a2 = axios.get("http://localhost:3000/scoreList",{params:{sex:"女"}});
axios.all([a1,a2]).then(axios.spread(function (one,two) {console.log(one,two);
}));

spread原理

         function spread(cb){return function (value) {// 成功// console.log("成功回调",value)// cb(value[0],value[1])// cb(...value);cb.apply(null,value);// cb.apply(null,[1,2])}}

3.6 取消请求

axios.Cancel():用于创建取消请求的错误对象

axios.CancelToken() : 用于创建取消请求的token对象

axios.isCancel(): 是否是一个取消请求的错误

  1. 基本流程

    配置cancelToken对象

    缓存用于取消请求的cancel函数

    在后面特定时机调用cancel函数取消请求

    在错误回调中判断如果error是cancel, 做相应处理

  2. 实现功能

    点击按钮, 取消某个正在请求中的请求

    在请求一个接口前, 取消前面一个未完成的请求

     const btns = document.querySelectorAll("button")let cancelFn = null;btns[0].onclick = function () {if (cancelFn instanceof Function) {cancelFn();}axios.get("http://127.0.0.1:3000/scoreList",{params:{sex:"男"},cancelToken:new axios.CancelToken(function(cb) {cancelFn = cb}),timeout:3000//json-server --watch data.json --delay 3000  延迟三秒反应 }).then(value=>{console.log(value)}).catch(err=>{console.log(axios.isCancel(err))//  判断错误信息是否为取消请求造成的。true})}btns[1].onclick = function () {cancelFn("取消请求")
}

取消原理

 const btns = document.querySelectorAll("button");let cancelFn = null;function axios(config){return new Promise((resolve,reject)=>{const xhr = new XMLHttpRequest();xhr.open("get",config.url);xhr.send();xhr.onload = function () {resolve(JSON.parse(xhr.responseText));}xhr.onabort = function () {reject("取消请求")}if(config.cancelToken){config.cancelToken.cancel.then(value=>{// console.log("执行了")xhr.abort();// 取消请求})}})}axios.CancelToken = function(callback){this.cancel = new Promise(resolve=>{callback(resolve)})}btns[1].onclick = function(){cancelFn()}btns[0].onclick = function () {axios({url:"http://localhost:3000/scoreList",cancelToken:new axios.CancelToken(function (cb) {cancelFn=cb;})}).then(value=>{console.log(value);}).catch(err=>{console.log("异常",err);})}

3 axios的理解

3.1. axios常用语法总结

axios(config): 通用/最本质的发任意类型请求的方式

axios(url[, config]): 可以只指定url发get请求

axios.request(config): 等同于axios(config)

axios.get(url[, config]): 发get请求

axios.delete(url[, config]): 发delete请求

axios.post(url[, data, config]): 发post请求

axios.put(url[, data, config]): 发put请求

axios.defaults.xxx: 请求的默认全局配置

axios.interceptors.request.use(): 添加请求拦截器

axios.interceptors.response.use(): 添加响应拦截器

axios.create([config]): 创建一个新的axios(它没有下面的功能)

axios.Cancel(): 用于创建取消请求的错误对象

axios.CancelToken(): 用于创建取消请求的token对象

axios.isCancel(): 是否是一个取消请求的错误

axios.all(promises): 用于批量执行多个异步请求

axios.spread(): 用来指定接收所有成功数据的回调函数的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gsaDLL5B-1621944122791)(file:///C:\Users\王秀\AppData\Local\Temp\ksohtml3944\wps4.png)]

工厂对象:调用方提供或者有默认值,直接调用

creat是axios下的属性和方法,两者都是函数

3.2. 难点语法的理解和使用

3.2.1. axios.create(config)

  1. 根据指定配置创建一个新的axios, 也就就每个新axios都有自己的配置

  2. 新axios只是没有取消请求和批量发请求的方法, 其它所有语法都是一致的

  3. 为什么要设计这个语法?

(1) 需求: 项目中有部分接口需要的配置与另一部分接口需要的配置不太一样, 如何处理

(2) 解决: 创建2个新axios, 每个都有自己特有的配置, 分别应用到不同要求的接口请求中

3.2.2 拦截器函数/ajax请求/请求的回调函数的调用顺序

  1. 说明: 调用axios()并不是立即发送ajax请求, 而是需要经历一个较长的流程

  2. 流程: 请求拦截器2 => 请求拦截器1 => 发ajax请求 => 响应拦截器1 => 响应拦截器2 => 请求的回调

  3. 注意: 此流程是通过promise串连起来的, 请求拦截器传递的是config, 响应拦截器传递的是response

  4. 请求以及响应拦截是可以写多个的,请求拦截先定义后执行,响应拦截先定义先执行

4 axios源码分析

4.1. 源码目录结构

├── /dist/                     # 项目输出目录
├── /lib/                      # 项目源码目录
│ ├── /adapters/               # 定义请求的适配器 xhr、http
│ │ ├── http.js                # 实现http适配器(包装http包)
│ │ └── xhr.js                 # 实现xhr适配器(包装xhr对象)
│ ├── /cancel/                 # 定义取消功能
│ ├── /core/                   # 一些核心功能
│ │ ├── Axios.js               # axios的核心主类
│ │ ├── dispatchRequest.js     # 用来调用http请求适配器方法发送请求的函数
│ │ ├── InterceptorManager.js  # 拦截器的管理器
│ │ └── settle.js              # 根据http响应状态,改变Promise的状态
│ ├── /helpers/                # 一些辅助方法
│ ├── axios.js                 # 对外暴露接口
│ ├── defaults.js              # axios的默认配置
│ └── utils.js                 # 公用工具
├── package.json               # 项目信息
├── index.d.ts                 # 配置TypeScript的声明文件
└── index.js                   # 入口文件

4.2 源码分析

4.2.1. axios与Axios的关系?

  1. 从语法上来说: axios不是Axios的实例

  2. 从功能上来说: axios是Axios的实例

  3. axios是Axios.prototype.request函数bind()返回的函数

  4. axios作为对象有Axios原型对象上的所有方法, 有Axios对象上所有属性

4.2.2. instance与axios的区别?

  1. 相同:

(1) 都是一个能发任意请求的函数: request(config)

(2) 都有发特定请求的各种方法: get()/post()/put()/delete()

(3) 都有默认配置和拦截器的属性: defaults/interceptors

  1. 不同:

(1) 默认配置很可能不一样

(2) instance没有axios后面添加的一些方法: create()/CancelToken()/all()

4.2.3. axios运行的整体流程?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WWeVGs13-1621944122795)(file:///C:\Users\王秀\AppData\Local\Temp\ksohtml3944\wps6.png)]

  1. 整体流程:

request(config) ==> dispatchRequest(config) ==> xhrAdapter(config)

  1. request(config):

将请求拦截器 / dispatchRequest() / 响应拦截器 通过promise链串连起来, 返回promise

  1. dispatchRequest(config):

转换请求数据 ===> 调用xhrAdapter()发请求 ===> 请求返回后转换响应数据. 返回promise

  1. xhrAdapter(config):

创建XHR对象, 根据config进行相应设置, 发送特定请求, 并接收响应数据, 返回promise

4.2.4. axios的请求/响应拦截器是什么?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O7K4tOi0-1621944122798)(file:///C:\Users\王秀\AppData\Local\Temp\ksohtml3944\wps7.png)]

  1. 请求拦截器:

在真正发送请求前执行的回调函数

可以对请求进行检查或配置进行特定处理

成功的回调函数, 传递的默认是config(也必须是)

失败的回调函数, 传递的默认是error

  1. 响应拦截器

在请求得到响应后执行的回调函数

可以对响应数据进行特定处理

成功的回调函数, 传递的默认是response

失败的回调函数, 传递的默认是error

4.2.5. axios的请求/响应数据转换器是什么?

  1. 请求转换器: 对请求头和请求体数据进行特定处理的函数

if (utils.isObject(data)) {

setContentTypeIfUnset(headers, ‘application/json;charset=utf-8’);

return JSON.stringify(data);

}

  1. 响应转换器: 将响应体json字符串解析为js对象或数组的函数

response.data = JSON.parse(response.data)

4.2.6. response的整体结构

{

​ data,

​ status,

​ statusText,

​ headers,

​ config,

​ request

}

4.2.7. error的整体结构

{

​ message,

​ response,

​ request,

}

4.2.8. 如何取消未完成的请求?

  1. 当配置了cancelToken对象时, 保存cancel函数

(1) 创建一个用于将来中断请求的cancelPromise

(2) 并定义了一个用于取消请求的cancel函数

(3) 将cancel函数传递出来

  1. 调用cancel()取消请求

(1) 执行cacel函数, 传入错误信息message

(2) 内部会让cancelPromise变为成功, 且成功的值为一个Cancel对象

(3) 在cancelPromise的成功回调中中断请求, 并让发请求的proimse失败, 失败的reason为Cancel对象

源码分析代码:

 function Axios(config){}Axios.prototype.request = function () {}Axios.prototype.get = function () {}Axios.prototype.post = function () {}function createInstance(defaultConfig) {// 实例化Axios,将默认参数(defaultConfig)进行传递var context = new Axios(defaultConfig);//将content(Axios的实例)当中的属性以及方法放置到request当中var instance = Axios.prototype.request.bind(context);// Copy axios.prototype to instance// instance:request函数(this指向到axios实例) ,原型对象,axios实例// 直白:将Axios原型对象当中的属性和方法,复制到了request函数对象中。Object.keys(Axios.prototype).forEach(key=>{if(Axios.prototype[key] instanceof  Function){instance[key] = Axios.prototype[key].bind(context);}else{instance[key]=  Axios.prototype[key];}})Object.keys(context).forEach(key=>{if(context[key] instanceof Function){instance[key]= context[key].bind(context);}else{instance[key] = context[key];}})return instance;}const axios = createInstance({});

4.3. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UidG0Yg0-1621944122800)(file:///C:\Users\王秀\AppData\Local\Temp\ksohtml3944\wps8.png)]Axios二次封装

4.3.1. 功能点

  1. 统一进行请求配置: 基础路径/超时时间等

  2. 请求过程中loading提示

  3. 请求可能需要携带token数据

  4. 请求成功的value不再是response, 而是response.data

  5. 请求失败/出错统一进行处理, 每个请求可以不用单独处理

4.3.2. 编码实现与测试

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Axios二次封装</title><link rel="stylesheet" href="https://cdn.bootcss.com/nprogress/0.2.0/nprogress.css">
</head>
<body><div><button onclick="getUsers()">获取用户列表</button><button onclick="getRepos()">获取仓库列表</button>
</div><!-- 测试接口1: https://api.github.com/search/repositories?q=v&sort=stars测试接口1: https://api.github.com/search/users?q=v--><!--1). 统一进行请求配置: 基础路径/超时时间等2). 请求过程中loading提示3). 请求可能需要携带token数据4). 请求成功的value不再是response, 而是response.data5). 请求失败/出错统一进行处理, 每个请求可以不用单独处理--><script src="https://cdn.bootcss.com/axios/0.19.0/axios.js"></script><script src="https://cdn.bootcss.com/nprogress/0.2.0/nprogress.js"></script><script>const instance = axios.create({baseURL: 'https://api.github.com',timeout: 15000})instance.interceptors.request.use(config => {NProgress.start()// 从local中读取前面保存的token数据const token = localStorage.getItem('token_key') || 'xxxxx'if (token) {config.headers['token'] = token  // 请求头的名称为后台接口指定}return config})instance.interceptors.response.use(response => {NProgress.done()return response.data},error => {NProgress.done()alert('请求出错了', error)return error})</script><script>function getUsers () {instance.get('/search/users', {params: {q: 'v'}}).then(result => {console.table(result.items)})}function getRepos () {instance.get('/search/repositories', {params: {q: 'v',sort: 'stars'}}).then(result => {console.table(result.items)})}</script>
</body>
</html>
 const token = localStorage.getItem('token_key') || 'xxxxx'if (token) {config.headers['token'] = token  // 请求头的名称为后台接口指定}return config
})instance.interceptors.response.use(response => {NProgress.done()return response.data},error => {NProgress.done()alert('请求出错了', error)return error}
)

```

axios-引入-常用语法-源码相关推荐

  1. axios从入门到源码分析 -http-xhr

    axios从入门到源码分析 1 HTTP相关 1.1.MDN文档 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Overview 1.2. HTT ...

  2. Android 系统(78)---《android framework常用api源码分析》之 app应用安装流程

    <android framework常用api源码分析>之 app应用安装流程 <android framework常用api源码分析>android生态在中国已经发展非常庞大 ...

  3. Python-3.7.0常用数据类型源码—列表

    Python-3.7.0常用数据类型源码-列表 list源码: /* List object implementation */#include "Python.h" #inclu ...

  4. axios网络请求框架源码解析

    早期axios0.1.0版本做了对IE浏览器与包含XmlHttpRequest的浏览器的支持.并且做了对请求参数拼接.Json对象序列化等基本功能. 到0.19.0版本时,内部请求已经变为了在Node ...

  5. vue甘特图(内网引入gantt-elastic以及源码扩展)

    前言:项目要求用甘特图展示计划的实施过程.之前用过dhtmlx,用着不是很舒服.又在网上看了jQueryGantt-master,wl-gantt-mater,gantt-elastic的大概源码,最 ...

  6. axios(三)- 源码

    0.热门请求库 1.axios的特性 2.axios的使用 3.核心目录结构 4.执行流程 5.Axios 中的公共方法 6.Axios 如何支持不同的使用方式? 7. 请求 / 响应拦截器是如何生效 ...

  7. 四款国内常用问答系统源码:oASK,Tipask,百姓问,齐博知道

    近日小编盘点了下站长之家收录的各款国内问答系统,也都下载试用了一番,今日便推荐以下四款较常用的问答系统:oASK问答系统.齐博知道系统.百姓问专业问答系统.Tipask问答系统,各款系统都各有特色,任 ...

  8. 记录一下常用的源码网站

    飞流资源 http://h.fl008.cn 资源邦 https://www.wazyb.com/ 米铺网 http://www.mipuo.com/ 小灰娱乐 http://www.xiaohuiy ...

  9. 粤嵌打卡第51天(小白带你进入bootstrap的学习(包含常用的页面插件源码供大家ctr+v))

    今天我们来学习下在做项目的过程中如何使用Bootstrap来引入比较美观的样式,学完了这篇博客,大家就可以尽情的使用模板了呀! bootstrap官网:https://v3.bootcss.com/g ...

最新文章

  1. 反斜杠在C/C++中的作用
  2. Linux命令解释之chmod
  3. java delphi aes加密算法_AES加密算法(Delphi源码)
  4. 如何启动软件YouTube频道
  5. 可靠性指标中MTTF MTBF MTTR
  6. Could not build wheels for cryptography which use PEP 517 and cannot be installed directly
  7. 微信小程序时间标签和时间范围的联动
  8. 网吧机子dns服务器没有响应,网吧找不到的服务器dns地址
  9. Linux 系统投屏显示
  10. CodeWarrior V5.1破解license+教程
  11. win7网络本地连接服务器未响应,Win7本地连接未识别无法连接网络如何解决
  12. MATLAB中的vpa()函数的使用(附例子和代码)
  13. jsp自定义标签的问题Unable to load tag handler class
  14. 小众爱好之滑翔伞运动
  15. Linux中的Tab键
  16. 高效能沟《关键对话》读书笔记PPT模板-优页文档
  17. 计算机中的桌面是什么样的,从整理电脑桌面中学习如何分类
  18. (int*)p、void(*p)()、(*p)()都是什么意思?
  19. C# delegate 移除 方法
  20. oel6mysql_OEL7.6安装Oracle Database 19C(VERSION 19.3.0.0)

热门文章

  1. php 保护连接字符串,PHP OOP更新扩展类__construct上的受保护字符串
  2. Oracle mysql 语句_Oracle 数据库常用操作语句大全
  3. php 整行插入mysql_MySQL的多行插入
  4. 常用的linux命令20条,Linux常用的20条命令
  5. excel python插件_如何利用Excel与Python制作PPT
  6. java经典面试题目
  7. 一个六年Java程序员的从业总结:比起掉发,我更怕掉队
  8. 配置electron
  9. [SDN] mininet walkthrough
  10. 计算机网络(9)-----TCP可靠传输的实现