Java 网络编程技术总结
Java 有关 UDP 和 TCP 两种协议的网络编程技术,在大部分情况下,很少会使用到,但是偶尔也会使用。对于大部分开发人员来说,最常遇到的使用场景有两种:一种场景是公司的产品或项目需要跟相关的硬件进行对接,另一种场景就是需要跟其它公司进行接口对接(比如某些银行提供的接口就要求使用 socket 对接),所以我们还是得要简单学习和了解一下 Java 的网络编程技术。
对于以上两种常见的使用场景来说,我们使用 Java 网络编程技术去解决,还是很容易的,比较容易上手。
本篇博客的主要目的在于对 Java 网络编程进行一个简单的总结,以便后续需要用到的时候,能够快速找到。
一、UDP 通信
UDP 协议是一种只管传输,不需要回应的网络协议,它在通信的两端各建立一个 Socket 对象,这俩 Socket 对象都可以发送数据和接收数据,但是不需要对方的回复,因此传输效率很高,对于基于 UDP 协议的通信双方而言,其实没有所谓的客户端和服务器的概念。Java 提供以下方法来实现 UDP 协议的通信:
方法名 | 说明 |
---|---|
DatagramSocket() | 创建 UDP Socket 对象 |
DatagramPacket(byte[] buf,int len,InetAddress add,int port) | 创建数据包,指定目标主机端口 |
void send(DatagramPacket p) | 发送数据报包 |
void close() | 关闭连接 |
void receive(DatagramPacket p) | 接收数据报包 |
发送端代码演示:
import java.io.*;
import java.net.*;
//使用控制台,循环发送字符串数据,如果发送的是字符串 exit ,双方服务都停止
public class ClientDemo {
public static void main(String[] args) {
//使用 BufferedReader 来作为控制台的字符串输入交互
//使用 DatagramSocket 创建 UDP 的 Socket 对象
try (BufferedReader bw = new BufferedReader(new InputStreamReader(System.in));
DatagramSocket ds = new DatagramSocket()) {
while (true) {
String str = bw.readLine();
byte[] bytes = str.getBytes();
//准备给本机的 8888 端口,发送 UDP 数据,
InetAddress address = InetAddress.getLocalHost();
int port = 8888;
//对于 UDP 协议来说,使用 DatagramPacket 来封装要发送的数据包
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
ds.send(dp);
//如果发送的是 exit 字符串,就停止发送服务
if ("exit".equals(str)) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
接收端代码演示:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
//循环接收发送来的数据,当接收到 exit 字符串时,停止服务不再接收数据
public class ServerDemo {
public static void main(String[] args) {
//使用 DatagramSocket 创建 UDP 的 Socket 对象
try (DatagramSocket ds = new DatagramSocket(8888);) {
while (true) {
//使用 DatagramPacket 封装接收的 UDP 数据包
DatagramPacket dp = new DatagramPacket(new byte[1024], 1024);
ds.receive(dp);
String result = new String(dp.getData(), 0, dp.getLength());
System.out.println(result);
//如果接收到 exit 字符串,则停止服务,不再接收
if ("exit".equals(result)) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
以上代码就是 Java 采用 UDP 协议进行通信的基本编码,大家可以根据工作中的实际需要进行改进,总体来说还算比较简单。
二、TCP 通信
TCP 协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过三次握手。
TCP 协议是我们最常使用的通信协议,对于客户端需要创建 Socket 对象,对于服务端需要创建 ServerSocket 对象。
无论对于客户端还是服务端,可以通过其 Scoket 对象获取到 InputStream 流和 OutputStream 流。通过 OutputStream 流写数据就是发送数据,通过 InputStream 流读数据就是接收数据。对于客户端 Socket 对象发送数据来说,需要特别注意:如果客户端 Socket 对象后续不再发送数据的话,需要调用其 shutdownOutput 方法明确告诉服务后续不再发送数据,这样服务端才会停止接收数据,否则服务端接收数据的代码将会阻塞,无法继续向下运行。下面我们还是通过代码来演示一下吧。
客户端代码演示:
import java.io.*;
import java.net.Socket;
//本代码演示客户端通过 TCP 协议,向服务端上传图片文件
public class ClientDemo {
public static void main(String[] args) {
//创建客户端 Socket 对象,创建字节缓冲流对象读取要上传的图片文件
try (Socket socket = new Socket("127.0.0.1", 8888);
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("d:\\数据产品组大合照.jpg"));) {
//获取 Socket 输出流对象,用于向服务端发送数据,
//由于该输出流是从 Socket 中获取,因此不需要主动关闭,
//因为 Socket 关闭后,该输出流会自动关闭
BufferedOutputStream bos =
new BufferedOutputStream(socket.getOutputStream());
byte[] bArr = new byte[1024];
int len;
while ((len = bis.read(bArr)) != -1) {
bos.write(bArr, 0, len);
}
bos.flush();
//注意:此行代码非常重要,告诉服务端:这边的客户端数据已经全部发送完毕了
socket.shutdownOutput();
//接收服务端返回来的消息,打印到控制台上,
//由于该输入流是从 Socket 中获取,因此不需要主动关闭,
//因为 Socket 关闭后,该输入流会自动关闭
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端的代码演示:
import java.io.*;
import java.net.*;
//接收客户端发送来的图片数据,并保存到硬盘中
public class ServerDemo {
public static void main(String[] args) {
//创建服务端 ServerSocket 对象,创建字节流缓冲对象把接收的图片保存到硬盘
try (ServerSocket ss = new ServerSocket(8888);
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("d:\\copy.jpg"));
//获取客户端的 socket 对象
//注意这里获取的客户端 Socket 对象由于在 try 小括号内初始化,
//因此出了 try 代码块后,会自动关闭客户端 Socket 对象
//这样客户端那边就可以结束了
Socket accept = ss.accept()) {
//接收客户端发来的数据,保存到硬盘中
//由于该输入流是从 Socket 中获取,因此不需要主动关闭,
//因为 Socket 关闭后,该输入流会自动关闭
BufferedInputStream bis =
new BufferedInputStream(accept.getInputStream());
byte[] bArr = new byte[1024];
int len;
while ((len = bis.read(bArr)) != -1) {
bos.write(bArr, 0, len);
}
//获取 Socket 输出流对象,用于向客户端发送数据,
//由于该输出流是从 Socket 中获取,因此不需要主动关闭,
//因为 Socket 关闭后,该输出流会自动关闭
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(accept.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
上面的服务端代码,只能接收到一个客户端的文件上传,为了能够支持多客户端,我们可以采用线程池进行改进,首先我们先写一个接收客户端上传的图片数据,并保存到硬盘的线程类,代码如下所示:
import java.io.*;
import java.net.Socket;
import java.util.UUID;
public class ThreadSocket implements Runnable {
private Socket acceptSocket;
public ThreadSocket(Socket accept) { this.acceptSocket = accept; }
@Override
public void run() {
//创建字节缓冲流对象,用于向硬盘保存图片数据
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("d:\\" + UUID.randomUUID() + ".jpg"))) {
//接收客户端发送过来的数据
BufferedInputStream bis =
new BufferedInputStream(acceptSocket.getInputStream());
byte[] bArr = new byte[1024];
int len;
while ((len = bis.read(bArr)) != -1) {
bos.write(bArr, 0, len);
}
//向客户端发送消息
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(acceptSocket.getOutputStream()));
bw.write("图片已经上传成功");
bw.newLine();
bw.flush();
//关闭服务器接收到的客户端 socket 对象,这个很重要,不然的话,客户端无法结束
acceptSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
然后我们使用线程池,改进服务端代码,这样就可以同时接纳多个客户端上传图片了,代码如下所示:
import java.net.*;
import java.util.concurrent.*;
public class ServerDemo {
public static void main(String[] args) {
try (ServerSocket ss = new ServerSocket(8888)) {
//创建线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
5,//核心线程数量
10,//线程池的总数量
20,//临时线程空闲时间
TimeUnit.SECONDS,//临时线程空闲时间的单位
new ArrayBlockingQueue<>(5),//阻塞队列
Executors.defaultThreadFactory(),//创建线程的方式
new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
);
while (true) {
Socket acceptSocket = ss.accept();
ThreadSocket ts = new ThreadSocket(acceptSocket);
//向线程池提交任务
pool.submit(ts);
}
//pool.shutdown(); //关闭线程池
} catch (Exception e) {
e.printStackTrace();
}
}
}
以上就是简单的介绍了 Java 有关 UDP 和 TCP 网络编程的相关技术,其中 UDP 示例是发送文字信息,TCP 示例是上传图片文件,并采用线程池进行了改进。
有关 TCP 发送和接收文字信息,由于非常简单,所以这里就不介绍了。之所以介绍 TCP 上传图片文件,是因为让大家注意客户端 Scoket 对象发送完数据后,要调用 shutdownOutput 告诉服务端,否则服务端是不知道客户端已经发送完毕了。
希望以上代码示例,大家能够看懂,对大家有所帮助。