Java 网络编程是指编写在多个设备(计算机)上运行的程序,这些设备通过网络相互连接。
- 创建客户端-服务器应用程序
- 实现网络协议
- 实现套接字(Socket)编程
- 创建 Web 服务
网络编程中使用的包
java.net 包是 J2SE API 的一部分,它包含一系列类和接口,提供了底层通信细节的支持,使开发者可以专注于解决实际问题。
java.net 包支持两种常见的网络协议:
- TCP:传输控制协议(Transmission Control Protocol),允许两个应用程序之间进行可靠的通信。TCP 通常与互联网协议(IP)结合使用,即 TCP/IP。
- UDP:用户数据报协议(User Datagram Protocol),是一种无连接协议,允许在应用程序之间传输数据包。
本章将详细介绍以下两个主题:
- 套接字编程(Socket Programming):这是网络编程中最广泛使用的概念,本文将详细讲解。
- URL 处理(URL Processing)
Java 中的套接字编程
套接字(Socket)提供了使用 TCP 协议在两台计算机之间进行通信的机制。客户端程序在其通信端创建一个套接字,并尝试将该套接字连接到服务器。
一旦连接建立,服务器也会在其通信端创建一个套接字对象。此时,客户端和服务器就可以通过读写套接字进行通信。
java.net.Socket类表示一个套接字。java.net.ServerSocket类为服务器程序提供监听客户端并建立连接的机制。
使用套接字建立 TCP 连接的步骤如下:
- 服务器实例化一个
ServerSocket对象,指定通信所用的端口号。 - 服务器调用
ServerSocket的accept()方法。该方法会一直等待,直到有客户端连接到指定端口。 - 客户端实例化一个
Socket对象,指定要连接的服务器名称和端口号。 Socket构造函数尝试连接到指定的服务器和端口。如果连接成功,客户端就拥有了一个可与服务器通信的Socket对象。- 在服务器端,
accept()方法返回一个指向新套接字的引用,该套接字与客户端的套接字相连。 - 连接建立后,双方可通过 I/O 流进行通信:
- 每个套接字都有一个
OutputStream和一个InputStream。 - 客户端的
OutputStream连接到服务器的InputStream。 - 客户端的
InputStream连接到服务器的OutputStream。
- 每个套接字都有一个
由于 TCP 是双向通信协议,因此数据可以同时在两个流中传输。
以下是用于实现套接字的常用类及其方法。
ServerSocket 类的构造方法
java.net.ServerSocket 类被服务器应用程序用于获取端口并监听客户端请求。
ServerSocket 类有四个构造方法:
| 序号 | 构造方法 | 说明 |
|---|---|---|
| 1 | public ServerSocket(int port) throws IOException |
尝试创建绑定到指定端口的服务器套接字。如果端口已被其他应用占用,则抛出异常。 |
| 2 | public ServerSocket(int port, int backlog) throws IOException |
与上一构造方法类似,backlog 参数指定等待队列中可容纳的客户端数量。 |
| 3 | public ServerSocket(int port, int backlog, InetAddress address) throws IOException |
与上一构造方法类似,InetAddress 参数指定要绑定的本地 IP 地址。适用于具有多个 IP 地址的服务器。 |
| 4 | public ServerSocket() throws IOException |
创建一个未绑定的服务器套接字。使用此构造方法时,需在准备好后调用 bind() 方法进行绑定。 |
如果 ServerSocket 构造方法未抛出异常,说明应用程序已成功绑定到指定端口,可以接收客户端请求。
ServerSocket 类的常用方法
| 序号 | 方法 | 说明 |
|---|---|---|
| 1 | public int getLocalPort() |
返回服务器套接字监听的端口号。如果构造时传入端口为 0(让系统自动分配),此方法非常有用。 |
| 2 | public Socket accept() throws IOException |
等待客户端连接。此方法会阻塞,直到有客户端连接或超时(若设置了超时)。 |
| 3 | public void setSoTimeout(int timeout) |
设置 accept() 方法等待客户端的超时时间(单位:毫秒)。 |
| 4 | public void bind(SocketAddress host, int backlog) |
将套接字绑定到指定的地址和端口。仅在使用无参构造方法创建 ServerSocket 时使用。 |
当 ServerSocket 调用 accept() 时,该方法会一直阻塞,直到有客户端连接。连接建立后,服务器会创建一个新的 Socket(使用任意可用端口),并返回该 Socket 的引用。此时,客户端与服务器之间的 TCP 连接已建立,可以开始通信。
Socket 类的构造方法
java.net.Socket 类表示客户端和服务器用于相互通信的套接字。
- 客户端通过实例化
Socket对象获得套接字。 - 服务器通过
accept()方法的返回值得到套接字。
客户端用于连接服务器的 Socket 类有五个构造方法:
| 序号 | 构造方法 | 说明 |
|---|---|---|
| 1 | public Socket(String host, int port) throws UnknownHostException, IOException |
尝试连接到指定主机和端口。若未抛出异常,则连接成功。 |
| 2 | public Socket(InetAddress host, int port) throws IOException |
与上一构造方法相同,但主机以 InetAddress 对象形式指定。 |
| 3 | public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException |
连接到指定主机和端口,并在本地指定 IP 和端口创建套接字。 |
| 4 | public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException |
与上一构造方法相同,但主机以 InetAddress 对象形式指定。 |
| 5 | public Socket() |
创建一个未连接的套接字。需调用 connect() 方法连接到服务器。 |
注意:除无参构造方法外,其他构造方法在返回时不仅创建了 Socket 对象,还实际尝试连接到指定服务器和端口。
Socket 类的常用方法
以下方法既可用于客户端,也可用于服务器(因为双方都持有 Socket 对象):
| 序号 | 方法 | 说明 |
|---|---|---|
| 1 | public void connect(SocketAddress host, int timeout) throws IOException |
连接到指定主机。仅在使用无参构造方法创建 Socket 时需要调用。 |
| 2 | public InetAddress getInetAddress() |
返回与此套接字连接的远程计算机的地址。 |
| 3 | public int getPort() |
返回远程机器上套接字绑定的端口。 |
| 4 | public int getLocalPort() |
返回本地机器上套接字绑定的端口。 |
| 5 | public SocketAddress getRemoteSocketAddress() |
返回远程套接字的地址。 |
| 6 | public InputStream getInputStream() throws IOException |
返回套接字的输入流,该流连接到远程套接字的输出流。 |
| 7 | public OutputStream getOutputStream() throws IOException |
返回套接字的输出流,该流连接到远程套接字的输入流。 |
| 8 | public void close() throws IOException |
关闭套接字,使其不能再连接任何服务器。 |
InetAddress 类的常用方法
InetAddress 类表示 Internet 协议(IP)地址。在套接字编程中常用的方法包括:
| 序号 | 方法 | 说明 |
|---|---|---|
| 1 | static InetAddress getByAddress(byte[] addr) |
根据原始 IP 地址返回 InetAddress 对象。 |
| 2 | static InetAddress getByAddress(String host, byte[] addr) |
根据主机名和 IP 地址创建 InetAddress。 |
| 3 | static InetAddress getByName(String host) |
根据主机名解析 IP 地址。 |
| 4 | String getHostAddress() |
以文本形式返回 IP 地址字符串。 |
| 5 | String getHostName() |
获取此 IP 地址对应的主机名。 |
| 6 | static InetAddress getLocalHost() |
返回本地主机的 InetAddress。 |
| 7 | String toString() |
将 IP 地址转换为字符串。 |
Java 网络编程示例
实现 Java 套接字客户端
以下 GreetingClient 是一个客户端程序,它通过套接字连接到服务器,发送问候信息,并等待服务器响应。
示例:套接字客户端
// 文件名:GreetingClient.java
import java.net.*;
import java.io.*;
public class GreetingClient {
public static void main(String [] args) {
String serverName = args[0];
int port = Integer.parseInt(args[1]);
try {
System.out.println("正在连接到 " + serverName + " 的端口 " + port);
Socket client = new Socket(serverName, port);
System.out.println("已连接到 " + client.getRemoteSocketAddress());
OutputStream outToServer = client.getOutputStream();
DataOutputStream out = new DataOutputStream(outToServer);
out.writeUTF("来自 " + client.getLocalSocketAddress() + " 的问候");
InputStream inFromServer = client.getInputStream();
DataInputStream in = new DataInputStream(inFromServer);
System.out.println("服务器回复:" + in.readUTF());
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
实现 Java 套接字服务器
以下 GreetingServer 是一个服务器程序,使用 Socket 类监听指定端口上的客户端连接。
示例:套接字服务器
// 文件名:GreetingServer.java
import java.net.*;
import java.io.*;
public class GreetingServer extends Thread {
private ServerSocket serverSocket;
public GreetingServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(10000); // 设置 10 秒超时
}
public void run() {
while(true) {
try {
System.out.println("正在端口 " +
serverSocket.getLocalPort() + " 上等待客户端...");
Socket server = serverSocket.accept();
System.out.println("已连接到 " + server.getRemoteSocketAddress());
DataInputStream in = new DataInputStream(server.getInputStream());
System.out.println(in.readUTF());
DataOutputStream out = new DataOutputStream(server.getOutputStream());
out.writeUTF("感谢连接到 " + server.getLocalSocketAddress() + "\n再见!");
server.close();
} catch (SocketTimeoutException s) {
System.out.println("套接字超时!");
break;
} catch (IOException e) {
e.printStackTrace();
break;
}
}
}
public static void main(String [] args) {
int port = Integer.parseInt(args[0]);
try {
Thread t = new GreetingServer(port);
t.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
编译并运行
首先编译客户端和服务器,然后按以下方式启动服务器:
$ java GreetingServer 6066
正在端口 6066 上等待客户端...
接着运行客户端:
$ java GreetingClient localhost 6066
正在连接到 localhost 的端口 6066
已连接到 localhost/127.0.0.1:6066
服务器回复:感谢连接到 /127.0.0.1:6066
再见!