在学习 Java 网络编程之前,我们先来了解什么是计算机网络。
计算机网络是指两台或更多的计算机组成的网络,在同一个网络中,任意两台计算机都可以直接通信,因为所有计算机都需要遵循同一种网络协议。
下面是一张简化的网络拓扑图。
- 用户设备:Laptop,用于访问网络资源。
- 网络交换机:Switch,用于连接局域网内的设备,例如 Laptop 和 Router。
- 路由器:Router,用于连接不同网络,将局域网与互联网相连。
- 防火墙:Firewall,用于保护网络内部资源,阻止未经授权的访问。
- 互联网:Internet,提供连接到其他网络和全球信息资源。
- 服务器:Server,用于托管网络应用程序和数据。
1.实现网络编程的三种要素
1.4.1、协议
计算机网络通信必须遵守的规则
1.4.2、IP地址
指互联网协议地址(Internet Protocol Address)**,俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”。
1.4.2.1、IPV4
IPV4是一个32位的二进制数,通常被分为4个字节,表示成
a.b.c.d
的形式,例如192.168.65.100
。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。1.4.2.2、IPV6
由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。有资料显示,全球IPv4地址在2011年2月分配完毕。
为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成
ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。
2 ip地址
IP地址可以理解为具体哪个计算机,端口理解计算机上的程序(一个程序一个端口),协议理解为电脑通过什么方式和外界交互
要素一:IP地址:设备在网络中的地址,是唯一的标识
详解:
(1)常见的IP分类为:
IPv4(32比特4字节)和IPv6(128位16个字节)--称号--可以标记地球上的每一粒沙子。
(2)IP地址的取经之路:
计算机:我要去找百度获取数据。
DNS服务器:发过来我看看哪个网址域名啊,给你指路具体的ip地址
计算机:知道了这个ip地址,我就可以去找具体要访问的服务器了
服务器:计算机老弟你来找我了啊,那我把你要的数据发给你吧。
(3)公网地址和私有地址(局域网使用)
公有地址是指可以在互联网上全球唯一标识一台设备的IP地址。公网地址通常由ISP来分配
私有地址用于局部网内部通信,这些地址在互联网上是不唯一的 ,不会被路由器转发到互联网上
192.168开头的就是常见的私有地址
公网 IP 地址可以直接被访问,内网 IP 地址只能在内网访问。内网 IP 地址类似于:
- 192.168.x.x
- 10.x.x.x
有一个特殊的 IP 地址,称之为本机地址,它总是
127.0.0.1
。IPv4 地址实际上是一个 32 位整数。例如:
1707762444 = 0x65ca630c = 65 ca 63 0c = 101.202.99.12
(4)获取IP地址的代码
要去实现这个IP地址的获取就要用到 InetAddress方法
public class Test {
public static void main(String[] args) throws Exception {
//1.获取本机地址ip对象
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());//获取主机名字
System.out.println(ip1.getHostAddress());//获取ip地址
//2.获取域名ip对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());//获取域名
System.out.println(ip2.getHostAddress());//获取域名的ip地址
//3.获取公网对象
InetAddress ip3 = InetAddress.getByName("112.80.248.76");
System.out.println(ip3.getHostName());//获取公网名字
System.out.println(ip3.getHostAddress());//获取公网ip地址
//判断网络是否能连接通信 ping 5s之前测试是否能通过
System.out.println(ip3.isReachable(5000));//通过会返回true
}
3 端口
端口:应用程序在设备中的唯一标识
一个主机设备中,端口号是唯一的
网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?
如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。
端口号:用两个字节表示的整数,它的取值范围是0~65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。
4 协议
网络技术中的端口默认指的是TCP/IP协议中的服务端口,一共有0-65535个端口,比如我们最常见的端口是80端口,默认访问网站的端口就是80,会发现浏览器默认把80去掉,就是因为这是个默认端口,所以没必要再显示出来,还有用于ftp文件传输的21端口,我们知道一台主机通常可以提供网页服务,ftp服务,邮件的SMTP服务等,都是可以同时在一个ip上进行的,那为什么不会造成混乱呢,原因就是通过ip+端口来区分这些服务,让每个端口有自己的分工,又能同时使用一个ip地址。
网络通信中一般使用的都是TCP/IP协议,TCP/IP是由一组具有专业用途的多个子协议组合而成的,这些子协议包括TCP、IP、UDP、ARP、ICMP等。TCP/IP凭借其实现成本低、在多平台间通信安全可靠以及可路由性等优势迅速发展,并成为Internet中的标准协议。在上世纪90年代,TCP/IP已经成为局域网中的首选协议,在最新的操作系统(如Windows7、Windows XP、Windows Server2003等)中已经将TCP/IP作为其默认安装的通信协议。
TCP(Transmission Control Protocol)
使用TCP协议,双方必须先建立连接,它是一种面向连接的可靠通信协议,传输前,要建立三次握手方式建立连接确认。连接和发送数据都需要确认。传输完成后,还需要释放已连接的通信,通信效率相对比较低。
使用场景:对安全需求较高的文件下载、金融数据通信等。
三次握手(Three-way Handshake)和四次挥手(Four-way Handshaking)是 TCP(传输控制协议)连接建立和终止的基本过程。下面我将通过一个简单的对话例子来解释这两个概念。
三次握手(建立连接)
假设 Alice 想要通过 TCP 连接与 Bob 通信。
1. SYN - Alice 向 Bob 发送一个 SYN 报文,表示她想开始通信。这个报文中包含一个序列号(例如 x),Alice 告诉 Bob 她准备好发送数据了。
2. SYN-ACK - Bob 收到 Alice 的 SYN 报文后,回复一个 SYN-ACK 报文,表示他也准备好发送和接收数据了。Bob 也发送一个序列号(例如 y),并且对 Alice 的 SYN 报文进行确认(ACK = x + 1)。
3. ACK - Alice 收到 Bob 的 SYN-ACK 报文后,发送一个 ACK 报文给 Bob,确认她收到了 Bob 的 SYN 报文,并且她也准备好接收数据了。ACK = y + 1。
至此,三次握手完成,TCP 连接建立成功,Alice 和 Bob 可以开始发送数据了。
四次挥手(终止连接)
当 Alice 和 Bob 完成通信后,他们需要终止连接。
1. FIN - Alice 决定结束通信,向 Bob 发送一个 FIN 报文,表示她已经没有数据要发送了。
2. ACK - Bob 收到 Alice 的 FIN 报文后,发送一个 ACK 报文给 Alice,确认他已经知道 Alice 没有数据要发送了。
3. FIN - Bob 也决定结束通信,向 Alice 发送一个 FIN 报文,表示他也没有数据要发送了。
4. ACK - Alice 收到 Bob 的 FIN 报文后,发送一个 ACK 报文给 Bob,确认她已经知道 Bob 没有数据要发送了。
至此,四次挥手完成,TCP 连接被正常关闭。下面是一个简单的例子:
想象一下,Alice 和 Bob 在一家餐厅约会:
• 三次握手:
• Alice 向 Bob 挥手说:“嘿,我准备好聊天了。”(SYN)
• Bob 回应:“好的,我也准备好了,我们可以开始聊天了。”(SYN-ACK)
• Alice 说:“太好了,我知道了,我们可以开始聊天。”(ACK)
• 四次挥手:
• Alice 说:“我觉得我们该走了,我没有什么要说的了。”(FIN)
• Bob 说:“好的,我明白了。”(ACK)
• Bob 接着说:“我也结束了,我们该走了。”(FIN)
• Alice 说:“好的,我知道了,我们结束聊天。”(ACK)
通过这个例子,你可以看到 TCP 连接是如何建立和终止的,以及为什么需要这样的过程来确保数据的可靠传输和连接的稳定关闭。
Socket
一个该类的对象就代表一个客户端程序。
Socket(String host, int port)
:根据ip地址字符串和端口号创建客户端Socket对象。只要执行该方法,就会立即连接指定的服务器程序,如果连接不成功,则会抛出异常。如果连接成功,则表示三次握手通过。
OutputStream getOutputStream()
:获得字节输出流对象。
InputStream getInputStream()
:获得字节输入流对象。
需要注意的是,套接字在建立的时候,如果远程主机不可访问,这段代码就会阻塞很长时间,直到底层操作系统的限制而抛出异常。所以一般会在套接字建立后设置一个超时时间。
Socket socket = new Socket(...);
socket.setSoTimeout(10000); // 单位为毫秒
套接字连接成功后,可以通过 java.net.Socket
类的 getInputStream()
方法获取输入流。有了 InputStream
对象后,可以借助文本扫描器类(Scanner)将其中的内容打印出来。
InputStream is = socket.getInputStream();
Scanner scanner = new Scanner(is, "gbk");
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
}
部分结果(完整结果自己亲手实践一下哦)如下图所示:
ServerSocket实例
接下来,我们模拟一个远程服务,通过 java.net.ServerSocket
实现。代码示例如下。
try (ServerSocket server = new ServerSocket(8888);
Socket socket = server.accept();
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
Scanner scanner = new Scanner(is)) {
PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "gbk"), true);
pw.println("我是大帅比");
boolean done = false;
while (!done && scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
if ("2048".equals(line)) {
done = true;
}
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
建立服务器端的套接字也比较简单,只需要指定一个能够独占的端口号就可以了(0~1023 这些端口都已经被系统预留了)。
ServerSocket server = new ServerSocket(8888);
调用 ServerSocket 对象的 accept()
等待客户端套接字的连接请求。一旦监听到客户端的套接字请求,就会返回一个表示连接已建立的 Socket 对象,可以从中获取到输入流和输出流。
Socket socket = server.accept();
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
客户端套接字发送的所有信息都会包裹在服务器端套接字的输入流中;而服务器端套接字发送的所有信息都会包裹在客户端套接字的输出流中。
服务器端可以通过以下代码向客户端发送消息。
PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "gbk"), true);
pw.println("我是大帅比");
服务器端可以通过以下代码读取客户端发送过来的消息。
Scanner scanner = new Scanner(is);
boolean done = false;
while (!done && scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
if ("2048".equals(line)) {
done = true;
}
}
为多个客户端服务
非常遗憾的是,上面的例子中,服务器端只能为一个客户端服务——这不符合服务器端一对多的要求。
优化方案也非常简单(你应该也能想得到):服务器端接收到客户端的套接字请求时,可以启动一个线程来处理,而主程序继续等待下一个连接。代码示例如下。
try (ServerSocket server = new ServerSocket(8888)) {
while (true) {
Socket socket = server.accept();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 套接字处理程序
}
});
thread.start();
}
} catch (IOException e) {
e.printStackTrace();
}
线程内部(run(){}
方法里)用来处理套接字,代码示例如下:
try {
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
Scanner scanner = new Scanner(is);
// 其他代码省略
// 向客户端发送消息
// 读取客户端发送过来的消息
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
服务器端代码优化后重新运行,你就可以通过 telnet 命令测试了。打开一个命令行窗口输入 telnet localhost 8888
,再打开一个新的命令行窗口输入 telnet localhost 8888
,多个窗口都可以和服务器端进行通信,除非服务器端代码中断运行。
DatagramSocket 实例
DatagramSocket 类是 Java 中实现 UDP 协议的核心类。与基于 TCP 的 Socket 和 ServerSocket 类不同,DatagramSocket 类提供了无连接的通信服务,发送和接收数据包。由于无需建立连接,UDP 通常比 TCP 更快,但可能不如 TCP 可靠。
以下是一个简单的 DatagramSocket 示例,展示了如何使用 UDP 协议在客户端和服务器之间发送和接收消息。
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPServer {
public static void main(String[] args) throws IOException {
int port = 12345;
DatagramSocket serverSocket = new DatagramSocket(port);
System.out.println("Server is listening on port " + port);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
serverSocket.receive(packet);
String message = new String(packet.getData(), 0, packet.getLength());
System.out.println("Received: " + message);
serverSocket.close();
}
}
客户端代码:
import java.io.IOException; // 导入IOException类,用于处理可能发生的I/O异常
import java.net.*; // 导入java.net包,包含网络编程相关的类和接口
public class UDPClient { // 定义一个公共类UDPClient
public static void main(String[] args) throws IOException { // 主方法入口,声明抛出IOException异常
String hostname = "localhost"; // 设置服务器的主机名,这里使用localhost表示本机
int port = 12345; // 设置服务器的端口号
InetAddress address = InetAddress.getByName(hostname); // 获取服务器的InetAddress对象
DatagramSocket clientSocket = new DatagramSocket(); // 创建一个DatagramSocket对象,用于UDP通信
String message = "Hello, server!"; // 定义要发送的消息
byte[] buffer = message.getBytes(); // 将消息转换为字节数组
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, port); // 创建一个DatagramPacket对象,包含消息数据和发送信息
clientSocket.send(packet); // 通过DatagramSocket发送DatagramPacket
System.out.println("Message sent"); // 打印消息已发送的确认
clientSocket.close(); // 关闭DatagramSocket,释放资源
}
}
例子:
一收一发
发送端(客户端)
package bao;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
//发送端
public class Test {
public static void main(String[] args) throws IOException {
System.out.println("==============客户端启动===============");
//1.创建发送通信管道
Socket socket = new Socket("127.0.0.1",7777);//参数一:服务端地址 参数二:服务端端口
//2.从scoket管道中获得一个字节输出流,负责发送数据
OutputStream os = socket.getOutputStream();
//3.字节流升级成打印流
PrintStream ps = new PrintStream(os);
//4.发送消息
ps.println("大哥,我来了");
ps.flush();//刷新
}
}
接收端 (服务端)
package bao;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
//服务端
public class Test1 {
public static void main(String[] args) throws IOException {
System.out.println("========服务端启动============");
//1.创建接收管道,注册端口
ServerSocket serverSocket = new ServerSocket(7777);//参数一:定义服务端口
//2.等待管道连接
Socket accept = serverSocket.accept();
//3.从管道中获取一个字节输入流
InputStream is = accept.getInputStream();
//4.字节流升级生缓冲输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//5.按照行读取消息会更好
String a;
if ((a = br.readLine())!=null){
System.out.println(accept.getRemoteSocketAddress()+"说了:"+a);
}
}
}
运行结果:
/127.0.0.1:60316说了:大哥,我来了
==============客户端启动===============
多收多发
发送端(客户端)
package bao; // 定义代码所属的包名。
import java.io.*; // 导入 java.io 包下的所有类,用于输入输出。
import java.net.InetAddress; // 导入用于 IP 地址的 InetAddress 类。
import java.net.Socket; // 导入 Socket 类,用于创建网络连接。
import java.util.Scanner; // 导入 Scanner 类,用于从控制台读取输入。
public class kehu {
public static void main(String[] args) throws Exception {
System.out.println("客户端启动"); // 打印启动信息。
// 1. 创建与服务端连接的管道
Socket s = new Socket(InetAddress.getLocalHost(), 9966); // 创建一个 Socket 对象,连接到本地主机的 9966 端口。
// 2. 创建一个线程负责客户端的消息读取
new ClientReaderThread(s).start(); // 创建 ClientReaderThread 对象,并启动线程来异步接收服务器的消息。
// 3. 创建一个字节输出流管道
OutputStream o = s.getOutputStream(); // 获取 Socket 对象的输出流,用于发送数据到服务器。
PrintStream p = new PrintStream(o); // 创建 PrintStream 对象,包装输出流,便于发送文本数据。
// 4. 客户端输入数据
Scanner sc = new Scanner(System.in); // 创建 Scanner 对象,用于读取控制台输入。
while (true) {
System.out.println("请输入:"); // 提示用户输入。
String s1 = sc.nextLine(); // 读取用户输入的一行文本。
p.println(s1); // 使用 PrintStream 对象发送数据到服务器。
p.flush(); // 刷新 PrintStream 对象,确保数据立即发送。
}
}
}
// 定义 ClientReaderThread 内部类,继承自 Thread 类
class ClientReaderThread extends Thread {
private Socket socket; // 定义 Socket 类型的成员变量,用于存储客户端的 Socket 对象。
public ClientReaderThread(Socket socket) {
this.socket = socket; // 构造函数,初始化 ClientReaderThread 对象的 Socket 对象。
}
@Override
public void run() {
try {
// 把字节输入流包装成字符输入流
InputStream i = socket.getInputStream(); // 获取 Socket 对象的输入流,用于接收服务器的数据。
BufferedReader b = new BufferedReader(new InputStreamReader(i)); // 包装成 BufferedReader,便于按行读取。
String s1;
while (true) {
if ((s1 = b.readLine()) != null) { // 循环读取服务器发送的每一行数据。
System.out.println("收到了消息" + s1); // 打印接收到的消息。
}
}
} catch (IOException e) {
System.out.println("服务器把你提出群聊"); // 捕获 IOException,打印异常信息。
}
}
}
接收端 (服务端)
package Text; // 定义代码所属的包名。
import java.io.*; // 导入 java.io 包下的所有类,用于输入输出。
import java.net.ServerSocket; // 导入 ServerSocket 类,用于监听客户端连接。
import java.net.Socket; // 导入 Socket 类,用于创建网络连接。
import java.util.ArrayList; // 导入 ArrayList 类,用于动态数组。
import java.util.List; // 导入 List 接口。
public class fuwu {
// 1. 定义一个静态变量储存全部管道(即客户端连接)
public static List<Socket> all_Sockets = new ArrayList<>();
public static void main(String[] args) throws IOException {
System.out.println("服务端启动成功"); // 打印服务端启动成功的信息。
// 2. 服务端口注册
ServerSocket ss = new ServerSocket(9966); // 创建 ServerSocket 对象,监听 9966 端口。
// 3. 管道死循环设置(持续监听客户端连接)
while (true) {
Socket s = ss.accept(); // 接受客户端的连接请求,返回一个新的 Socket 对象。
System.out.println(s.getRemoteSocketAddress() + "上线了"); // 打印客户端上线信息。
all_Sockets.add(s); // 将新的 Socket 对象添加到 all_Sockets 列表中。
new fuwuThread(s).start(); // 创建并启动 fuwuThread 线程来处理该客户端的通信。
}
}
}
// 定义 fuwuThread 内部类,继承自 Thread 类。
class fuwuThread extends Thread {
private Socket socket; // 定义 Socket 类型的成员变量,用于存储当前线程的 Socket 对象。
public fuwuThread(Socket socket) { // 构造函数,初始化 fuwuThread 对象的 Socket 对象。
this.socket = socket;
}
@Override
public void run() {
try {
InputStream i = socket.getInputStream(); // 获取 Socket 对象的输入流。
BufferedReader b = new BufferedReader(new InputStreamReader(i)); // 包装成 BufferedReader,用于按行读取。
String s1;
// 循环读取客户端发送的数据
while ((s1 = b.readLine()) != null) {
System.out.println(socket.getRemoteSocketAddress() + "说" + s1); // 打印客户端发送的消息。
sendMessage(s1); // 调用 sendMessage 方法将消息广播给所有客户端。
}
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress() + "离线了"); // 打印客户端离线信息。
fuwu.all_Sockets.remove(socket); // 从 all_Sockets 列表中移除当前 Socket 对象。
}
}
// 定义 sendMessage 方法,用于向所有客户端发送消息
private void sendMessage(String s1) throws IOException {
for (Socket s : fuwu.all_Sockets) { // 遍历 all_Sockets 列表中的所有 Socket 对象。
OutputStream o = s.getOutputStream(); // 获取 Socket 对象的输出流。
PrintStream p = new PrintStream(o); // 创建 PrintStream 对象,包装输出流。
p.println(s1); // 发送消息。
p.flush(); // 刷新 PrintStream 对象,确保消息立即发送。
}
}
}
UDP(User Datagram Protocol)
用户数据报协议,是一种无连接的传输协议
由于计算机网络从底层的传输到高层的软件设计十分复杂,要合理地设计计算机网络模型,必须采用分层模型,每一层负责处理自己的操作。OSI(Open System Interconnect)网络模型是 ISO 组织定义的一个计算机互联的标准模型,注意它只是一个定义,目的是为了简化网络各层的操作,提供标准接口便于实现和维护。这个模型从上到下依次是:
- 应用层,提供应用程序之间的通信;
- 表示层:处理数据格式,加解密等等;
- 会话层:负责建立和维护会话;
- 传输层:负责提供端到端的可靠传输;
- 网络层:负责根据目标地址选择路由来传输数据;
- 链路层和物理层负责把数据进行分片并且真正通过物理网络传输,例如,无线网、光纤等。
互联网实际使用的 TCP/IP 模型并不是对应到 OSI 的 7 层模型,而是大致对应 OSI 的 5 层模型:
(2)UDP协议:(速度快,无连接,不可靠)
不需要建立连接(因为把数据源IP、目的地IP、端口封装成数据包),每个数据包在64KB内,只管发,不管对方有没有接到确认什么的。
优点:可以广播发送,发送数据结束时无需释放资源,开销小,速度快。
使用场景:语言通话、视频通话等。
ps:这个就是一股脑的什么都封装一起,直接往外抛就什么都不管了,当然快了。
例子:
一收一发
发送端(客户端)
package bao;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Test {
public static void main(String[] args) throws Exception {
//一、发送端(测试时候先启动接收再发送端)
//1.创建发送端对象,发送端自带默认端口号(人)
System.out.println("========客户端启动============");
DatagramSocket socket1 = new DatagramSocket();//不定义就默认端口
//2.创建一个要发送的数据容器(容器里面有数据)
byte[] a ="我是水".getBytes();
//3.创建一个数据包对象把容器装起来
DatagramPacket packet1 = new DatagramPacket(a,a.length, InetAddress.getLocalHost(),8899);//数据,大小,服务端的IP,服务端的端口
//4.发送出去
socket1.send(packet1);
//5.关闭资源,避免资源浪费
socket1.close();
}
}
接收端 (服务端)
package bao;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Test1 {
public static void main(String[] args) throws Exception {
//二、接收端(测试时候先启动接收再发送端)
//1.创建接收端对象,注册端口(人)
System.out.println("=========接收端启动===============");
DatagramSocket socket2 = new DatagramSocket(8899);
//2.创建一个要接收的数据容器(等待接收数据)
byte[]b =new byte[1024*64];
//3.把容器数据打包
DatagramPacket packet2 = new DatagramPacket(b,b.length);
//4.等待接收数据
socket2.receive(packet2);
//5.读取多少倒出多少
int len = packet2.getLength();
String rs = new String(b,0,len);
System.out.println("接收到了数据了"+rs);
//6.关闭资源,避免资源浪费
socket2.close();
}
}
//额外知识点,获取对方端口和ip地址
//String ip = packet2.getAddress().toString();
//System.out.println("对方IP地址为"+ip);
//int port = packet2.getPort();
//System.out.println("对方端口位置"+port);
运行结果:
========客户端启动============
=========接收端启动===============
接收到了数据了我是水
多收多发
思想:把一收一发代码拿来改进就好了
发送端:把主要发送的代码写入死循环并写一个键盘输入代码,只有用户输入exit才能退出循环。
接收端:把等待接收的封装包开始写入死循环里面,然后再把释放资源的代码注释掉才能一直接收
发送端(客户端)
package bao;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
//发送端
public class Test4 {
public static void main(String[] args) throws Exception {
//一、1.创建发送端对象,发送端自带默认端口号(人)
DatagramSocket socket1 = new DatagramSocket();//不定义就默认端口
System.out.println("========客户端启动============");
//二.6创建键盘录入
Scanner sc = new Scanner(System.in);
while (true) {//二、5.死循环把代码封起来(多收多发步骤)
//二、6.接收键盘录入
System.out.println("请输入:");
String msg = sc.nextLine();
//二、7.设置exit退出
if ("exit".equals(msg)){
System.out.println("离线成功");
socket1.close();//释放资源
break;
}
//2.创建一个要发送的数据容器(容器里面有数据)
byte[] a =msg.getBytes();
//3.创建一个数据包对象把容器装起来
DatagramPacket packet1 = new DatagramPacket(a,a.length, InetAddress.getLocalHost(),8899);//数据,大小,服务端的IP,服务端的端口
//4.发送出去
socket1.send(packet1);
}
}
}
接收端 (服务端)
package bao;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
//二、接收端
public class Test1 {
public static void main(String[] args) throws Exception {
//1.创建接收端对象,注册端口(人)
System.out.println("=========接收端启动===============");
DatagramSocket socket2 = new DatagramSocket(8899);
//2.创建一个要接收的数据容器(等待接收数据)
byte[]b =new byte[1024*64];
//3.把容器数据打包
DatagramPacket packet2 = new DatagramPacket(b,b.length);
while (true) {//二、6.把封装代码写入死循环并删掉释放资源的代码(多收多发步骤)
//4.等待接收数据
socket2.receive(packet2);
//5.读取多少倒出多少
int len = packet2.getLength();
String rs = new String(b,0,len);
System.out.println("接收到了来自:"+packet2.getAddress()+"对方端口是:"+packet2.getPort()+rs);
}
}
}
//额外知识点,获取对方端口和ip地址
//String ip = packet2.getAddress().toString();
//System.out.println("对方IP地址为"+ip);
//int port = packet2.getPort();
//System.out.println("对方端口位置"+port);
运行结果:
========客户端启动============
请输入:
你在吗
请输入:
在干嘛
请输入:
=========接收端启动===============
接收到了来自:(隐私不展示)对方端口是:(隐私不展示)你在吗
接收到了来自:(隐私不展示)对方端口是:(隐私不展示)在干嘛
单播,组播,广播
- 对于单播,在Java中使用TCP协议来实现对点之间的通信,使用UDP协议来实现实时性要求高、数据量小、可靠性要求不高的对点之间的通信。
- 对于组播,在Java中常使用UDP协议来实现组播。组播是将数据包从一个源节点传送到一组目标节点,可以更有效的利用网络带宽。
- 对于广播,在Java中使用UDP协议来实现广播。广播是将数据包从一个源节点传送到所有的目标节点,可以在局域网中快速的传输信息。
使用Java实现单播
使用TCP实现单播
服务端
import java.net.*;
import java.io.*;
/**
* Java实现单播
* 服务端
*/
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8888);
Socket socket = serverSocket.accept();
System.out.println("连接客户端成功");
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
String message = in.readLine();
System.out.println("收到客户端消息: " + message);
System.out.println("你好,客户端。");
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端
import java.net.*;
import java.io.*;
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 8888);
System.out.println("连接服务端成功");
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
System.out.println("你好,服务端。");
String message = in.readLine();
System.out.println("收到服务端消息: " + message);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
先启动服务端,再启动客户端。
结果打印:
服务端
客户端
使用Java实现UDP组播
TCP是点对点的,所以实现组播得使用UDP协议
接收端
import java.io.IOException; // 导入IOException类,以便处理可能发生的输入输出异常。
import java.net.DatagramPacket; // 导入DatagramPacket类,用于处理数据报文。
import java.net.InetAddress; // 导入InetAddress类,用于处理网络地址。
import java.net.MulticastSocket; // 导入MulticastSocket类,用于创建用于多播的套接字。
public class MulticastReceiver {
public static void main(String[] args) {
try {
// 创建一个多播地址对象,这里使用的是IPv4的多播地址224.0.0.1。
InetAddress group = InetAddress.getByName("224.0.0.1");
// 多播地址是用于一组主机的IP地址,在这个例子中,224.0.0.1是多播组的地址。
// 在端口8888上创建一个多播套接字。
MulticastSocket multicastSocket = new MulticastSocket(8888);
// MulticastSocket是DatagramSocket的子类,专门用于发送和接收多播数据。
// 将套接字加入到多播组224.0.0.1,这样它就可以接收发送到该组的多播数据。
multicastSocket.joinGroup(group);
// 创建一个字节数组buffer,用于存储接收到的数据。
byte[] buffer = new byte[1024];
// 这个数组的大小决定了可以接收的数据的最大长度。
// 创建一个DatagramPacket对象,用来接收数据。
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// DatagramPacket包含了网络数据报的地址信息和数据。
// 接收一个数据报文,存储到packet对象中。
multicastSocket.receive(packet);
// 这个方法阻塞直到接收到一个数据报文。
// 从接收到的数据报文中提取数据,并将其转换为字符串。
String message = new String(buffer, 0, packet.getLength());
// 通过调用String构造函数,使用packet中的数据创建一个新的字符串。
// 打印接收到的消息。
System.out.println("收到消息: " + message);
// 离开多播组,不再接收发送到该组的数据。
multicastSocket.leaveGroup(group);
// 关闭多播套接字,释放资源。
multicastSocket.close();
} catch (IOException e) {
// 如果在尝试接收数据时发生IOException,则打印异常信息。
e.printStackTrace();
}
}
}
发送端
import java.io.IOException; // 导入IOException类,以便处理可能发生的IO异常。
import java.net.DatagramPacket; // 导入DatagramPacket类,用于创建和处理UDP数据包。
import java.net.InetAddress; // 导入InetAddress类,用于处理网络地址。
import java.net.MulticastSocket; // 导入MulticastSocket类,用于创建多播套接字。
public class MulticastSender {
public static void main(String[] args) {
try {
// 创建一个组播地址对象,这里使用的是IPv4的多播地址224.0.0.1。
InetAddress group = InetAddress.getByName("224.0.0.1");
// 多播地址是用于一组主机的IP地址,224.0.0.1是多播组的地址。
// 创建一个多播套接字,没有指定端口,因为发送时不需要绑定特定端口。
MulticastSocket multicastSocket = new MulticastSocket();
// MulticastSocket是用于发送和接收多播数据的套接字。
// 将要发送的消息转换为字节数组。
byte[] data = "Hello, 组播!".getBytes();
// 这里将字符串"Hello, 组播!"转换为字节序列,以便通过网络发送。
// 创建一个DatagramPacket对象,包含要发送的数据。
DatagramPacket packet = new DatagramPacket(data, data.length, group, 8888);
// DatagramPacket包含数据包的数据、长度、目的地地址和端口。
// 使用多播套接字发送数据包。
multicastSocket.send(packet);
// send方法将数据包发送到指定的多播地址和端口。
// 关闭多播套接字,释放资源。
multicastSocket.close();
} catch (IOException e) {
// 如果在发送数据时发生IOException,打印异常信息。
e.printStackTrace();
}
}
}
先启动接收端,再启动发送端。
执行结果如下:
使用Java实现UDP广播
发送端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 广播
* 服务端
*/
public class UdpBroadcastServer {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket();
String message = "这是一条广播!";
byte[] data = message.getBytes();
/*我们使用的目标地址是 255.255.255.255,这个地址表示广播给本地网络中所有的设备。*/
InetAddress address = InetAddress.getByName("255.255.255.255");
DatagramPacket packet = new DatagramPacket(data, data.length, address, 8888);
socket.send(packet);
System.out.println("发送消息: " + message);
socket.close();
}
}
接收端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* 客户端
*/
public class UdpBroadcastClient {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(8888);
byte[] data = new byte[1024];
DatagramPacket packet = new DatagramPacket(data, data.length);
socket.receive(packet);
String message = new String(packet.getData(), 0, packet.getLength());
System.out.println("收到消息: " + message);
socket.close();
}
}