java基础-网络编程
12-1-网络编程和UDP
01-网络编程三要素-概述
网络编程
- 在网络通讯协议下,不同计算机上运行的程序,可以进行数据传输。
思考:
A电脑的飞秋,发消息给B电脑的飞秋,需要知道哪些条件才能发送?
网络编程三要素:
ip地址
设备在网络中的地址,是唯一标识。
端口
应用程序在设备中的唯一标识
协议
数据在网络传输中的规则,常见UDP、TCP。
02-网络编程三要素-IP
“互联网协议地址”,是分配给上网设备的数字标签,常见IPv6、IPv4。
平时访问网站,访问的其实是网站的ip,可以ping:
过程:
- 计算机将域名给dns服务器
- 服务器返回域名对应的ip地址给计算机
- 计算机访问ip对应的服务器
- 服务器返回数据给计算机
IPv4:每8位为一个字节,共4个字节。
为了方便表示和记忆,将字节转成10进制,用点号分割:
每个字节的范围是0~255,每个字节最多表示256个数字。导致IPv4最多能够表示256^4个IP。不够用了。
推出了IPv6:128位地址长度,分成8组,每组2个字节(16位)。
可以省略前面的0:
如果有多个连续的0:
采用0位压缩表示法:读时,自动补全为8组。
03-网络编程-常见命令
ipconfig、ping
04-网络编程-InetAddress类
为了方便我们对IP地址的获取和操作,Java提供了一个类InetAddress
类供我们使用。此类表示Internet协议地址。
public class InetAddressDemo {
public static void main(String[] args) {
try {
InetAddress address = InetAddress.getByName("DESKTOP-JCMLI7D");
System.out.println("address.getHostName() = " + address.getHostName());
System.out.println("address.getAddress() = " + address.getHostAddress());
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
}
05-网络编程三要素-端口
应用程序在设备中唯一的标识。用两个字节表示的整数,取值范围是0~65535
0~1013之间的端口号用于一些知名的网络应用程序,自己写的程序用1024以上的端口号就可以了,注意不要超过范围、
一个端口号只能被一个应用程序使用。
06-网络编程三要素-协议
协议:计算机网络中,连接和通信的规则被称为网络通信协议。
UDP:
- 用户数据报协议(User Datagram Protocol)
- 面向无连接通信协议
- 速度快
- 大小限制64k
- 数据不安全、易丢失
TCP:
- 传输控制协议(Transmission Control Protocol)
- 面向连接的通信协议
- 速度慢
- 没有大小限制
- 数据安全
07-UDP-发送端
步骤:
- 创建发送端
DatagramSocket
对象 - 创建数据,并把数据打包(
DatagramPacket
) - 调用
DatagramSocket
对象的方法发送数据 - 释放资源
public class UdpSendDemo {
public static void main(String[] args) {
DatagramSocket datagramSocket = null;
try {
// 创建DatagramSocket对象
datagramSocket = new DatagramSocket();
// 要发送的消息
String msg = "你好,我是赖卓成";
byte[] buf = msg.getBytes();
// 发送到哪台主机、哪个端口号
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 10000;
// 创建报文
DatagramPacket datagramPacket = new DatagramPacket(buf,buf.length,address,port);
// 发送
datagramSocket.send(datagramPacket);
} catch (SocketException e) {
throw new RuntimeException(e);
} catch (UnknownHostException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 释放资源
if (Objects.nonNull(datagramSocket)){
datagramSocket.close();
}
}
}
}
运行后,没有任何结果,因为udp是面向无连接的。
08-UDP-接收端
步骤:
- 创建接收端
DatagramSocket
对象 - 创建
DatagramPacket
对象,用于接收数据 - 调用
DatagramSocket
的方法接受数据,并将数据放入DatagramPacket
- 解析数据,打印
- 释放资源
public class UdpAcceptDemo {
public static void main(String[] args) {
DatagramSocket datagramSocket = null;
try {
datagramSocket = new DatagramSocket(10000);
// 创建字节数组用于接收数据
byte[] buf = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
// 接收数据
datagramSocket.receive(datagramPacket);
byte[] data = datagramPacket.getData();
String msg = new String(data,0,datagramPacket.getLength());
System.out.println("msg = " + msg);
} catch (SocketException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
if (Objects.nonNull(datagramSocket)){
datagramSocket.close();
}
}
}
}
先运行接收端,再运行发送端:
测试之后,可以发到腾讯云服务器上,能接收到。
09-UDP-练习
发送数据,键盘录入,如果内容是886则结束,接收端不知道什么时候结束,所以一直死循环。
太简单,不写了。
10-UDP-三种通讯方式
单播:一个发送端对应一个接收端
组播:一对多
广播:一对所有
11-UDP-组播代码实现
组播的发送端跟单播是类似的,区别在接收端。组播的接收端是多台计算机。
组播地址:224.0.0.0~239.255.255.255,其中224.0.0.0~224.0.0.255为预留的组播地址。
发送端:
/**
* udp 组播发送端
*
* @author 赖卓成
* @date 2023/07/05
*/
public class UdpMultiCastSendDemo {
public static void main(String[] args) {
DatagramSocket datagramSocket = null;
try {
// 组播地址
InetAddress address = InetAddress.getByName("224.0.1.0");
// 和单播一样、创建DatagramSocket对象
datagramSocket = new DatagramSocket();
int port = 10000;
byte[] bytes = "你好,我是赖卓成".getBytes();
// 数据包
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, address, port);
datagramSocket.send(datagramPacket);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
datagramSocket.close();
}
}
}
接收端:与单播不同的是需要创建MulticastSocket
对象来接收,并且需要加入组播地址。
/**
* udp组播接受
*
* @author 赖卓成
* @date 2023/07/05
*/
public class UdpMultiCastAcceptDemo {
public static void main(String[] args) {
while (true){
int port = 10000;
MulticastSocket multicastSocket = null;
try {
multicastSocket = new MulticastSocket(port);
// 加入组播地址
InetAddress address = InetAddress.getByName("224.0.1.0");
multicastSocket.joinGroup(address);
DatagramPacket datagramPacket = new DatagramPacket(new byte[1024], 1024);
// 接收数据
multicastSocket.receive(datagramPacket);
byte[] data = datagramPacket.getData();
String msg = new String(data,0, datagramPacket.getLength());
System.out.println("msg = " + msg);
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
multicastSocket.close();
}
}
}
}
本地运行可以正常发送、接收数据包,但是不能发到服务器上(原因还没搞明白)。
12-UDP-广播代码实现
广播地址:255.255.255.255
需要注意的是,在发送时,需要指定广播地址,而在接受时,不需要指定广播地址。
/**
* udp广播代码实现--发送端
*
* @author lzc
* @date 2023/07/05
*/
public class UdpCastSendDemo {
public static void main(String[] args) {
DatagramSocket datagramSocket = null;
try {
// 广播地址 表示消息是广播的
InetAddress address = InetAddress.getByName("255.255.255.255");
int port = 10000;
datagramSocket = new DatagramSocket();
byte[] bytes = "你好,这是广播消息".getBytes();
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length, address, port);
datagramSocket.send(datagramPacket);
} catch (UnknownHostException e) {
throw new RuntimeException(e);
} catch (SocketException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
datagramSocket.close();
}
}
}
/**
* udp广播--接收端
*
* @author lzc
* @date 2023/07/05
*/
public class UdpCastAcceptDemo {
public static void main(String[] args) {
DatagramSocket datagramSocket = null;
try {
// 接受广播消息不需要指定ip地址
datagramSocket = new DatagramSocket(10000);
// 定义数组用于接收数据
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, 1024);
datagramSocket.receive(datagramPacket);
String s = new String(bytes, 0, datagramPacket.getLength());
System.out.println(s);
} catch (SocketException e) {
throw new RuntimeException(e);
} catch (UnknownHostException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
datagramSocket.close();
}
}
}
12-1-TCP通讯程序
13-TCP-发送端
TCP通信协议是一种可靠的网络协议,在通信的两端,各建立一个Socket
对象。
在传输数据之前,需要保证连接已经建立。
通过Socket
产生IO流来进行网络通信。
步骤:
- 创建客户端
Socket
对象与指定服务端连接,指定host、port - 获取输出流(OutputStream)
- 释放资源
代码:
public class TcpSend {
public static void main(String[] args) {
while (true) {
try {
// 创建连接
Socket socket = new Socket("127.0.0.1", 9118);
// 获得输出流
OutputStream outputStream = socket.getOutputStream();
// 写数据
Scanner scanner = new Scanner(System.in);
String msg = scanner.nextLine();
outputStream.write(msg.getBytes());
// 关闭流
outputStream.close();
// 关闭Socket
socket.close();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
}
这样写是没问题,但运行会报一次,因为TCP通信前,需要建立好连接,此时我们还没有编写服务器端代码。
14-TCP-服务器端
步骤:
- 创建服务器端的
Socket
对象(ServerSocket
) - 监听客户端连接,返回一个
Socket
对象,accept
方法,返回一个Socket
对象,如果没有客户端来连接,就会一直等待。 - 获得输入流对象,
InputStream
,读取数据。 - 释放资源
public class TcpAccept {
public static void main(String[] args) {
while (true) {
try {
// 创建服务端Socket对象
ServerSocket serverSocket = new ServerSocket(9118);
// 等待连接,连接成功以后,会返回一个Socket对象
Socket socket = serverSocket.accept();
// 获取输入流
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s;
while (( s= bufferedReader.readLine())!=null&&s.length()>0) {
System.out.println(s);
}
inputStream.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
}
运行结果:
15-TCP-原理分析
上面的例子中,需要先执行服务端,再执行发送端。在执行服务端的时候,accept
方法,会阻塞,等待发送端来连接,当客户端创建Socket
的时候,就会进行连接。发送数据(字节输出流写数据)会通过建立好的通道,将数据发送到服务端,进行接收(字节输入流读数据)。
accept
方法是阻塞的,作用是等待客户端的连接,如果连接上就返回Socket
对象,否则一直等待。客户端创建对象,连接服务器,会通过三次握手协议,保证连接成功。
针对客户端,是往外写,所以是字节输出流。
针对服务端,是往里读,所以是字节输入流。
read
方法也是阻塞的,如果客户端没有关闭字节输出流,服务端就会一直等待。客户端在关闭
OutPutStream
时,会往服务器发一个结束标记。最后一步关闭连接,会通过四次挥手协议,进行断开连接。
16-TCP-三次握手
17-TCP-四次挥手
四次挥手,多了一个数据处理的过程。
18-TCP-练习1
需求:
客户端:发送数据,接收服务器反馈
服务端:接收数据,给出反馈
注意两个方法:
shutdownInput
,shutdownOutput
客户端:
public class TcpClientDemo01 {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 2975);
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("你好,我是赖卓成");
// 将缓冲区数据写入字节流
// 强制将缓冲区的数据写入到输出流中。
// 在写入数据时,数据并不是立即发送到对方,而是先存储在缓冲区,等到缓冲区满了或者手动调用了 flush() 方法时才会将缓冲区的数据发送出去
bufferedWriter.flush();
// 告诉服务端,客户端已经发送完数据,不再向服务端发送数据了。这样服务端就可以通过读取输入流来获取完整的客户端请求
// 否则服务端可能会一直等待客户端发送数据,导致阻塞。
socket.shutdownOutput();
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
bufferedReader.lines().forEach(System.out::println);
bufferedReader.close();
socket.close();
}
}
服务端:
public class TcpServerDemo01 {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(2975);
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
bufferedReader.lines().forEach(System.out::println);
// 告诉客户端,服务端已经接收完数据,不再接收客户端发送的数据了。
// 这样客户端就可以通过读取输入流来获取完整的服务端响应
socket.shutdownInput();
// 响应客户端
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("你好,我是服务端");
bufferedWriter.close();
socket.close();
serverSocket.close();
}
}
19-TCP-练习2
需求:客户端发送文件,并接收服务器的反馈。服务端接收客户端发送的文件,给出反馈。
/**
* TCP练习2-客户端
*
* @author lzc
* @date 2023/07/06
*/
public class TcpClientDemo02 {
public static void main(String[] args) throws IOException {
// 创建连接
Socket socket = new Socket("127.0.0.1", 2975);
// 读本地文件
File file = new File("D:\\dev\\workfile\\TcpClientDemo02.txt");
if (!file.exists()&&!file.isFile()){
throw new RuntimeException("文件不存在");
}
// 创建文件输入流
FileInputStream fileInputStream = new FileInputStream(file);
// 获取socket的输出流
OutputStream outputStream = socket.getOutputStream();
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
byte[] bytes = new byte[8192];
int len;
// 使用缓冲字节流读数据,使用缓冲字节流写数据
while ((len = bufferedInputStream.read(bytes))!=-1){
bufferedOutputStream.write(bytes,0,len);
}
// 写完后关闭流
bufferedInputStream.close();
// 告诉服务端 写完了
System.out.println("客户端已发送文件!");
bufferedOutputStream.flush();
socket.shutdownOutput();
// 获取socket的输出流 输出服务端响应
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
Stream<String> lines = bufferedReader.lines();
lines.forEach(System.out::println);
// 关闭输入流
bufferedReader.close();
socket.close();
}
}
public class TcpServerDemo02 {
public static void main(String[] args) throws IOException {
// 创建ServerSocket对象
ServerSocket serverSocket = new ServerSocket(2975);
// 等待客户端连接,连接后返回Socket对象
Socket socket = serverSocket.accept();
// 获取socket的输入流对象
InputStream inputStream = socket.getInputStream();
// 创建文件字节输出流,用于保存文件
FileOutputStream fileOutputStream = new FileOutputStream("D:\\dev\\workfile\\TcpServerDemo01-copy.txt");
// 创建缓冲字节输出流
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
// 创建数组,用于读数据,临时存储
byte[] bytes = new byte[8192];
// 每次读取的长度
int len;
while((len = bufferedInputStream.read(bytes))!=-1){
bufferedOutputStream.write(bytes,0,len);
}
// 读完关闭文件字节输出流,并调用socket方法告诉客户端,读完了
socket.shutdownInput();
bufferedOutputStream.close();
// 获取socket的输出流
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("服务端已收到文件!");
bufferedWriter.close();
// 关闭socket
socket.close();
serverSocket.close();
}
}
20-TCP-服务端的优化-循环
上面写的代码, 服务端每次接受到文件,都会关闭socket
,只能使用一次,所以加个循环:
21-TCP-服务端优化-UUID
每次上次文件名都一样,加个UUID
22-TCP-服务端优化-多线程
仅仅使用循环,无法同时处理多个客户端的请求。
创建类,实现Runnable
接口,每次accept
,如果没有接受到连接,会一直阻塞,接收到就返回Socket
对象,我们就可以在每次接受到不同的请求,返回不同的Socket
对象后,交给不同的线程去处理,就能实现同时处理多个客户端的请求。
public class MySocketRunnable implements Runnable {
private final Socket socket;
public MySocketRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedOutputStream bufferedOutputStream = null;
try {
// 获取socket的输入流对象
InputStream inputStream = socket.getInputStream();
// 创建文件字节输出流,用于保存文件
File dir = new File("D:\\dev\\workfile");
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File(dir, UUID.randomUUID() +".txt");
FileOutputStream fileOutputStream = new FileOutputStream(file);
// 创建缓冲字节输出流
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
// 创建数组,用于读数据,临时存储
byte[] bytes = new byte[8192];
// 每次读取的长度
int len;
while ((len = bufferedInputStream.read(bytes)) != -1) {
bufferedOutputStream.write(bytes, 0, len);
}
// 读完关闭文件字节输出流,并调用socket方法告诉客户端,读完了
socket.shutdownInput();
// 获取socket的输出流
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("服务端已收到文件!");
bufferedWriter.flush();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (Objects.nonNull(socket)) {
// 关闭socket
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (Objects.nonNull(bufferedOutputStream)){
try {
bufferedOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
public class TcpServerDemo03 {
public static void main(String[] args) throws IOException {
// 创建ServerSocket对象
ServerSocket serverSocket = new ServerSocket(2975);
while (true) {
// 等待客户端连接,连接后返回Socket对象
Socket socket = serverSocket.accept();
MySocketRunnable mySocketRunnable = new MySocketRunnable(socket);
new Thread(mySocketRunnable).start();
}
}
}
23-TCP-服务端优化-线程池
每次接受到请求,都重新开启线程,很消耗资源,我们可以使用线程池,减少创建和销毁线程的性能消耗。
public class TcpServerDemo03 {
public static void main(String[] args) throws IOException {
// 创建ServerSocket对象
ServerSocket serverSocket = new ServerSocket(2975);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
// 核心线程
3,
// 最大线程
10,
// 非核心线程空闲多少秒销毁
60,
TimeUnit.SECONDS
// 阻塞队列
,
new ArrayBlockingQueue<>(5),
// 创建线程的方式 -默认
Executors.defaultThreadFactory()
,
// 拒绝策略--
new ThreadPoolExecutor.AbortPolicy()
);
while (true) {
// 等待客户端连接,连接后返回Socket对象
Socket socket = serverSocket.accept();
MySocketRunnable mySocketRunnable = new MySocketRunnable(socket);
// 一旦有请求连接,就提交给线程池处理
threadPoolExecutor.submit(mySocketRunnable);
}
}
}