网络模型概述和图解

网络模型概述

  • 计算机网络之间以何种规则进行通信,就是网络模型研究问题。

  • 网络模型一般是指OSI(Open System Interconnection开放系统互连)七层参考模型

  • TCP/IP四层参考模型主机至网络层(物理层 , 数据链路层) , 网际层 , 传输层 , 应用层(应用层 , 表示层 , 会话层)

网络模型7层概述

  • 1.物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后在转化为1、0)。这一层的数据叫做比特。
  • 2.数据链路层:主要将从物理层接收的数据进行MAC地址(网卡的地址)的封装与解封装。常把这一层的数据叫做帧。在这一层工作的设备是交换机,数据通过交换机来传输。
  • 3.网络层:主要将从下层接收到的数据进行IP地址(例192.168.0.1)的封装与解封装。在这一层工作的设备是路由器,常把这一层的数据叫做数据包。
  • 4.传输层:定义了一些传输数据的协议和端口号(WWW端口80等),如:TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的).主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。常常把这一层数据叫做段。
  • 5.会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。
    • 主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识可以是IP也可以是MAC或者是主机名)
  • 6.表示层:主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等(也就是把计算机能够识别的东西转换成人能够能识别的东西(如图片、声音等)。
  • 7.应用层: 主要是一些终端的应用,比如说FTP(各种文件下载),WEB(IE浏览),QQ之类的(可以把它理解成我们在电脑屏幕上可以看到的东西.就是终端应用)。

网络编程三要素

  • A:IP地址:InetAddress: 网络中设备的标识,不易记忆,可用主机名
  • B:端口号: 用于标识进程的逻辑地址,不同进程的标识
  • C:传输协议: 通讯的规则常见协议:TCP,UDP

端口

  • 物理端口 网卡口
  • 逻辑端口 我们指的就是逻辑端口
    • a:每个网络程序都会有一个逻辑端口
    • b:用于标识进程的逻辑地址,不同进程的标识
    • c:有效端口:065535,其中01024系统使用或保留端口。

协议

  • UDP
    • 将数据源和目的封装成数据包中,不需要建立连接;
    • 每个数据报的大小在限制在64k;
    • 因无连接,是不可靠协议;
    • 不需要建立连接,速度快
  • TCP
    • 建立连接,形成传输数据的通道;
    • 在连接中进行大数据量传输;
    • 需要连接所以是可靠协议;
    • 必须建立连接,效率会稍低

Socket通信原理图解

  • A:Socket套接字概述:
    • 网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
  • B:Socket原理机制:
    • 通信的两端都有Socket。
    • 网络通信其实就是Socket间的通信。
    • 数据在两个Socket间通过IO传输。

网络体系结构

  • 即经常看到的计算机网络体系的分层结构,理清这个还是有必要的,防止对Http和Tcp两个根本不在同一层的协议纠缠不清。根据不同的参考模型,分层结构有几个不同的版本,如OSI模型以及TCP/IP模型,下面就以比较经常看到的的5层结构为例

    1
    2
    3
    4
    5
    6
    分层
    应用层 (HTTP、FTP、DNS、SMTP等等)
    运输层 (TCP、UDP)
    网络层 (IP等)
    数据链路层 (ARP等)
    物理层
  • 五层的体系结构至上往下,最终可以实现端对端之间的数据传输与通信,他们各自负责一些什么,最终如何实现端对端之间的通信?

    • 1.应用层:如http协议,它实际上是定义了如何包装和解析数据,应用层是http协议的话,则会按照协议规定包装数据,如按照请求行、请求头、请求体包装,包装好数据后将数据传至运输层。

    • 2.运输层:运输层有TCP和UDP两种协议,分别对应可靠的运输和不可靠的运输,如TCP因为要提供可靠的传输,所以内部要解决如何建立连接、如何保证传输是可靠的不丢数据、如何调节流量控制和拥塞控制。关于这一层,我们平常一般都是和Socket打交道,Socket是一组封装的编程调用接口,通过它,我们就能操作TCP、UDP进行连接的建立等。我们平常使用Socket进行连接建立的时候,一般都要指定端口号,所以这一层指定了把数据送到对应的端口号。

    • 3.网络层:这一层IP协议,以及一些路由选择协议等等,所以这一层的指定了数据要传输到哪个IP地址。中间涉及到一些最优线路,路由选择算法等等。

    • 4.数据链路层:印象比较深的就是ARP协议,负责把IP地址解析为MAC地址,即硬件地址,这样就找到了对应的唯一的机器。

    • 5.物理层:这一层就是最底层了,提供二进制流传输服务,也就是也就是真正开始通过传输介质(有线、无线)开始进行数据的传输了。

    • 所以通过上面五层的各司其职,实现物理传输介质–MAC地址–IP地址–端口号–获取到数据根据应用层协议解析数据最终实现了网络通信和数据传输。

Http相关知识点

无连接与无状态

  • 无连接并不是说不需要连接,Http协议只是一个应用层协议,最终还是要靠运输层的如TCP协议向上提供的服务进行连接。
  • 无连接的含义是:http约定了每次连接只处理一个请求,一次请求完成后就断开连接,这样主要是为了缓解服务器的压力,减小连接对服务器资源的占用。我的理解是,建立连接实际上是运输层的事,面向应用层的http来说的话,它就是无连接的,因为上层对下层无感知。
  • 无状态的指:每个请求之间都是独立的,对于之前的请求事务没有记忆的能力。所以就出现了像Cookie这种,用来保存一些状态的东西。

HTTP的缓存机制

  • Http的缓存主要利用header里的两个字段来控制

  • Cache-control主要包含以及几个字段:

    1
    2
    3
    4
    5
    private:则只有客户端可以缓存
    public:客户端和代理服务器都可以缓存
    max-age:缓存的过期时间
    no-cache:需要使用对比缓存来验证缓存数据
    no-store:所有内存都不会进行缓存
  • 实际上就是在这里面设置了一个缓存策略,由服务端第一次通过header下发给客户端,可以看到:

  • max-age即缓存过期的时间,则之后再次请求,如果没有超过缓存失效的时间则可以直接使用缓存。

  • no-cache:表示需要使用对比缓存来验证缓存数据,如果这个字段是打开的,则就算max-age缓存没有失效,则还是需要发起一次请求向服务端确认一下资源是否有更新,是否需要重新请求数据,至于怎么做对比缓存,就是下面要说的Etag的作用。如果服务端确认资源没有更新,则返回304,取本地缓存即可,如果有更新,则返回最新的资源。

  • no-store:这个字段打开,则不会进行缓存,也不会取缓存。

  • ETag:即用来进行对比缓存,Etag是服务端资源的一个标识码

  • 当客户端发送第一次请求时服务端会下发当前请求资源的标识码Etag,下次再请求时,客户端则会通过header里的If-None-Match将这个标识码Etag带上,服务端将客户端传来的Etag与最新的资源Etag做对比,如果一样,则表示资源没有更新,返回304。

  • 通过Cache-control和Etag的配合来实现Http的缓存机制。

短连接与长连接

  • 当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
  • 长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
  • 从 HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 Connection : close
  • 在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 Connection : Keep-Alive

缓存

  • 优点

    • 缓解服务器压力;
    • 降低客户端获取资源的延迟:缓存通常位于内存中,读取缓存的速度更快。并且缓存在地理位置上也有可能比源服务器来得近,例如浏览器缓存。
  • 实现方法

  • 让代理服务器进行缓存;

  • 让客户端浏览器进行缓存。

  • Cache-Control

    • HTTP/1.1 通过 Cache-Control 首部字段来控制缓存。
  • 禁止进行缓存

    • no-store 指令规定不能对请求或响应的任何一部分进行缓存。
    1
    Cache-Control: no-store
  • 强制确认缓存

    • no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源的有效性,只有当缓存资源有效才将能使用该缓存对客户端的请求进行响应。
    1
    Cache-Control: no-cache
  • 私有缓存和公共缓存

    • private指令规定了将资源作为私有缓存,只能被单独用户所使用,一般存储在用户浏览器中。
    1
    Cache-Control: private
    • public 指令规定了将资源作为公共缓存,可以被多个用户所使用,一般存储在代理服务器中。
    1
    Cache-Control: public
  • 缓存过期机制

    • max-age指令出现在请求报文中,并且缓存资源的缓存时间小于该指令指定的时间,那么就能接受该缓存。
    • max-age指令出现在响应报文中,表示缓存资源在缓存服务器中保存的时间。
    1
    Cache-Control: max-age=31536000
    • Expires首部字段也可以用于告知缓存服务器该资源什么时候会过期。
    1
    Expires: Wed, 04 Jul 2012 08:26:05 GMT
    • 在 HTTP/1.1 中,会优先处理 max-age 指令;
    • 在 HTTP/1.0 中,max-age 指令会被忽略掉。
  • 缓存验证

    • 需要先了解ETag首部字段的含义,它是资源的唯一标识。URL 不能唯一表示资源,例如 http://www.google.com/ 有中文和英文两个资源,只有 ETag 才能对这两个资源进行唯一标识。
    1
    ETag: "82e22293907ce725faf67773957acd12"
    • 可以将缓存资源的ETag值放入If-None-Match 首部,服务器收到该请求后,判断缓存资源的 ETag 值和资源的最新ETag值是否一致,如果一致则表示缓存资源有效,返回 304 Not Modified。
    1
    If-None-Match: "82e22293907ce725faf67773957acd12"
    • Last-Modified首部字段也可以用于缓存验证,它包含在源服务器发送的响应报文中,指示源服务器对资源的最后修改时间。但是它是一种弱校验器,因为只能精确到一秒,所以它通常作为 ETag 的备用方案。如果响应首部字段里含有这个信息,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存。服务器只在所请求的资源在给定的日期时间之后对内容进行过修改的情况下才会将资源返回,状态码为 200 OK。如果请求的资源从那时起未经修改,那么返回一个不带有消息主体的 304 Not Modified 响应。

      1
      2
      3
      Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT

      If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT

Https相关知识点

HTTPS为什么安全

  • Https=Http+Ssl
  • Https保证了我们数据传输的安全,Https=Http+Ssl
  • 之所以能保证安全主要的原理就是利用了非对称加密算法,平常用的对称加密算法之所以不安全,是因为双方是用统一的密匙进行加密解密的,只要双方任意一方泄漏了密匙,那么其他人就可以利用密匙解密数据。
  • 非对称加密算法之所以能实现安全传输的核心精华就是:公钥加密的信息只能用私钥解开,私钥加密的信息只能被公钥解开。

非对称加密算法为什么安全

  • 简述非对称加密算法为什么安全:
  • 服务端申请CA机构颁发的证书,则获取到了证书的公钥和私钥,私钥只有服务器端自己知道,而公钥可以告知其他人,如可以把公钥传给客户端,这样客户端通过服务端传来的公钥来加密自己传输的数据,而服务端利用私钥就可以解密这个数据了。由于客户端这个用公钥加密的数据只有私钥能解密,而这个私钥只有服务端有,所以数据传输就安全了。
  • 常用的对称加密算法有:DES、3DES、RC2、RC4、AES
  • 常用的非对称加密算法有:RSA、DSA、ECC、DSS
  • 使用单向散列函数的加密算法:MD5、SHA
  • 上面只是简单说了一下非对称加密算法是如何保证数据安全的,实际上Https的工作过程远比这要复杂

HTTPS问题答疑

  • 问题1:客户端如何验证服务器证书的合法性的问题?

  • 一个是客户端还需要验证服务端传来的CA证书的合法性、有效性,因为存在传输过程CA证书被人调包的风险,涉及到客户端如何验证服务器证书的合法性的问题,保证通信双方的身份合法。

  • 问题2:非对称算法虽然保证了数据的安全,但是效率相对于对称算法来说比较差,如何来优化,实现既保证了数据的安全,又提高了效率。

    • 在正式的使用场景中一般都是对称加密和非对称加密结合使用,
    • 使用非对称加密完成秘钥的传递,然后使用对称秘钥进行数据加密和解密。
    • 二者结合既保证了安全性,又提高了数据传输效率。
    • 客户端请求后,服务端响应
      1. 客户端告诉服务端算法和对称密钥,用公钥加密(对称加密的算法和对称密钥)
      2. 服务器收到信息后,用私钥解密,提取出对称加密算法和对称密钥后,服务器发消息(对称密钥加密)
      3. 后续两者之间信息的传输就可以使用对称加密的方式了
      4. 但是这种方式还是会存在一种安全性;
        • 中间人虽然不知道服务器的私钥,但是可以做到偷天换日,将拦截服务器的公钥key;并将自己生成的一对公/私钥的公钥发送给客户端;
        • 客户端并不知道公钥已经被偷偷换过;按照之前的流程,B通过公钥加密自己生成的对称加密秘钥key2;发送给服务器;
        • 这次通信再次被中间人拦截,尽管后面的通信,两者还是用key2通信,但是中间人已经掌握了Key2;可以进行轻松的加解密;还是存在被中间人攻击风险;
        • 解决困境:权威的证书颁发机构CA来解决;
  • 问题3:客户端如何验证证书合法性

    • 现实生活中,政府发的身份证可以证明一个人的身份,对应到计算机世界里,也有类似政府的这么一个发证机构,它叫Certificate Authority,简称CA,CA为每个使用公钥的用户发放一个数字证书数字证书可以证明公钥的合法性。

    • 制作证书:

      • CA对网站的资料和公钥使用hash函数得到摘要,再用CA的私钥将摘要加密生成数字签名

      • CA将网站的资料和公钥以及数字签名再加上证书有效期,生成数字证书

    • 校验证书真伪:

      • 假设数字签名被篡改,数字签名就无法用CA的公钥解密;
      • 假设数字证书的公钥被篡改,将数字证书的公钥和网站信息使用相同的hash函数得到hash值A,然后用CA的公钥解密数字签名得到hash值B,对比A和B,如果不一样,说明公钥被篡改

HTTP协议Keep-Alive模式

什么是Keep-Alive模式?

  • 我们知道HTTP协议采用“请求-应答”模式,当使用普通模式,即非KeepAlive模式时,每个请求/应答客户和服务器都要新建一个连接,完成 之后立即断开连接(HTTP协议为无连接的协议);当使用Keep-Alive模式(又称持久连接、连接重用)时,Keep-Alive功能使客户端到服 务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。
  • http 1.0中默认是关闭的,需要在http头加入”Connection: Keep-Alive”,才能启用Keep-Alive;http 1.1中默认启用Keep-Alive,如果加入”Connection: close “,才关闭。目前大部分浏览器都是用http1.1协议,也就是说默认都会发起Keep-Alive的连接请求了,所以是否能完成一个完整的Keep- Alive连接就看服务器设置情况。

启用Keep-Alive的优点

  • 启用Keep-Alive模式肯定更高效,性能更高。因为避免了建立/释放连接的开销。

Keep-Alive方式下如何判断消息内容/长度的大小?

  • Keep-Alive模式,客户端如何判断请求所得到的响应数据已经接收完成(或者说如何知道服务器已经发生完了数据)?我们已经知道 了,Keep-Alive模式发送玩数据HTTP服务器不会自动断开连接,所有不能再使用返回EOF(-1)来判断(当然你一定要这样使用也没有办法,可 以想象那效率是何等的低)!下面我介绍两种来判断方法。
  • 使用消息首部字段Conent-Length
    • Conent-Length表示实体内容长度,客户端(服务器)可以根据这个值来判断数据是否接收完成。但是如果消息中没有Conent-Length,那该如何来判断呢?又在什么情况下会没有Conent-Length呢?请继续往下看……
  • 使用消息首部字段Transfer-Encoding
    • 当客户端向服务器请求一个静态页面或者一张图片时,服务器可以很清楚的知道内容大小,然后通过Content-length消息首部字段告诉客户端 需要接收多少数据。但是如果是动态页面等时,服务器是不可能预先知道内容大小,这时就可以使用Transfer-Encoding:chunk模式来传输 数据了。即如果要一边产生数据,一边发给客户端,服务器就需要使用”Transfer-Encoding: chunked”这样的方式来代替Content-Length。
    • chunk编码将数据分成一块一块的发生。Chunked编码将使用若干个Chunk串连而成,由一个标明长度为0 的chunk标示结束。每个Chunk分为头部和正文两部分,头部内容指定正文的字符总数(十六进制的数字 )和数量单位(一般不写),正文部分就是指定长度的实际内容,两部分之间用回车换行(CRLF) 隔开。在最后一个长度为0的Chunk中的内容是称为footer的内容,是一些附加的Header信息(通常可以直接忽略)。

url特性

拼接url链接字符串工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 拼接字符串
* @param url url
* @param map map集合
* @return
*/
public static String getUrl(String url, HashMap<String, String> map){
if(TextUtils.isEmpty(url)){
return null;
}
//解析一个url
Uri uri = Uri.parse(url);
Uri.Builder builder = uri.buildUpon();
if (map != null && map.size() > 0) {
//使用迭代器进行遍历
for (Object o : map.entrySet()) {
Map.Entry entry = (Map.Entry) o;
String key = (String) entry.getKey();
String value = (String) entry.getValue();
//对键和值进行编码,然后将参数追加到查询字符串中。
builder.appendQueryParameter(key, value);
}
}
return builder.toString();
}

获取Uri对象的各种属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//解析一个url
Uri uri = Uri.parse(url);
// 完整的url信息
String urlStr = uri.toString();
Log.e( "UrlUtils","url: " + urlStr);
// scheme部分
String scheme = uri.getScheme();
Log.e( "UrlUtils","scheme: " + scheme);
// host部分
String host = uri.getHost();
Log.e( "UrlUtils","host: " + host);
//port部分
int port = uri.getPort();
Log.e( "UrlUtils","port: " + port);
// 访问路劲
String path = uri.getPath();
Log.e( "UrlUtils","path: " + path);
List<String> pathSegments = uri.getPathSegments();
Log.e( "UrlUtils","pathSegments: " + pathSegments.toString());
// Query部分
String query = uri.getQuery();
Log.e( "UrlUtils","query: " + query);
//获取此URI的解码权限部分。对于服务器地址,权限的结构如下:Examples: "google.com", "bob@google.com:80"
String authority = uri.getAuthority();
Log.e( "UrlUtils","authority: " + authority);
//从权限获取已解码的用户信息。例如,如果权限为“任何人@google.com”,此方法将返回“任何人”。
String userInfo = uri.getUserInfo();
Log.e( "UrlUtils","userInfo: " + userInfo);


//UrlUtils: url: https://m.dev.haowumc.com/app/financialManagement
//UrlUtils: scheme: https
//UrlUtils: host: m.dev.haowumc.com
//UrlUtils: port: -1
//UrlUtils: path: /app/financialManagement
//UrlUtils: pathSegments: [app, financialManagement]
//UrlUtils: query: null
//UrlUtils: authority: m.dev.haowumc.com
//UrlUtils: userInfo: null

其他知识点

URL编码

  • http协议中请求的url不支持中文和特殊字符(如&?),所以需要对url进行编码和解码,编码使用的是URLEncoder,解码使用的是URLDecoder;

    1
    2
    3
    4
    //进行url编码
    URLEncoder.encode(url)
    //进行url解码
    URLDecoder.decode(encodeUrl)

Http 2.0

  • Okhttp支持配置使用Http 2.0协议
  • Http2.0相对于Http1.x来说提升是巨大的,主要有以下几点:
  • 二进制格式:http1.x是文本协议,而http2.0是二进制以帧为基本单位,是一个二进制协议,一帧中除了包含数据外同时还包含该帧的标识:StreamIdentifier,即标识了该帧属于哪个request,使得网络传输变得十分灵活。多路复用:一个很大的改进,原先http1.x一个连接一个请求的情况有比较大的局限性,也引发了很多问题,如建立多个连接的消耗以及效率问题。
  • http1.x为了解决效率问题,可能会尽量多的发起并发的请求去加载资源,然而浏览器对于同一域名下的并发请求有限制,而优化的手段一般是将请求的资源放到不同的域名下来突破这种限制。
  • 而http2.0支持的多路复用可以很好的解决这个问题,多个请求共用一个TCP连接,多个请求可以同时在这个TCP连接上并发,一个是解决了建立多个TCP连接的消耗问题,一个也解决了效率的问题。
  • 那么是什么原理支撑多个请求可以在一个TCP连接上并发呢?基本原理就是上面的二进制分帧,因为每一帧都有一个身份标识,所以多个请求的不同帧可以并发的无序发送出去,在服务端会根据每一帧的身份标识,将其整理到对应的request中。
  • header头部压缩:主要是通过压缩header来减少请求的大小,减少流量消耗,提高效率。因为之前存在一个问题是,每次请求都要带上header,而这个header中的数据通常是一层不变的。
    支持服务端推送

TCP和UDP的区别

TCP和UDP的区别

  • ①UDP协议:
    • 面向无连接
    • 每个数据报的大小在限制在64k内
    • 因为是面向无连接,所以是不可靠协议
    • 不需要建立连接,速度快
  • ②TCP协议:
    • 必须建立连接,形成传输数据的通道
    • 在连接中可进行大数据量传输
    • 通过三次握手完成连接,是可靠协议
    • 必须建立连接,效率会稍低
  • 注:三次握手:
    • 第一次:我问你:在么?
    • 第二次:你回答:在。
    • 第三次:我反馈:哦,我知道你在。

Socket介绍

  • ocket简单介绍
    • Socket就是为网络服务提供的一种机制
    • 通信的两端都有Socket
    • 网络通信其实就是Socket间的通信
    • 数据在两个Socket间通过IO传输
    • 玩Socket主要就是记住流程,代码查文档就行
    • Socket的简单使用的话应该都会,两个端各建立一个Socket,服务端的叫ServerSocket,然后建立连接即可。

Socket工作图解

UDP协议传输数据

客户端发送数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 端口号
*/
private static final int PORT = 8080;

/*
* UDP协议发送数据:
* 1.创建发送端Socket对象
* 2.创建数据,并把数据打包
* 3.调用Socket对象发送方法发送数据包
* 4.释放资源
*/
private void udpSendMessage(String serverAddress) {
String content = "yang";
// 创建发送端Socket对象
try {
// 创建发送端Socket对象
DatagramSocket ds = new DatagramSocket();
// 创建数据,并把数据打包
byte[] bys = content.getBytes();
InetAddress byName = InetAddress.getByName(serverAddress);
DatagramPacket dp = new DatagramPacket(bys, bys.length,byName ,PORT);
// 调用Socket对象发送方法发送数据包
ds.send(dp);
// 释放资源
ds.close();
} catch (Exception e) {
e.printStackTrace();
}
}

服务端接收数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
* UDP协议接收数据:
* 1.创建接收端Socket对象
* 2.创建一个数据包(接收容器)
* 3.调用Socket对象接收方法接收数据包
* 4.解析数据包
* 5.释放资源
*/
private void receive_udp() {
try {
// 创建接收端Socket对象
DatagramSocket ds = new DatagramSocket(PORT);
// 创建一个数据包(接收容器)
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
// 调用Socket对象接收方法接收数据包
ds.receive(dp);
// 获取对方的ip
String ip = dp.getAddress().getHostAddress();
// 解析数据
String data = new String(dp.getData(), 0, dp.getLength());
Message message = new Message();
message.obj = data;
message.what = 2;
handler.sendMessage(message);
// 关闭数据库
ds.close();
} catch (Exception e) {
e.printStackTrace();
}
}

TCP协议传输数据

客户端发送数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 端口号
*/
private static final int PORT = 8080;

/**
* @param serverAddress 要发送到服务端的ip
* 1.创建socket并指定ip和端口号
* 2.获取输出流,写数据
* 3.释放资源
* 4.Tcp一定要先开接收端
*/
public void send_tcp(String serverAddress) {
try {
Socket s = new Socket(serverAddress, PORT);
//为了发送数据,应该获得socket流中的输出流
OutputStream out = s.getOutputStream();
String content = "yang";
out.write(content.getBytes());
s.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

服务端接收数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* TCP协议接收数据
* 1.创建接收端的Socket对象
* 2.监听客户端接收,返回一个Socket对象
* 3.获取输入流,读取数据显示在控制台
* 4.释放资源
*/
public void receive_tcp() {
try {
//1.建立连接,监听端口
ServerSocket ss = new ServerSocket(PORT);
//2.连接客户端对象
while (true) {
//阻塞式方法,只有客户端连接了才会继续往下运行
Socket accept = ss.accept();
//获取ip
String ip = accept.getInetAddress().getHostAddress();
//3.获取客户端发送过来的数据
InputStream in = accept.getInputStream();
//4.开始读取,获取输入信息
BufferedReader bff = new BufferedReader(new InputStreamReader(in));
//读取信息
String line;
final StringBuilder sb = new StringBuilder();
while ((line = bff.readLine()) != null) {
sb.append(line);
}
Message message = new Message();
message.obj = sb.toString();
handler.sendMessage(message);
//5.关闭
//ss.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}

三次握手建立连接

三次握手原理图

  • 第一次:发送SNY=1表示此次握手是请求建立连接的,然后seq生成一个客户端的随机数X
  • 第二次:发送SNY=1,ACK=1表示是回复请求建立连接的,然后ack=客户端的seq+1(这样客户端收到后就能确认是之前想要连接的那个服务端),然后把服务端也生成一个代表自己的随机数seq=Y发给客户端。
  • 第三次:ACK=1。 seq=客户端随机数+1,ack=服务端随机数+1(这样服务端就知道是刚刚那个客户端了)

为什么建立连接需要三次握手

  • 首先非常明确的是两次握手是最基本的,
    • 第一次握手,C端发了个连接请求消息到S端,S端收到后S端就知道自己与C端是可以连接成功的
    • 但是C端此时并不知道S端是否接收到这个消息,所以S端接收到消息后得应答,C端得到S端的回复后,才能确定自己与S端是可以连接上的,这就是第二次握手。
  • C端只有确定了自己能与S端连接上才能开始发数据。所以两次握手肯定是最基本的。
  • 那么为什么需要第三次握手呢?假设一下如果没有第三次握手,而是两次握手后我们就认为连接建立,那么会发生什么?
  • 第三次握手是为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误
  • 具体情况就是:
  • C端发出去的第一个网络连接请求由于某些原因在网络节点中滞留了,导致延迟,直到连接释放的某个时间点才到达S端,这是一个早已失效的报文,但是此时S端仍然认为这是C端的建立连接请求第一次握手,于是S端回应了C端,第二次握手。
  • 如果只有两次握手,那么到这里,连接就建立了,但是此时C端并没有任何数据要发送,而S端就会傻傻的等待着,造成很大的资源浪费。所以需要第三次握手,只有C端再次回应一下,就可以避免这种情况。

四次握手断开连接

  • 如图所示:
  • 经过上面的建立连接图的解析,这个图应该不难看懂,这里主要有一个问题:为什么比建立连接时多了一次握手?
  • 可以看到这里服务端的ACK(回复客户端)和FIN(终止)消息并不是同时发出的,而是先ACK,然后再FIN,这也很好理解,当客户端要求断开连接时,此时服务端可能还有未发送完的数据,所以先ACK,然后等数据发送完再FIN。这样就变成了四次握手了。