|
| 1 | +# 独立启动 UDPServer |
| 2 | + |
| 3 | +tio-boot 内置了 UDPServer,开发者口可以通过两种方式启动 |
| 4 | + |
| 5 | +- 独立方式 UDPServer 占用单独的端口 |
| 6 | +- 联合方式 UDPServer 和其他协议共用一个端口 |
| 7 | + |
| 8 | +## 操作系统会自动区分 TCP 和 UDP |
| 9 | + |
| 10 | +TCP 和 UDP 的区分是由操作系统的网络协议栈进行的。当数据包到达网络接口时,操作系统会检查数据包的协议头信息来确定它是 TCP 数据包还是 UDP 数据包。然后,根据这个信息,操作系统将数据包路由到相应的处理程序或应用程序。 |
| 11 | + |
| 12 | +TCP(传输控制协议)和 UDP(用户数据报协议)都建立在 IP(互联网协议)之上,但它们在传输层提供不同的服务模型: |
| 13 | + |
| 14 | +1. **TCP** 提供一种可靠的数据传输服务,它确保数据准确无误地从发送方传输到接收方。TCP 通过序号、确认回应、重传机制、流量控制等方式来保证这种可靠性。由于这些特性,TCP 适用于要求高可靠性的应用,如网页浏览、文件传输、电子邮件等。 |
| 15 | + |
| 16 | +2. **UDP** 提供一种无连接的服务,允许数据以数据报文的形式发送,而不保证传输的可靠性、顺序或数据完整性。UDP 的这种简单性使其成为对实时性要求高(如在线游戏、语音或视频会议等)和/或对系统资源使用敏感的应用程序的理想选择。 |
| 17 | + |
| 18 | +当操作系统收到一个数据包时,它会检查 IP 头部中的协议字段来确定上层使用的是哪个协议(例如,TCP 或 UDP)。然后,它会检查数据包的端口号,并将数据包传递给在该端口监听的应用程序。如果是 TCP 数据包,它还会处理与 TCP 连接相关的各种控制消息,如 SYN、ACK 等。对于 UDP,由于其无连接的特性,操作系统的处理相对简单。 |
| 19 | + |
| 20 | +这种区分和处理都是透明的,通常对应用程序开发者和用户而言是不可见的。操作系统和网络协议栈负责确保网络通信的正确性和效率,而应用程序可以通过标准的网络接口(如套接字)来进行通信,无需关心底层的复杂性。 |
| 21 | + |
| 22 | +## 使用 Java 代码启动 TCP 端口和 UDP 端口 |
| 23 | + |
| 24 | +在 Java 中,要同时在同一个端口上处理 TCP 和 UDP,你需要分别为 TCP 和 UDP 创建不同的服务端套接字,并且这两个套接字监听同一个端口号。通常,你会为 TCP 使用`ServerSocket`类,并为 UDP 使用`DatagramSocket`类。 |
| 25 | + |
| 26 | +下面是一个简单的示例,展示了如何同时监听同一端口上的 TCP 和 UDP 请求: |
| 27 | + |
| 28 | +```java |
| 29 | +package org.tio.showcase.udp.demo; |
| 30 | + |
| 31 | +import java.io.IOException; |
| 32 | +import java.net.DatagramPacket; |
| 33 | +import java.net.DatagramSocket; |
| 34 | +import java.net.ServerSocket; |
| 35 | +import java.net.Socket; |
| 36 | + |
| 37 | +public class TcpUdpServer { |
| 38 | + private static final int PORT = 12345; // 选择一个端口号 |
| 39 | + |
| 40 | + public static void main(String[] args) throws IOException { |
| 41 | + // 创建并启动TCP服务器线程 |
| 42 | + Thread tcpThread = new Thread(() -> { |
| 43 | + try (ServerSocket serverSocket = new ServerSocket(PORT)) { |
| 44 | + System.out.println("TCP Server is running on port " + PORT); |
| 45 | + while (true) { |
| 46 | + Socket clientSocket = serverSocket.accept(); |
| 47 | + // 处理TCP连接... |
| 48 | + System.out.println("TCP Connection established"); |
| 49 | + // 可以为每个连接创建新线程或使用线程池来处理 |
| 50 | + } |
| 51 | + } catch (IOException e) { |
| 52 | + e.printStackTrace(); |
| 53 | + } |
| 54 | + }); |
| 55 | + |
| 56 | + // 创建并启动UDP服务器线程 |
| 57 | + Thread udpThread = new Thread(() -> { |
| 58 | + try (DatagramSocket datagramSocket = new DatagramSocket(PORT)) { |
| 59 | + System.out.println("UDP Server is running on port " + PORT); |
| 60 | + byte[] receiveData = new byte[1024]; |
| 61 | + while (true) { |
| 62 | + DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); |
| 63 | + datagramSocket.receive(receivePacket); |
| 64 | + // 处理UDP包... |
| 65 | + System.out.println("UDP Packet received"); |
| 66 | + // 可以在这里处理数据或者创建新线程进行处理 |
| 67 | + } |
| 68 | + } catch (IOException e) { |
| 69 | + e.printStackTrace(); |
| 70 | + } |
| 71 | + }); |
| 72 | + |
| 73 | + tcpThread.start(); // 启动TCP服务器线程 |
| 74 | + udpThread.start(); // 启动UDP服务器线程 |
| 75 | + } |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +在这个例子中: |
| 80 | + |
| 81 | +- 对于 TCP,我们创建了一个`ServerSocket`,它在指定的端口上监听传入的 TCP 连接请求。每当接受一个连接时,`accept`方法会返回一个新的`Socket`实例,用于与客户端通信。 |
| 82 | +- 对于 UDP,我们创建了一个`DatagramSocket`,它可以在指定的端口上接收 UDP 数据包。每次调用`receive`方法都会从套接字的队列中取出一个数据包,我们可以处理这个数据包。 |
| 83 | + |
| 84 | +两个服务器(TCP 和 UDP)都在自己的线程中运行,这样它们就可以并行处理数据,并独立地接收和处理各自协议的数据。记住在实际应用中,你需要适当处理异常和多线程同步等问题。 |
| 85 | + |
| 86 | +## 独立启动 udp 服务 |
| 87 | + |
| 88 | +本文档提供了一个简单 UDP 服务器的实现方法 |
| 89 | + |
| 90 | +### 1. 主程序入口 (`Main.java`) |
| 91 | + |
| 92 | +这个方法会自动扫描带有`@AComponentScan`注解的组件并进行初始化。 |
| 93 | + |
| 94 | +```java |
| 95 | +package demo.udp; |
| 96 | + |
| 97 | +import com.litongjava.hotswap.wrapper.tio.boot.TioApplicationWrapper; |
| 98 | +import com.litongjava.jfinal.aop.annotation.AComponent; |
| 99 | +import com.litongjava.jfinal.aop.annotation.AComponentScan; |
| 100 | + |
| 101 | +@AComponentScan |
| 102 | +public class Main { |
| 103 | + public static void main(String[] args) { |
| 104 | + long start = System.currentTimeMillis(); |
| 105 | + TioApplicationWrapper.run(Main.class, args); |
| 106 | + long end = System.currentTimeMillis(); |
| 107 | + System.out.println((end - start) + "(ms)"); |
| 108 | + } |
| 109 | +} |
| 110 | +``` |
| 111 | + |
| 112 | +### 2. UDP 消息处理 (`DemoUdpHandler.java`) |
| 113 | + |
| 114 | +该类负责接收和处理 UDP 消息。它读取数据包内容,记录发送者信息,并将收到的消息回发给发送者。 |
| 115 | + |
| 116 | +```java |
| 117 | +package demo.udp.handler; |
| 118 | + |
| 119 | +import java.net.DatagramPacket; |
| 120 | +import java.net.DatagramSocket; |
| 121 | +import java.net.InetSocketAddress; |
| 122 | + |
| 123 | +import com.litongjava.tio.core.Node; |
| 124 | +import com.litongjava.tio.core.udp.UdpPacket; |
| 125 | +import com.litongjava.tio.core.udp.intf.UdpHandler; |
| 126 | + |
| 127 | +import lombok.extern.slf4j.Slf4j; |
| 128 | + |
| 129 | +@Slf4j |
| 130 | +public class DemoUdpHandler implements UdpHandler { |
| 131 | + /** |
| 132 | + * 处理udp消息 |
| 133 | + */ |
| 134 | + public void handler(UdpPacket udpPacket, DatagramSocket datagramSocket) { |
| 135 | + byte[] data = udpPacket.getData(); |
| 136 | + String msg = new String(data); |
| 137 | + Node remote = udpPacket.getRemote(); |
| 138 | + |
| 139 | + log.info("收到来自{}的消息:【{}】", remote, msg); |
| 140 | + DatagramPacket datagramPacket = new DatagramPacket(data, data.length, |
| 141 | + new InetSocketAddress(remote.getIp(), remote.getPort())); |
| 142 | + try { |
| 143 | + datagramSocket.send(datagramPacket); |
| 144 | + } catch (Throwable e) { |
| 145 | + log.error(e.toString(), e); |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | +} |
| 150 | +``` |
| 151 | + |
| 152 | +## 3. UDP 服务器配置 (`UdpServerConfig.java`) |
| 153 | + |
| 154 | +这个类用于配置和启动 UDP 服务器。它创建了一个`UdpServerConf`实例,指定服务器的端口和处理器,然后启动服务器。 |
| 155 | + |
| 156 | +``` |
| 157 | +import java.net.SocketException; |
| 158 | +
|
| 159 | +import com.litongjava.jfinal.aop.annotation.AConfiguration; |
| 160 | +import com.litongjava.jfinal.aop.annotation.AInitialization; |
| 161 | +import com.litongjava.tio.core.udp.UdpServer; |
| 162 | +import com.litongjava.tio.core.udp.UdpServerConf; |
| 163 | +
|
| 164 | +import demo.udp.handler.DemoUdpHandler; |
| 165 | +import lombok.extern.slf4j.Slf4j; |
| 166 | +
|
| 167 | +@Slf4j |
| 168 | +@AConfiguration |
| 169 | +public class UdpServerConfig { |
| 170 | +
|
| 171 | + @AInitialization |
| 172 | + public void config() { |
| 173 | + DemoUdpHandler fpmsUdpHandler = new DemoUdpHandler(); |
| 174 | + UdpServerConf udpServerConf = new UdpServerConf(3000, fpmsUdpHandler, 5000); |
| 175 | + UdpServer udpServer; |
| 176 | + try { |
| 177 | + udpServer = new UdpServer(udpServerConf); |
| 178 | + udpServer.start(); |
| 179 | + log.info("udp started"); |
| 180 | + } catch (SocketException e) { |
| 181 | + e.printStackTrace(); |
| 182 | + } |
| 183 | +
|
| 184 | + } |
| 185 | +} |
| 186 | +``` |
| 187 | + |
| 188 | +## Udp 客户端 |
| 189 | + |
| 190 | +tio-boot 同样提供了 UDP 客户端,你可以使用 UDP 客户端给上面的服务发送数据 |
| 191 | + |
| 192 | +``` |
| 193 | +package demo.udp.client; |
| 194 | +
|
| 195 | +import com.litongjava.tio.core.udp.UdpClient; |
| 196 | +import com.litongjava.tio.core.udp.UdpClientConf; |
| 197 | +
|
| 198 | +public class UdpClientDemo { |
| 199 | +
|
| 200 | + public static void main(String[] args) { |
| 201 | + UdpClientConf udpClientConf = new UdpClientConf("127.0.0.1", 3000, 5000); |
| 202 | + UdpClient udpClient = new UdpClient(udpClientConf); |
| 203 | + udpClient.start(); |
| 204 | +
|
| 205 | + long start = System.currentTimeMillis(); |
| 206 | + for (int i = 0; i < 1000000; i++) { |
| 207 | + String str = i + "、" + "hello"; |
| 208 | + udpClient.send(str.getBytes()); |
| 209 | + } |
| 210 | + long end = System.currentTimeMillis(); |
| 211 | + long iv = end - start; |
| 212 | + System.out.println("耗时:" + iv + "ms"); |
| 213 | + } |
| 214 | +} |
| 215 | +``` |
| 216 | + |
| 217 | +## 测试代码地址 |
| 218 | + |
| 219 | +https://github.com/litongjava/java-ee-tio-boot-study/tree/main/tio-boot-latest-study/tio-boot-udp-demo01 |
0 commit comments