做网站要好多钱,天河手机建网站,中国建设银行网站首页u盾登入,分享站wordpress主题#x1f38f;#xff1a;你只管努力#xff0c;剩下的交给时间 #x1f3e0; #xff1a;小破站 springbootnetty化身Udp服务端#xff0c;go化身客户端模拟设备实现指令联动 #x1f517;涉及链接前言异步通信的优势异步通信的优势#xff1a;异步通信的应用场景… 你只管努力剩下的交给时间 小破站 springbootnetty化身Udp服务端go化身客户端模拟设备实现指令联动 涉及链接前言异步通信的优势异步通信的优势异步通信的应用场景 项目实现逻辑图springboot与Netty结合1. 添加依赖2. 创建UDP服务端3. 创建UDP消息处理器4. 在Spring Boot中集成UDP服务端5.controller实现 Go语言模拟设备运行和测试性能优化与调优性能优化技巧在高负载环境中调整UDP通信 安全性考量与加密通信UDP通信的安全性问题如何实现UDP通信的加密传输 涉及链接
探秘网络通信UDP与TCP/IP的奥秘
CompletableFuture探秘解锁Java并发编程的新境界
前言
在通信的大舞台上UDP是一位默默贡献的明星。而当它与Spring Boot和Netty联手再搭配Go语言的模拟设备将掀起异步通信的新篇章。今天我们将一同踏入这个奇妙的领域揭开Spring Boot和Netty在UDP通信中的神秘面纱。
异步通信的优势 异步通信具有许多优势特别是在处理大量连接、高并发和I/O密集型操作时。 异步通信的优势
高并发处理 异步通信使得系统可以在一个线程中处理多个请求提高了系统的并发处理能力特别适用于高并发的网络应用场景。资源节约 相比于同步阻塞模型异步通信可以减少线程的创建和管理节省系统资源提高系统的性能和可伸缩性。响应性 异步通信允许系统在处理请求的同时继续接受新的请求提高了系统的响应性用户在得到响应之前不需要一直等待。非阻塞I/O 异步通信中I/O操作是非阻塞的一个线程可以处理多个I/O操作避免了线程在等待I/O完成时的阻塞。
异步通信的应用场景
网络服务 适用于网络服务特别是需要高并发和低延迟的场景如实时通信、在线游戏等。大规模连接 适用于需要处理大量连接的场景如聊天服务器、消息推送服务器等。I/O密集型任务 适用于处理大量I/O密集型任务如文件操作、数据库操作等。事件驱动 适用于事件驱动的应用如消息队列、日志系统等。
项目实现逻辑图 springboot与Netty结合
将Spring Boot与Netty结合是为了利用Netty的高性能网络通信能力而Spring Boot则提供了便捷的开发和集成环境。下面是详细介绍如何搭建一个高效的UDP服务端使用Spring Boot和Netty实现。
1. 添加依赖
首先在Spring Boot项目的pom.xml中添加Netty的依赖
dependencygroupIdio.netty/groupIdartifactIdnetty-all/artifactIdversion4.1.69.Final/version !-- 替换为最新版本 --
/dependency2. 创建UDP服务端
创建一个UDP服务端使用Netty实现。下面是一个简单的示例
package com.todoitbo.baseSpringbootDasmart.netty.server;import com.todoitbo.baseSpringbootDasmart.netty.handler.UdpHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import lombok.extern.slf4j.Slf4j;import java.net.InetSocketAddress;/*** author todoitbo* date 2023/11/29*/
Slf4j
public class NettyUdpServer {private final int nettyPort;public static Channel channel;public NettyUdpServer(int port) {this.nettyPort port;}/*** 启动服务** throws InterruptedException*/public void start() throws InterruptedException {// 连接管理线程池EventLoopGroup mainGroup new NioEventLoopGroup(2);EventLoopGroup workGroup new NioEventLoopGroup(8);try {// 工作线程池Bootstrap bootstrap new Bootstrap();bootstrap.group(mainGroup)// 指定 nio 通道支持 UDP.channel(NioDatagramChannel.class)// 广播模式.option(ChannelOption.SO_BROADCAST, true)// 设置读取缓冲区大小为 10M.option(ChannelOption.SO_RCVBUF, 1024 * 1024 * 10)// 设置发送缓冲区大小为 10M.option(ChannelOption.SO_SNDBUF, 1024 * 1024 * 10)// 线程池复用缓冲区.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)// 指定 socket 地址和端口.localAddress(new InetSocketAddress(nettyPort))// 添加通道 handler.handler(new ChannelInitializerNioDatagramChannel() {Overrideprotected void initChannel(NioDatagramChannel nioDatagramChannel) throws Exception {nioDatagramChannel.pipeline()// 指定工作线程提高并发性能.addLast(workGroup,new UdpHandler());}});// 异步绑定服务器调用sync()方法阻塞等待直到绑定完成ChannelFuture sync bootstrap.bind().sync();channel sync.channel();log.info(---------- [init] UDP netty server start ----------);// 阻塞等待服务器关闭channel.closeFuture().sync();} finally {// 释放资源mainGroup.shutdownGracefully();workGroup.shutdownGracefully();}}
}
3. 创建UDP消息处理器
创建一个简单的UDP消息处理器用于处理接收到的消息且使用CompletableFuture来实现异步收发
package com.todoitbo.baseSpringbootDasmart.netty.handler;import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;import java.net.InetSocketAddress;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;import static com.todoitbo.baseSpringbootDasmart.controller.SysUploadController.socketAddressMap;/*** author todoitbo* date 2023/11/29*/
public class UdpHandler extends SimpleChannelInboundHandlerDatagramPacket {// 使用 CompletableFuture 用于异步获取客户端的响应public static CompletableFutureString responseFuture new CompletableFuture();Overrideprotected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {// 从DatagramPacket中获取数据和发送者信息byte[] data;int len packet.content().readableBytes();if (packet.content().hasArray()) {data packet.content().array();} else {data new byte[len];packet.content().getBytes(packet.content().readerIndex(), data);}String senderAddress packet.sender().getAddress().getHostAddress();int senderPort packet.sender().getPort();// 处理接收到的数据String message new String(data);System.out.println(Received message from senderAddress : senderPort - message);if (message.contains(test)) {responseFuture.complete(message);}// 构建响应消息String response Hello, client!;byte[] responseData response.getBytes();// 创建响应的DatagramPacket并发送给发送者InetSocketAddress senderSocketAddress new InetSocketAddress(senderAddress, senderPort);socketAddressMap.put(test, senderSocketAddress);DatagramPacket responsePacket new DatagramPacket(Unpooled.copiedBuffer(responseData), senderSocketAddress);ctx.writeAndFlush(responsePacket);}/*Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {// 处理异常情况cause.printStackTrace();ctx.close();}*/// 在接口调用后等待客户端响应的方法public static String waitForClientResponse() {try {// 使用 CompletableFuture 的 get 方法来阻塞等待客户端的响应String s responseFuture.get(500, TimeUnit.MILLISECONDS);responseFuture new CompletableFuture();return s; // 等待时间为 1 秒} catch (Exception e) {// 发生超时或其他异常可以根据实际情况处理return 456; // 超时返回默认值 456}}
} ⚠️注意 在某些上下文中将 CompletableFuture 声明为 public static 可行但请注意这并不总是一个最佳实践。做出这个决定时需要考虑以下几点 线程安全性 - CompletableFuture 是线程安全的但是如果你在多个线程中设置其结果你可能会遇到异常因为 CompletableFuture 的结果只能被设置一次。 共享状态 - 任何可以访问这个 public static 变量的代码都可以改变其状态。这可能会导致你的代码难于理解和维护。 生命周期 - 这个 CompletableFuture 的生命周期与应用程序的生命周期一致除非显式地设置为 null。 这可能在某些情况下会导致内存泄漏。 如果是为了协调或表示一个跨类或跨方法的异步操作的结果使用 public static CompletableFuture 是可以接受的。但你需要意识到在静态上下文中共享的状态可能会导致的问题并以适当的同步机制处理它们。 4. 在Spring Boot中集成UDP服务端
创建一个Spring Boot应用并在应用启动时启动UDP服务端
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;SpringBootApplication
public class BaseSpringbootDasmartApplication {/* static {AspectLogEnhance.enhance();}//进行日志增强自动判断日志框架*/public static void main(String[] args) {// System.setProperty(log4j2.isThreadContextMapInheritable, Boolean.TRUE.toString());SpringApplication.run(BaseSpringbootDasmartApplication.class, args);try {// new NettyWebsocketServer(13025).run();new NettyUdpServer(13026).start();} catch (Exception e) {throw new BusinessException(-----启动失败-----, e.getMessage()).setCause(e).setLog();}}
}5.controller实现
GetMapping(/login/{message})
public String login(PathVariable String message) throws NacosException, InterruptedException {byte[] responseData message.getBytes();// 创建响应的DatagramPacket并发送给发送者DatagramPacket responsePacket new DatagramPacket(Unpooled.copiedBuffer(responseData), socketAddressMap.get(test));NettyUdpServer.channel.writeAndFlush(responsePacket);// 客户端是否响应响应返回传入值否则返回456响应时间不超过0.5s如果10.5s还未响应则返回456return UdpHandler.waitForClientResponse();
}Go语言模拟设备 下面是一个简单的Go语言程序用于模拟UDP客户端发送和接收指令。在这个例子中我们使用Go的net包来处理UDP通信。下面的代码可以直接放到main中 // Author todoitbo 2023/11/29 14:26:00
package utilsimport (contextfmtnetstringssync
)// UDPClient 是一个简单的 UDP 客户端
type UDPClient struct {conn *net.UDPConnmu sync.Mutex
}// NewUDPClient 创建一个新的 UDP 客户端
func NewUDPClient(serverAddr string) (*UDPClient, error) {client : UDPClient{}addr, err : net.ResolveUDPAddr(udp, serverAddr)if err ! nil {return nil, err}conn, err : net.DialUDP(udp, nil, addr)if err ! nil {return nil, err}client.conn connmessage : []byte(你好)_, err conn.Write(message)return client, nil
}// Close 关闭 UDP 客户端连接
func (c *UDPClient) Close() {c.mu.Lock()defer c.mu.Unlock()if c.conn ! nil {c.conn.Close()}
}// ListenForMessages 启动 Goroutine 监听服务端的实时消息
func (c *UDPClient) ListenForMessages(ctx context.Context, wg *sync.WaitGroup) {defer wg.Done() // 在 Goroutine 结束时通知 WaitGroupbuffer : make([]byte, 1024)for {select {case -ctx.Done():// 收到关闭信号结束 Goroutinereturndefault:c.mu.Lock()conn : c.connc.mu.Unlock()if conn nil {// 客户端连接已关闭return}n, _, err : conn.ReadFromUDP(buffer)if err ! nil {fmt.Println(Error reading from server:, err)return}// 处理收到的消息可以根据实际需求进行逻辑处理message : string(buffer[:n])if strings.Contains(message, test) {c.SendMessage(message)}// hexString : hex.EncodeToString(message)// 将 3600 转换为字符串/*expectedValue : 3600if hexString expectedValue {c.SendMessage(test)}else {c.SendMessage(message)}*/fmt.Println(Received message from server: , message)}}
}// SendMessage 向服务端发送消息
func (c *UDPClient) SendMessage(message string) error {c.mu.Lock()defer c.mu.Unlock()if c.conn nil {return fmt.Errorf(client connection is closed)}_, err : c.conn.Write([]byte(message))return err
}func InitUDPClient(ctx context.Context, wg *sync.WaitGroup, serverAddr string) (*UDPClient, error) {client, err : NewUDPClient(serverAddr)if err ! nil {return nil, err}// 启动 Goroutine 监听服务端的实时消息wg.Add(1)go client.ListenForMessages(ctx, wg)return client, nil
}func init() {InitUDPClient(context.Background(), sync.WaitGroup{}, 127.0.0.1:13026)
}⚠️上面代码需要注意的地方
1️⃣通用udp客户端建立
2️⃣ListenForMessages 启动 Goroutine 监听服务端的实时消息
3️⃣消息的处理这里我使用的是字符串来接收真正的设备应该是接收16进制的指令。
运行和测试
1️⃣运行Spring Boot应用UDP服务端将会在13026端口启动。你可以使用UDP客户端发送消息到该端口然后在控制台看到服务端输出的消息。
2️⃣运行go程序可以在springboot控制台看到打印如下 3️⃣调用接口可以同时看到go程序与springboot打印数据如下 这只是一个简单的示例实际应用中可能需要根据具体需求进行更复杂的处理和逻辑。 Netty提供了强大的异步事件模型适用于构建高性能、可伸缩的网络应用程序而Spring Boot则为我们提供了更便捷的开发体验和集成环境。通过整合Spring Boot和Netty你可以在网络通信方面获得更好的性能和灵活性。
性能优化与调优 性能优化和调优在高负载环境中是至关重要的特别是在UDP通信这种无连接、不可靠的场景中。以下是一些性能优化的技巧和在高负载环境中调整UDP通信以获得最佳性能的建议 性能优化技巧 使用连接池 对于UDP通信中的连接考虑使用连接池来减少连接的创建和销毁开销提高资源利用率。 调整缓冲区大小 根据实际情况调整UDP通信中的缓冲区大小以优化数据传输效率。 合并和拆分消息 对于小消息可以考虑合并多个小消息为一个大消息发送减少网络开销。相反对于大消息可以考虑拆分为多个小消息发送避免一次传输过大数据。 异步处理 使用异步编程模型将耗时的操作放在异步任务中处理避免阻塞主线程。 压缩数据 在需要传输大量数据时可以考虑对数据进行压缩减少数据传输的大小。 避免频繁GC 减少对象的创建特别是在高频率的UDP通信中频繁的垃圾回收会对性能产生不利影响。
在高负载环境中调整UDP通信 调整线程池大小 在高负载环境中适当调整线程池的大小确保有足够的线程处理并发请求避免线程池饱和。 优化消息处理逻辑 对消息的处理逻辑进行优化确保处理时间短避免阻塞提高处理能力。 调整超时设置 对于需要等待响应的场景调整超时设置以适应高负载的情况避免长时间的等待。 流量控制 在高负载环境中考虑实施流量控制限制每个连接的最大流量防止过多的数据堆积。 网络拓扑优化 对于涉及多台服务器的场景优化网络拓扑减少数据传输的跳数提高数据传输效率。 监控和调优 使用性能监控工具对UDP通信进行监控识别潜在的性能瓶颈并进行相应的调优。 负载均衡 对于UDP通信的负载均衡确保负载均衡器能够合理地分发请求避免某个节点过载。
在进行性能优化和调优时需要根据具体的应用场景和性能测试结果进行调整。优化的效果可能因应用的特性而异因此在实施之前最好进行充分的性能测试。
安全性考量与加密通信
UDP通信的主要特性是无连接和不可靠相对于TCP它缺乏内建的安全性机制因此在UDP通信中需要额外关注安全性问题。以下是一些UDP通信中的安全性问题和如何实现UDP通信的加密传输的建议
UDP通信的安全性问题 数据完整性 UDP不提供数据完整性验证因此数据在传输过程中可能会被篡改。攻击者可以修改、删除或注入数据。 数据机密性 UDP通信默认是明文传输的攻击者可以轻松截取和查看通信中的数据这对于敏感信息是一种风险。 重放攻击 由于UDP通信不具备连接的概念攻击者可以通过重放已经捕获的UDP数据包来模拟合法的通信。
如何实现UDP通信的加密传输 使用加密算法 选择合适的加密算法如AES、DES等对通信中的数据进行加密。确保使用足够强度的加密算法并定期更新密钥。 消息认证码MAC 使用消息认证码对消息进行签名以验证消息的完整性和真实性。HMAC基于散列的消息认证码是一个常见的选择。 密钥交换 定期更换加密密钥可以通过安全的密钥交换协议如Diffie-Hellman密钥交换来确保密钥的安全性。 防重放攻击 使用时间戳或一次性令牌One-Time Token等机制防止重放攻击。在通信中引入时序元素可以有效地防止攻击者重放过期的数据包。 数字签名 对通信中的重要信息进行数字签名确保数据的真实性和完整性。公钥基础设施PKI可以用于验证数字签名。 实现安全通信协议 考虑使用已有的安全通信协议如DTLSDatagram Transport Layer Security它是基于UDP的TLS版本提供了加密和认证。 使用VPN或隧道 在通信的底层使用安全的VPNVirtual Private Network或隧道技术将UDP数据包进行封装提供额外的安全性保障。 防止拒绝服务攻击 在UDP通信中由于缺少连接状态可能容易受到拒绝服务攻击。采用流量限制、频率控制等手段来减缓拒绝服务攻击的影响。
实现UDP通信的加密传输需要综合考虑数据的机密性、完整性和身份验证等因素。选择合适的安全机制和协议取决于具体的应用场景和安全需求。