# 基础知识
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)
- IPv6 有 3 种表示方法,分别是:
- 冒分十六进制表示法:用冒号分割 8 个区块,每个区块 4 个十六进制数字,如
2400:cb00:2048:1:0:0:6ca2:c665
- 0 位压缩表示法:两个冒号标识多个 0 区块,但每个地址中双冒号最多出现一次,如
2001:1234:4567:0000:0000:0000:8888
可缩写为2001:1234:4567::8888
- 冒分十六进制表示法:用冒号分割 8 个区块,每个区块 4 个十六进制数字,如
- 内嵌 IPv4 地址表示法:IPv6 和 IPv4 的混合网络中,IPv6 地址的最后 4 字节有时可用 IPv4 的点分四段地址,如
2001:1234:4567::c0a8:0a64
可以写为2001:1234:4567::192.168.10.100
端口
端口是一个 16 位的整数,用于表示数据交给哪个通信程序处理,分为:- 公认端口:从 0 到 1023
- 注册端口:从 1024 到 49151
- 动态和/或私有端口:从 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();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Socket 通信
Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层,它是一组接口 在设计模式中,Socket 其实就是一个门面模式,它把复杂的 TCP/IP 协议族隐藏在 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 字符串之间的相互转换
String URLDecoder.decode(String s, String enc)
:使用指定字符集将特殊字符串转换成普通字符串(解码)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 对象
- 通过构造器 URI(String str)、URI(String scheme, String userInfo, String host, int port, String path, String query, String fragment)
- 通过静态方法
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 流进行通信
- 三次握手建连、四次挥手断连
建连时,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();
}
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();
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