Java网络编程
内容大纲
方便快速了解文章内容
网络基础知识
IPV4、IPV6、IP地址与域名、端口
网络通讯协议:TCP、IP协议
TCP和UDP协议的对比(三次握手,四次挥手)
TCP:面向连接,三次握手,四次挥手
UDP:无连接,不可靠,但速度快
InetAddress类的常用方法
静态方法:获取本机,根据主机名获取,根据域名获取
InetAddress.getLocalHost()
InetAddress.getByName(“WIN-04BRQU4AJTC”);
InetAddress.getByName(“www.baidu.com”);
package com.boot.net;
import java.net.InetAddress;
/**
* @author bbyh
* @date 2022/11/7 0007 13:15
* @description
*/
public class Test {
public static void main(String[] args) throws Exception {
// 获取本机
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);
System.out.println(localHost.getHostName());
System.out.println(localHost.getHostAddress());
System.out.println("-------------------------------------");
// 根据主机名获取一个
InetAddress address = InetAddress.getByName("WIN-04BRQU4AJTC");
System.out.println(address);
System.out.println("-------------------------------------");
// 根据主机名获取所有
InetAddress[] inetAddresses = InetAddress.getAllByName("WIN-04BRQU4AJTC");
for (InetAddress inetAddress : inetAddresses) {
System.out.println(inetAddress.getHostName() + " : " + inetAddress.getHostAddress());
}
System.out.println("-------------------------------------");
// 根据域名获取
InetAddress inetAddress = InetAddress.getByName("www.baidu.com");
System.out.println(inetAddress);
System.out.println("-------------------------------------");
InetAddress[] inetAddresses1 = InetAddress.getAllByName("www.baidu.com");
for (InetAddress inetAddress1 : inetAddresses1) {
System.out.println(inetAddress1.getHostName() + " : " + inetAddress1.getHostAddress());
}
System.out.println("-------------------------------------");
}
}
TCP网络通信(Socket)
基本的简单客户端和服务器
单向通信–客户端向服务器端发送一次数据并退出,服务器端接收并退出
服务器端
package com.boot.net;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author bbyh
* @date 2022/11/7 0007 13:45
* @description
*/
public class Server {
public static final int PORT = 9999;
public static final String IP = "127.0.0.1";
public static void main(String[] args) throws Exception {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("服务器开始监听" + PORT + "端口");
Socket accept = serverSocket.accept();
System.out.println("服务器接收到连接");
InputStream inputStream = accept.getInputStream();
byte[] buf = new byte[1024];
int read;
while ((read = inputStream.read(buf)) != -1) {
System.out.println("接收到来自客户端的数据: " + new String(buf, 0, read));
}
System.out.println("服务器端接收完成,退出");
inputStream.close();
accept.close();
}
}
}
客户端
package com.boot.net;
import java.io.OutputStream;
import java.net.Socket;
/**
* @author bbyh
* @date 2022/11/7 0007 13:45
* @description
*/
public class Client {
public static void main(String[] args) throws Exception {
try (Socket socket = new Socket(Server.IP, Server.PORT)) {
System.out.println("客户端连接" + Server.IP + ": " + Server.PORT + "成功");
OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello,Server1".getBytes());
outputStream.write("Hello,Server2".getBytes());
outputStream.close();
System.out.println("客户端信息发送完成,退出");
}
}
}
双向通信一次
客户端向服务器端发送数据,并接收来自服务器端的数据,服务器端接收来自客户端的数据并向客户端发送一次数据
这次的双向通信,代表着双方需要知道对方是否已经数据发送完成,所以需要设置一个分隔符,采用accept.shutdownOutput(); 、 socket.shutdownInput(); 实现
服务器端
package com.boot.net;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author bbyh
* @date 2022/11/7 0007 13:45
* @description
*/
public class Server {
public static final int PORT = 9999;
public static final String IP = "127.0.0.1";
public static void main(String[] args) throws Exception {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("服务器开始监听" + PORT + "端口");
Socket accept = serverSocket.accept();
System.out.println("服务器接收到连接");
InputStream inputStream = accept.getInputStream();
byte[] buf = new byte[1024];
int read;
while ((read = inputStream.read(buf)) != -1) {
System.out.println("接收到来自客户端的数据: " + new String(buf, 0, read));
}
accept.shutdownInput();
System.out.println("服务器端接收完成");
OutputStream outputStream = accept.getOutputStream();
outputStream.write("Hello,Client1".getBytes());
outputStream.write("Hello,Client2".getBytes());
accept.shutdownOutput();
System.out.println("服务器端信息发送完成, 退出");
inputStream.close();
outputStream.close();
accept.close();
}
}
}
客户端(实际上只在 Client 端设置分隔,也能够正常执行)
package com.boot.net;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* @author bbyh
* @date 2022/11/7 0007 13:45
* @description
*/
public class Client {
public static void main(String[] args) throws Exception {
try (Socket socket = new Socket(Server.IP, Server.PORT)) {
System.out.println("客户端连接" + Server.IP + ": " + Server.PORT + "成功");
OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello,Server1".getBytes());
outputStream.write("Hello,Server2".getBytes());
socket.shutdownOutput();
System.out.println("客户端信息发送完成");
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int read;
while ((read = inputStream.read(buf)) != -1) {
System.out.println("接收到来自服务器端的数据: " + new String(buf, 0, read));
}
socket.shutdownInput();
System.out.println("客户端接收完成,退出");
outputStream.close();
inputStream.close();
}
}
}
采用字符流进行双向通信
上面的都是字节流,这里采用字符流进行双向通信;(大致的逻辑没有变化,只是对字节流进行转换为字符流)
对于字符流而言,在写入完成后需要使用 writer.flush(); 进行数据写入到管道中,不然无法发送给接收端
这里的 readLine 在读到末尾时是返回 null,而不是空字符串
package com.boot.net;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Objects;
/**
* @author bbyh
* @date 2022/11/7 0007 13:45
* @description
*/
public class Server {
public static final int PORT = 9999;
public static final String IP = "127.0.0.1";
public static void main(String[] args) throws Exception {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("服务器开始监听" + PORT + "端口");
Socket accept = serverSocket.accept();
System.out.println("服务器接收到连接");
BufferedReader reader = new BufferedReader(new InputStreamReader(accept.getInputStream()));
String readLine;
while (!Objects.equals(readLine = reader.readLine(), null)) {
System.out.println("接收到来自客户端的数据: " + readLine);
}
accept.shutdownInput();
System.out.println("服务器端接收完成");
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
writer.write("Hello,客户端1\n");
writer.write("Hello,客户端2\n");
writer.flush();
accept.shutdownOutput();
System.out.println("服务器端信息发送完成, 退出");
reader.close();
writer.close();
accept.close();
}
}
}
客户端
package com.boot.net;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Objects;
/**
* @author bbyh
* @date 2022/11/7 0007 13:45
* @description
*/
public class Client {
public static void main(String[] args) throws Exception {
try (Socket socket = new Socket(Server.IP, Server.PORT)) {
System.out.println("客户端连接" + Server.IP + ": " + Server.PORT + "成功");
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
writer.write("Hello,服务器1\n");
writer.write("Hello,服务器2\n");
writer.flush();
socket.shutdownOutput();
System.out.println("客户端信息发送完成");
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readLine;
while (!Objects.equals(readLine = reader.readLine(), null)) {
System.out.println("接收到来自服务器端的数据: " + readLine);
}
socket.shutdownInput();
System.out.println("客户端接收完成,退出");
writer.close();
reader.close();
}
}
}
网络上传文件
采用 Socket 进行文件上传
这里面会遇到一些小问题,即如何进行二进制传输时,将文件流与文件名进行区分;以及对于方法传递的最大字节数组范围为64KB,所以需要进行一些修改
目前还没有查到为啥方法传递字节数组的大小不能超多64KB,我觉得应该和安全有关
FileUtils.java (设置了方法传递的最大字节数组大小为64KB,这是Java底层设计的,这里进行了限制,防止出现数据错误;以及文件最大大小为1MB)
采用 Arrays.copyOf(bytes, read) 对结果数组进行尾部去除
package com.boot.net;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
/**
* @author bbyh
* @date 2022/11/7 0007 15:41
* @description
*/
public class FileUtils {
public static final int MAX_SIZE = 1024 * 1024;
public static final int METHOD_BUF_SIZE = 1024 * 64;
public static byte[] read(String fileName) {
byte[] bytes = new byte[MAX_SIZE];
try (InputStream inputStream = Files.newInputStream(Paths.get(fileName))) {
int read = inputStream.read(bytes);
if (read != -1) {
return Arrays.copyOf(bytes, read);
}
} catch (Exception e) {
System.out.println("文件未找到或文件过大");
}
throw new UnsupportedOperationException("文件未找到或文件过大");
}
}
服务器端 (限制了每次读取的字节数组大小为64KB)
package com.boot.net;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* @author bbyh
* @date 2022/11/7 0007 13:45
* @description
*/
public class Server {
public static final int PORT = 9999;
public static final String IP = "127.0.0.1";
private static final String DESC_FILENAME = "src/image/2.jpg";
public static void main(String[] args) throws Exception {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("服务器开始监听" + PORT + "端口");
Socket accept = serverSocket.accept();
System.out.println("服务器接收到连接");
OutputStream outputStream = accept.getOutputStream();
InputStream inputStream = accept.getInputStream();
OutputStream writer = Files.newOutputStream(Paths.get(DESC_FILENAME));
byte[] buf = new byte[FileUtils.METHOD_BUF_SIZE];
int read;
while ((read = inputStream.read(buf)) != -1) {
writer.write(buf, 0, read);
}
outputStream.write("上传成功".getBytes());
accept.shutdownOutput();
accept.shutdownInput();
inputStream.close();
outputStream.close();
accept.close();
}
}
}
客户端
package com.boot.net;
import java.io.*;
import java.net.Socket;
/**
* @author bbyh
* @date 2022/11/7 0007 13:45
* @description
*/
public class Client {
private static final String SRC_FILENAME = "src/image/1.jpg";
public static void main(String[] args) throws Exception {
try (Socket socket = new Socket(Server.IP, Server.PORT)) {
System.out.println("客户端连接" + Server.IP + ": " + Server.PORT + "成功");
byte[] transform = FileUtils.read(SRC_FILENAME);
OutputStream outputStream = socket.getOutputStream();
outputStream.write(transform);
socket.shutdownOutput();
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int read = inputStream.read(buf);
if (read != -1) {
System.out.println("文件上传结果: " + new String(buf, 0, read));
}
socket.shutdownInput();
inputStream.close();
outputStream.close();
}
}
}
由客户端同时上传文件名
服务器端(设置好文件名的字节大小,在客户端进行了检验,当文件名过长,会提示不支持)
实际上对于客户端和服务器端的各个内容的字节数没有做统一处理,是有一定的问题的
package com.boot.net;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* @author bbyh
* @date 2022/11/7 0007 13:45
* @description
*/
public class Server {
public static final int PORT = 9999;
public static final String IP = "127.0.0.1";
public static final int FILENAME_SIZE = 80;
public static void main(String[] args) throws Exception {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("服务器开始监听" + PORT + "端口");
Socket accept = serverSocket.accept();
System.out.println("服务器接收到连接 " + accept.getInetAddress() + " : " + accept.getPort());
OutputStream outputStream = accept.getOutputStream();
InputStream inputStream = accept.getInputStream();
byte[] fileName = new byte[FILENAME_SIZE];
int fileSize = inputStream.read(fileName);
OutputStream writer = Files.newOutputStream(Paths.get(new String(fileName, 0, fileSize)));
byte[] buf = new byte[FileUtils.METHOD_BUF_SIZE];
int read;
while ((read = inputStream.read(buf)) != -1) {
writer.write(buf, 0, read);
}
outputStream.write("上传成功".getBytes());
accept.shutdownOutput();
accept.shutdownInput();
inputStream.close();
outputStream.close();
accept.close();
}
}
}
客户端(在这里对文件名进行了限制)
package com.boot.net;
import java.io.*;
import java.net.Socket;
/**
* @author bbyh
* @date 2022/11/7 0007 13:45
* @description
*/
public class Client {
private static final String SRC_FILENAME = "src/image/1.jpg";
private static final String DESC_FILENAME = "src/image/4.jpg";
public static void main(String[] args) throws Exception {
try (Socket socket = new Socket(Server.IP, Server.PORT)) {
System.out.println("客户端连接" + Server.IP + ": " + Server.PORT + "成功");
byte[] transform = FileUtils.read(SRC_FILENAME);
OutputStream outputStream = socket.getOutputStream();
if (DESC_FILENAME.getBytes().length > Server.FILENAME_SIZE) {
throw new UnsupportedOperationException("上传到文件名过长,请重新设定文件名");
}
outputStream.write(DESC_FILENAME.getBytes());
outputStream.write(transform);
socket.shutdownOutput();
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int read = inputStream.read(buf);
if (read != -1) {
System.out.println("文件上传结果: " + new String(buf, 0, read));
}
socket.shutdownInput();
inputStream.close();
outputStream.close();
}
}
}
netstat指令
netstat -an ,查看当前网络主机情况,包括端口监听和网络连接
netstat -an | more 分页显示(按空格进行下一页切换)
netstat -anb 查看连接的应用
UDP网络通信
没有明确的服务器端和客户端,而是变成接收端和发送端
关于UDP数据包的最大长度为64KB,且单个报文片为1472字节,建议在这个大小以下,这篇文章有介绍
但是具体的报文格式还需要再查一下资料
简单的单向通信版
仅实现发送端向接收端发送消息,接收端接收到消息并显示到控制台
接收端
package com.boot.net.udp;
import java.net.*;
/**
* @author bbyh
* @date 2022/11/7 0007 20:06
* @description
*/
public class Receive {
public static final String IP = "127.0.0.1";
public static final int RECEIVE_PORT = 9999;
public static final int MAX_SIZE = 1024;
public static void main(String[] args) throws Exception {
try (DatagramSocket socket = new DatagramSocket(RECEIVE_PORT)) {
byte[] buf = new byte[MAX_SIZE];
DatagramPacket packet = new DatagramPacket(buf, MAX_SIZE);
System.out.println("接收端等待接收数据中...");
// 接收数据报,是阻塞模式
socket.receive(packet);
System.out.println("接收端接收到来自: " + packet.getAddress() + " : " + packet.getPort() + " 的数据");
int length = packet.getLength();
byte[] data = packet.getData();
System.out.println("长度为: " + length + " 数据为: " + new String(data));
}
}
}
发送端,采用 new DatagramPacket(buf, buf.length, InetAddress.getByName(Receive.IP), Receive.RECEIVE_PORT); 构造器进行数据报的指定发送
package com.boot.net.udp;
import java.net.*;
/**
* @author bbyh
* @date 2022/11/7 0007 20:06
* @description
*/
public class Send {
private static final int SEND_PORT = 8888;
public static void main(String[] args) throws Exception {
try (DatagramSocket socket = new DatagramSocket(SEND_PORT)) {
byte[] buf = "Hello,Receiver".getBytes();
DatagramPacket packet = new DatagramPacket(buf, buf.length, InetAddress.getByName(Receive.IP), Receive.RECEIVE_PORT);
packet.setData(buf);
socket.send(packet);
System.out.println("发送端数据发送数据结束");
}
}
}
双向通信版本
实现接收端接收到来自发送端的数据,并向发送端发送数据;发送端向接收端发送数据,并接收到来自接收端的数据
该版本需要先执行接收端,再执行发送端,因为没有设计循环接收与发送的功能,所以对顺序要求严格
接收端
package com.boot.net.udp;
import java.net.*;
/**
* @author bbyh
* @date 2022/11/7 0007 20:06
* @description
*/
public class Receive {
public static final String RECEIVE_IP = "127.0.0.1";
public static final int RECEIVE_PORT = 9999;
public static final int MAX_SIZE = 1024;
public static void main(String[] args) throws Exception {
try (DatagramSocket socket = new DatagramSocket(RECEIVE_PORT)) {
byte[] buf = new byte[MAX_SIZE];
DatagramPacket packet = new DatagramPacket(buf, MAX_SIZE);
System.out.println("接收端等待接收数据中...");
// 接收数据报,是阻塞模式
socket.receive(packet);
System.out.println("接收端接收到来自: " + packet.getAddress() + " : " + packet.getPort() + " 的数据");
int length = packet.getLength();
byte[] data = packet.getData();
System.out.println("长度为: " + length + " 数据为: " + new String(data));
byte[] send = "Hello,Sender".getBytes();
DatagramPacket sendPacket = new DatagramPacket(send, send.length, InetAddress.getByName(Send.SEND_IP), Send.SEND_PORT);
sendPacket.setData(send);
socket.send(sendPacket);
System.out.println("接收端发送数据结束");
}
}
}
发送端
package com.boot.net.udp;
import java.net.*;
/**
* @author bbyh
* @date 2022/11/7 0007 20:06
* @description
*/
public class Send {
public static final String SEND_IP = "127.0.0.1";
public static final int SEND_PORT = 8888;
public static void main(String[] args) throws Exception {
try (DatagramSocket socket = new DatagramSocket(SEND_PORT)) {
byte[] buf = "Hello,Receiver".getBytes();
DatagramPacket packet = new DatagramPacket(buf, buf.length, InetAddress.getByName(Receive.RECEIVE_IP), Receive.RECEIVE_PORT);
packet.setData(buf);
socket.send(packet);
System.out.println("发送端数据发送数据结束");
System.out.println("发送端等待接收数据中...");
byte[] receive = new byte[Receive.MAX_SIZE];
DatagramPacket receivePacket = new DatagramPacket(receive, Receive.MAX_SIZE);
socket.receive(receivePacket);
System.out.println("发送端接收到来自: " + receivePacket.getAddress() + " : " + receivePacket.getPort() + " 的数据");
int length = receivePacket.getLength();
byte[] data = receivePacket.getData();
System.out.println("长度为: " + length + " 数据为: " + new String(data));
}
}
}
TCP实现文件下载
客户端发送文件名,服务器端返回对应文件,如果文件不存在则返回默认文件
在服务器端进行了文件名,下载结果,默认图片,默认文件夹的设置,方便统一
以及对接收到的下载文件名进行校验,当没有下载文件名时提示错误
package com.boot.net.socket;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* @author bbyh
* @date 2022/11/7 0007 13:45
* @description
*/
public class Server {
public static final int PORT = 9999;
public static final String IP = "127.0.0.1";
public static final int FILENAME_SIZE = 80;
public static final int UPLOAD_RESULT_SIZE = 20;
private static final String DEFAULT_FILENAME = "default.jpg";
private static final String SRC_DIR = "src/image/";
public static void main(String[] args) throws Exception {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("服务器开始监听" + PORT + "端口");
Socket accept = serverSocket.accept();
System.out.println("服务器接收到连接 " + accept.getInetAddress() + " : " + accept.getPort());
OutputStream outputStream = accept.getOutputStream();
InputStream inputStream = accept.getInputStream();
byte[] fileName = new byte[FILENAME_SIZE];
int fileSize = inputStream.read(fileName);
InputStream reader;
if (fileSize == -1) {
throw new UnsupportedOperationException("未设置下载文件名");
} else {
File file = new File(SRC_DIR + new String(fileName, 0, fileSize));
if (file.exists()) {
reader = Files.newInputStream(Paths.get(SRC_DIR + new String(fileName, 0, fileSize)));
} else {
reader = Files.newInputStream(Paths.get(SRC_DIR + DEFAULT_FILENAME));
}
}
byte[] buf = new byte[FileUtils.MAX_SIZE];
int read = reader.read(buf);
byte[] uploadResult = new byte[Server.UPLOAD_RESULT_SIZE];
byte[] bytes = "下载成功".getBytes();
System.arraycopy(bytes, 0, uploadResult, 0, bytes.length);
outputStream.write(uploadResult);
outputStream.write(buf, 0, read);
accept.shutdownOutput();
accept.shutdownInput();
inputStream.close();
outputStream.close();
accept.close();
}
}
}
客户端,则对上传的文件名进行校验,及按顺序读出下载结果及下载的文件的字节数组。并保存到指定文件夹的指定文件中;考虑到 OutputStream 无法对多级目录下的文件进行不存在自动创建操作,所以需要先判断文件夹是否存在,先创建文件夹
package com.boot.net.socket;
import java.io.*;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Objects;
/**
* @author bbyh
* @date 2022/11/7 0007 13:45
* @description
*/
public class Client {
private static final String DESC_DIR = "src/image/download/";
private static final String FILENAME = "1.jpg";
public static void main(String[] args) throws Exception {
try (Socket socket = new Socket(Server.IP, Server.PORT)) {
System.out.println("客户端连接" + Server.IP + ": " + Server.PORT + "成功");
if (FILENAME.getBytes().length > Server.FILENAME_SIZE) {
throw new UnsupportedOperationException("文件名过长, 请重新设置");
} else if (Objects.equals(FILENAME, "")) {
throw new UnsupportedOperationException("请设置要下载的文件名");
}
File file = new File(DESC_DIR);
if (!file.exists()) {
boolean mkdir = file.mkdir();
if (!mkdir) {
throw new UnsupportedOperationException("未知错误导致文件夹创建失败");
}
}
OutputStream outputStream = socket.getOutputStream();
outputStream.write(FILENAME.getBytes());
socket.shutdownOutput();
InputStream inputStream = socket.getInputStream();
OutputStream writer = Files.newOutputStream(Paths.get(DESC_DIR + FILENAME));
byte[] buf = new byte[1024];
byte[] uploadResult = new byte[Server.UPLOAD_RESULT_SIZE];
int read = inputStream.read(uploadResult);
if (read != -1) {
System.out.println("文件下载结果: " + new String(uploadResult, 0, read));
}
while ((read = inputStream.read(buf)) != -1) {
writer.write(buf, 0, read);
}
socket.shutdownInput();
writer.close();
inputStream.close();
outputStream.close();
}
}
}