Java - 网络编程概述
1. 概述
- java是Internet上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序
- Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在Java的本级安装系统里,由JVM进行控制。
并且Java实现了一个跨平台的网卡库,程序员面对的是一个统一的网络编程环境。
2. 网络通信要素要素1:IP地址和端口号
import org.junit.Test;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @ClassName: InetAddressTest
* @Description: Java - 网络编程测试
* @author: Mr.zhi
* @version: v1.0
* @data: 2022/3/19 17:31
* @node:
* 1. 在Java中使用InetAddress类表示IP
* 2. 可以通过InetAddress.getByName("192.168.0.1"); 来获取IP地址
* 3. 使用域名:www.baidu.com www.sina.com
* 4. 本级IP:loaclhost / 127.0.0.1
* 5. 如何实例化InetAddress:
* 5.1 getByName(String host)
* 5.2 getLocalHost();
* 6. 两个常用方法:getHostName() / getHostAddress() : 获取域名和Ip地址
*
* 7. 端口号:要求不同的进程有不同的端口号:0~65535
*
*/
public class InetAddressTest {
@Test
public void InetAddressTest01() {
try {
// 网络IP地址获取测试1
InetAddress inet1 = InetAddress.getByName("192.168.0.1");
System.out.println(inet1);
InetAddress[] inet2 = InetAddress.getAllByName("www.baidu.com");
for (int i = 0; i < inet2.length; ++i) {
System.out.println(inet2[i]);
}
InetAddress inet3 = InetAddress.getByName("127.0.0.1");
System.out.println(inet3);
// 可通过 直接获取本地的IP地址
InetAddress inet4 = InetAddress.getLocalHost();
System.out.println(inet4);
// getHostName() : 获取Ip地址的域名
System.out.println(inet2[0].getHostName());
// getHostAddress() : 获取域名的IP地址
System.out.println(inet4.getHostName());
System.out.println(inet4.getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
3. 通信要素2:网络协议
3.1 TCP
- 使用TCP协议前,须先建立TCP连接,形成传输数据通道
- 传输前,采用“三次握手”方式,点对点通信,是可靠的
- TCP协议进行通信的两个应用进程:客户端、服务器
- 在连接中可进行大数据量的传输
- 传输完毕后,需释放已建立的连接,效率低
3.2 UDP
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据报的大小限制在64K内
- 发送不管对方会是否准备好,接收方收到也不确认,故为不可靠的
- 可以广播发送
- 发送数据结束时无序释放资源,开销小,速度快
3.3 TCP代码测试
- Server端
public void server() {
ServerSocket ssc = null;
Socket sc = null;
InputStream is = null;
ByteArrayOutputStream bios = null;
try {
// 1. 创建服务端Socket,必须指定端口号,否则无法创建Socket套接字
ssc = new ServerSocket(7859);
// 2. 接收客户端的socket
sc = ssc.accept();
// 3. 获取输入流
is = sc.getInputStream();
bios = new ByteArrayOutputStream();
byte[] buffer = new byte[100];
int len = -1;
while ((len = is.read(buffer)) != -1) {
bios.write(buffer, 0, len);
}
// 4. 打印数据
if (bios.size() != 0) {
System.out.print("服务器接收到:" + sc.getInetAddress().getHostAddress() + " 发送过来的数据: ");
System.out.println(bios);
bios.reset();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 关闭流和套接字
if (bios != null) {
try {
bios.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (sc != null) {
try {
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ssc != null) {
try {
ssc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- client端
public void client() {
Socket sc = null; // 指明IP地址和端口号
OutputStream os = null;
try {
// 1. 创建Socket对象,指明服务器端的IP地址和端口号
InetAddress addr = InetAddress.getByName("127.0.0.1");
sc = new Socket(addr, 7859);
// 2. 获取一个输出流,用于输出数据
os = sc.getOutputStream();
// 3. 写出数据
// 循环写出数据,直到输入exit
StringBuilder stringBuilder = null;
os.write(new String("栋梁栋梁!这里是TCP的Client客户端,收到请回复!").getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 关闭Socket对象
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (sc != null) {
try {
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.4 TCP测试二:实现文件的复制
// note: 在实际开发中,一定要使用try-catch-finally进行异常的处理,而不是直接抛出。这里为了显示代码逻辑进行了直接抛出
@Test
public void client() throws IOException {
Socket sc = new Socket( InetAddress.getByName("127.0.0.1"), 8001);
OutputStream outputStream = sc.getOutputStream();
FileInputStream fis = new FileInputStream("photo.jpg");
byte[] data = new byte[1024];
int len = -1;
while ((len = fis.read(data)) != -1) {
outputStream.write(data,0, len);
}
System.out.println("文件复制完成");
fis.close();
outputStream.close();
sc.close();
}
@Test
public void server() throws IOException {
ServerSocket serverSocketc = new ServerSocket(8001);
Socket socket = serverSocketc.accept();
InputStream inputStream = socket.getInputStream();
FileOutputStream fos = new FileOutputStream("photo2.jpg");
byte[] buffer = new byte[1024];
int len = -1;
while ((len = inputStream.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println("文件复制完成");
fos.close();
inputStream.close();
socket.close();
serverSocketc.close();
}
3.5 TCP测试三:当服务端收到通知后回复客户端
- note: 需要注意的是,因为read()为阻塞式的,因此服务端需要知道客户端什么时候发送结束。
- 因此在客户端需要使用socket.shutdownOutput()方法
// note: 在实际开发中,一定要使用try-catch-finally进行异常的处理,而不是直接抛出。这里为了显示代码逻辑进行了直接抛出
@Test
public void client() throws IOException {
Socket sc = new Socket( InetAddress.getByName("127.0.0.1"), 8001);
OutputStream outputStream = sc.getOutputStream();
outputStream.write("栋梁栋梁!这里是Clinet客户端,收到请回复!".getBytes(StandardCharsets.UTF_8));
// 当客户端想要接收数据时,需要告诉服务端什么时候发送完成
// 因为read()为阻塞式的
sc.shutdownOutput();
InputStream inputStream = sc.getInputStream();
byte[] buffer = new byte[1024];
int len = -1;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((len = inputStream.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
System.out.println(baos.toString());
baos.close();
inputStream.close();
outputStream.close();
sc.close();
}
@Test
public void server() throws IOException {
ServerSocket serverSocketc = new ServerSocket(8001);
Socket socket = serverSocketc.accept();
InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = -1;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((len = inputStream.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
System.out.println(baos);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("收到回复!".getBytes(StandardCharsets.UTF_8));
System.out.println("已回复");
outputStream.close();
inputStream.close();
socket.close();
serverSocketc.close();
}
结果:
TCP服务端:
TCP客户端:
4. UDP 网络通信测试
- 类 DatagramSocket 和 DatagramPacket 实现了基于UDP协议网络程序
- UDP数据报通过套接字 DatagramSocket 发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
- DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号
- UDP协议中每个数据报都给出了完成的地址信息,因此无序建立发送方和接收方的连接。
4.1 UDP代码测试一:
import org.junit.Test;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
/**
* @ClassName: UDPTest
* @Description: Java - 网络编程之UDP测试
* @author: Mr.zhi
* @version: v1.0
* @data: 2022/3/20 21:50
* @node:
* - 类 DatagramSocket 和 DatagramPacket 实现了基于UDP协议网络程序
* - UDP数据报通过套接字 DatagramSocket 发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
* - DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号
* - UDP协议中每个数据报都给出了完成的地址信息,因此无序建立发送方和接收方的连接。
*/
public class UDPTest {
@Test
// UDP的发送端
public void sender() {
DatagramSocket datagramSocket = null; // 此时可以不用指定IP地址和端口号
try {
// 1. 使用类DatagramSocket 建立UDP的套接字
datagramSocket = new DatagramSocket();
// 2. 使用类DatagramPacket 建立UDP的数据报,来存储数据
// 如果 datagramSocket 没有指定IP和端口号,则需要在这里进行绑定
String str = new String("Hello, 这是使用UDP发送的数据报!");
byte[] data = str.getBytes(StandardCharsets.UTF_8);
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
DatagramPacket datagramPacket = new DatagramPacket(data, 0, data.length, inetAddress, 8081);
// 3. 调用DatagramSocket的send()方法发送数据
datagramSocket.send(datagramPacket);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 关闭套接字
if (datagramSocket != null) {
datagramSocket.close();
}
}
}
// UDP的接收端
@Test
public void receiver() {
DatagramSocket socket = null;
try {
// 1. 建立UDP套接字,因为是服务端,所以需要指定端口号
socket = new DatagramSocket(8081);
// 2. 建立数据报
byte[] buffer = new byte[100]; // 因此使用UDP接收数据报时,需要一次性全部接收,如果接收不完则会抛弃,因此可以将该数组设置大一点
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
// 3. 调用DatagramSocket的receive()方法进行数据接收,并存放如packet中
socket.receive(packet);
// 4. 将数据进行打印
System.out.println(new String(packet.getData(), 0, packet.getLength()));
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 关闭套接字
socket.close();
}
}
}
UDP服务端的控制台输出:
4.2 UDP代码测试二:可以连续发送数据
public class UDPTest {
public static void main(String[] args) {
UDPTest.sender();
}
// @Test
// UDP的发送端
public static void sender() {
DatagramSocket datagramSocket = null; // 此时可以不用指定IP地址和端口号
try {
// 1. 使用类DatagramSocket 建立UDP的套接字
datagramSocket = new DatagramSocket();
// 2. 使用类DatagramPacket 建立UDP的数据报,来存储数据
// 如果 datagramSocket 没有指定IP和端口号,则需要在这里进行绑定
String str = new String("Hello, 这是使用UDP发送的数据报!");
byte[] data = str.getBytes(StandardCharsets.UTF_8);
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
DatagramPacket datagramPacket = null;
datagramPacket = new DatagramPacket(data, 0, data.length, inetAddress, 8081);
datagramSocket.send(datagramPacket);
// 连续发送数据
while (true) {
str = new String(new Scanner(System.in).nextLine());
data = str.getBytes(StandardCharsets.UTF_8);
datagramPacket = new DatagramPacket(data, 0, data.length, inetAddress, 8081);
System.out.println(str);
// 3. 调用DatagramSocket的send()方法发送数据
datagramSocket.send(datagramPacket);
if ("exit".equalsIgnoreCase(str) || "e".equalsIgnoreCase(str)) {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 关闭套接字
if (datagramSocket != null) {
datagramSocket.close();
}
}
}
// UDP的接收端
@Test
public void receiver() {
DatagramSocket socket = null;
try {
// 1. 建立UDP套接字,因为是服务端,所以需要指定端口号
socket = new DatagramSocket(8081);
// 2. 建立数据报
byte[] buffer = null; // 因此使用UDP接收数据报时,需要一次性全部接收,如果接收不完则会抛弃,因此可以将该数组设置大一点
DatagramPacket packet = null;
// 连续接收数据,直到收到exit或e后退出
while (true) {
buffer = new byte[100];
packet = new DatagramPacket(buffer, 0, buffer.length);
// 3. 调用DatagramSocket的receive()方法进行数据接收,并存放如packet中
socket.receive(packet);
// 4. 将数据进行打印
String str = new String(new String(packet.getData(), 0, packet.getLength()));
if ("exit".equalsIgnoreCase(str) || "e".equalsIgnoreCase(str)) {
break;
}
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 关闭套接字
socket.close();
}
}
}
5. URL 编程
5.1 URL概述
- URL(Uniform Resource Locator) : 统一资源定位符,它表示Internet上某一资源的地址
- 它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何loacte这个资源。
- 通过URL我们可以访问Internet上的各种网络资源,比如最常见的www, ftp站点。
浏览通过解析给定的URL可以在网络上查找相应的文件或其他资源。 - URL的基本结构由5部分组成:
- <传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
- 例如:
http://192.168.0.100:8080/helloworld/index.jsp#a?username=zhilx&passwd=123456
- #片段名:即锚点,例如看小说,直接定位到章节
- 参数列表格式:参数名=参数值&参数名=参数值…
5.2 URL常用方法代码测试:
public void test01() {
// 1. 创建URL标识
try {
URL url = new URL("http://192.168.0.100:8080/helloworld/index.jsp#a?username=zhilx&passwd=123456");
// 2. URL常用方法测试
// 2.1 使用 getProtocol获取URL的协议名
System.out.println(url.getProtocol());
// 2.2 使用getHost()获取URL的主机名
System.out.println(url.getHost());
// 2.3 使用getPost()获取URL的端口号
System.out.println(url.getProtocol());
// 2.4 使用getPath()获取文件路径
System.out.println(url.getPath());
// 2.5 使用getFile()获取URL的文件名
System.out.println(url.getFile());
// 2.6 使用getQuery() 获取URL的查询名
System.out.println(url.getQuery());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
5.3 URL方法测试:从指定页面下载文件
// 2. URL编程实现网络资源的下载
// TCP、UDP的博客中的图片: https://i-blog.csdnimg.cn/blog_migrate/b0db95a88d3e0fe8988ac2279c931c4c.png
public void test02(){
InputStream inputStream = null;
FileOutputStream fos = null;
try {
// 1. 指定URL的指定文件位置
URL url = new URL(" https://i-blog.csdnimg.cn/blog_migrate/b0db95a88d3e0fe8988ac2279c931c4c.png");
// 2. 获取URL连接
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
// 3. 建立连接(一定要有这个步骤)
urlConnection.connect();
// 4. 下载数据
inputStream = urlConnection.getInputStream();
fos = new FileOutputStream("7-网络编程\\src\\figure1.png");
// 5. 将数据写入到文件中
byte[] buffer = new byte[1024];
int len = -1;
while ((len = inputStream.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println("文件下载完成!");
} catch (IOException e) {
e.printStackTrace();
} finally {// 6. 关闭流和套接字
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}