提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
Java是一种广泛应用于网络编程的编程语言。通过Java的网络编程能力,我们可以构建强大的网络应用程序。本文将介绍Java网络编程的基础知识、常用API和一些实践技巧,帮助读者更好地理解和应用Java网络编程。
一、Java网络编程基础
在开始探讨Java网络编程之前,我们需要了解一些基本概念和术语。网络编程是指通过计算机网络进行数据交换和通信的过程。Java提供了一套完善的网络编程API,涵盖了各种协议和服务,如TCP/IP、HTTP、UDP等。
1.网络应用模型
-
**C/S:**这里的C是指Client,即客户端。而S是指Server,即服务端。网络上的的应用本质上就是两台计算机上的软件进行交互。而客户端和服务端就是对应的两个应用程序,即客户端应用程序和服务端应用程序
-
**B/S:**这里的B是Browser,即浏览器,而S是指Server。浏览器是一个通用的客户端,可以与不同的服务端进行交互。但是本质上B/S还是C/S结构,只不过浏览器是一个通用的客户端而已。
2.可靠传输与不可靠传输
TCP协议与UDP协议都是传输协议,客户端程序与服务端程序基于这些协议完成网络间的数据交互。 -
TCP是可靠传输协议,是面向连接的协议,保证数据传输中的可靠性和完整性。
TCP保证可靠传输,但是传输效率低,占用带宽高。 -
UDP是不可靠传输协议,不保证数据传输的完整性。
UDP不保证可靠传输,但是传输速度块,占用带宽小
3.Socket与ServerSocket
- java.net.Socket
Socket(套接字)封装了TCP协议的通讯细节,是的我们使用它可以与服务端建立网络链接,并通过 它获取两个流(一个输入一个输出),然后使用这两个流的读写操作完成与服务端的数据交互
java.net.ServerSocket -
ServerSocket运行在服务端,作用有两个:
- 向系统申请服务端口,客户端的Socket就是通过这个端口与服务端建立连接的。
- 监听服务端口,一旦一个客户端通过该端口建立连接则会自动创建一个Socket,并通过该Socket与客户端进行数据交互。
二、Java网络编程常用API
1.Socket类:Socket类是Java网络编程中最常用的类之一,它提供了创建客户端套接字的方法和与服务器进行通信的能力。通过Socket,可以建立与远程服务器的连接,并进行数据传输。
Socket提供了两个重要的方法:
- OutputStream getOutputStream()
该方法会获取一个字节输出流,通过这个输出流写出的字节数据会通过网络发送给对方。 - InputStream getInputStream()
通过该方法获取的字节输入流读取的是远端计算机发送过来的数据。
原理图
例:
String host = "127.0.0.1";
int port = 8080;
// 创建Socket对象并连接服务器
Socket socket = new Socket(host, port);
// 获取输入流和输出流进行数据传输
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
// 向服务器发送数据
String message = "Hello, Server!";
outputStream.write(message.getBytes());
// 从服务器接收数据
byte[] buffer = new byte[1024];
int length = inputStream.read(buffer);
String response = new String(buffer, 0, length);
// 关闭资源
outputStream.close();
inputStream.close();
socket.close();
2.ServerSocket类:ServerSocket类用于创建服务器套接字,接受来自客户端的连接请求,并创建对应的Socket对象进行通信。通过ServerSocket,可以监听指定的端口,等待客户端的连接。
int port = 8080;
// 创建ServerSocket对象并监听指定端口
ServerSocket serverSocket = new ServerSocket(port);
// 接受客户端的连接请求
Socket socket = serverSocket.accept();
// 获取输入流和输出流进行数据传输
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
// 接收客户端发送的数据
byte[] buffer = new byte[1024];
int length = inputStream.read(buffer);
String request = new String(buffer, 0, length);
// 处理请求并返回响应数据
String response = "Hello, Client!";
outputStream.write(response.getBytes());
// 关闭资源
outputStream.close();
inputStream.close();
socket.close();
serverSocket.close();
3.URL类:URL类提供了对统一资源定位符(URL)的解析和处理能力,它可以用于打开和读取网络资源。通过URL,可以创建一个表示网络资源的对象,并获取资源的各种属性信息。
String urlString = "https://www.example.com";
// 创建URL对象
URL url = new URL(urlString);
// 打开连接并获取输入流
InputStream inputStream = url.openStream();
// 读取数据
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
StringBuilder responseBuilder = new StringBuilder();
while ((line = reader.readLine()) != null) {
responseBuilder.append(line);
}
// 关闭资源
reader.close();
inputStream.close();
4.HttpURLConnection类:HttpURLConnection类是一种常用的建立HTTP连接的方式,它继承自URLConnection类,在Java中用于发送HTTP请求和接收HTTP响应。通过HttpURLConnection,可以设置请求头、发送请求参数,以及获取服务器返回的响应数据。
String urlString = "https://www.example.com";
// 创建URL对象
URL url = new URL(urlString);
// 打开连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置请求方法
connection.setRequestMethod("GET");
// 发送请求并获取响应码
int responseCode = connection.getResponseCode();
// 读取响应数据
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder responseBuilder = new StringBuilder();
while ((line = reader.readLine()) != null) {
responseBuilder.append(line);
}
// 关闭资源
reader.close();
connection.disconnect();
5.DatagramSocket类:DatagramSocket类用于进行UDP数据包的发送和接收,适用于一些实时性要求较高的应用场景。通过DatagramSocket,可以实现基于UDP协议的数据传输。
1.发送数据报文
String host = "127.0.0.1";
int port = 9999;
// 创建DatagramSocket对象
DatagramSocket socket = new DatagramSocket();
// 准备数据
String message = "Hello, Server!";
byte[] data = message.getBytes();
// 创建DatagramPacket对象并设置目标地址和端口号
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName(host), port);
// 发送数据报文
socket.send(packet);
// 关闭资源
socket.close();
2.接收数据报文
int port = 9999;
// 创建DatagramSocket对象并监听指定端口
DatagramSocket socket = new DatagramSocket(port);
// 准备接收数据
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 接收数据报文
socket.receive(packet);
// 处理接收到的数据
String request = new String(packet.getData(), 0, packet.getLength());
// 关闭资源
socket.close();
三、Java网络编程实践技巧
与服务端建立连接案例
例:聊天室
package socket;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/**
* 聊天室客户端
*/
public class Client {
/*
java.net.Socket 套接字
Socket封装了TCP协议的通讯细节,我们通过它可以与远端计算机建立链接,
并通过它获取两个流(一个输入,一个输出),然后对两个流的数据读写完成
与远端计算机的数据交互工作。
我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克
风(输出流),通过它们就可以与对方交流了。
*/
private Socket socket;
/**
* 构造方法,用来初始化客户端
*/
public Client() {
try {
System.out.println("正在连接服务器......");
/*
实例化Socket时要传入两个参数
参数1:服务端的地址信息
可以是IP地址,如果链接本机可以写"localhost"
参数2:服务端开启的服务端口
我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上
的服务端应用程序。
实例化的过程就是链接的过程,如果链接失败会抛出异常:
java.net.ConnectException: Connection refused: connect
*/
socket = new Socket("localhost",8088);
System.out.println("与服务器建立连接了!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 客户端开始工作的方法
*/
public void start() {
try {
/*
Socket提供了一个方法:
OutputStream getOutputStream()
该方法获取的字节输出流写出的字节会通过网络发送给对方计算机。
*/
//低级流,将字节通过网络发送给对方
OutputStream out = socket.getOutputStream();
//高级流,负责衔接字节流与字符流,并将写出的字符按指定字符集转字节
OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
//高级流,负责块写文本数据加速
BufferedWriter bw = new BufferedWriter(osw);
//高级流,负责按行写出字符串,自动行刷新
PrintWriter pw = new PrintWriter(bw,true);
Scanner scanner = new Scanner(System.in);
while(true) {
String line = scanner.nextLine();
if("exit".equalsIgnoreCase(line)) {
break;
}
pw.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
/*
通讯完毕后调用socket的close方法。
该方法会给对方发送断开信号。
*/
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Client client = new Client();
client.start();
}
}
package socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
/**
* 聊天室服务器
*/
public class Server {
/**
* 运行在服务端的ServerSocket主要完成两个工作:
* 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
* 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
* 就可以和该客户端交互了
*
* 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
* 电话使得服务端与你沟通。
*/
private ServerSocket serverSocket;
/**
* 服务端构造方法,用来初始化
*/
public Server() {
try {
/*
ServerSocket实例化的同时指定服务端口
如果该端口被其他程序占用则会抛出异常:
java.net.BindException:address already in use
此时我们需要更换端口,或者杀死占用该端口的进程。
端口号范围:0-65535
*/
System.out.println("正在启动服务......");
/*
实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
应用程序占用的端口相同,否则会抛出异常:
java.net.BindException:address already in use
端口是一个数字,取值范围:0-65535之间。
6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
*/
serverSocket = new ServerSocket(8088);
System.out.println("服务器启动完毕!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 服务端开始工作的方法
*/
public void start() {
try {
/*
ServerSocket的一个重要方法:
Socket accept()
该方法用于接受客户端的连接。这是一个阻塞方法,调用后会"卡住",直到
一个客户端与ServerSocket连接,此时该方法会立即返回一个Socket实例
通过这个Socket实例与该客户端对等连接并进行通讯。
相当于"接电话"的动作
*/
while(true){
System.out.println("等待客户端连接......");
Socket socket = serverSocket.accept();
System.out.println("一个客户端连接了!");
//启动一个线程负责与客户端交互
ClientHandler handler = new ClientHandler(socket);
//创建线程
Thread t = new Thread(handler);
//启动线程
t.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server = new Server();
server.start();
}
/**
* 创建线程的方式:实现Runnable接口单独定义线程任务
* 这个线程任务就是让一个线程与指定的客户端进行交互
*/
private class ClientHandler implements Runnable{
private Socket socket;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in,StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr);
String line;
while((line = br.readLine()) != null){
System.out.println("客户端说:" + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
需要注意的几个点:
- 当客户端不再与服务端通讯时,需要调用socket.close()断开链接,此时会发送断开链接的信号给服
务端。这时服务端的br.readLine()方法会返回null,表示客户端断开了链接。 - 当客户端链接后不输入信息发送给服务端时,服务端的br.readLine()方法是出于阻塞状态的,直到
读取了一行来自客户端发送的字符串。
总结
通过深入理解Java网络编程,我们可以构建强大的网络应用程序,满足不同的业务需求。希望读者通过本文的学习,能够掌握Java网络编程的核心概念和技术,提升自己的开发能力。