目录
1.前言
哈喽大家好吖,今天继续给大家分享计算机网络相关的知识,先讲解传输层两个重要的UDP和TCP协议,再讲解在UDP中通过socket API来进行网络编程。
2.正文
2.1TCP协议与UDP协议
在讲解二者的概念与对比之前,先提前介绍几个涉及到的概念:
有连接/无连接:指的是通信的A和B两端是否互相保留对方的信息(即彼此之间是否知道是谁和它建立连接)。
可靠传输/不可靠传输:在网络上,很容易出现丢包这种情况,因为光信号和电信号都可能受到外界干扰,如果在传输中识别到某些出错的数据,打包丢掉,保证正常数据传输,这个叫做可靠传输,反之只管发送,不管中间的错误就是不可靠传输。
面向字节流/面向数据包:面向字节流,读写数据的时候,是以字节为单位面向数据报,读写数据的时候,以一个数据报为单位(不是字符)。
全双工/半双工:一个通信链路,支持双向通信(能读,也能写) / 一个通信链路,只支持单向通信~~(要么读,要么写)
讲解完以上概念,正文就可以顺利开始~
TCP(传输控制协议)和UDP(用户数据报协议)是互联网中最重要的两种传输层协议,负责在应用程序之间传输数据。它们在设计目标、工作原理和应用场景上有显著差异。以下是详细的对比和解析:
TCP
特点:
面向连接:通信前需通过三次握手建立可靠连接,结束时通过四次挥手释放连接。
可靠传输:通过确认应答(ACK)、超时重传、数据排序、流量控制和拥塞控制确保数据完整、有序、无丢失。
全双工通信:支持双向数据流传输。
字节流模式:数据被看作无结构的字节流,由TCP自行分割和重组。
优点:
数据可靠性高,适合文件传输、网页浏览等场景。
自动处理数据分片、重组和错误恢复。
缺点:
建立和释放连接的开销大。
传输延迟较高。
UDP
特点:
无连接:直接发送数据,无需建立连接。
不可靠传输:不保证数据到达、顺序或完整性。
数据报模式:每个数据包独立处理,保留边界。
全双工通信:支持双向数据流传输。
优点:
传输速度快、延迟低。
资源占用少,适合实时性要求高的场景。
支持广播和多播(一对多通信)。
缺点:
不保证数据可靠性,可能丢失或乱序。
2.2socket API进行网络编程
接下来,就到了咱们的代码环节了,我们将利用socket API来创建一个本地的UDP回显服务器。那么何为回显服务器呢?
UDP回显服务器是一种简单的网络服务,它接收客户端发送的UDP数据报,并将相同的数据报原样返回给客户端,即请求和响应相同。
2.2.1DatagramPacket类
在开始创建一个本地的UDP回显服务器之前,需要先介绍一下我们要使用的DatagramPacket类:
DatagramPacket
类用于封装UDP数据报。它表示一个UDP数据报的结构,包括数据内容、目标地址、目标端口、数据长度等信息。
2.2.1.1发送数据报
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
buf
:要发送的数据,存储在字节数组中。
length
:要发送的数据的长度。
address
:目标主机的 IP 地址,类型为InetAddress
。
port
:目标主机的端口号。
2.2.1.2接收数据报
DatagramPacket(byte[] buf, int length)
buf
:用于存储接收到的数据的字节数组。
length
:字节数组的长度,即最大可接收的数据长度。
2.2.1.3获取数据报内容
getData()
:返回数据报的数据,类型为byte[]
。
getLength()
:返回数据报的实际数据长度。
getAddress()
:返回发送方的 IP 地址(接收时使用)。
getPort()
:返回发送方的端口号(接收时使用)。
2.2.1.4设置数据报内容
setData(byte[] buf)
:设置数据报的数据。
setLength(int length)
:设置数据报的长度。
setAddress(InetAddress address)
:设置目标地址。
setPort(int port)
:设置目标端口。
2.2.2DatagramSocket类
DatagramSocket
类用于发送和接收UDP数据报。它表示一个UDP端点,可以绑定到本地端口,并通过网络发送和接收数据报。
2.2.2.1构造方法
- DatagramSocket():系统随机分配一个未使用的本地端口。
- DatagramSocket(int port):绑定指定的本地端口。
- DatagramSocket(int port, InetAddress address):绑定到指定的本地端口和地址。
2.2.2.2常用方法
- void send(DatagramPacket p):发送数据报。
- void receive(DatagramPacket p):接收数据报。
- void close():关闭套接字。
2.2.3具体代码与解释
下面是具有详细的注释的代码与讲解:
UdpEchoServer:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
// 指定了一个固定端口号, 让服务器来使用.
socket = new DatagramSocket(port);
}
public void start() throws IOException {
// 启动服务器
System.out.println("服务器启动");
while (true) {
// 循环一次, 就相当于处理一次请求.
// 处理请求的过程, 典型的服务器都是分成三个步骤的.
// 1. 读取请求并解析.
// DatagramPacket 表示一个 UDP 数据报. 此处传入的字节数组, 就保存 UDP 的载荷部分.
DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(requestPacket);
// 把读取到的二进制数据, 转成字符串. 只是构造有效的部分.
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
// 2. 根据请求, 计算响应. (服务器最关键的逻辑)
// 但是此处写的是回显服务器. 这个环节相当于省略了.
String response = process(request);
// 3. 把响应返回给客户端
// 根据 response 构造 DatagramPacket, 发送给客户端.
// 此处不能使用 response.length()
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
requestPacket.getSocketAddress());
// 此处还不能直接发送. UDP 协议自身没有保存对方的信息(不知道发给谁)
// 需要指定 目的 ip 和 目的端口.
socket.send(responsePacket);
// 4. 打印一个日志
System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(), requestPacket.getPort(),
request, response);
}
}
// 后续如果要写别的服务器, 只修改这个地方就好了.
// 不要忘记, private 方法不能被重写. 需要改成 public
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
UdpEchoClient:
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
// UDP 本身不保存对端的信息, 就自己的代码中保存一下
private String serverIp;
private int serverPort;
// 和服务器不同, 此处的构造方法是要指定访问的服务器的地址.
public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
this.serverIp = serverIp;
this.serverPort = serverPort;
socket = new DatagramSocket();
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true) {
// 1. 从控制台读取用户输入的内容.
System.out.println("请输入要发送的内容:");
if (!scanner.hasNext()) {
break;
}
String request = scanner.next();
// 2. 把请求发送给服务器, 需要构造 DatagramPacket 对象.
// 构造过程中, 不光要构造载荷, 还要设置服务器的 IP 和端口号
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
InetAddress.getByName(serverIp), serverPort);
// 3. 发送数据报
socket.send(requestPacket);
// 4. 接收服务器的响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(responsePacket);
// 5. 从服务器读取的数据进行解析, 打印出来.
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
client.start();
}
}
实现核心思路:
服务器端:
打开一个UDP套接字(Socket)。
绑定到一个指定的端口,等待客户端发送数据。
接收客户端发送的UDP数据报。
将接收到的数据报原样发送回客户端。
重复上述过程,直到服务器关闭。
客户端:
打开一个UDP套接字。
向服务器的指定端口发送数据报。
接收服务器返回的数据报。
比较发送和接收的数据是否一致,以验证数据传输的完整性。
关闭套接字。
3.小结
今天的分享到这里就结束了,喜欢的小伙伴不要忘记点点赞点个关注,你的鼓励就是对我最大的支持,加油!