上篇:网络编程基础与UDP套接字编程
一、为什么需要网络编程?
平时我们在浏览器里打开优酷看视频,本质上是通过网络获取到了远端的视频资源。和打开本地视频文件类似,区别只在于资源的来源------一个是本地磁盘,一个是网络。
网络资源包括视频、图片、文本等各种数据,而这些资源的传输,都离不开网络编程。
二、什么是网络编程?
网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(网络数据传输)。
只要满足"不同进程"这个条件,即使是在同一台主机上,基于网络协议传输数据,也属于网络编程。当然,实际开发中,我们最终目的是实现不同主机之间的数据传输。
三、网络编程中的基本概念
1. 发送端和接收端
- 发送端:数据的发送方进程,对应源主机。
- 接收端:数据的接收方进程,对应目的主机。
- 两者是相对概念,一次数据传输中角色可能互换。
2. 请求和响应
获取一个网络资源通常涉及两次传输:
- 请求:客户端发送请求数据。
- 响应:服务端返回响应数据。
就像点外卖:先下单(请求),商家再送餐(响应)。
3. 客户端和服务端
- 服务端:提供服务的一方,对外提供服务。
- 客户端:获取服务的一方。
服务端常见的两种服务模式:
- 客户端获取服务端资源(如看视频)。
- 客户端保存资源到服务端(如上传文件)。
4. 常见的客户端服务端模型
- 客户端发送请求到服务端。
- 服务端处理业务。
- 服务端返回响应。
- 客户端展示处理结果。
四、Socket套接字
Socket套接字是由系统提供的、用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。
按传输层协议分为三类:
- 流套接字:使用TCP协议,有连接、可靠传输、面向字节流。
- 数据报套接字:使用UDP协议,无连接、不可靠、面向数据报、大小受限(64K)。
- 原始套接字:用于自定义传输层协议,简单了解即可。
五、Java数据报套接字(UDP)通信模型
UDP通信基于DatagramSocket和DatagramPacket。
一次完整的UDP通信流程:
- 发送端创建
DatagramSocket。 - 构造
DatagramPacket(包含数据、目标IP、端口)。 - 调用
socket.send(packet)发送。 - 接收端创建
DatagramSocket并绑定端口。 - 调用
socket.receive(packet)接收数据。 - 解析数据,处理业务,再构造响应包发回。
六、UDP编程核心API
DatagramSocket
| 方法 | 说明 |
|---|---|
DatagramSocket() |
绑定任意随机端口(客户端常用) |
DatagramSocket(int port) |
绑定指定端口(服务端常用) |
receive(DatagramPacket p) |
接收数据报,阻塞等待 |
send(DatagramPacket p) |
发送数据报,不阻塞 |
close() |
关闭套接字 |
DatagramPacket
| 方法 | 说明 |
|---|---|
DatagramPacket(byte[] buf, int length) |
用于接收数据报 |
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) |
用于发送数据报 |
getAddress() |
获取IP地址 |
getPort() |
获取端口号 |
InetSocketAddress
| 方法 | 说明 |
|---|---|
InetSocketAddress(InetAddress addr, int port) |
创建包含IP和端口的Socket地址 |
七、UDP Echo Server 代码示例
java
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 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. 返回响应
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
response.getBytes().length, requestPacket.getSocketAddress());
socket.send(responsePacket);
System.out.printf("[%s:%d] req: %s, resp: %s\n",
requestPacket.getAddress(), requestPacket.getPort(), request, response);
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
八、UDP Echo Client 代码示例
java
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIp;
private int serverPort;
public UdpEchoClient(String ip, int port) throws SocketException {
serverIp = ip;
serverPort = port;
socket = new DatagramSocket();
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("客户端启动!!");
while (true) {
System.out.print("-> ");
String request = scanner.next();
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
request.getBytes().length, InetAddress.getByName(serverIp), serverPort);
socket.send(requestPacket);
DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(responsePacket);
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字典服务器(继承Echo服务器)
java
public class UdpDictServer extends UdpEchoServer {
private Map<String, String> dict = new HashMap<>();
public UdpDictServer(int port) throws SocketException {
super(port);
dict.put("cat", "小猫");
dict.put("dog", "小狗");
dict.put("fuck", "卧槽");
}
@Override
public String process(String request) {
return dict.getOrDefault(request, "该词没有查询到!");
}
public static void main(String[] args) throws IOException {
UdpDictServer server = new UdpDictServer(9090);
server.start();
}
}
十、端口被占用怎么办?
如果启动时出现java.net.BindException: Address already in use,说明端口已被占用。
排查方式:
bash
netstat -ano | findstr 端口号
找到PID后,在任务管理器"详细信息"中按PID查找进程,决定关闭或更换端口。
小结
UDP编程简单直接:无连接、面向数据报、一次收发一整块数据。适合对实时性要求高、但允许少量丢包的场景。掌握DatagramSocket和DatagramPacket即可完成基本的UDP通信。