Posted by 伍世谦的博客 on March 1, 2020

网络编程

上过了计算机网络的课程,总结一下学过的知识。

网络分层

计算机网络体系结构一般采用五层协议。

1.物理层 2.数据链路层 3.网络层 4.传输层 5.应用层

分层 说明
应用层(HTTP、FTP、DNS、SMTP 等) 定义了如何包装和解析数据,应用层是 http 协议的话,则会按照协议规定包装数据,如按照请求行、请求头、请求体包装,包装好数据后将数据传至运输层
运输层(TCP、UDP 等) 运输层有 TCP 和 UDP 两种,分别对应可靠和不可靠的运输。在这一层,一般都是和 Socket 打交道,Socket 是一组封装的编程调用接口,通过它,我们就能操作 TCP、UDP 进行连接的建立等。这一层指定了把数据送到对应的端口号
网络层(IP 等) 这一层IP协议,以及一些路由选择协议等等,所以这一层的指定了数据要传输到哪个IP地址。中间涉及到一些最优线路,路由选择算法等
数据链路层(ARP) 负责把 IP 地址解析为 MAC 地址,即硬件地址,这样就找到了对应的唯一的机器
物理层 提供二进制流传输服务,也就是真正开始通过传输介质(有线、无线)开始进行数据的传输

TCP/IP

​ IP(Internet Protocol)协议提供了主机和主机间的通信,为了完成不同主机的通信,我们需要某种方式来唯一标识一台主机,这个标识,就是著名的 IP 地址。通过IP地址,IP 协议就能够帮我们把一个数据包发送给对方。

​ TCP 的全称是 Transmission Control Protocol,TCP 协议在 IP 协议提供的主机间通信功能的基础上,完成这两个主机上进程对进程的通信。

三次握手

所谓三次握手(Three-way Handshake),是指建立一个 TCP 连接时,需要客户端和服务器总共发送3个包。

三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。在 socket 编程中,客户端执行 connect() 时。将触发三次握手。

  • 第一次握手(SYN=1, seq=x):

客户端发送一个 TCP 的 SYN 标志位置 1 的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号 (Sequence Number) 字段里。发送完毕后,客户端进入 SYN_SEND 状态。

  • 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):

服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为 1。服务器端选择自己 ISN 序列号,放到 Seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即 X+1。 发送完毕后,服务器端进入 SYN_RCVD 状态。

  • 第三次握手(ACK=1,ACKnum=y+1)

客户端再次发送确认包(ACK),SYN 标志位为 0,ACK 标志位为 1,并且把服务器发来 ACK 的序号字段 +1,放在确定字段中发送给对方,并且在数据段放写 ISN 的 +1

发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手结束。

四次挥手

​ TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),也叫做改进的三次握手。客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行 close() 操作即可产生挥手操作。

  • 第一次挥手(FIN=1,seq=x)

假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。

发送完毕后,客户端进入 FIN_WAIT_1 状态。

  • 第二次挥手(ACK=1,ACKnum=x+1)

服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。

发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。

  • 第三次挥手(FIN=1,seq=y)

服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。

发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。

  • 第四次挥手(ACK=1,ACKnum=y+1)

客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。

服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。

客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。

摘自《计算机网络》(第7版)。

TCP 与 UDP 的区别

| 区别点 | TCP | UDP | | ——– | ——– | —— | | 连接性 | 面向连接 | 无连接 | | 可靠性 | 可靠 | 不可靠| | 有序性 | 有序 | 无序 | | 面向 | 字节流 | 报文(保留报文的边界) | | 有界性 | 有界 | 无界 | | 流量控制 | 有(滑动窗口) | 无 | | 拥塞控制 | 有(慢开始、拥塞避免、快重传、快恢复) | 无 | | 传输速度 | 慢 | 快 | | 量级 | 重量级 | 轻量级 | | 双工性 | 全双工 | 一对一、一对多、多对一、多对多 | | 头部 | 大(20-60 字节) | 小(8 字节) | | 应用 | 文件传输、邮件传输、浏览器等 | 即时通讯、视频通话等 |

Socket

Socket 是一组操作 TCP/UDP 的 API,像 HttpURLConnection 和 Okhttp 这种涉及到比较底层的网络请求发送的,最终当然也都是通过 Socket 来进行网络请求连接发送,而像 Volley、Retrofit 则是更上层的封装。

使用示例

使用 socket 的步骤如下:

  • 创建 ServerSocket 并监听客户连接;
  • 使用 Socket 连接服务端;
  • 通过 Socket.getInputStream()/getOutputStream() 获取输入输出流进行通信。
public class EchoClient {
 
    private final Socket mSocket;
 
    public EchoClient(String host, int port) throws IOException {
        // 创建 socket 并连接服务器
        mSocket = new Socket(host, port);
    }
 
    public void run() {
        // 和服务端进行通信
        Thread readerThread = new Thread(this::readResponse);
        readerThread.start();
 
        OutputStream out = mSocket.getOutputStream();
        byte[] buffer = new byte[1024];
        int n;
        while ((n = System.in.read(buffer)) > 0) {
            out.write(buffer, 0, n);
        }
    }

    private void readResponse() {
        try {
            InputStream in = mSocket.getInputStream();
            byte[] buffer = new byte[1024];
            int n;
            while ((n = in.read(buffer)) > 0) {
                System.out.write(buffer, 0, n);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
 
    public static void main(String[] argv) {
        try {
            // 由于服务端运行在同一主机,这里我们使用 localhost
            EchoClient client = new EchoClient("localhost", 9877);
            client.run();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

HTTP协议

HTTP简介

​ 超文本传输协议已经演化出了很多版本,它们中的大部分都是向下兼容的。在 RFC 2145 中描述了HTTP版本号的用法。客户端在请求的开始告诉服务器它采用的协议版本号,而后者则在响应中采用相同或者更早的协议版本。

HTTP/0.9 已过时。只接受GET一种请求方法,没有在通讯中指定版本号,且不支持请求头。由于该版本不支持POST方法,因此客户端无法向服务器传递太多信息。

HTTP/1.0 这是第一个在通讯中指定版本号的HTTP协议版本,至今仍被采用,特别是在代理服务器中。

HTTP/1.1 持久连接被默认采用,并能很好地配合代理服务器工作。还支持以管道方式在同时发送多个请求,以便降低线路负载,提高传输速度。

HTTP/1.1相较于HTTP/1.0协议的区别主要体现在:

  • 缓存处理
  • 带宽优化及网络连接的使用
  • 错误通知的管理
  • 消息在网络中的发送
  • 互联网地址的维护
  • 安全性及完整性

HTTP/2 当前版本,于2015年5月作为互联网标准正式发布。Http2.0 相对于 Http1.x 来说提升是巨大的,主要有以下几点:

  • 二进制格式:http1.x 是文本协议,而 http2.0 是二进制以帧为基本单位,是一个二进制协议,一帧中除了包含数据外同时还包含该帧的标识:Stream Identifier,即标识了该帧属于哪个 request,使得网络传输变得十分灵活。
  • 多路复用:多个请求共用一个TCP连接,多个请求可以同时在这个 TCP 连接上并发,一个是解决了建立多个 TCP 连接的消耗问题,一个也解决了效率的问题。
  • header 压缩:主要是通过压缩 header 来减少请求的大小,减少流量消耗,提高效率。
  • 支持服务端推送

HTTP协议的主要特点

  • 支持C/S(客户/服务器)模式。
  • 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST,每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
  • 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
  • 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
  • 无状态:HTTP协议是无状态协议,无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

HTTP URL的格式:

http://host[":"port][abs_path]

http表示要通过HTTP协议来定位网络资源;host表示合法的Internet主机域名或者IP地址;port指定一个端口号,为空则使用默认端口80;abs_path指定请求资源的URI(Web上任意的可用资源)。

请求报文

<method> <request-URL> <version>
<headers>

<entity-body>

Method表示请求方法;Request-URI是一个统一资源标识符;HTTP-Version表示请求的HTTP协议版本;HTTP请求方法有8种,分别是GET、POST、DELETE、PUT、HEAD、TRACE、CONNECT 、OPTIONS。其中PUT、DELETE、POST、GET分别对应着增删改查,最常用的就是POST和GET了。

  • GET:请求获取Request-URI所标识的资源
  • POST:在Request-URI所标识的资源后附加新的数据
  • HEAD:请求获取由Request-URI所标识的资源的响应消息报头
  • PUT: 请求服务器存储一个资源,并用Request-URI作为其标识
  • DELETE :请求服务器删除Request-URI所标识的资源
  • TRACE : 请求服务器回送收到的请求信息,主要用于测试或诊断
  • CONNECT: HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
  • OPTIONS :请求查询服务器的性能,或者查询与资源相关的选项和需求

例:访问 GET https://wushiqian.github.io/ HTTP/1.1

在请求行之后会有0个或者多个请求报头,每个请求报头都包含一个名字和一个值,它们之间用“:”分割。请求头部会以一个空行,发送回车符和换行符,通知服务器以下不会有请求头。 请求数据不在GET方法中使用,而是在POST方法中使用。POST方法适用于需要客户填写表单的场合,与请求数据相关的最常用的请求头是Content-Type和Content-Length。 ### 响应报文 ![](https://ws1.sinaimg.cn/large/006tNc79gy1g50k2sq6faj30ax06m74u.jpg) HTTP 响应与 HTTP 请求相似,HTTP响应也由3个部分构成,分别是: * 状态行 * 响应头(Response Header) * 响应正文 状态行由协议版本、数字形式的状态代码、及相应的状态描述,各元素之间以空格分隔。 ``` HTTP-Version Status-Code Reason-Phrase CRLF ``` 常见的状态码有如下几种: * 200 OK 客户端请求成功 * 301 Moved Permanently 请求永久重定向 * 302 Moved Temporarily 请求临时重定向 * 304 Not Modified 文件未修改,可以直接使用缓存的文件。 * 400 Bad Request 由于客户端请求有语法错误,不能被服务器所理解。 * 401 Unauthorized 请求未经授权。这个状态代码必须和WWW-Authenticate报头域一起使用 * 403 Forbidden 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因 * 404 Not Found 请求的资源不存在,例如,输入了错误的URL * 500 Internal Server Error 服务器发生不可预期的错误,导致无法完成客户端的请求。 * 503 Service Unavailable 服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常。 下面是一个HTTP响应的例子: ``` HTTP/1.1 200 OK Server:Apache Tomcat/5.0.12 Date:Mon,6Oct2003 13:23:42 GMT Content-Length:112 ... ``` ### 消息报头 ​ 消息报头分为通用报头、请求报头、响应报头、实体报头等。消息头由键值对组成,每行一对,关键字和值用英文冒号“:”分隔。 #### 通用报头 既可以出现在请求报头,也可以出现在响应报头中 * Date:表示消息产生的日期和时间 * Connection:允许发送指定连接的选项,例如指定连接是连续的,或者指定“close”选项,通知服务器,在响应完成后,关闭连接 * Cache-Control:用于指定缓存指令,缓存指令是单向的(响应中出现的缓存指令在请求中未必会出现),且是独立的(一个消息的缓存指令不会影响另一个消息处理的缓存机制) #### 请求报头 请求报头通知服务器关于客户端求求的信息,典型的请求头有: * Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机 * User-Agent:发送请求的浏览器类型、操作系统等信息 * Accept:客户端可识别的内容类型列表,用于指定客户端接收那些类型的信息 * Accept-Encoding:客户端可识别的数据编码 * Accept-Language:表示浏览器所支持的语言类型 * Connection:允许客户端和服务器指定与请求/响应连接有关的选项,例如这是为Keep-Alive则表示保持连接。 * Transfer-Encoding:告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式。 #### 响应报头 用于服务器传递自身信息的响应,常见的响应报头: * Location:用于重定向接受者到一个新的位置,常用在更换域名的时候 * Server:包含可服务器用来处理请求的系统信息,与User-Agent请求报头是相对应的 #### 实体报头 实体报头用来定于被传送资源的信息,既可以用于请求也可用于响应。请求和响应消息都可以传送一个实体,常见的实体报头为: * Content-Type:发送给接收者的实体正文的媒体类型 * Content-Lenght:实体正文的长度 * Content-Language:描述资源所用的自然语言,没有设置则该选项则认为实体内容将提供给所有的语言阅读 * Content-Encoding:实体报头被用作媒体类型的修饰符,它的值指示了已经被应用到实体正文的附加内容的编码,因而要获得Content-Type报头域中所引用的媒体类型,必须采用相应的解码机制。 * Last-Modified:实体报头用于指示资源的最后修改日期和时间 * Expires:实体报头给出响应过期的日期和时间 ### Charles抓包 ​ Charles是一个HTTP代理服务器,HTTP监视器,反转代理服务器,当程序连接Charles的代理访问互联网时,Charles可以监控这个程序发送和接收的所有数据。它允许一个开发者查看所有连接互联网的HTTP通信,这些包括request, response和HTTP headers (包含cookies与caching信息)。 Charles主要功能: 1. 支持SSL代理。可以截取分析SSL的请求。 2. 支持流量控制。可以模拟慢速网络以及等待时间(latency)较长的请求。 3. 支持AJAX调试。可以自动将json或xml数据格式化,方便查看。 4. 支持AMF调试。可以将Flash Remoting 或 Flex Remoting信息格式化,方便查看。 5. 支持重发网络请求,方便后端调试。 6. 支持修改网络请求参数。 7. 支持网络请求的截获并动态修改。 8. 检查HTML,CSS和RSS内容是否符合W3C标准。 参考:[参考链接](https://cloud.tencent.com/developer/article/1032655). ## HttpClient 与 HttpURLConnection android6.0已经删除了HttpClient。 封装一个类来获取HttpURLConnection ```java public static HttpURLConnection getHttpURLConnection(String url){ HttpURLConnection mHttpURLConnection=null; try { URL mUrl=new URL(url); mHttpURLConnection=(HttpURLConnection)mUrl.openConnection(); //设置链接超时时间 mHttpURLConnection.setConnectTimeout(15000); //设置读取超时时间 mHttpURLConnection.setReadTimeout(15000); //设置请求参数 mHttpURLConnection.setRequestMethod("POST"); //添加Header mHttpURLConnection.setRequestProperty("Connection","Keep-Alive"); //接收输入流 mHttpURLConnection.setDoInput(true); //传递参数时需要开启 mHttpURLConnection.setDoOutput(true); } catch (IOException e) { e.printStackTrace(); } return mHttpURLConnection ; } ``` 发送POST请求 ```java public static void postParams(OutputStream output,ListparamsList) throws IOException{ StringBuilder mStringBuilder=new StringBuilder(); for (NameValuePair pair:paramsList){ if(!TextUtils.isEmpty(mStringBuilder)){ mStringBuilder.append("&"); } mStringBuilder.append(URLEncoder.encode(pair.getName(),"UTF-8")); mStringBuilder.append("="); mStringBuilder.append(URLEncoder.encode(pair.getValue(),"UTF-8")); } BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(output,"UTF-8")); writer.write(mStringBuilder.toString()); writer.flush(); writer.close(); } private void useHttpUrlConnectionPost(String url) { InputStream mInputStream = null; HttpURLConnection mHttpURLConnection = UrlConnManager.getHttpURLConnection(url); try { List postParams = new ArrayList<>(); //要传递的参数 postParams.add(new BasicNameValuePair("username", "moon")); postParams.add(new BasicNameValuePair("password", "123")); UrlConnManager.postParams(mHttpURLConnection.getOutputStream(), postParams); mHttpURLConnection.connect(); mInputStream = mHttpURLConnection.getInputStream(); int code = mHttpURLConnection.getResponseCode(); String respose = converStreamToString(mInputStream); Log.i("wangshu", "请求状态码:" + code + "\n请求结果:\n" + respose); mInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } ``` 开启线程访问 ```java private void useHttpUrlConnectionGetThread() { new Thread(new Runnable() { @Override public void run() { useHttpUrlConnectionPost("http://www.baidu.com"); } }).start(); } ``` ### Volley #### 基本用法 - Volley网络请求队列 - StringRequest的用法 - JsonRequest的用法 - 使用ImageRequest加载图片 - 使用ImageLoader加载图片 - 使用NetworkImageView加载图片 #### 源码解析 - RequestQuene - CacheDispatcher缓存调度线程 - NetworkDispatcher网络调度线程 [Volley用法全解析](http://liuwangshu.cn/application/network/3-volley.html) [从源码解析Volley](http://liuwangshu.cn/application/network/4-volley-sourcecode.html) ### Okhttp OkHttp 是一个适用于 Android、Kotlin 和 Java 应用的 HTTP 和 HTTP/2 客户端,它的使用非常简单,支持阻塞式的同步请求和带回调的异步请求。 参考: ### Retrofit 使用运行时注解的方式提供功能