Java网络编程笔记
一、Java网络类核接口
Java中,有关网络方面的功能都定义在 java.net
包中。
Java所提供的网络功能可分为以下三类:
1. URL 和 URLConnection
URLConnection是所有类的超类,它代表应用程序和URL之间的通信链接。
此类的实例可用于读取和写入此URL引用的资源。
2. Socket
Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。
3. Datagram
Datagram(数据包)是一种尽力而为的传送数据的方式,它只是把数据的目的地记录在数据包中,然后就直接放在网络上,系统不保证数据能不能安全送到,或者什么时候可以送到,也就是说,他并不保证传送质量。
二、InetAddress类
InetAddress类是Java的IP地址封装类,该类没有构造函数,可以通过该类的静态方法创建该类的实例对象。
这些静态方法如下所示:
方法名 | 说明 |
---|---|
static InetAddress[] getAllByName(String host) | 在给定主机名的情况下,根据系统上配置的名称服务返回其IP地址所组成的数组。 |
static InetAddress getByAddress(byte[] addr) | 在给定原始IP地址的情况下,返回InetAddress对象。 |
static InetAddress getByAddress(String host, byte[] addr) | 根据提供的主机名和IP地址创建InetAddress。 |
static InetAddress getByName(String host) | 在给定主机名的情况下确定主机的IP地址。 |
static InetAddress getLocalHost() | 返回本地主机。 |
【案例:演示InetAddress相关用法】
InternetDemo.java,代码如下:
import java.net.InetAddress;
public class InternetDemo {
public static void main(String[] args) throws Exception {
InetAddress inetAddress = InetAddress.getLocalHost();
// 将此IP地址转换为String
System.out.println(inetAddress.toString());
// 获取此IP地址的主机名。
System.out.println(inetAddress.getHostName());
// 获取IP
System.out.println(inetAddress.getHostAddress());
}
}
URL和URLConnection类
1. URL类
URL是Uniform Resource Location的缩写,即“统一资源定位符”。
通俗地说,URL是在Internet上用来描述信息资源的字符串,主要用在各种WWW客户程序和服务程序上。
采用URL,可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等,这种格式已经成为描述数据资源位置的标准方式。
可以通过URL类提供的构造方法,来获取URL实例对象。
URL类的常用构造方法如下所示:
方法名 | 说明 |
---|---|
URL(String spec) | 根据String表示形式创建URL对象。 |
URL(String protocol, String host, int port, String file) | 根据指定的protocol、host、port和file创建URL对象。 |
URL(String protocol, String host, String file) | 根据指定的protocol、host和file创建URL。 |
URL(URL context, String spec) | 通过在指定的上下文中对给定的spec进行解析,创建URL。 |
URL类中一些很基本的方法如下:
- public String getProtocol():获取该URL的协议名。
- public String getHost():获取该URL的主机名。
- public int getPort():获取该URL的端口号。
- public String getPath():获取该URL的文件路径。
- public String getFile():获取该URL的文件名。
- public String getRef():获取该URL在文件中的相对位置。
- public String getQuery():获取该URL的查询名。
- public final Object getContent():获取传输协议。
- public final InputStream openStream():打开到此URL的连接并返回一个用于从该连接读入的InputStream。
【案例:演示URL类相关用法】
URLReader.java,代码如下:
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class URLReader {
public static void main(String[] args) throws Exception {
URL url = new URL("http://www.baidu.com");
// 获取协议
System.out.println("protocol: " + url.getProtocol());
// 获取主机名
System.out.println("hostname: " + url.getHost());
// 获取端口号
System.out.println("port: " + url.getPort());
// 获取文件
System.out.println("file: " + url.getFile());
// 把URL转化为字符串
System.out.println("toString: " + url.toString());
System.out.println("========================================");
InputStream inputStream = url.openStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line = null;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
}
}
运行此程序,运行结果如下:
protocol: http
hostname: www.baidu.com
port: -1
file:
toString: http://www.baidu.com
========================================
<!DOCTYPE html>
<!--STATUS OK--><html> <head>
......
注意:在程序中,必须选择
public InputStreamReader(InputStream in, String charsetName)
构造器来读取数据,否则,在获取的数据中,中文将显示为乱码。
通过URL类的toString()
方法,可以很清晰的看出URL的格式有三部分组成:
- 协议(或称为服务方式);
- 存有该资源的主机IP地址(有时也包括端口号);
- 主机资源的具体地址,如目录和文件名等。
前两者之间用“????/”符号隔开,后两者用“/”符号隔开。其中前两者是不可缺少的。
2. URLConnection类
URLConnection类是一个抽象类,它代表应用程序与URL之间的通信连接。
URLConnection类的实例可用于读取和写入此URL引用的资源。
URLConnection允许用POST、PUT和其他的HTTP请求方法将数据送回服务器。
使用URLConnection对象的一般步骤如下:
- 创建一个URL对象;
- 通过URL对象的openConnection方法创建URLConnection对象;
- 配置参数和一般请求属性;
- 获取输入流并读数据;
- 获取输出流并写数据;
- 关闭连接。
提示:
使用URLConnection对象并不是必须要按以上步骤完成。
用户如果使用URL类的默认设置,就可以省略第3步。
有时候仅需要从服务器读取数据,并不需要向服务器发送数据,这种情况下也可以省略第5步。
【案例:用URLConnection类获取博客园首页的相关内容】
新建TestURL.java,代码如下:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
public class TestURL {
public static void main(String[] args) throws Exception {
// 实例化URL
URL url = new URL("https://www.cnblogs.com");
// 打开连接
URLConnection connection = url.openConnection();
// 获取输入流
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
// 循环读取每一行数据
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// 释放资源
reader.close();
}
}
提示:
URL类提供的openStream()
方法是打开连接到此URL的连接并返回一个用于从该连接读入的InputStream。openStream()
方法实际上是openConnection().getInputStream()
方法的编写。【案例:用HTTPURLConnection类提交请求到百度搜索并获取搜索后的结果】
首先打开www.baidu.com,在查询框中输入“java”,单击“百度一下”按钮,提交请求信息。
在查询结果页面可以看到,请求被提供到“www.baidu.com/s?wd=java”,搜索出与java相关的信息约有53,900,000个。
新建TestParamURL.java,代码如下:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
public class TestParamURL {
public static void main(String[] args) throws Exception{
String url = "http://www.baidu.com/s";
String param = "wd=java";
String result = sendGet(url, param);
System.out.println(result);
}
// 以GET方式提交HTTP请求到服务器,并返回结果。
public static String sendGet(String url, String param) throws Exception {
StringBuffer result = new StringBuffer();
String urlName = url + "?" + param;
URL urlObject = new URL(urlName);
URLConnection connection = urlObject.openConnection();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
String line = null;
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}
reader.close();
return result.toString();
}
// 以POST方式提交HTTP请求到服务器,并返回结果。
public static String sendPost(String url, String param) throws Exception{
StringBuffer result = new StringBuffer();
URL urlObject = new URL(url);
HttpURLConnection connection = (HttpURLConnection) urlObject.openConnection();
// 设置是否向connection输出,因为这个是POST请求。
// 参数要放在HTTP正文中,因此需要设为true。默认请情况下是false。
connection.setDoOutput(true);
// 设置是否从connection读入,默认情况下是true。
connection.setDoInput(true);
// POST请求不能使用缓存。
connection.setUseCaches(false);
// 设定传送的内容类型是可序列化的Java对象
// (如果不设定此项,在传送序列化对象时,当Web服务器默认的不是这种类型时,可能会抛出 java.io.EOFException 异常)。
connection.setRequestProperty("Content-type", "application/x-java-serialized-object");
// 设定请求的方式为POST,默认是GET。
connection.setRequestMethod("POST");
// 利用输出流向服务器传送参数,参数形式为“参数名=值&参数名=值”
PrintWriter out = new PrintWriter(connection.getOutputStream());
out.print(param);
out.flush();
out.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
String line = null;
while ((line = reader.readLine()) !=null){
result.append(line).append("\n");
}
reader.close();
return result.toString();
}
}
四、Socket套接字
套接字(Socket)允许程序把网络连接当成一个流,可以通过流的方式实现数据的交换。
Java中有专门的Socket类处理用户的请求和响应,利用Socket类可以轻松实现两台计算机间的通信。
Socket类是Java的基础类,用于执行客户端的TCP操作。
- 客户端套接字
- 服务端套接字
1. 客户端套接字
可以通过构造方法来获取客户端Socket实例对象:
| 方法名 | 说明 |
| Socket(String host, int port) | 创建一个流套接字,并将其连接到指定主机上的指定端口号。 |
| Socket(InetAddress address, int port) | 创建一个流套接字,并将其连接到指定IP地址的指定端口号。 |
常用方法如下所示:
- public InetAddress getInetAddress():返回此套接字连接到的远程IP地址,如果套接字是未连接的,则返回null。
- public int getPort():返回次套接字连接到的远程端口。如果上为连接套接字,则返回0。
- public int getLocalPort():返回此套接字绑定到的本地端口,如果上为绑定套接字,则返回-1。
- public InetAddress getLocalAddress():湖区套接字绑定的本地地址,如果尚未绑定套接字,则返回InetAddress.anyLocalAddress()。
- public InputStream getInputStream() throws IOException:返回此套接字的输入流。
- public OutputStream getOutputStream() throws IOException:返回此套接字的输出流。
- public void close() throws IOException:关闭此套接字。
2. 服务端套接字
每个服务器端套接字运行在服务器上特定的端口,监听在这个端口的TCP连接。
当远程客户端的Socket试图与服务器指定端口建立连接时,服务器被**,判定客户程序的连接,并打开两个主机之间固有的连接。
一旦客户端与服务器建立了连接,则两者之间就可以传送数据。
可以通过构造方法来获取服务器端Socket实例对象:
方法名 | 说明 |
---|---|
ServerSocket() | 创建非绑定服务器套接字。 |
ServerSocket(int port) | 创建绑定到特定端口的服务器套接字。 |
ServerSocket(int port, int backlog) | 利用指定的backlog创建服务器套接字,并将其绑定到指定的本地端口号。 |
ServerSocket(int port, int backlog, InetAddress bingAddr) | 使用指定的端口、监听backlog和要绑定的本地IP地址创建服务器。 |
常用方法如下所示:
- Socket accept():监听并接受到此套接字的连接。当用户未连接上来时,此方法一直是闲置状态。此方法会返回一个Socket(它使用的端口与ServerSocket不同),这样ServerSocket可以空出来等待其他用户的连接。
- void close():关闭此套接字。
不管一个Socket通信程序的功能多么齐全、程序多么复杂,其基本结构都是一样的,都包括以下步骤: - 在客户端和服务器创建Socket/ServerSocket实例。
- 打开连接到Socket的输入/输出流。
- 利用输入/输出流,按照一定的协议对Socket进行读/写操作。
- 关闭输入/输出流和Socket。
【案例:演示Socket使用方法】
服务端TCPServer.java:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class TCPServer {
public static void main(String[] args) throws Exception{
// 1. 建立Socket
ServerSocket serverSocket = new ServerSocket(8888);
// 2. 等待客户端连接
while (true) {
// 3. 接收客户端连接
Socket socket = serverSocket.accept();
// 4. 在客户端和服务端同时打开输入/输出流
// BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8));
// writer.write("你好, " + socket.getInetAddress() + ":" + socket.getPort());
// writer.close();
// 5. 服务端读取信息
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
String line = reader.readLine();
System.out.println("客户端说:" + line);
reader.close();
}
}
}
提示:
端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口)。
客户端TCPClient.java:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class TCPClient {
public static void main(String[] args) throws Exception {
// 1. 建立Socket
Socket socket = new Socket("127.0.0.1", 8888);
// 2. 在客户端和服务区段同时打开输入/输出流
// BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
// String line = reader.readLine();
// System.out.println("服务器说:" + line);
// reader.close();
// 3. 向服务端写入
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8));
writer.write("你好, " + socket.getInetAddress() + ":" + socket.getPort());
writer.close();
}
}
前面这是实现了一个服务器端和一个客户端通信的功能。
如何实现服务器端与多个客户端程序通信呢?这就需要多线程。
其中主线程有一个Socket绑定在一个固定端口上,负责监听客户端的Socket信息。
每当启动一个客户端程序,客户端发送来一个Socket连接请求,服务端就新开启一个线程,并在其中创建一个Socket与该客户端的Socket通信,直到客户端程序关闭,结束该线程。
主线程中的Socket在应用程序退出时关闭。
客户端代码如下:
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class TCPClient {
public static void main(String[] args) throws Exception {
Scanner input = new Scanner(System.in);
// 1. 建立Socket
Socket socket = new Socket("127.0.0.1", 8888);
// 2. 在客户端和服务区段同时打开输入/输出流
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
// 3. 向服务端写
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8));
PrintWriter out = new PrintWriter(writer, true);
System.out.println("现在开始对话!");
String content = input.next();
while (true){
if (!content.equals("end")) {
out.println("client:" + content);
String line = reader.readLine();
System.out.println(line);
content = input.next();
} else {
out.println("end");
break;
}
}
}
}
服务端线程ServerThread.java:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class ServerThread extends Thread {
private Socket socket;
private BufferedReader reader;
private PrintWriter out;
public ServerThread(Socket socket) throws Exception {
this.socket = socket;
this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
this.out = new PrintWriter(socket.getOutputStream(), true);
}
@Override
public void run() {
try {
while (true) {
String line = this.reader.readLine();
if (line.equalsIgnoreCase("end")) {
break;
}
System.out.println("来自客户端的消息:" + line);
out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
服务端TCPServer.java:
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) throws Exception{
// 1. 建立Socket
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器启用完成...");
// 2. 等待客户端连接
while (true) {
// 3. 接收客户端连接
Socket socket = serverSocket.accept();
// 4. 实例化新线程
Thread thread = new ServerThread(socket);
// 5. 启动线程
thread.start();
}
}
}