一、网络编程简介
网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)。
介绍 Java 网络编程基础,涵盖 Socket 套接字概念及 TCP/UDP 协议区别。重点讲解 UDP 数据报套接字(DatagramSocket)和数据报包(DatagramPacket)的 API 用法,并提供回显服务器与客户端的完整代码示例,演示 UDP 通信的实现流程。

网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)。
网络编程涉及基本概念:
一次网络数据传输时: 发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。 接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。 收发端:发送端和接收端两端,也简称为收发端。
请求和响应: 一般来说,获取一个网络资源,涉及到两次网络数据传输: 第一次:请求数据的发送。 第二次:响应数据的发送。
客户端和服务端 服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务。 客户端:获取服务的一方进程,称为客户端。
Socket 套接字,是由系统提供用于网络通信的技术,是基于 TCP/IP 协议的网络通信的基本操作单元。基于 Socket 套接字的网络程序开发就是网络编程。
Socket 套接字主要针对传输层协议划分为如下三类:
socket api:传输层提供给应用层的 API。 传输层的两个核心协议是:TCP 与 UDP 协议。
有/无连接:是一个抽象的概念,虚拟的逻辑上的连接。就像前面的链表的学习一样,物理上不连续,但通过节点之间的关联使其逻辑上连续。
TCP 协议中:保存了对端的信息,让两个通信之间的设备有连接。就如 AB 通信,A 保存 B 信息,B 保存 A 信息。
UDP 协议中:UDP 本身不保存对端信息,但可以自己使用代码保存。
丢包:网络上数据发生丢失的情况就是丢包。
传输中丢包情况造成原因:
可靠传输:不是保证数据报 100% 到达,不造成一点丢失。而是尽可能提高传输成功的概率,如果丢包还会进行记录。
不可靠传输:在发出数据后就不管了。
面向数据流:读写数据(读–>接收数据,写–> 发送数据)的时候,以字节为单位。支持读写任意长度,有粘包问题。 面向数据报:读写数据(读–>接收数据,写–> 发送数据)的时候,以一个数据报为单位。有长度限制。
全双工:读写数据(读–>接收数据,写–> 发送数据),支持双向通信,能读能写。 半双工:只支持单向通信,要么读要么写。
计算机中的文件通常是一个广义的概念,文件除了指代一些我们常说的文件外,还能指代一些硬件设备。操作系统管理硬件设备也是抽象成文件。
DatagramSocket 是 UDP Socket,用于发送和接收 UDP 数据报。
| 方法签名 | 方法说明 |
|---|---|
| public DatagramSocket() throws SocketException | 创建一个 UDP 数据报套接字的 Socket,绑定到本机任意一个随机端口(一般用于客户端) |
| public DatagramSocket(int port) throws SocketException | 创建一个 UDP 数据报套接字的 Socket,绑定到本机指定的端口(一般用于服务端) |
| public DatagramSocket(int port, InetAddress laddr) throws SocketException | 创建一个 UDP 数据报套接字的 Socket,绑定到指定的 IP 即 laddr 和端口 port |
| public DatagramSocket(SocketAddress bindaddr) throws SocketException | 创建一个 UDP 数据报套接字的 Socket,绑定到 bindaddr 指定的 IP 和端口 |
| 方法签名 | 作用 |
|---|---|
| public void send(DatagramPacket p) throws IOException | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
| public synchronized void receive(DatagramPacket p) throws IOException | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待) |
| public void close() | 关闭此数据报套接字 |
DatagramPacket 是 UDP Socket 发送和接收的数据报。
| 方法签名 | 作用 |
|---|---|
| public DatagramPacket(byte[] buf, int length) | 构造一个 DatagramPacket 用来接收数据报,接收的数据保存在字节数组(第一个参数 buf)中,接收指定长度(第二个参数 length) |
| public DatagramPacket(byte[] buf, int offset, int length) | 构造一个 DatagramPacket 用来接收数据报,接收的数据保存在字节数组(第一个参数 buf)中,接收指定长度(第二个参数 length)从 buf 的下标 offset 位置开始接收 |
| public DatagramPacket(byte[] buf[], int offset, int length, SocketAddress address) | 构造一个 DatagramPacket 用来发送数据报,发送的数据为字节数组(第一个参数 buf)中,从 offset 到指定长度(第二个参数 length)。address 指定目的主机的 IP 和端口号 |
| public DatagramPacket(byte[] buf[], int offset, int length, InetAddress address, int port) | 构造一个 DatagramPacket 用来发送数据报,发送的数据为字节数组(第一个参数 buf)中,从 0 到指定长度(第二个参数 length)。address 指定目的主机的 IP,port 指定目的主机的端口号 |
| public DatagramPacket(byte[] buf[], int length, SocketAddress address) | 构造一个 DatagramPacket 用来发送数据报,发送的数据为字节数组(第一个参数 buf)中,从 0 到指定长度(第二个参数 length)。address 指定目的主机的 IP 和端口号 |
| public DatagramPacket(byte[] buf[], int length, InetAddress address, int port) | 构造一个 DatagramPacket 用来接收数据报,接收的数据保存在字节数组(第一个参数 buf)中,接收指定长度(第二个参数 length)address 指定目的主机的 IP,port 指定目的主机的端口号 |
| 方法签名 | 作用 |
|---|---|
| public synchronized byte[] getData() | 获取数据报中的数据 |
| public synchronized int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号 |
| public synchronized InetAddress getAddress() | 从接收的数据报中,获取发送端主机 IP 地址;或从发送的数据报中,获取接收端主机 IP 地址 |
回显:请求是啥,响应就是啥。
private DatagramSocket socket = null;public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
DatagramPacket requestPacket = new DatagramPacket(new byte[2024], 2024);
4.2 读取请求 socket.receive(requestPacket);
4.3 把读取到有效数据的解析为字符串。
通过 getData 方法获取 DatagramPacket 中的字节数组,getLength 方法获取 DatagramPacket 有效数据长度,根据字节数组构造字符串。
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());String response = process(request);
private String process(String request) {
return request;
}
socket.send(new DatagramPacket(request.getBytes(), request.getBytes().length, requestPacket.getSocketAddress()));
System.out.printf("[%s : %d], request:%s , response:%s\n", requestPacket.getAddress().toString(), requestPacket.getPort(), request, response);
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
// 先创建一个成员变量:Socket 对象,用于接收发送数据报
private DatagramSocket socket = null;
// 构造方法,指定一个固定端口号给服务器使用,将 Socket 使用这个端口号实例化
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
public void start() throws IOException {
while (true) {
// 构造一个 DatagramPacket 对象,初始化为 0(不是 null),用于保存 UDP 的载荷部分。
DatagramPacket requestPacket = new DatagramPacket(new byte[2024], 2024);
// 接收请求
socket.receive(requestPacket);
// 把读取到有效数据的解析为字符串
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
// 计算响应
String response = process(request);
// 把响应返回给客户端
socket.send(new DatagramPacket(request.getBytes(), request.getBytes().length, requestPacket.getSocketAddress()));
// 打印一个日志
System.out.printf("[%s : %d], request:%s , response:%s\n", requestPacket.getAddress().toString(), requestPacket.getPort(), request, response);
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(6666);
server.start();
}
}
private DatagramSocket socket = null;private String serverIp;
private int port;
public UdpEchoClient(String serverIp, int port) throws SocketException {
this.serverIp = serverIp;
this.port = port;
socket = new DatagramSocket();
}
System.out.println("请输入要传输的内容");
Scanner scanner = new Scanner(System.in);
if (!scanner.hasNext()) {
break;
}
String request = scanner.next();
socket.send(new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIp), port));
DatagramPacket responsePacket = new DatagramPacket(new byte[2024], 2024);
socket.receive(responsePacket);
String response = new String(responsePacket.getData(), 0, responsePacket.getData().length);
System.out.println(response);
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
// 先创建一个成员变量:Socket 对象,用于接收发送数据报
private DatagramSocket socket = null;
// UDP 本身不保存对端的信息,自己的代码中保存一下
private String serverIp;
private int port;
public UdpEchoClient(String serverIp, int port) throws SocketException {
this.serverIp = serverIp;
this.port = port;
socket = new DatagramSocket();
}
public void start() throws IOException {
while (true) {
// 1. 从控制台读取用户输入的内容。
System.out.println("请输入要传输的内容");
Scanner scanner = new Scanner(System.in);
if (!scanner.hasNext()) {
break;
}
String request = scanner.next();
// 发送数据报。把请求发送给服务器,需要构造 DatagramPacket 对象。构造过程中,不光要构造载荷,还要设置服务器的 IP 和端口号。
socket.send(new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIp), port));
// 接收服务器的响应
DatagramPacket responsePacket = new DatagramPacket(new byte[2024], 2024);
socket.receive(responsePacket);
// 从服务器读取的数据进行解析,打印出来。
String response = new String(responsePacket.getData(), 0, responsePacket.getData().length);
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1", 6666);
client.start();
}
}

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online