InetAddress
InetAddress 类用于封装 IP 地址或者域名,支持 IPv4 和 IPv6。
创建 InetAddress 对象需要使用工厂方法,因为没有提供显式构造器。工厂方法如下
static InetAddress getLocalhost();
static InetAddress getByName(String hostName);
// 一个域名对应多个 IP 地址时,返回这些地址构造的 InetAddress 对象
static InetAddress[] getAllByName(String hostName);
public static void main(String[] args) throws UnknownHostException {
// 主机 ip 地址
// InetAddress 输出格式:主机名称/ip地址
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);
// 域名 id 地址
InetAddress bing = InetAddress.getByName("cn.bing.com");
System.out.println(bing);
// 域名对应的多个 ip 地址
InetAddress[] bings = InetAddress.getAllByName("cn.bing.com");
for( InetAddress e : bings) {
System.out.println(e);
}
}
getByAddress()
方法接收一个 IP 地址,返回一个 InetAddress 对象。
InetAddress 类有两个子类 Inet4Address 和 Inet6Address,分别表示 IPv4 和 IPv6 地址。
TCP/IP 客户端 Sockets
socket 用于连接 I/O 系统和其他程序。
ServerSocket
类用于服务器,作为“监听器”,等待客户端的请求。Socket
类用于客户端,用于连接服务器 socket 和发起协议交换过程。
Socket 对象的创建隐式建立了客户端和服务器的连接。该对象的方法可以获取服务器的地址、端口号等信息。
Socket(String hostName, int port);
Socket(InetAddress ipAddress, int port);
getInputStream()
和 getOutputStream()
方法用于获取和 Socket 关联的输入流、输出流。
InputStream getInputStream();
OutputStream getOutputStream();
关闭 socket 时,与它关联的 I/O 流也会关闭。
public static void m() {
try (var s = new Socket("xx.xxx.com", 80)) {
var in = s.getInputStream();
var out = s.getOutputStream();
byte[] buf = "send information".getBytes();
// 发送
out.write(buf);
int c;
// 读取响应
while ((c = in.read()) != -1) {
System.out.println((char)c);
}
}
}
URL
统一资源定位符(Uniform Resource Locator,URL)唯一标识网络中某个资源的地址。
URL 由 4 部分构成,第一个部分是使用的协议;第二个部分是主机名或 IP 地址;第三个部分是端口号,可选;第四个部分是文件的实际路径。
常用构造器如下
URL(String urlSpecifier);
URL(String protocol, String host, int port, String path);
URL(String protocol, String host, String path);
URL(URL url, String urlSpecifier);
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("https://cn.bing.com");
System.out.println(url.getHost());
// 没有指定端口时返回 -1
System.out.println(url.getPort());
}
为了获取 URL 对象表示的信息,需要调用 openConnection()
方法获取 URLConnection 对象。
URL Connection
URLConnection 类用于访问远程资源属性。这些属性由 HTTP 协议指定。URLConnection 对象的方法主要用于获取 HTTP 头属性,比如 getHeaderFields()
方法返回所有 HTTP 头属性,由 map 存储。
public static void main(String[] args) throws Exception {
URL url = new URL("http://www.internic.net");
URLConnection urlC = url.openConnection();
System.out.println(urlC.getContentType());
System.out.println(urlC.getContentEncoding());
}
HttpURLConnection
HttpURLConnection 类是 URLConnection 的子类,用于 HTTP 连接。调用 openConnection()
方法然后将返回结果显示转换成 HttpURLConnection 类得到 HttpURLConnection 对象。需保证打开的连接是 HTTP 连接。
public static void m() {
var u = new URL("http://xxx.xx.com");
var huc = (HttpURLConnection)u.openConnection();
Map<String, List<String>> header = huc.getHeaderFields();
Set<String> keys = header.keySet();
for (String k : keys) {
System.out.println(k + " : " + header.get(k));
}
}
URI 类封装了统一资源标识符(Uniform Resource Identifier,URI)。URI 是表示资源的标准方式,URL 是它的子集。
TCP/IP Server Sockets
ServerSocket 对象用于监听是否有本地或者远程的程序连接自己发布的端口。
ServerSocket 的构造器可以指定队列长度。默认是 50。表示当请求的数量小于等于该队列容量时可以得到处理,大于时,后到达的拒绝连接。构造器如下
// 指定监听的端口号
ServerSocket(int port);
ServerSocket(int port, int maxQueue);
// 允许时,该对象与指定地址绑定
ServerSocket(int port, int maxQueue, InetAddress localAddress);
accept()
调用后等待客户端发起通信,当接收到请求时,返回一个 Socket 用于和客户端通信。
Datagrams
数据报(Datagrams)是机器之间传输的一组信息,它发送之后,不保证目标方能否收到,收到的数据报是否正确。Java 在 UDP 协议上层实现了数据报,使用两个类:DatagramPacket 和 DatagramSocket。其中,DatagramPacket 是数据容器,DatagramSocket 提供了发送和接收 DatagramPacket 对象的机制。
DatagramSocket 类的构造器如下
// 和本机上任意一个未使用的端口号绑定
DatagramSocket();
DatagramSocket(int port);
DatagramSocket(int port, InetAddress ipAddress);
DatagramSocket(SocketAddress address);
SocketAddress 是抽象类,InetSocketAddress 是它的实现类,InetSocketAddress 类封装了 IP 地址和端口号。
两个重要方法如下
void send(DatagramPacket packet);
void receive(DatagramPacket packet);
send()
方法将分组 packet 发送到指定端口,receive()
方法等待接收分组。
DatagramPacket
类定义的构造器如下
// 指定接收数据的缓冲区和分组大小
DatagramPacket(byte[] data, int size);
// offset 指定数据在缓冲区开始存储的索引位置
DatagramPacket(byte[] data, int offset, int size);
// 指定接收方的 ip 地址和端口号
DatagramPacket(byte[] data, int size, InetAddress ipAddress, int port);
// 从 data 的 offset 索引位置开始传输数据
DatagramPacket(byte[] data, int offset, int size, InetAddress ip, int port);
class Demo {
public static int sPort = ;
public static int cPort = ;
public static int bSize = 1024;
public static byte[] buf = new byte[bSize];
public static DatagramSocket ds;
public static void server() {
int pos = 0;
while (true) {
int c = System.in.read();
switch(c) {
case -1:
System.out.println("quit");
ds.close();
break;
case '\r':
break;
case '\n':
ds.send(new DatagramPacket(buf, pos, InetAddress.getLocalHost(), cPort));
pos = 0;
break;
default:
buf[pos++] = (byte)c;
}
}
}
public static void client() {
while (true) {
var p = new DatagramPacket(buf, buf.length);
ds.receive(p);
System.out.println(new String(p.getData(), 0, p.getLength()));
}
}
public static void main(String[] args) {
if (args.length == 1) {
ds = new DatagramSocket(sPort);
server();
} else {
ds = new DatagramSocket(cPort);
clien();
}
}
}
java.net.http
JDK11 引入了 java.net.http 包,这些新加入的 API 称为 HTTP 客户端 API。支持异步通信 、HTTP/2、流控制(flow control)、双向通信的 WebSocket 协议。
有 3 个核心的 HTTP 客户端 API,HttpClient 封装了 HTTP 客户端,提供发送请求和接收响应的方法;HttpRequest 封装了请求;HttpResponse 封装了响应。三者共同支持 HTTP 的请求和响应特性。
首先,创建 HttpClient 和 HttpRequest ,然后调用 HttpClient 的 send 方法发送 HttpRequest ,该方法返回响应,可以从响应中获取响应头和响应体。
HttpClient 封装了 HTTP 请求/响应机制,支持同步和异步通信。可以使用它发送请求和接收响应。
HttpClient 是一个抽象类,只能使用工厂方法获得实例。它的静态方法 newBuilder()
返回一个 HttpClient 建造者(builder),该建造者实现了 HttpClient.Builder 接口,这个接口提供了若干个用于配置 HttpClient 的方法。建造者调用 build()
方法创建并返回 HttpClient 实例。
// 使用默认配置创建 HttpClient 实例
HttpClient hc = HttpClient.newBuilder().build();
HttpClient.Builder 接口定义的 followRedirects()
方法用于设置重定向,默认情况下不允许重定向。该方法的参数必须为 HttpClient.Redirect 枚举类型的值:ALWAYS、NEVER 和 NORMAL。NORMAL 表示允许重定向,除非从 https 站点重定向到 http 站点。
HttpClient.Builder b =
HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL);
HttpClient hc = b.build();
HttpClient 的静态方法 newHttpClient()
返回默认配置的 HttpClient 实例。
var hc = HttpClient.newHttpClient();
HttpClient 对象调用 send()
方法可以发送一个同步请求。
<T> HttpResponse<T> send(HttpRequest req, HttpResponse.BodyHandler<T> handler);
handler 表示如何处理返回的响应。
HttpRequest 抽象类封装了请求,HttpRequest 对象需要使用建造者创建。newBuilder()
方法返回建造者。
// 创建默认建造者
static HttpRequest.Builder newBuilder();
static HttpRequest.Builder newBuilder(URI uri);
HttpRequest.Builder 提供了若干方法用于设置请求,默认的请求方式是 GET。HttpRequest 对象的 method()
返回请求方式的字符串表示,建造者实例调用 build()
方法创建 HttpRequest 实例。
HttpResponse 接口的实现类封装了响应。
HttpResponse<T>
T 指定了体的类型。
请求发送后,会返回一个 HttpResponse 对象,有若干个方法用于访问响应信息。其中,body()
方法返回对体的引用,引用的类型由 send()
方法中 handler 指定。
T body()
int statusCode()
方法返回状态码;HttpHeaders headers()
方法返回响应头。HttpHeaders 类的 map()
方法以键值对的方式返回所有的响应头信息。
Map<String, List<String>> map()
HttpResponse.BodyHandler 接口的实现类处理响应,HttpResponse.BodyHandlers 类预定义了若干体处理工厂方法。
// 将响应体写入 fileName 指定的文件中,HttpResponse.body() 返回该文件的 Path 对象
static HttpResponse.BodyHandler<Path> ofFile(Path fileName);
// 将响应体和输入流绑定,HttpResponse.body() 返回该输入流的引用
static HttpResponse.BodyHandler<InputStream> ofInputStream();
// 将响应体转换成字符串,HttpResponse.body() 返回该字符串
static HttpResponse.BodyHandler<String> ofString();
使用 ofInputStream()
方法时,需要将流完全读取。
class N {
public static void main(String[] args) {
var hc = HttpClient.newHttpClient();
var hq = HttpRequest.newBuilder(new URI("http://www.xxx.com/")).build();
HttpResponse<InputStream> hr =
hc.send(hq, HttpResponse.BodyHandlers.ofInputStream());
// 请求方法
hq.method();
// 响应状态码
hr.statusCode();
InputStream input = hr.body();
int c;
while ((c = input.read()) != -1) {
System.out.println((char)c);
}
}
}
参考
[1] Herbert Schildt, Java The Complete Reference 11th, 2019.