# 基础知识

OSI七层模型

OSI和TCP分层模型

ICMP 协议,属于网络层协议,主要用于在 IP 主机、路由器之间传递控制消息(网络通不通、主机是否可达、路由是否可用等)。ping 和 tracert 都利用 ICMP 协议来实现网络功能。

  • IP 地址

    • IP 地址是一个 32 位(32bit)或 128 位的二进制数,用于唯一地标识网络中的一个通信实体
    • IPv4
      • IPv4 地址被分成了 A、B、C、D、E 五类
      • IPv4 地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a, b, c, d 都是 0~255 之间的十进制整数
      • 表示本机:127.0.0.1 localhost 本机 IP
      • 本地局域网:10.0.0.0 - 10.255.255.255、192.168.0.0 - 192.168.255.255
      • IPv4 专用地址 (opens new window)
        IPv4专用地址
    • IPv6 有 3 种表示方法,分别是:
      1. 冒分十六进制表示法:用冒号分割 8 个区块,每个区块 4 个十六进制数字,如 2400:cb00:2048:1:0:0:6ca2:c665
      2. 0 位压缩表示法:两个冒号标识多个 0 区块,但每个地址中双冒号最多出现一次,如 2001:1234:4567:0000:0000:0000:8888 可缩写为 2001:1234:4567::8888
    1. 内嵌 IPv4 地址表示法:IPv6 和 IPv4 的混合网络中,IPv6 地址的最后 4 字节有时可用 IPv4 的点分四段地址,如 2001:1234:4567::c0a8:0a64 可以写为 2001:1234:4567::192.168.10.100
  • 端口
    端口是一个 16 位的整数,用于表示数据交给哪个通信程序处理,分为:

    1. 公认端口:从 0 到 1023
    2. 注册端口:从 1024 到 49151
    3. 动态和/或私有端口:从 49152 到 65535
  • 协议
    Java 默认提供了对 file、ftp、gopher、http、https、jar、mailto、netdoc 协议的支持

  • socket(套接字)
    源 IP 地址和目的 IP 地址以及源端口号和目的端口号的组合
    不同主机之间的进程进行双向通信的端点

// 获取非回环地址
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
    NetworkInterface intf = en.nextElement();
    for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
        InetAddress inetAddress = enumIpAddr.nextElement();
        if (!inetAddress.isLoopbackAddress() && !inetAddress.isLinkLocalAddress() && inetAddress.isSiteLocalAddress()) {
            System.out.println(inetAddress.getHostAddress());
        }

    }
}

// org.springframework.cloud.commons.util.InetUtils
InetUtilsProperties target = new InetUtilsProperties();
String ipAddress = new InetUtils(target).findFirstNonLoopbackHostInfo().getIpAddress();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Socket 通信

Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层,它是一组接口 在设计模式中,Socket 其实就是一个门面模式,它把复杂的 TCP/IP 协议族隐藏在 Socket 接口后面,对用户来说,一组简单的接口就是全部,让 Socket 去组织数据,以符合指定的协议

Socket通信

# Java 的基本网络支持

  • java.net 包下

# InetAddress

  • 代表 IP 地址

  • 类方法
    InetAddress getByName(String host):根据主机获取对应的 IP 对象
    InetAddress getLocalHost():类方法,获取本机 IP 地址对应的 IP 对象
    isReachable():测试是否可以在指定时间内到达该地址

  • 实例方法
    String getHostAddress():返回该 InetAddress 实例对应的 IP 地址字符串
    String getHostName():获取此 IP 地址的主机名
    String getCanonicalHostName():获取此 IP 地址的全限定域名

# URLDecoder 和 URLEncoder

  • 用于完成 URL 地址中普通字符串和 application/x-www-form-urlencoded MIME 字符串之间的相互转换
  1. String URLDecoder.decode(String s, String enc):使用指定字符集将特殊字符串转换成普通字符串(解码)
  2. String URLEncoder.encode(String s, String enc):使用指定字符集将普通字符串转换成特殊字符串(编码)(~ ! * ( ) - _ ' . 不会被编码)

# URI 和 URL

  • URI:统一资源标示符(绝对 URI、相对 URI) 语法:[scheme:]scheme-specific-part

  • URL:统一资源定位符,是 URI 的子集;它除了标识资源的位置,还提供一种定位该资源的主要访问机制(如其网络“位置”),即提供具体方式找到该资源(位置+方式)

    • URL 的格式由下列三部分组成:第一部分是协议或称为服务方式(指定低层使用的协议,例如:http、https、ftp),第二部分是存有该资源的主机 IP 地址(有时也包括端口号),第三部分是主机资源的具体地址(如目录和文件名等)
    • 第一部分和第二部分之间用 "😕/" 符号隔开,第二部分和第三部分用 "/" 符号隔开
    • 第一部分和第二部分是不可缺少的,第三部分有时可以省略

# URI

  • URI 实例代表一个统一资源标识符,不能用于定位任何资源,唯一作用是解析(resolve)
  • 创建 URI 对象
    1. 通过构造器 URI(String str)、URI(String scheme, String userInfo, String host, int port, String path, String query, String fragment)
    2. 通过静态方法 URI create(String str)

# URL

  • URL 对象代表一个统一资源定位符,通过定位的方式确定一个资源
  • URL 可以由协议名、主机、端口和资源名组成
    格式:protocol://host:port/resourceName,如 http://www.example.org/index.html
  • 创建 URL 对象
    URL(String spec):根据 String 表示形式创建 URL 对象
    URL(String protocol, String host, int port, String file):根据指定协议、主机、端口号(-1,表示使用指定协议的默认端口)和资源文件(如 "/index.html")创建 URL 对象
  • 访问 URL 对应的资源
    String getFile():获取该 URL 的资源名
    String getHost():获取该 URL 的主机名
    String getPath():获取该 URL 的路径部分
    int getPort():获取该 URL 的端口号
    String getProtocol():获取该 URL 的协议名称
    String getQuery():获取该 URL 的查询字符串部分
    URLConnection openConnection():返回一个 URLConnection 对象,它代表了与 URL 所引用的远程对象的连接
    InputStream openStream():打开与此 URL 的连接,并返回一个用于读取该 URL 资源的输入流

# URLConnection

  • 封装访问远程网络资源一般方法的类

  • 通过 URLConnection 实例向该 URL 发送请求、读取 URL 引用的资源

  • 实例方法
    InputStream getInputStream():返回该 URLConnection 对应的输入流,用于获取 URLConnection 响应的内容
    OutputStream getOutputStream():返回该 URLConnection 对应的输出流,用于向 URLConnection 发送请求参数

  • 发送 GET 请求时只需将请求参数放在 URL 字符串之后,以?隔开,程序直接调用 URLConnection 对象的 connect() 方法即可

  • 发送 POST 请求,则需要先设置 doln 和 doOut 两个请求头字段的值,再使用 URLConnection 对应的输出流来发送请求参数

# 基于 TCP 协议的网络编程

  • TCP/IP 通信协议是一种可靠的网络协议,它在通信的两端各建立一个 Socket,从而在通信的两端之间形成网络虚拟链路
  • TCP 协议:面向连接(经历三次握手)、传输可靠(保证数据正确性、数据顺序)、用于传输大量数据(字节流模式)、速度慢,建立连接需要开销较多(时间、系统资源)
  • IP 协议负责将消息从一个主机传送到另一个主机,消息在传送的过程中被分割成一个个的小包
  • TCP 协议负责收集这些信息包,并将其按适当的次序放好传送
  • 服务器端通过 ServerSocket 建立监听,客户端通过 Socket 连接到指定服务器后,通信双方就可以通过 IO 流进行通信

Socket通信模型

  • 三次握手建连、四次挥手断连

TCP三次握手

TCP四次挥手

建连时,Server 端的 SYN 和 ACK 合并为一次发送,而断连时,两个方向上停止数据发送的时间可能不同,所以不能合并发送 FIN 和 ACK

需要等待 2 倍最大报文段生存时长(2MSL)之后再关闭连接:保证 TCP 协议的全双工连接能够可靠关闭;保证这次连接的重复数据段从网络中消失,防止端口被重用时可能产生数据混淆

# 使用 ServerSocket 创建 TCP 服务器端

  • 构造器
    ServerSocket(int port):用指定的端口 port 来创建一个 ServerSocket
  • 监听来自客户端连接请求
    Socket accept():如果接收到一个客户端 Socket 的连接请求,该方法将返回一个与客户端 Socket 对应的 Socket,否则该方法将一直处于等待状态,线程也被阻塞
    InetAddress getInetAddress():返回此服务器端的 IP 对象
  • 服务器端应该为每个 Socket 单独启动一个线程,每个线程负责与一个客户端进行通信
// 创建一个 ServerSocket,用于监听客户端 Socket 的连接请求
ServerSocket ss = new ServerSocket(8888);
// 采用循环不断接受来自客户端的请求
while (true) {
    // 每当接受到客户端 Socket 的请求,服务器端也对应产生一个 Socket
    Socket s = ss.accept();
    System.out.println("连接的客户端地址:" + s.getInetAddress());

    BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
    System.out.println("客户端传来数据:" + in.readLine());

    PrintStream out = new PrintStream(s.getOutputStream(), true);
    out.println("hello!"); // 发送消息

    in.close();
    out.close();
    s.close();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 使用 Socket 进行通信

  • Java 使用 Socket 对象来代表两端的通信端口,并通过 Socket 产生 IO 流来进行网络通信
  • 使用 Socket 的构造器来连接到指定服务器
    Socket(InetAddress/String remoteAddress, int port):创建连接到指定远程主机、远程端口的 Socket,使用本地主机的默认 IP 地址,系统动态分配的端口
    Socket(InetAddress/String remoteAddress, int port, InetAddress localAddr, int localPort):创建连接到指定远程主机、远程端口的 Socket,并指定本地 IP 地址和本地端口
  • 获取输入流和输出流
    InputStream getInputStream():返回该 Socket 对象对应的输入流,让程序通过该输入流从 Socket 中取出数据
    OutputStream getOutputStream():返回该 Socket 对象对应的输出流,让程序通过该输出流向 Socket 中输出数据
    InetAddress getInetAddress():返回该 Socket 对象的的 IP 对象
  • 客户端应该单独启动一个线程,该线程专门负责读取服务器端数据
Socket socket = new Socket("127.0.0.1", 8888);

PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println(new BufferedReader(new InputStreamReader(System.in)).readLine()); // 发送消息

BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("服务端传来的数据:" + in.readLine());

out.close();
in.close();
socket.close();
1
2
3
4
5
6
7
8
9
10
11

# 基于 UDP 协议的网络编程

  • UDP 协议:面向无连接、传输不保证可靠(丢包)、用于传输少量数据(数据包模式)、速度快。发送端和接收端
  • UDP 协议的主要作用是完成网络数据流和数据包之间的转换
  • 使用 DatagramSocket 来发送、接收数据包(DatagramPacket)
    使用 MulticastSocket 来实现多点广播通信

# 使用 DatagramSocket 发送、接收数据包

  • DatagramSocket 的构造器
    DatagramSocket():创建一个 DatagramSocket 实例,并将该对象绑定到本机默认 IP 地址、系统动态分配的端口
    DatagramSocket(int port, InetAddress laddr):创建一个 DatagramSocket 实例,并将该对象绑定到指定 IP 地址、指定端口

  • 接收和发送数据
    void receive(DatagramPacket p):从该 DatagramSocket 中接收数据包
    void send(DatagramPacket p):以该 DatagramSocket 对象向外发送数据包

# 使用 DatagramPacket 来代表数据包

  • DatagramPacket 的构造器
    DatagramPacket(byte[]buf, int length):以一个空数组来创建 DatagramPacket 对象,该对象的作用是接收 DatagramSocket 中的数据
    DatagramPacket(byte[] buf, int offset, int length):以一个空数组来创建 DatagramPacket 对象,并指定接收到的数据放入 buf 数组中时从 offset 开始,最多放 length 个字节
    DatagramPacket(byte[] buf, int length, InetAddress addr, int port):以包含数据的数组来创建一个用于发送的 DatagramPacket 对象,创建该 DatagramPacket 对象时还指定了 IP 地址和端口(该数据包的目的地)
    DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port):以包含数据的数组来创建一个用于发送的 DatagramPacket 对象,指定发送 buf 数组中从 offset 开始,总共 length 个字节

  • 获取发送者的 IP 地址和端口
    InetAddress getAddress():返回此数据包的目标机器/发送主机的 IP 地址
    int getPort():返回此数据包的目标机器/发送主机的端口
    SocketAddress getSocketAddress():返回此数据包的目标 SocketAddress 或发送此数据包的主机的 SocketAddress

Updated at: 2021-06-10 10:27:21