Netty实现udp服务器

1、TCP与UDP通信协议

网络传输层协议有两种,一种是TCP,另外一种是UDP。

TCP是一种面向连接的协议,提供可靠的数据传输。TCP通过三次握手建立连接,并通过确认和重传机制,保证数据的完整性和可靠性。TCP适用于对数据准确性要求较高、对实时性要求较低的应用场景,如网页浏览、文件传输等。

UDP是一种无连接的协议,不保证数据的可靠性传输。UDP通过尽力交付数据包的方式进行传输,不对数据包的传输状态进行确认和重传,因此速度较快。UDP适用于对实时性要求较高、对数据准确性要求较低的应用场景,如视频传输、语音通信等。

那么,这两种有哪些区别呢?请看下面:

  • 连接方式:UDP是无连接的,TCP是面向连接的;
  • 可靠性:UDP不保证数据的可靠性传输,TCP保证数据的可靠传输;
  • 速度:UDP传输速度较快,TCP传输速度较慢;
  • 传输方式:UDP采用尽力交付的方式传输数据包,不进行确认和重传;TCP通过确认和重传机制保证数据的完整性和可靠性;
  • 开销:UDP的开销较低,TCP的开销较高。

总结:我们可以根据具体的应用场景和需求选择使用UDP或TCP进行数据传输。如果对数据的实时性要求较高,且对数据准确性要求较低,可以选择使用UDP。如果对数据的准确性要求较高,可以选择使用TCP。

2、游戏行业选择的通信协议

游戏由于对数据的准确性(不允许丢包,乱序)非常高,一般都是选择基于TCP的socket通信。但UDP的低时延,快速传输对实时性要求非常高的游戏类型也是非常大的吸引力。据说,魔兽世界以及Dota2使用UDP开发。当然,使用udp通信的游戏肯定在通信层做了适配,保证关键数据不丢包,不乱序。

近年来,基于UDP协议的KCP(Kuai Control Protocol,快速可靠传输协议),也得到了快速的发展。据说,原神是使用KCP通信的。

3、Netty使用UDP协议

netty使用udp协议,网上的例子都是非常简单的。都是两个类搞定。没有解决以下几个问题:

  • 如何与现有网络框架适配(支持私有协议栈,支持javabean,不单单是字符串)
  • 如何主动与客户端通信(不再是客户端发一个消息,服务器直接推送一个回包)

本文主要就这两个问题进行案例说明。

3.1、netty数据包载体

udp是无连接的,这意味着通信双方无需像TCP那般“三次握手四次释放”。只要知道对方的socket地址(Ip+Port),即可发送数据,发完即终止。在Netty里,udp的通信载体叫做DatagramPacket,负责将数据(ByteBuf)从源头发送到目的地。

public class DatagramPacket extends DefaultAddressedEnvelope<ByteBuf, InetSocketAddress> implements ByteBufHolder {public DatagramPacket(ByteBuf data, InetSocketAddress recipient) {super(data, recipient);}public DatagramPacket(ByteBuf data, InetSocketAddress recipient, InetSocketAddress sender) {super(data, recipient, sender);}
}

3.2私有协议栈编解码

3.2.1、通信基类

而我们的网络底层通信是基于javabean的,因此定义我们的消息基类如下:

public class UdpMessage implements Message {private String senderIp;private int senderPort;private String receiverIp;private int receiverPort;}

3.2.2、私有协议栈消息编码

我们将私有协议栈定义为 包头(消息类型id),包体(具体消息经编码后的字节数组)。将自定义消息UdpMessage转为DatagramPacket

public class UdpProtocolEncoder extends MessageToMessageEncoder<UdpMessage> {private static final Logger logger = LoggerFactory.getLogger("socketserver");private final MessageFactory messageFactory;private final MessageCodec messageCodec;public UdpProtocolEncoder(MessageFactory messageFactory, MessageCodec messageCodec) {this.messageFactory = messageFactory;this.messageCodec = messageCodec;}@Overrideprotected void encode(ChannelHandlerContext ctx, UdpMessage message, List<Object> out) throws Exception {// ----------------protocol pattern-------------------------// packetLength | cmd | body// int int byte[]int  cmd = messageFactory.getMessageId(message.getClass());try {byte[] body = messageCodec.encode(message);//消息内容长度ByteBuf buf = Unpooled.buffer(body.length+4);// 写入cmd类型buf.writeInt(cmd);buf.writeBytes(body);out.add(new DatagramPacket(buf, new InetSocketAddress(message.getReceiverIp(), message.getReceiverPort())));} catch (Exception e) {logger.error("wrote message {} failed", cmd, e);}}
}

这里消息pojo编码,使用了jforgame的组件,根据javabean的字段元信息,自动编码为byte数组。

依赖申明如下:

<dependency><groupId>io.github.jforgame</groupId><artifactId>jforgame-codec-struct</artifactId><version>1.1.0</version>
</dependency>

3.2.3、私有协议栈消息解码

私有协议栈解码负责将数据包DatagramPacket转为UdpMessage。将底层数据流转为ByteBuf之后,还需要将字节数据进行解码,才可以转换为应用程序认识的消息。

public class UdpProtocolDecoder extends MessageToMessageDecoder<DatagramPacket> {private final MessageFactory messageFactory;private final MessageCodec messageCodec;public UdpProtocolDecoder(MessageFactory messageFactory, MessageCodec messageCodec) {this.messageFactory = messageFactory;this.messageCodec = messageCodec;}@Overrideprotected void decode(ChannelHandlerContext ctx, DatagramPacket msg, List<Object> out) throws Exception {ByteBuf in = msg.content();int length = in.readableBytes();int cmd = in.readInt();byte[] body = new byte[length - 4];in.readBytes(body);Class<?> msgClazz = messageFactory.getMessage(cmd);out.add(messageCodec.decode(msgClazz, body));}}

4、服务端代码

4.1、会话管理

服务端会话管理(建立及摧毁),以及消息接受(下文的channelRead方法)。

@ChannelHandler.Sharable
public class UdpChannelIoHandler extends ChannelInboundHandlerAdapter {private final static Logger logger = LoggerFactory.getLogger("socketserver");/** 消息分发器 */private final SocketIoDispatcher messageDispatcher;public UdpChannelIoHandler(SocketIoDispatcher messageDispatcher) {super();this.messageDispatcher = messageDispatcher;}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {Channel channel = ctx.channel();ChannelUtils.duplicateBindingSession(ctx.channel(), new NSession(channel));SessionManager.getInstance().buildSession(ChannelUtils.getSessionBy(channel));System.out.println("socket register " + channel);}@Overridepublic void channelRead(ChannelHandlerContext context, Object packet) throws Exception {logger.debug("receive pact, content is {}", packet.getClass().getSimpleName());final Channel channel = context.channel();IdSession session = ChannelUtils.getSessionBy(channel);messageDispatcher.dispatch(session, packet);}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {Channel channel = ctx.channel();System.out.println("socket inactive " + channel);IdSession userSession = ChannelUtils.getSessionBy(channel);messageDispatcher.onSessionClosed(userSession);}}

需要注意的是,UDP Socket Server的链接建立,只在启动的时候触发一次。之后,无论有多少个客户端,上文的channelActive都不会再次触发。也就是说,客户端的链接不是一对一的,全局只有一个服务端链接。这点与tcp不同。那么,怎么区分不同的客户端呢?后文例子揭晓。

4.2、消息路由

具体的消息处理(通过消息路由及消息处理器注解)。网关接受到消息之后,自动把消息分发到对应的处理器。类似与springmvc的Controller以及RequestMapper功能。

public class MessageIoDispatcher extends ChainedMessageDispatcher {private MessageHandlerRegister handlerRegister;MessageFactory messageFactory = GameMessageFactory.getInstance();private MessageParameterConverter msgParameterConverter= new DefaultMessageParameterConverter(messageFactory);public MessageIoDispatcher() {LoginRouter router = new LoginRouter();this.handlerRegister = new CommonMessageHandlerRegister(Collections.singletonList(router), messageFactory);MessageHandler messageHandler = (session, message) -> {int cmd = GameMessageFactory.getInstance().getMessageId(message.getClass());MessageExecutor cmdExecutor = handlerRegister.getMessageExecutor(cmd);if (cmdExecutor == null) {logger.error("message executor missed,  cmd={}", cmd);return true;}Object[] params = msgParameterConverter.convertToMethodParams(session, cmdExecutor.getParams(), message);Object controller = cmdExecutor.getHandler();MessageTask task = MessageTask.valueOf(session, session.hashCode(), controller, cmdExecutor.getMethod(), params);task.setRequest(message);// 丢到任务消息队列,不在io线程进行业务处理GameServer.getMonitorGameExecutor().accept(task);return true;};addMessageHandler(messageHandler);}@Overridepublic void onSessionCreated(IdSession session) {}@Overridepublic void onSessionClosed(IdSession session) {}}

4.3、服务端启动代码

public class UdpSocketServer implements ServerNode {private static final Logger logger = LoggerFactory.getLogger("socketserver");private EventLoopGroup group = new NioEventLoopGroup();protected HostAndPort nodesConfig = HostAndPort.valueOf(8088);public SocketIoDispatcher socketIoDispatcher;public MessageFactory messageFactory;public MessageCodec messageCodec;@Overridepublic void start() throws Exception {try {SessionManager.getInstance().schedule();Bootstrap bootstrap = new Bootstrap();bootstrap.group(group).channel(NioDatagramChannel.class).handler(new LoggingHandler(LogLevel.DEBUG)).handler(new ChannelInitializer<DatagramChannel>() {@Overridepublic void initChannel(DatagramChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast("protocolDecoder", new UdpProtocolDecoder(messageFactory, messageCodec));pipeline.addLast("protocolEncoder", new UdpProtocolEncoder(messageFactory, messageCodec));pipeline.addLast(new UdpChannelIoHandler(socketIoDispatcher));}});logger.info("socket server is listening at " + nodesConfig.getPort() + "......");bootstrap.bind(nodesConfig.getPort()).sync().channel().closeFuture().sync();} catch (Exception e) {logger.error("", e);group.shutdownGracefully();}}@Overridepublic void shutdown() throws Exception {group.shutdownGracefully();}public static void main(String[] args) throws Exception {UdpSocketServer udpSocketServer = new UdpSocketServer();udpSocketServer.messageFactory = GameMessageFactory.getInstance();udpSocketServer.messageCodec = new StructMessageCodec();udpSocketServer.socketIoDispatcher = new MessageIoDispatcher();udpSocketServer.start();}
}

5、客户端代码

5.1、客户端启动代码

public class UdpSocketClient extends AbstractSocketClient {private final EventLoopGroup group = new NioEventLoopGroup(1);private HostAndPort nativeHostPort;public UdpSocketClient(SocketIoDispatcher messageDispatcher, MessageFactory messageFactory, MessageCodec messageCodec, HostAndPort hostPort) {this.ioDispatcher = messageDispatcher;this.messageFactory = messageFactory;this.messageCodec = messageCodec;this.targetAddress = hostPort;}@Overridepublic IdSession openSession() throws IOException {try {final NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup();Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioDatagramChannel.class);bootstrap.group(nioEventLoopGroup);bootstrap.handler(new LoggingHandler(LogLevel.INFO));bootstrap.handler(new UdpProtoBufClientChannelInitializer());ChannelFuture f = bootstrap.connect(new InetSocketAddress(targetAddress.getHost(), targetAddress.getPort()),new InetSocketAddress(nativeHostPort.getHost(), nativeHostPort.getPort())).sync();IdSession session = new NSession(f.channel());this.session = session;return session;} catch (Exception e) {group.shutdownGracefully();throw new IOException(e);}}@Overridepublic void close() throws IOException {this.session.close();}public void send(UdpMessage message) {message.setSenderIp(nativeHostPort.getHost());message.setSenderPort(nativeHostPort.getPort());message.setReceiverIp(targetAddress.getHost());message.setReceiverPort(targetAddress.getPort());session.send(message);}class UdpProtoBufClientChannelInitializer extends ChannelInitializer<NioDatagramChannel> {@Overrideprotected void initChannel(NioDatagramChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast("protocolDecoder", new UdpProtocolDecoder(messageFactory, messageCodec));pipeline.addLast("protocolEncoder", new UdpProtocolEncoder(messageFactory, messageCodec));pipeline.addLast(new UdpChannelIoHandler(ioDispatcher));}}
}

这里需要注意下:

客户端需要指定端口与服务器通信,这样才能方便消息包携带自己的地址信息。

5.2、测试代码

客户端测试代码如下:

模拟10个玩家登录。读者可自行修改程序。udp是不可靠链接,不保证交付。如果在外网跑,是会出现消息丢包,或者乱序。在本地跑,是不太可能出现这种情况。读者可自行测试,同一个角色发送一大堆消息(附加上次序字段),看服务器收到的消息序号是否完整有序。

 private static AtomicLong idFactory = new AtomicLong(1000);public static void main(String[] args) throws Exception {MessageCodec messageCodec = new StructMessageCodec();GameMessageFactory.getInstance().registeredClassTypes().forEach(Codec::getSerializer);for (int i = 0; i < 10; i++) {System.out.println("----------i=" + i);UdpSocketClient socketClient = new UdpSocketClient(new SocketIoDispatcherAdapter() {@Overridepublic void dispatch(IdSession session, Object message) {System.out.println("receive package ---------" + JsonUtil.object2String(message));}}, GameMessageFactory.getInstance(), messageCodec, HostAndPort.valueOf(8088));socketClient.nativeHostPort = HostAndPort.valueOf(8099 + i);socketClient.openSession();for (int j = 0; j < 1; j++) {ReqLogin req = new ReqLogin();req.setPlayerId(idFactory.getAndIncrement());socketClient.send(req);}}}

6、游戏服务器示例功能

6.1、demo逻辑

我们以上面的代码,实现一个简单的游戏逻辑。

  1. 玩家根据服务端的地址进行udp通信。链接建立之后,模拟账号登录逻辑。
  2. 服务器接收到登录消息之后,立马推送登录成功的协议。
  3. 服务器每隔一段时间,向所有注册的客户端推送一个消息包。

6.2、登录请求/响应包

@MessageMeta(cmd = 55555)
public class ReqLogin extends UdpMessage {private long playerId;
}
@MessageMeta(cmd = 55556)
public class ResPlayerLogin extends UdpMessage {private long playerId;
}

6.3、登录路由

@MessageRoute
public class LoginRouter {@RequestHandlerpublic void reqTime(IdSession session, ReqLogin req) {long playerId = req.getPlayerId();System.out.println("player login" + playerId);Player player = new Player();player.setId(playerId);player.setRemoteAddr(HostAndPort.valueOf(req.getSenderIp(), req.getSenderPort()));SessionManager.getInstance().register(playerId, player);ResPlayerLogin resp = new ResPlayerLogin();resp.setPlayerId(playerId);player.receive(session, resp);}
}

其中SessionManager 类缓存服务器的全局session,以及各个客户端环境的通信地址。以及,每隔一段时间主动向客户端推送消息。

public class SessionManager {private static SessionManager inst = new SessionManager();private ConcurrentMap<Long, Player> id2Players = new ConcurrentHashMap<>();private IdSession serverSession;public static SessionManager getInstance() {return inst;}public void register(long playerId, Player player) {id2Players.put(playerId, player);}public void buildSession(IdSession session) {serverSession = session;}public void schedule() {SchedulerManager.getInstance().scheduleAtFixedRate(()->{id2Players.forEach((key, value) -> {ResWelcome push = new ResWelcome();push.setTime(System.currentTimeMillis());value.receive(serverSession, push);});}, TimeUtil.MILLIS_PER_MINUTE, 10*TimeUtil.MILLIS_PER_SECOND);}}

需要注意的是,客户端只有在登录成功之后,服务器才能绑定玩家与对应的客户端地址,才能主动推送消息。也就是说,在登录之前,服务器也是无法主动推送消息的,在业务上来说,也是没有意义的。

6.4、客户端测试代码输出

7、总结

udp是一种无连接的协议,t提供不可靠性传输。UDP通过尽力交付数据包的方式进行传输,不对数据包的传输状态进行确认和重传,因此速度较快。UDP适用于对实时性要求较高、对数据准确性要求较低的应用场景。例如,如果语音视频服务。如果游戏服务器确实需要使用udp协议的话,需要在应用层解决丢包乱序问题。

对于乱序,接受方可以先把数据都缓存起来,等一段窗口期的数据接受完毕后再分发给业务层。对于丢包,无论是发送方还是接受方,都需要缓存最近发送的消息。以便用于丢包重传。当然,这只是基本的思路,实际处理起来可能会非常复杂。如果考虑到UDP协议的话,可能KCP会是更好的选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/806512.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于lora技术对Gemma(2B)大模型的微调实践

一、概述 本文主要基于Lora技术&#xff0c;在Google colab上用A100对Gemma 2B大模型进行了指令微调&#xff0c;第一次指令微调是采用databricks-dolly-15k 作为数据集&#xff0c;取得了不错的微调效果&#xff0c;能准确用英文回答问题&#xff0c;但databricks-dolly-15k …

智慧公厕中的大数据、云计算和物联网技术引领未来公厕管理革命

现代社会对于公共卫生和环境保护的要求越来越高&#xff0c;智慧公厕作为城市基础设施建设的重要组成部分&#xff0c;正引领着公厕管理的革命。随着科技的不断进步&#xff0c;大数据、云计算和物联网技术的应用为智慧公厕带来了全新的可能性&#xff0c;&#xff08;ZonTree中…

Spring Boot统一功能处理之拦截器

本篇主要介绍Spring Boot的统一功能处理中的拦截器。 目录 一、拦截器的基本使用 二、拦截器实操 三、浅尝源码 初始化DispatcherServerlet 处理请求&#xff08;doDispatch) 四、适配器模式 一、拦截器的基本使用 在一般的学校或者社区门口&#xff0c;通常会安排几个…

selenium添加代理(有账号密码)

以下为各种尝试的记录&#xff0c;正确实现可直接参考最后一条&#xff01; 1&#xff0c;导入Proxy库来添加capabilities属性&#xff1a;可以访问网站&#xff0c;但ip还是本机ip from selenium import webdriver from selenium.webdriver.chrome.options import Options f…

【TensorRT】TensorRT C# API 项目更新 (1):支持动态Bath输入模型推理(下篇)

4. 接口应用 关于该项目的调用方式在上一篇文章中已经进行了详细介绍&#xff0c;具体使用可以参考《最新发布&#xff01;TensorRT C# API &#xff1a;基于C#与TensorRT部署深度学习模型》&#xff0c;下面结合Yolov8-cls模型详细介绍一下更新的接口使用方法。 4.1 创建并配…

Java零基础入门-Java反射机制

一、概述 我们都听说过java有个反射机制&#xff0c;通过反射机制我们可以更深入的控制程序的运行过程。例如&#xff0c;在程序进入到运行期间&#xff0c;由用户输入一个类名&#xff0c;然后我们可以动态获取到该类拥有的所有类结构、属性名和方法&#xff0c;甚至还可以任意…

Java快速入门系列-9(Spring框架与Spring Boot —— 深度探索及实践指南)

第九章:Spring框架与Spring Boot —— 深度探索及实践指南 9.1 Spring框架概述9.2 Spring IoC容器9.3 Spring AOP9.4 Spring MVC9.5 Spring Data JPA/Hibernate9.6 Spring Boot快速入门与核心特性9.7 Spring Boot的自动配置与启动流程详解9.8 创建RESTful服务与数据库交互实践…

专为苹果系统设计的精美可视化图表 | 开源日报 No.219

danielgindi/Charts Stars: 27.3k License: Apache-2.0 Charts 是为 iOS/tvOS/OSX 提供美观图表的开源项目&#xff0c;是跨平台 MPAndroidChart 在苹果设备上的实现。该项目提供了以下主要功能和优势&#xff1a; 支持 iOS、tvOS 和 macOS 平台使用 Swift 编写&#xff0c;可…

Ceph学习 -6.Nautilus版本集群部署

文章目录 1.集群部署1.1 环境概述1.1.1 基础知识1.1.2 环境规划1.1.3 小结 1.2 准备工作1.2.1 基本环境1.2.2 软件安装1.2.3 小结 1.3 Ceph部署1.3.1 集群创建1.3.2 部署Mon1.3.3 小结 1.4 Ceph部署21.4.1 Mon认证1.4.2 Mgr环境1.4.3 小结 1.5 OSD环境1.5.1 基本环境1.5.2 OSD实…

数据结构-移除元素(简单)

题目描述 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出…

可视化大屏的应用(11):智慧运维领域的得力助手

一、什么是智慧运维 智慧运维&#xff08;Smart Operations and Maintenance&#xff0c;简称智慧运维&#xff09;是一种利用先进的信息技术和数据分析手段&#xff0c;对设备、设施或系统进行监测、分析和优化管理的运维方式。它通过实时监测数据、智能分析和预测&#xff0…

Redis中的集群(五)

集群 在集群中执行命令 MOVED错误。 当节点发现键所在的槽并非由自己负责处理的时候&#xff0c;节点就会向客户端返回一个MOVED错误&#xff0c;指引客户端转向至正在负责槽的节点&#xff0c;MOVED错误的格式为: MOVED <slot> <ip>:<port>其中slot为键…

centos 7.9 nginx本地化安装,把镜像改成阿里云

1.把centos7.9系统切换到阿里云的镜像源 1.1.先备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup1.2.下载新的CentOS-Base.repo配置文件 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo特别…

盲人独立出行的新里程:“盲人软件”赋能无障碍生活

作为一名资深记者&#xff0c;我始终致力于探索并分享那些以科技之力提升特殊群体生活质量的故事。最近&#xff0c;一款名为蝙蝠避障的盲人软件进入了我的视野&#xff0c;其强大的避障导航功能正悄然改变着视障人士的出行方式&#xff0c;赋予他们前所未有的独立生活能力。 …

【多线程】线程池Future和FutureTask

【多线程】线程池Future和FutureTask 【一】Future概述【1】Future的出现原因【2】Future结构图 【二】Future详解【1】Future接口源码【2】Future的5个方法【3】ThreadPoolExecutor提供了三个方法&#xff0c;来获取返回值&#xff08;1&#xff09;submit(Runnable r)&#x…

【ARM 裸机】汇编 led 驱动之原理分析

1、我们为什么要学习汇编&#xff1f;&#xff1f;&#xff1f; 之前我们或许接触过 STM32 以及其他的 32 位的 MCU ,都是基于 C 语言环境进行编程的&#xff0c;都没怎么注意汇编&#xff0c;是因为 ST 公司早已将启动文件写好了&#xff0c;新建一个 STM32 工程的时候&#…

回归预测 | Matlab实现SSA-GRNN麻雀算法优化广义回归神经网络多变量回归预测(含优化前后预测可视化)

回归预测 | Matlab实现SSA-GRNN麻雀算法优化广义回归神经网络多变量回归预测(含优化前后预测可视化) 目录 回归预测 | Matlab实现SSA-GRNN麻雀算法优化广义回归神经网络多变量回归预测(含优化前后预测可视化)预测效果基本介绍程序设计参考资料预测效果

图像生成:Pytorch实现一个简单的对抗生成网络模型

图像生成&#xff1a;Pytorch实现一个简单的对抗生成网络模型 前言相关介绍具体步骤准备并读取数据集定义生成器定义判别器定义损失函数定义优化器开始训练完整代码 训练生成的图片 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评改正。更多精彩内容&…

实战Java高并发程序设计课

课程介绍 实战Java高并发程序设计课是一门针对Java开发者的培训课程&#xff0c;重点关注如何设计和优化高并发的程序。学员将学习到并发编程的基本概念、线程池的使用、锁机制、并发集合等技术&#xff0c;并通过实际案例进行实践操作。这门课程旨在帮助开发者掌握并发编程的…

最祥解决python 将Dataframe格式数据上传数据库所碰到的问题

碰到的问题 上传Datafrane格式的数据到数据库 会碰见很多错误 举几个很普遍遇到的问题(主要以SqlServer举例) 这里解释下 将截断字符串或二进制数据 这个是字符长度超过数据库设置的长度 然后还有字符转int失败 或者字符串转换日期/或时间失败 这个是碰到的需要解决的最多的问…