Java 网络编程
本文将介绍 Java 中的网络通信。
一、基础知识
1. 计算机网络
计算机网络,指将计算机等设备用通信线路互连成网络系统,从而实现众多设备之间信息的互通。
- 根据计算机网络的规模大小和延伸范围,可以分为:
- 局域网 LAN
- 城域网 MAN
- 广域网 WAN
- 根据计算机网络的拓扑结构,可以分为:
- 星型网络
- 总线型网络
- 环型网络
- 树型网络
- 星型环型网络
- 根据计算机网络的传输介质,可以分为:
- 双绞线网络
- 同轴电缆网
- 光纤网
- 卫星网
2. 计算机网络分层
根据国际标准化组织提出的“开放系统互连参考模型”,计算机网络被分为:
- 物理层
- 数据链路层
- 网络层
- 传输层
- 会话层
- 表示层
- 应用层
3. 网络编程三要素
IP 地址
端口
协议
4. IP 地址
IP 地址用于唯一地标识网络中的通信实体,两个通信实体不能共用 IP 地址。
在基于 IP 协议网络中进行网络传输,都应该使用 IP 地址进行设备的标识。
5. 端口
网络中的通信往往是应用程序之间的通信,并且一个通信实体往往有着多个应用程序。
例如:
一台电脑,安装了 QQ 和 微信,QQ 需要与 QQ 服务器进行通信,微信需要与微信服务器进行通信。
端口用于唯一地标识通信实体中的应用程序,两个应用程序不能共用端口。
端口号可以为 0~65535
,通常分为以下三类:
- 公认端口:
0~1023
- 注册端口:
1024~49151
- 动态端口/私有端口:
49152~65535
6. 协议
在计算机网络中,连接和通信的规则被称为网络通信协议。它对数据的传输格式、传输速率、传输步骤等做了统一的规定,只有通信双方均遵守规定才能完成数据通信。
常见的协议有:
- UDP 协议:不可靠,差错控制开销小,传输大小限制在 64kb 以下,不需要建立连接
- TCP 协议:可靠,差错控制开销大,传输大小无限制,需要建立连接
二、InetAddress
1. 什么是 InetAddress?
Java 提供了 InetAddress 类用于标识 IP 地址。
2. 获取 InetAddress 对象
InetAddress 类没有构造方法,通过静态方法来获取 InetAddress 对象。
方法 | 说明 |
---|---|
getByName(String str) | 根据主机名 / IP 地址的字符串获取对象 |
getByAddress(byte[] bytes) | 根据原始 IP 地址获取对象 |
getLocalHost() | 获取本机 IP 地址对应的对象 |
3. 常用方法
方法 | 说明 |
---|---|
getHostAddress() | 获取字符串形式的 IP 地址 |
getHostName() | 获取 IP 地址对应的主机名 |
isReachable(int time) | 判断是否能够 |
三、UDP 通信
1. UDP 协议
UDP 协议,又称用户数据报协议,是无连接通信协议,是一种不可靠的网络协议。在传输时,数据的发送端和接收端不建立逻辑连接。
当发送端发送数据时,不会事先确认接收端是否存在,也不会管接收端是否受到数据;
当接收端接收数据时,并不会向发送端发出反馈。
使用 UDP 协议进行通信,消耗资源小,通信效率高,因此适用于实时性强、数据完整性要求不高的数据传输。
2. Java 中的 UDP
使用 DatagramSocket 代表 UDP 中的 Socket,用于发送和接收数据报;
使用 DatagramPacket 代表 UDP 中的 数据报,用于被发送和接收。
3. DatagramSocket
(1) 构造器
方法 | 说明 |
---|---|
DatagramSocket() | 创建对象,并将对象绑定至本机 IP,随机端口 |
DatagramSocket(int prot) | 创建对象,并将对象绑定至本机 IP,指定端口 |
DatagramSocket(int prot, InetAddress inetAddress) | 创建对象,并将对象绑定至指定 IP,指定端口 |
(2) 常用方法
方法 | 说明 |
---|---|
send(DatagramPacket datagramPacket) | 发送数据报 |
receive(DatagramPacket datagramPacket) | 接收数据报 |
需要注意的是:
DatagramSocket 发送数据和接收数据时仅填入一个数据报参数,DatagramSocket 并不关心数据的来源和去向,只是进行了发送和接收的动作。
4. DatagramPacket
(1) 构造器
用于发送的 DatagramPacket 对象:
方法 | 说明 |
---|---|
DatagramPacket(byte[] bytes, int length, InetAddress inetAddress, int prot) | 用 bytes 数组中 0~length-1 的数据建立 DatagramPacket 对象,并指定去向 |
DatagramPacket(byte[] bytes, int offset, int length, InetAddress inetAddress, int prot) | 用 bytes 数组中 offset~length-1 的数据建立 DatagramPacket 对象,并指定去向 |
用于接收的 DatagramPacket 对象:
方法 | 说明 |
---|---|
DatagramPacket(byte[] bytes, int length) | 用于接收数据的对象,数据会放入 bytes 数组的 0~length-1 处 |
DatagramPacket(byte[] bytes, int offset, int length) | 用于接收数据的对象,数据会放入 bytes 数组的 offset~length-1 处 |
(2) 常用方法
虽然 UDP 协议并不会在发送和接收端之间建立通信,但可以通过 DatagramPacket 的方法来获取部分信息。
方法 | 说明 |
---|---|
getAddress() | 在发送端,获取目标 IP ;在接收端,获取发送端 IP |
getProt() | 在发送端,获取目标端口;在接收端,获取发送端端口 |
5. 发送数据
- 用数据建立 DatagramPacket 对象,并指定去向
- 创建 DatagramSocket 对象
- 调用 DatagramSocket 对象的
send()
方法发送数据 - 关闭 DatagramSocket 对象
1 |
|
6. 接收数据
创建用于接收数据的 DatagramSocket 对象,指定端口号
创建 DatagramSocket 对象
调用 DatagramSocket 对象的
receive()
方法接收数据关闭 DatagramSocket 对象
1 |
|
7. 示例
发送端
1 |
|
接收端
1 |
|
四、TCP 通信
先连接,再传输
1. TCP 协议
(1) 什么是 TCP 协议?
TCP 协议是一种可靠的网络协议,通过 TCP 协议可以实现可靠无差别的数据传输。
(2) 连接阶段
在通信前需要进行连接,此时需要明确客户端和服务器端,由客户端发起连接请求,经过服务器端和客户端的三次握手之后,建立网络虚拟链路。
(3) 通信阶段
在网络虚拟链路建立完成后,无需再区分服务器端和客户端,而是通过各自的 Socket 进行通信。
2. 三次握手
在 TCP 协议中,通信之前需要先建立网络虚拟链路,由客户端向服务器端发出连接请求,经过三次握手后完成连接的创建。
- 第一次握手:客户端向服务器端发出连接请求
- 第二次握手:服务器端收到请求后,向客户端发出通知,表明收到了连接请求
- 第三次握手:客户端再次向服务器端发出确认信息,确认连接
3. Java 中的 TCP
在建立连接时,使用 ServerSocket 代表服务器,Socket 代表客户端;
在进行传输时,通过 Socket 产生的 IO 流来进行网络通信。
4. 服务器端
(1) ServerSocket
ServerSocket 代表服务器端。
ServerSocket 对象用于监听来自客户端的连接请求,建立连接并返回对应的 Socket 对象。
(2) 构造器
方法 | 说明 |
---|---|
ServerSocket(int prot) | 用指定端口构造 ServerSocket |
ServerSocket(int prot, int backlog) | 用指定端口构造 ServerSocket,设置连接请求队列的最大长度 |
ServerSocket(int prot, int backlog, InetAddress inetAddress) | 用指定 IP,指定端口构造 ServerSocket,设置连接请求队列的最大长度 |
当机器存在多个 IP 地址时,允许显式指定 ServerSocket 要绑定的 IP 地址。
(3) 常用方法
方法 | 说明 |
---|---|
accept() | 用于从连接请求队列中取出一个连接请求,并创建与客户端相对应的 Socket 对象;通常情况下,服务器端应该通过循环不断接收请求;如果连接请求队列为空,则会一直等待,直至收到连接请求。 |
close() | 用于关闭 ServerSocket 对象 |
5. Socket
(1) Socket
Socket 是对网络通信中两个端点的抽象,在服务器端与客户端建立网络虚拟链路后,通过 Socket 进行数据的传输。
(2) 服务器端的 Socket
服务器端的 accept()
方法会进行连接,并返回与连接的客户端 Socket 对象相对应的 Socket 对象。
(3) 客户端的 Socket
通过以下构造方法构造 Socket 对象。
方法 | 说明 |
---|---|
Socket(InetAddress inetAddress, int prot) | 创建连接到指定 IP,指定端口的 Socket(使用 inetAddress 对象描述指定 IP) |
Socket(String str, int prot) | 创建连接到指定 IP,指定端口的 Socket(使用主机名 / IP 地址的字符串描述指定 IP) |
Socket(InetAddress inetAddress, int prot, InetAddress localInetAddress, int localProt) | 创建连接到指定 IP,指定端口的 Socket(使用 inetAddress 对象描述指定 IP),并且指定本地 IP 和本地端口 |
Socket(String str, int prot, InetAddress localInetAddress, int localProt) | 创建连接到指定 IP,指定端口的 Socket(使用主机名 / IP 地址的字符串描述指定 IP),并且指定本地 IP 和本地端口 |
(4) 常用方法
方法 | 说明 |
---|---|
getInputStream() | 获取 Socket 对象对应的输入流 |
getOutputStream() | 获取 Socket 对象对应的输出流 |
shutdownInput() | 关闭输入流 |
shutdownOutput() | 关闭输出流 |
(5) 连接超时
如果希望为 Socket 对象设置连接服务器的超时时长,
经过指定时间后,若还没有连接上,则认为连接超时
应该先创建一个无连接的 Socket 对象,再调用 connect()
方法连接服务器端,调用方法时传入超时时长参数。
1 |
|
(6) 读写超时
Socket 对象提供了 setSoTimeout()
用于设置超时时长。
设置之后如果读、写操作超过时间限制,将会抛出异常,程序可以捕获并进行处理。
1 |
|
6. 连接
服务器端
1 |
|
客户端
1 |
|
7. 通信
输出
1 |
|
输入
1 |
|
8. 客户端的多线程
在实际的应用中,客户端和服务器端之间需要保持长时间通信,不断地进行信息的输入和输出。此时如果不启用多线程,将会发生堵塞的情况。
为了使一个服务器端可以同时服务于多个客户端,可以加入多线程:
多线程类
1 |
|
服务器端
1 |
|
参考
疯狂 Java 讲义