文章摘要: 提供实现网络操作相关的类。
介绍
简要说明
- 提供了用于网络通信的类和接口。
- 这个包支持多种网络协议,包括 TCP/IP 和 UDP,以及用于 URL 处理的类。
主要功能
- URL 处理:提供了 URL 类来表示统一资源定位符,以及 URLConnection 类来处理与 URL 的连接。
- Socket 编程:支持客户端和服务器端的 Socket 编程,包括面向连接的 TCP(通过 Socket 类)和面向无连接的 UDP(通过 DatagramSocket 类)。
- 网络接口:提供了 InetAddress 类来表示互联网协议(IP)地址,以及 NetworkInterface 类来获取本地机器的网络接口信息。
- HTTP 通信:提供了 HttpURLConnection 类,它支持 HTTP 特定的功能,如 GET 和 POST 请求。
- 代理和隧道:支持使用代理服务器,以及通过代理服务器的隧道通信。
- 多播数据报:支持多播通信,允许数据报被发送到多个目的地。
注意事项
- 安全性:网络通信可能涉及敏感数据,因此需要使用 SSL/TLS 等加密协议来确保数据安全。
- 异常处理:网络操作可能会抛出多种异常,如 IOException,因此需要妥善处理这些异常。
- 资源管理:网络资源(如 Socket)在使用完毕后应正确关闭,以避免资源泄露。
- 性能考虑:网络操作可能比本地操作慢,应考虑异步处理或使用连接池来提高性能。
- 线程安全:某些网络类不是线程安全的,需要开发者注意并发访问的问题。
适用场景
- 网络应用开发:任何需要网络通信的 Java 应用程序,如 Web 客户端、服务器端应用、网络服务。
- 分布式系统:在分布式系统中,不同节点之间需要通过网络进行通信和数据交换。
- 物联网(IoT):物联网设备之间通过网络进行数据传输和指令下发。
- Web 服务:开发基于 HTTP 协议的 Web 服务客户端或服务器端。
- 网络诊断工具:编写用于网络诊断的工具,如检查网络连接、获取网络信息等。
主要类和接口
InetAddress
表示互联网协议(IP)地址。
getByName(String host): 根据主机名获取InetAddress实例。getAllByName(String host): 根据主机名获取InetAddress实例数组,用于处理主机名对应多个 IP 地址的情况。getLocalHost(): 获取本地主机的InetAddress实例。getHostAddress(): 返回 IP 地址字符串。
URL
表示统一资源定位符,它是一个指向互联网资源的指针。
openStream(): 打开到此 URL 的连接并返回一个用于从该连接读入的InputStream。openConnection(): 返回一个URLConnection对象,表示与 URL 引用的资源的通信链接。
URLConnection
抽象类,是所有类的超类,代表到 URL 引用的资源的连接。
connect(): 建立到远程对象的实际连接。getInputStream(): 返回从此打开的连接读取的输入流。getContent(): 获取此 URL 连接的内容。
HttpURLConnection
是 URLConnection 的子类,用于 HTTP 协议。
getResponseCode(): 获取服务器响应的状态码。getResponseMessage(): 获取服务器响应的状态消息。setRequestMethod(String method): 设置请求方法,如 “GET” 或 “POST”。
Socket
实现客户端套接字(也可以是服务器端),用于 TCP 网络通信。
connect(SocketAddress endpoint): 将此套接字连接到服务器。getInputStream(): 返回此套接字的输入流。getOutputStream(): 返回此套接字的输出流。close(): 关闭此套接字。
ServerSocket
实现服务器端套接字,用于监听客户端的 TCP 连接请求。
bind(SocketAddress endpoint): 将 ServerSocket 绑定到指定的地址和端口。accept(): 监听并接受到此套接字的连接。
DatagramPacket
表示数据报包,用于实现无连接的UDP网络通信。
getData(): 返回数据缓冲区。setSocketAddress(SocketAddress address): 设置要发送或接收数据报的远程地址。
DatagramSocket
用于发送和接收 UDP 数据报包。
send(DatagramPacket p): 发送数据报包。receive(DatagramPacket p): 从此套接字接收数据报包。
学习总结
- 网络基础:
java.net包提供了网络编程的基础,包括 IP 地址、URL、套接字等。 - URL处理: 使用
URL和URLConnection可以轻松地读取网络资源。 - HTTP协议:
HttpURLConnection提供了处理 HTTP 请求的方法,如设置请求方法、获取响应代码等。 - 套接字编程:
Socket和ServerSocket用于实现基于 TCP 的客户端和服务器端通信。 - 无连接通信:
DatagramPacket和DatagramSocket用于实现基于 UDP 的无连接通信。 - 异常处理: 网络编程中可能会遇到多种异常,如
UnknownHostException,IOException等,需要进行适当的异常处理。 - 流操作: 网络通信涉及大量的流操作,需要熟悉
InputStream和OutputStream的使用。 - 线程安全: 网络操作可能是阻塞的,因此在多线程环境下使用时需要注意线程安全。
- 资源管理: 使用完网络资源后,应该及时关闭套接字和流,以释放资源。
InetSocketAddress
- 用于封装 IP 地址和端口号的类,它仅仅是一个地址信息的容器,本身不提供网络通信的功能。
- 包路径:
java.net.InetAddress
创建对象
- 静态方法,无需实例化,即不需要通过
new来创建对象。
| 方法 | 返回值 | 说明 |
|---|---|---|
InetAddress.getLocalHost() | InetAddress | 获取本机IP,返回inetAddress对象 |
InetAddress.getByName(String host) | InetAddress | 根据ip地址或域名,例如:"www.baidu.com" |
相关方法
| 方法 | 返回值 | 说明 |
|---|---|---|
<InetAddress>.getHostName() | String | 获取给ip地址对象中的主机名 |
<InetAddress>.getHostAddress() | String | 获取该ip地址对象中的ip地址 |
<InetAddress>.isReachable(int timeout) | boolean | 在指定的毫秒时间内判断主机与该ip对应的主机是否能连通 |
URL
解析传入的RUL字符串,拆解得到:协议、域名、端口、请求资源。
包路径:
java.net.URL例子:
http://www.baidu.com:80/index.html?name=xiaoming&age=18#a
| 方法 | 返回值 | 说明 |
|---|---|---|
new URL(String url) | 实例化 | |
<URL>.getProtocol() | 返回协议,如http | |
<URL>.getHost() | 返回IP/域名,如www.baidu.com | |
<URL>.getPort() | 返回端口,如80 | |
<URL>.getPath() | 返回请求资源,如/index.html | |
<URL>.getQuery() | 返回参数,如name=xiaoming&age=18 | |
<URL>.getFile() | 返回请求资源和参数,如/index.html?name=xiaoming&age=18 | |
<URL>.getRef() | 返回锚点,如a |
TCP
TCP流程图
创建客户端
- 包路径:
java.net.Socket
客户端代码实现步骤
- 创建客户端的Socket对象,请求与服务端的连接。
- 使用Socket对象调用
getOutputStream()方法得到字节输出流。 - 使用字节输出流完成数据的发送。
- 释放资源:关闭socket管道。
构造方法
| 方法 | 说明 |
|---|---|
new Socket(String host, int port) | 指定的服务器IPhost和端口port,来请求与服务器与服务端建立连接,连接通过获得客户端Socket |
相关方法
| 方法 | 说明 |
|---|---|
<Socket>.getOutputStream() | 获得字节输出流对象OutputStream |
<Socket>.getInputStream() | 获得字节输入流对象InputStream |
客户端案例
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
/**
* 客户端
*/
public class Client {
public static void main(String[] ages) {
try {
// 创建服务端,并给指定 “服务端IP中的程序端口” 建立管道连接
Socket socket = new Socket("127.0.0.1", 4545);
System.out.println("客户端已启动");
// 创建 “字节输出流” 来发送数据给服务端中的程序
OutputStream os = socket.getOutputStream();
// 将 “字节输出流” 包装成 “数据字节输出流” 对象
DataOutputStream dos = new DataOutputStream(os);
// 实例化接收用户输入
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print(">>> ");
// 接收用户输入
String userInput = scanner.next();
// 判断是否退出客户端
if ("exit".equals(userInput)) {
// 释放 “数据字节输出流”
dos.close();
// 释放 “连接资源”
socket.close();
// 关闭客户端
System.out.println("客户端已关闭");
break;
}
// 开始写入数据
dos.writeUTF(userInput);
// 更新,防止存放在缓冲区没有真正发送出去
dos.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
创建服务端
- 包路径:
java.net.ServerSocket
服务端代码实现步骤
- 创建ServerSocket对象,注册服务端端口。
- 调用ServerSocket对象的
accept()方法,等待客户端的连接,并得到Socket管道对象。 - 通过Socket对象调用
getInputStream()方法得到字节输入流,完成数据的接收。 - 释放资源,关闭socket管道。
构造方法
| 方法 | 说明 |
|---|---|
new ServerSocket(int port) | 为服务端程序注册端口 |
相关方法
| 方法 | 说明 |
|---|---|
<ServerSocket>.accept() | 阻塞等待客户端的连接请求,一旦与某个客户端成功连接,则返回服务端这边的Socket对象 |
<ServerSocket>.getRemoteSocketAddress() | 获得客户端的IP和端口 |
服务端案例
import java.io.IOException;
import java.io.InputStream;
import java.io.DataInputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务端
*/
public class Server {
public static void main(String[] ages) {
try {
// 创建服务端,并为服务端程序注册端口
ServerSocket serverSocket = new ServerSocket(4545);
System.out.println("服务端已启动");
// 等待接收客户端的连接请求
Socket socket = serverSocket.accept();
// 使用 “字节输入流” 来接收客户端发送的数据
InputStream is = socket.getInputStream();
// 将 “字节输入流” 包装成 “数据字节输入流” 对象
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
// “解码” 接收到的客户端发送的数据
String data = dis.readUTF();
// 在控制台显示结果
System.out.print(socket.getRemoteSocketAddress() + " : ");
System.out.println(data);
} catch (IOException e) {
// 当客户端程序关闭时,表示断开了连接
System.out.println("提示:客户端 \"" + socket.getRemoteSocketAddress() + "\" 已断开连接");
// 释放资源
dis.close();
socket.close();
// 关闭服务端
System.out.println("服务端已关闭");
break;
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
案例 - TCP协议的多客户端同时通讯
- 需要使用Java的多线程技术
客户端代码
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
/**
* 客户端
*/
public class Client {
public static void main(String[] ages) throws IOException {
try {
// 创建服务端,并给指定 “服务端IP中的程序端口” 建立管道连接
Socket socket = new Socket("127.0.0.1", 4545);
System.out.println("客户端已启动");
// 创建 “字节输出流” 来发送数据给服务端中的程序
OutputStream os = socket.getOutputStream();
// 将 “字节输出流” 包装成 “数据字节输出流” 对象
DataOutputStream dos = new DataOutputStream(os);
// 实例化接收用户输入
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print(">>> ");
// 接收用户输入
String userInput = scanner.next();
// 判断是否退出客户端
if ("exit".equals(userInput)) {
// 释放 “数据字节输出流”
dos.close();
// 释放 “连接资源”
socket.close();
// 关闭客户端
System.out.println("客户端已关闭");
break;
}
// 开始写入数据
dos.writeUTF(userInput);
// 更新,防止存放在缓冲区没有真正发送出去
dos.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
服务端代码
- 服务端需要编写两个文件。
Server.java - 主线程代码
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务端
*/
public class Server {
public static void main(String[] ages) {
try {
// 创建服务端,并为服务端程序注册端口
ServerSocket serverSocket = new ServerSocket(4545);
System.out.println("服务端已启动");
while (true) {
// 等待接收客户端的连接请求
Socket socket = serverSocket.accept();
// 提示客户端与服务器建立连接
System.out.println("消息:客户端 \"" + socket.getRemoteSocketAddress() + "\" 与服务器建立连接");
// 将该客户端对象的socket通信管道,交给一个独立的线程负责处理
new ServerReaderThread(socket).start();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
ServerReaderThread.java - 多线程代码
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket; // 全局变量,记录建立连接的客户端对象
// 构造方法
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
// 重写,执行核心代码
@Override
public void run() {
try {
// 使用 “字节输入流” 来接收客户端发送的数据
InputStream is = socket.getInputStream();
// 将 “字节输入流” 包装成 “数据字节输入流” 对象
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
// “解码” 接收到的客户端发送的数据
String data = dis.readUTF();
// 在控制台显示结果
System.out.print(socket.getRemoteSocketAddress() + " : ");
System.out.println(data);
} catch (IOException e) {
// 当客户端程序关闭时,表示断开了连接
// 释放资源
dis.close();
socket.close();
// 关闭当前服务端线程
System.out.println("提示:客户端 \"" + socket.getRemoteSocketAddress() + "\" 已断开连接,关闭对该客户端的服务进程");
break;
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
案例 - TCP协议的端口转发技术实现群聊
客户端代码
客户端代码 - 主进程
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
/**
* 客户端
*/
public class Client {
public static void main(String[] ages) throws IOException {
try {
// 创建客户端,并给指定 “服务端IP中的程序端口” 建立管道连接
Socket socket = new Socket("127.0.0.1", 4545);
System.out.println("客户端已启动");
// 创建独立下线程,来接收服务端发送过来的消息
new ClientReaderThread(socket).start();
// 创建 “字节输出流” 来发送数据给服务端中的程序
OutputStream os = socket.getOutputStream();
// 将 “字节输出流” 包装成 “数据字节输出流” 对象
DataOutputStream dos = new DataOutputStream(os);
// 实例化接收用户输入
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print(">>> ");
// 接收用户输入
String userInput = scanner.next();
// 判断是否退出客户端
if ("exit".equals(userInput)) {
// 释放 “数据字节输出流”
dos.close();
// 释放 “连接资源”
socket.close();
// 关闭客户端
System.out.println("客户端已关闭");
break;
}
// 将数据 “编码” 成UTF-8,并发送给服务端
dos.writeUTF(userInput);
// 更新,防止存放在缓冲区没有真正发送出去
dos.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
客户端代码 - 子进程
- 用于不断接收服务器发来的信息
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
/**
* 客户端线程
*/
public class ClientReaderThread extends Thread {
private Socket socket;
// 构造方法
public ClientReaderThread(Socket socket) {
this.socket = socket;
}
// 重写,执行核心代码
@Override
public void run() {
try {
// 使用 “字节输入流” 来接收服务器发送的数据
InputStream is = socket.getInputStream();
// 将 “字节输入流” 包装成 “数据字节输入流” 对象
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
// “解码” 接收到的服务端发送的数据
String data = dis.readUTF();
// 在控制台显示结果
System.out.println(data);
} catch (IOException e) {
// 当客户端程序关闭时,表示断开了连接
// 释放资源
dis.close();
socket.close();
break;
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
服务端代码
服务端代码 - 主进程
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* 服务端
*/
public class Server {
// 创建存储与服务器连接的客户端Socket对象的集合
public static List<Socket> onLineSockets = new ArrayList<>();
public static void main(String[] ages) {
try {
// 创建服务端,并为服务端程序注册端口
ServerSocket serverSocket = new ServerSocket(4545);
System.out.println("服务端已启动");
while (true) {
// 等待接收客户端的连接请求
Socket socket = serverSocket.accept();
// 将客户端对象添加到集合中
onLineSockets.add(socket);
// 提示客户端与服务器建立连接
System.out.println("消息:客户端 \"" + socket.getRemoteSocketAddress() + "\" 与服务器建立连接");
// 将该客户端对象的socket通信管道,交给一个独立的线程负责处理
new ServerReaderThread(socket).start();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
服务端代码 - 子进程
- 用于处理客户端发来的信息,并将信息同步发送给其他在线的客户端
import java.io.*;
import java.net.Socket;
/**
* 服务端线程
*/
public class ServerReaderThread extends Thread {
private Socket socket;
// 构造方法
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
// 重写,执行核心代码
@Override
public void run() {
try {
// 使用 “字节输入流” 来接收客户端发送的数据
InputStream is = socket.getInputStream();
// 将 “字节输入流” 包装成 “数据字节输入流” 对象
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
// “解码” 接收到的客户端发送的数据
String data = dis.readUTF();
// 在控制台显示结果
System.out.println(socket.getRemoteSocketAddress() + " : " + data);
// 将客户端发送的信息,同步发送给其他有建立连接的客户端
sendMsgToAll(data);
} catch (IOException e) {
// 当客户端程序关闭时,表示断开了连接
// 释放资源
dis.close();
socket.close();
// 更新存放客户端集合,将当断开连接的客户端在集合中删除掉
Server.onLineSockets.remove(socket);
// 关闭当前服务端线程
System.out.println("提示:客户端 \"" + socket.getRemoteSocketAddress() + "\" 已断开连接,关闭对该客户端的服务进程");
break;
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 同步发送信息给其他客户端
private void sendMsgToAll(String data) throws IOException {
for (Socket onLineSocket : Server.onLineSockets) {
// 判断该消息是否是当前客户端发送来,即不需要给当前客户端再发信息回去
if (socket == onLineSocket) {
continue;
}
// 创建 “字节输出流” 来发送数据给客户端
OutputStream os = onLineSocket.getOutputStream();
// 将 “字节输出流” 包装成 “数据字节输出流” 对象
DataOutputStream dos = new DataOutputStream(os);
// 将数据 “编码” 成UTF-8,并发送给服务端
dos.writeUTF(data);
// 更新,防止存放在缓冲区没有真正发送出去
dos.flush();
}
}
}
UDP
相关信息
流程图

创建客户端、服务端
java.net.DatagramSocket
构造方法
| 方法 | 说明 |
|---|---|
new DatagramSocket() | 创建客户端的Socket对象,系统随即分配一个端口号 |
new DatagramSocket(int port) | 创建服务端的Socket对象,并指定端口号 |
相关方法
| 方法 | 说明 |
|---|---|
<DatagramSocket>.send(DatagramSocket dp) | 发送数据包 |
<DatagramSocket>.receive(DatagramSocket p) | 使用数据包接收数据 |
<DatagramSocket>.close() | 关闭客户端对象 |
创建数据包
java.net.DatagramPacket
构造方法
| 方法 | 说明 |
|---|---|
new DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 创建用于发送数据的数据包对象,buf字节数据包、length数据包的字节长度、address接收端的IP地址、port接收端的端口号 |
new DatagramPacket(byte[] buf, int length) | 创建用来接收数据的数据包对象,buf存放接收到的数据包字节数组大小、length接收到的数据包字节数组长度 |
相关方法
| 方法 | 说明 |
|---|---|
<DatagramPacket>.getLength() | 获取数据包,实际接收到的字节个数 |
案例
客户端代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 客户端
*/
public class Client {
public static void main(String[] ages) throws IOException {
// 实例化一个客户端对象
DatagramSocket socket = new DatagramSocket(8888);
System.out.println("客户端已启动");
// 创建字节输出流对象
FileOutputStream fos = new FileOutputStream("webapps/pages/login.html");
// 包装成数据流
DataOutputStream dos = new DataOutputStream(fos);
// 将需要发送的数据进行“编码”,转换成字节码
byte[] dataByte = new;
// 实例化数据包对象,用于封装要发出去的数据(将要发出去的字节数据、字节数据的长度、接收端)
DatagramPacket packet = new DatagramPacket(dataByte, 0, dataByte.length, new InetSocketAddress("127.0.0.1", 9999));
// 开始将数据包发送到服务端,阻塞
socket.send(packet);
// 释放资源,即关闭客户端
socket.close();
}
}
服务端代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
* 服务端
*/
public class Server {
public static void main(String[] ages) throws IOException {
// 实例化一个服务端对象
DatagramSocket socket = new DatagramSocket(9999);
System.out.println("启动服务端");
// 创建存放接收数据包的字节数组,大小为64kb
byte[] dataByte = new byte[1024 * 64];
// 实例化一个数据包对象,用于接收数据
DatagramPacket packet = new DatagramPacket(dataByte, 0, dataByte.length, new InetSocketAddress("127.0.0.1", 8888));
while (true) {
// 开始使用数据包来接收客户端发来的数据
socket.receive(packet);
// 获取本次数据包中接收到了多少字节数组
int len = packet.getLength();
// 将接收到的数据进行“解码”,转换成字符串
String data = new String (dataByte, 0, len);
// 在控制台输出接收到的数据
System.out.println(packet.getAddress().getHostAddress() + " : " + data);
System.out.println("-------------------------");
}
}
}
