封面来源:碧蓝航线 微层混合 活动CG

本文参考:尚硅谷 宋红康 Java 零基础教程 P620-P630

1. 网络编程概述

Java 是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序。

Java 提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由JVM进行控制。 Java 还实现了一个跨平台的网络库, 程序员面对的是一个统一的网络编程环境

计算机网络

把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。

网络编程的目的

直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。

网络编程中有 两个主要的问题

  • 如何准确地定位网络上一台或多台主机,定位主机上的特定的应用

  • 找到主机后如何可靠高效地进行数据传输

2. 网络通信要素概述

如何实现网络中的主机互相通信

1、通信双方的地址(对应问题一)

  • IP
  • 端口号

2、一定的规则(即:网络通信协议,有两套参考模型,对应问题二)

  • OSI 参考模型:模型过于理想化,未能在因特网上进行广泛推广
  • TCP / IP 参考模型(或 TCP / IP 协议):事实上的国际标准

网络通信协议

网络通信协议

3. IP 与端口号

3.1 IP 地址

IP 地址(可以在 Java 中使用 InetAddress 类代表 IP),唯一的标识 Internet 上的计算机(通信实体)

本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost

IP 地址的分类方式

IP地址分类方式1: IPV4IPV6

IPV4:4 个字节组成,4 个 0-255。 大概有 42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如 192.168.0.1

VIPV6: 128位 (16个字节),写成8个无符号整数,每个整数用四个十六进制位表示,数之间用冒号 : 分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984

IP地址分类方式2: 公网地址(万维网使用)私有地址(局域网使用)192.168.开头的就是私有址址,范围即为 192.168.0.0-192.168.255.255, 专门为组织机构内部使用

特点:不易记忆

InetAddress 类的实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void main(String[] args) {
try {
// ip
InetAddress inet1 = InetAddress.getByName("192.168.10.14");
System.out.println(inet1);

// 域名
InetAddress inet2 = InetAddress.getByName("www.baidu.com");
System.out.println(inet2);

// 127.0.0.1
InetAddress inet3 = InetAddress.getByName("127.0.0.1");
InetAddress inet4 = InetAddress.getLocalHost();
System.out.println(inet3);
System.out.println(inet4);

// 获取域名
System.out.println(inet2.getHostName());
// 获取主机地址
System.out.println(inet2.getHostAddress());

} catch (UnknownHostException e) {
e.printStackTrace();
}
}

3.2 端口号

端口号标识正在计算机上运行的进程(程序):

  • 不同的进程有不同的端口号

  • 端口号被规定为一个 16 位的整数 0~65535。

端口分类

公认端口:0~1023。表示被预先定义的服务通信占用(如:HTTP 占用端口 80,FTP 占用端口 21,Telnet 占用端口 23)

注册端口:1024~49151。分配给用户进程或应用程序( 如:Tomcat 占用端口 8080,MySQL 占用端口3306,Oracle 占用端口 1521 等)。

动态/私有端口:49152~65535。

端口号与IP地址的组合得出一个网络套接字:Socket。

正因如此,网络通信也被叫做 Socket 通信,网络编程也被叫做 Socket 编程。

4. 网络协议

网络通信协议

计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。

协议的出现就会出现一个问题:网络协议太复杂
计算机网络通信涉及内容很多,比如指定源地址和目标地址,加密解密,压缩解压缩,差错控制,流量控制,路由控制,如何实现如此复杂的网络协议呢?

通信协议分层的思想

在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系。各层互不影响,利于系统的开发和扩展。


传输层协议中有两个非常重要的协议:

  • 传输控制协议 TCP(Transmission Control Protocol)

  • 用户数据报协议 UDP(User Datagram Protocol)

TCP / IP 以其两个主要协议: 传输控制协议(TCP)和网络互联协议(IP) 而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。

IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信。

TCP / IP 协议模型从更实用的角度出发,形成了高效的四层体系结构,即物理链路层、IP层、传输层和应用层。

TCP 与 UDP 的对比

TCP 协议:

  • 使用 TCP 协议前,须先建立 TCP 连接,形成传输数据通道

  • 传输前,采用“ 三次握手 ”方式,点对点通信,是可靠的

  • TCP 协议进行通信的两个应用进程:客户端、服务端

  • 在连接中可进行大数据量的传输

  • 传输完毕,需释放已建立的连接,效率低

TCP 在生活中的案例就类似于打电话。

UDP 协议:

  • 将数据、源、目的封装成数据包,不需要建立连接;

  • 每个数据报的大小限制在 64K 内

  • 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的

  • 可以广播发送

  • 发送数据结束时无需释放资源,开销小,速度快

UDP 在生活中的案例就类似于发送短信、发电报

由于 UDP 协议传输特点(开销小,速度快),在实际应用场景中,视频直播、网络视频等都可以使用 UDP 协议。

5. TCP 网络编程

客服端向服务端发送一条信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package com.yang.tcp;

import org.junit.Test;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author 默烦 2020/10/14
* <p>
* 例一:客户端发送信息给服务端,服务端将数据显示在控制台上
*/
public class TCPTest1 {

// 客户端
@Test
public void client() {
Socket socket = null;
OutputStream os = null;
try {
// 1. 创建 Socket 对象,指明服务器端的 ip 和端口号
InetAddress inet = InetAddress.getByName("127.0.0.1");
socket = new Socket(inet, 8899);
// 2. 获取输出流,用于输出数据
os = socket.getOutputStream();
// 3. 写入数据的操作
os.write("你好,我是客户端 mm ".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 关闭资源
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

// 服务端
@Test
public void server() {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
// 1. 创建服务器端的 serverSocket,指明自己的端口号
serverSocket = new ServerSocket(8899);
// 2. 调用 accept() 表示接受来自于客户端的 socket
socket = serverSocket.accept();
// 3. 获取输入流
is = socket.getInputStream();

// 不建议这样写,可能会有乱码
/*byte[] bytes = new byte[20];
int len;
while ((len = is.read(bytes)) != -1) {
String s = new String(bytes, o, len);
System.out.println(s);
}*/

// 4. 读取输入流中的数据
baos = new ByteArrayOutputStream();
byte[] bytes = new byte[5];
int len;
while ((len = is.read(bytes)) != -1) {
baos.write(bytes, 0, len);
}

System.out.println(baos.toString());
System.out.println("收到了来自于:"+socket.getInetAddress().getHostAddress()+" 的信息");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 关闭资源
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

由于是基于 TCP 的网络编程,因此需要先建立 TCP 连接,形成传输数据通道。对于上述代码来说,需要先运行服务端,再运行客户端,运行了客户端之后,就可在服务端看到客户端发送的数据。

客户端向服务端发送一张图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.yang.tcp;

import org.junit.Test;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author 默烦 2020/10/14
*
* 例二:客户端发送文件给服务端,服务端将文件保存到本地(当前 Module)
*
* 异常处理仍需采用 try-catch-finally ,示例代码为了节省篇幅直接抛出
*/
public class TCPTest2 {

@Test
public void client() throws IOException {
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9090);
OutputStream os = socket.getOutputStream();

FileInputStream fis = new FileInputStream(new File("妖梦.jpg"));
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
os.write(bytes, 0, len);
}

fis.close();
os.close();
socket.close();

}

@Test
public void server() throws IOException {
ServerSocket serverSocket = new ServerSocket(9090);
Socket socket = serverSocket.accept();

InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream(new File("魂魄妖梦.jpg"));
byte[] bytes = new byte[1024];
int len;
while ((len = is.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}

fos.close();
is.close();
socket.close();
serverSocket.close();
}
}

同样需要先运行服务端,然后在运行客户端。

实现客户端与服务端的交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.yang.tcp;

import org.junit.Test;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author 默烦 2020/10/14
*
* 例三:从客户端发送文件给服务端,服务端保存到本地后,并返回“发送成功”给客服端
* 并关闭相应的连接
*
* 异常处理仍需采用 try-catch-finally ,示例代码为了节省篇幅直接抛出
*/
public class TCPTest3 {

@Test
public void client() throws IOException {
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9090);
OutputStream os = socket.getOutputStream();

FileInputStream fis = new FileInputStream(new File("妖梦.jpg"));
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
os.write(bytes, 0, len);
}

// 关闭数据的输出
socket.shutdownOutput();

// 接受来自服务器的数据,并显示到控制台上
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bytes1 = new byte[20];
int len1;
while ((len1 = is.read(bytes1)) != -1) {
baos.write(bytes1, 0, len1);
}
System.out.println(baos.toString());

baos.close();
is.close();
fis.close();
os.close();
socket.close();

}

@Test
public void server() throws IOException {
ServerSocket serverSocket = new ServerSocket(9090);
Socket socket = serverSocket.accept();

InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream(new File("Youmu.jpg"));

byte[] bytes = new byte[1024];
int len;
while ((len = is.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}

System.out.println("图片传输完成");

// 服务器给予客户端反馈
OutputStream os = socket.getOutputStream();
os.write("图片发送成功".getBytes());

fos.close();
is.close();
socket.close();
serverSocket.close();
os.close();
}
}

依旧需要先运行服务端,然后在运行客户端。

客户端 - 服务端选择

客户端:自定义、浏览器

服务端:自定义、Tomcat 服务器

6. UDP 网络编程

UDP 网络通信

DatagramSocketDatagramPacket实现了基于 UDP 协议网络程序。

UDP 数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证 UDP 数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。

DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的 IP 地址和端口号以及接收端的 IP 地址和端口号。

UDP 协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接,就如同发快递包裹一 样。

UDP 网络编程实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.yang.udp;

import org.junit.Test;

import java.io.IOException;
import java.net.*;

/**
* @author 默烦 2020/10/14
*/
public class UDPTest {

// 发送端
@Test
public void sender() throws IOException {
DatagramSocket socket = new DatagramSocket();

String str = "我是 UDP 发送的信息";
byte[] data = str.getBytes();
InetAddress inet = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(data, 0, data.length, inet, 9090);

socket.send(packet);

socket.close();
}

// 接收端
@Test
public void receiver() throws IOException {
DatagramSocket socket = new DatagramSocket(9090);
byte[] bytes = new byte[100];
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);

socket.receive(packet);

System.out.println(new String(packet.getData(), 0, packet.getLength()));
socket.close();
}
}

需要先启动接收端再启动发送端才能接收到数据,UDP 编程中就没有服务端和客户端的概念。

也可以先启动发送端,只是启动了发送端再启动接收端是无法收到发送的消息的,但先启动发送端并不会报错。

在 TCP 编程中,如果先启动客户端,就会直接报错,显示 ConnectException 异常。这主要是因为 TCP 必须先建立连接,不然就会报错,而对于 UDP 就没有这样的硬性要求。

TCP 就相当于点外卖时,必须保证电话的接通,外卖小哥到了给你打电话,电话畅通,你去拿外卖。如果外卖小哥打了电话,但是电话不通,外卖小哥就会骂娘(简称,抛异常😂)。

UDP 就相当于去菜鸟驿站取快递,快递到了,给你发个短信,可能会接收到也可能没接收到,如果就收到,那就去拿,接受不到,驿站也不会怎么样(现实中驿站会将包裹退回)。

7. URL 编程

URL 类

URL(Uniform Resource Locator):统一 资源定位符,它表示 Internet 上某一资源的地址。

它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何 locate 这个资源。

通过 URL 我们可以访问 Internet 上的各种网络资源,比如最常见的 www,ftp 站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源。

URL的基本结构由5部分组成:

1
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表

例如:

1
http://192.168.1.100:8080/helloworld/index.jsp#a?username=mofan&password=123

#片段名:即锚点,例如看小说,直接定位到章节(HTML 中 <a> 标签)

参数列表格式:参数名 = 参数值 & 参数名 = 参数值…

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
try {
URL url = new URL("http://localhost:8080/examples/beauty.jpg?username=mofan");
// 获取该 URL 的协议名
System.out.println(url.getProtocol());
// 获取该 URL 的主机名
System.out.println(url.getHost());
// 获取该 URL 的端口号
System.out.println(url.getPort());
// 获取该 URL 的文件路径
System.out.println(url.getPath());
// 获取该 URL 的文件名
System.out.println(url.getFile());
// 获取该 URL 的查询名
System.out.println(url.getQuery());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}

使用 URL 下载 B 站视频封面图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.yang.url;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
* @author 默烦 2020/10/14
*
* 下载下列图片(B站视频封面图)到当前 Module 下
* http://i1.hdslb.com/bfs/archive/0690508887cf16017efc7e7e29b1ec58d289b341.jpg
*/
public class URLTest1 {

public static void main(String[] args){
InputStream is = null;
FileOutputStream fos = null;
HttpURLConnection urlConnection = null;

try {
URL url = new URL("http://i1.hdslb.com/bfs/archive/0690508887cf16017efc7e7e29b1ec58d289b341.jpg");

urlConnection = (HttpURLConnection)url.openConnection();
urlConnection.connect();
is = urlConnection.getInputStream();
// internet 是当前 Module 名
// 在 IDEA 中不加当前项目名,默认下载到项目目录下,Eclipse 不存在这种情况
fos = new FileOutputStream("internet\\pic.jpg");

byte[] bytes = new byte[1024];
int len;
while ((len = is.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
System.out.println("下载完成!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
}

运行代码后,可以在当前 Module 下看到名为 pic.jpg 的图片。


Java 网络编程基础完