Netty高性能数据结构
文章目录
- Netty高性能数据结构
- FastThreadLocal
- HashedWheelTimer时间轮
- Mpsc无锁队列
Netty高性能数据结构
Netty 用高性能数据结构的主要目的是为了提高网络通信的效率和系统的整体性能。
所谓的高性能数据结构是指,那些在特定场景下优化了性能和效率的数据结构,通常能够提供更快的操作速度、更低的内存消耗或更高的并发处理能力。
| 数据结构 | 原因 | 好处 |
|---|---|---|
| FastThreadLocal | ThreadLocal 在高并发场景中性能较低且容易造成内存泄漏。 | 更快的访问速度,优化线程本地存储;避免内存泄漏,提供更安全的内存管理。 |
| HashedWheelTimer | 传统定时任务调度效率低,在高负载环境中成为性能瓶颈。 | 高效调度,时间轮算法优化任务组织;减少资源消耗,通过批量处理任务。 |
| MpscLinkedQueue | 传统队列实现性能瓶颈,需要无锁队列支持多个生产者。 | 提高并发性,支持多个线程同时添加任务;高效任务处理,减少锁竞争。 |
FastThreadLocal
FastThreadLocal是Netty针对高并发场景优化的线程本地存储解决方案,通过减少内存占用、锁竞争和性能开销,提供了比ThreadLocal更高效的线程本地存储能力。
在Netty中,FastThreadLocal被广泛使用于处理网络事件和任务调度。主要包括:
EventLoop:Netty的EventLoop组件使用FastThreadLocal来存储和管理线程本地的数据,如Channel处理上下文、任务调度信息等。- 线程池管理:
FastThreadLocal用于优化线程池中的线程本地存储,来提高任务处理的效率。
FastThreadLocal对比ThreadLocal优势主要在快速的定位数据和更高的安全性。FastThreadLocal的设计是为了在高性能需求场景下提供优化的性能表现。
快速定位数据具体体现在,当调用ThreadLocal.set()添加Entry对象时,ThreadLocal是使用线性探测法解决Hash冲突的。线性探测法是一种用于解决哈希表中冲突的开放地址法。它的基本思路是在哈希表中发生冲突时,线性地探测哈希表中的下一个位置来寻找空闲的存储位置。当两个或更多的元素被哈希到相同的槽位时,会发生冲突。当发生冲突时,探测下一个位置。如果下一个位置也被占用,则继续探测下一个位置,直到找到一个空闲的位置。
为了便于理解,我们采用一组简单的数据模拟ThreadLocal.set()的过程是如何解决Hash冲突的。
threadLocalHashCode = 4,threadLocalHashCode & 15 = 4;此时数据应该放在数组下标为4的位置。下标4的位置正好没有数据,可以存放。threadLocalHashCode = 19,threadLocalHashCode & 15 = 4;但是下标4的位置已经有数据了,如果当前需要添加的Entry与下标4位置已存在的Entry两者的key相同,那么该位置Entry的value将被覆盖为新的值。我们假设key都是不相同的,所以此时需要向后移动一位,下标5的位置没有冲突,可以存放。threadLocalHashCode = 33,threadLocalHashCode & 15 = 3;下标3的位置已经有数据,向后移一位,下标4位置还是有数据,继续向后查找,发现下标6没有数据,可以存放。
ThreadLocal.get()的过程也是类似的,也是根据threadLocalHashCode的值定位到数组下标,然后判断当前位置Entry对象与待查询Entry对象的key是否相同,如果不同,继续向下查找。由此可见,ThreadLocal.set()/get()方法在数据密集时很容易出现Hash冲突,需要O(n)时间复杂度解决冲突问题,效率较低。
FastThreadLocal在定位数据的时候可以直接根据数组下标index获取,时间复杂度O(1)。此外,FastThreadLocal相比ThreadLocal数据扩容更加简单高效,FastThreadLocal以index为基准向上取整到2的次幂作为扩容后容量,然后把原数据拷贝到新数组。而ThreadLocal由于采用的哈希表,所以在扩容后需要再做一轮rehash。
安全性体现在,ThreadLocal使用不当可能造成内存泄漏,只能等待线程销毁。在使用线程池的场景下,ThreadLocal只能通过主动检测的方式防止内存泄漏,从而造成了一定的开销。然而FastThreadLocal不仅提供了remove()主动清除对象的方法,而且在线程池场景中Netty还封装了FastThreadLocalRunnable,FastThreadLocalRunnable最后会执行FastThreadLocal.removeAll()将Set集合中所有FastThreadLocal对象都清理掉。
FastThreadLocal使用示例:
public class FastThreadLocalExample {private static final FastThreadLocal<String> threadLocal = new FastThreadLocal<String>() {@Overrideprotected String initialValue() {return "Initial Value";}};public static void main(String[] args) {// 创建并启动两个线程FastThreadLocalThread thread1 = new FastThreadLocalThread(() -> {String value = threadLocal.get();System.out.println("Thread1 initial value: " + value);threadLocal.set("Thread1 Value");System.out.println("Thread1 new value: " + threadLocal.get());});FastThreadLocalThread thread2 = new FastThreadLocalThread(() -> {String value = threadLocal.get();System.out.println("Thread2 initial value: " + value);threadLocal.set("Thread2 Value");System.out.println("Thread2 new value: " + threadLocal.get());});thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}}
}
HashedWheelTimer时间轮
为了实现高性能的定时任务调度,Netty引入了时间轮算法驱动定时任务的执行。它通过将定时任务分配到轮盘上的不同槽位来避免高频率的任务调度开销,这种设计主要用于处理大量的定时任务。
在Netty中,HashedWheelTimer主要用于处理任务的延迟执行,例如定时任务、超时处理等。HashedWheelTimer是一种基于时间轮算法的定时任务调度器,主要用于高效地管理和执行大量定时任务。时间轮算法将时间分割成一个个固定大小的时间片,并将任务安排到相应的时间片上,从而优化了定时任务的管理。
时间轮可以理解为一种环形结构,像钟表一样被分为多个slot槽位。每个slot代表一个时间段,每个slot中可以存放多个任务,使用的是链表结构保存该时间段到期的所有任务。时间轮通过一个时针随着时间一个个slot转动,并执行slot中的所有到期任务。
时间轮有点类似HashMap,如果多个任务如果对应同一个slot,处理冲突的方法采用的是拉链法。在任务数量比较多的场景下,适当增加时间轮的slot数量,可以减少时针转动时遍历的任务个数。时间轮定时器最大的优势就是,任务的新增和取消都是O(1)时间复杂度,而且只需要一个线程就可以驱动时间轮进行工作。
时间轮是以时间作为刻度组成的一个环形队列,所以叫做时间轮。这个环形队列采用数组来实现HashedWheelBucket[],数组的每个元素称为槽,每个槽可以存放一个定时任务列表,叫HashedWheelBucket,它是一个双向链表,链表的每个节点表示一个定时任务项(HashedWheelTimeout),其中封装了真正的定时任务TimerTask。
public HashedWheelTimer(ThreadFactory threadFactory,long tickDuration, TimeUnit unit, int ticksPerWheel, boolean leakDetection,long maxPendingTimeouts) {// 省略其他代码wheel = createWheel(ticksPerWheel); // 创建时间轮的环形数组结构mask = wheel.length - 1; // 用于快速取模的掩码long duration = unit.toNanos(tickDuration); // 转换成纳秒处理// 省略其他代码workerThread = threadFactory.newThread(worker); // 创建工作线程leak = leakDetection || !workerThread.isDaemon() ? leakDetector.track(this) : null; // 是否开启内存泄漏检测this.maxPendingTimeouts = maxPendingTimeouts; // 最大允许等待任务数,HashedWheelTimer 中任务超出该阈值时会抛出异常// 如果 HashedWheelTimer 的实例数超过 64,会打印错误日志if (INSTANCE_COUNTER.incrementAndGet() > INSTANCE_COUNT_LIMIT &&WARNED_TOO_MANY_INSTANCES.compareAndSet(false, true)) {reportTooManyInstances();}
}
HashedWheelTimer的核心组件:
HashedWheelTimeout,任务的封装类,包含任务的到期时间、需要经历的圈数remainingRounds等属性。HashedWheelBucket,相当于时间轮的每个slot,内部采用双向链表保存了当前需要执行的HashedWheelTimeout列表。Worker,HashedWheelTimer的核心工作引擎,负责处理定时任务。
HashedWheelTimer的工作流程:
- 新增任务:通过当前时间和任务的延迟时间计算出任务的到期时间。根据任务的到期时间,计算出任务应放置在时间轮的哪个槽位。这个计算基于当前时间与到期时间的差值,经过一定的模运算。将任务封装成
HashedWheelTimeout对象,并插入到对应的HashedWheelBucket中。public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {// 计算任务的到期时间long deadline = System.nanoTime() + unit.toNanos(delay);// 计算任务应插入的槽位int tick = (int) ((deadline / tickDurationNanos) & mask);// 创建 HashedWheelTimeout 对象HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline, tick);// 将任务添加到相应的 HashedWheelBucket 中wheel[tick].add(timeout);return timeout; } - 执行任务:
HashedWheelTimer使用Worker线程来周期性地推进时间轮。Worker线程每隔一个刻度时间推进时间轮一个槽位。每次推进时,它检查当前槽位中的所有任务。对于到期的任务,Worker线程将其从槽位中移除并执行。任务执行后,HashedWheelTimeout对象会被标记为已完成,执行相关的回调方法。private final class Worker implements Runnable {@Overridepublic void run() {while (workerState == WORKER_STATE_STARTED) {try {Thread.sleep(tickDuration);advanceClock(); // 推进时间轮processTimeouts(); // 执行到期任务} catch (InterruptedException e) {// 处理异常}}}private void advanceClock() {// 更新当前时间轮的槽位}private void processTimeouts() {// 遍历当前槽位,执行到期任务} } - 取消任务:如果需要取消一个任务,可以调用
Timeout对象的cancel方法。取消任务会从槽位中移除相应的HashedWheelTimeout对象,确保它不会被执行。public boolean cancel() {boolean removed = bucket.remove(this); // 从槽位中移除if (removed) {// 执行取消回调等操作}return removed; } - 停止定时器:当需要停止
HashedWheelTimer时,stop方法会被调用。使用CAS操作将Worker线程的状态更新为SHUTDOWN,确保不会再有新的任务被添加。中断Worker线程并等待其停止。处理线程中断异常,确保线程被完全停止。关闭内存泄漏检测器并减少实例计数,返回所有未处理的任务。
public Set<Timeout> stop() {// 如果当前线程是 workerThread,则抛出异常if (Thread.currentThread() == workerThread) {throw new IllegalStateException(HashedWheelTimer.class.getSimpleName() +".stop() cannot be called from " +TimerTask.class.getSimpleName());}// 尝试通过 CAS 操作将工作线程的状态更新为 SHUTDOWNif (!WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_STARTED, WORKER_STATE_SHUTDOWN)) {if (WORKER_STATE_UPDATER.getAndSet(this, WORKER_STATE_SHUTDOWN) != WORKER_STATE_SHUTDOWN) {INSTANCE_COUNTER.decrementAndGet();if (leak != null) {boolean closed = leak.close(this);assert closed;}return Collections.emptySet();}}try {boolean interrupted = false;// 中断 workerThread 并等待其停止while (workerThread.isAlive()) {workerThread.interrupt(); // 中断工作线程try {workerThread.join(100); // 等待工作线程停止} catch (InterruptedException ignored) {interrupted = true;}}if (interrupted) {Thread.currentThread().interrupt();}} finally {INSTANCE_COUNTER.decrementAndGet(); // 减少实例计数if (leak != null) {boolean closed = leak.close(this);assert closed;}}return worker.unprocessedTimeouts(); // 返回未处理的任务
}
以下是HashedWheelTimer使用示例,展示了如何创建定时器、添加定时任务、执行任务以及停止定时器:
public class HashedWheelTimerExample {public static void main(String[] args) {// 创建 HashedWheelTimer 实例,刻度为1秒,时间轮槽的数量为512HashedWheelTimer timer = new HashedWheelTimer(1, TimeUnit.SECONDS, 512);// 定义一个定时任务TimerTask task = new TimerTask() {@Overridepublic void run(Timeout timeout) {System.out.println("定时任务执行时间: " + System.currentTimeMillis());}};// 添加定时任务到时间轮,设置任务延迟5秒执行timer.newTimeout(task, 5, TimeUnit.SECONDS);// 等待10秒,让定时任务有机会执行try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}// 停止时间轮定时器,并返回未处理的定时任务数量Set<Timeout> unprocessedTimeouts = timer.stop();System.out.println("未处理的定时任务数量: " + unprocessedTimeouts.size());}
}
Mpsc无锁队列
Mpsc的全称是Multi Producer Single Consumer,多生产者单消费者。Mpsc Queue可以保证多个生产者同时访问队列是线程安全的,而且同一时刻只允许一个消费者从队列中读取数据。Netty Reactor线程中任务队列必须满足多个生产者可以同时提交任务,所以Mpsc Queue非常适合Netty Reactor线程模型。
在Netty中,EventLoopGroup负责管理和调度处理网络事件的线程。EventLoopGroup的实现使用了无锁队列来维护待处理的任务和事件。具体来说是SingleThreadEventLoop和NioEventLoop这两个类使用无锁队列来存储任务和事件。这些队列是Mpsc类型的无锁队列,允许多个线程同时提交任务,而由单个事件循环线程处理这些任务。无锁队列的使用有效地提高了并发性能,减少了锁竞争的开销,使得事件处理更加高效。
在传统的锁机制下,当多个线程尝试同时访问共享数据结构时,会产生竞争,从而导致性能下降。使用无锁队列可以避免使用锁,减少线程间的竞争和上下文切换,提高并发性能。它的核心在于利用原子操作来保证线程安全。原子操作是指在执行过程中不会被其他线程打断的操作,保证数据的一致性。通常实现是使用链表结构,其中每个节点包含数据和指向下一个节点的引用。队列的头节点和尾节点通过原子操作来维护,插入和删除操作通过调整节点的链接来完成。
public class MpscQueue<E> {private final Node<E> head;private final AtomicReference<Node<E>> tail;public MpscQueue() {head = new Node<>(null);tail = new AtomicReference<>(head);}public void offer(E item) {Node<E> newTail = new Node<>(item);Node<E> oldTail = tail.getAndSet(newTail);oldTail.next = newTail;}public E poll() {Node<E> headNode = head.next;if (headNode == null) {return null; // 队列为空}head.next = headNode.next;return headNode.item;}private static class Node<E> {private final E item;private Node<E> next;Node(E item) {this.item = item;}}
}
相关文章:
Netty高性能数据结构
文章目录 Netty高性能数据结构FastThreadLocalHashedWheelTimer时间轮Mpsc无锁队列 Netty高性能数据结构 Netty 用高性能数据结构的主要目的是为了提高网络通信的效率和系统的整体性能。 所谓的高性能数据结构是指,那些在特定场景下优化了性能和效率的数据结构&am…...
关于百度、微软语音合成的实现案例
关键词 自助机产品、排队呼叫功能、网络喇叭、百度语音合成SDK、微软TTS 阅读建议 对自助机产品功能扩展感兴趣的读者、需要实现远程语音呼叫功能的开发者、想要了解网络喇叭选型及其使用的技术人员、对百度语音合成SDK和微软TTS感兴趣的开发者 阅读时长 预计阅读时长…...
二叉树:镜像树,子结构,二叉树转链表,二叉树的倒数K个数,对称,Z型打印
1.把一棵二叉树转换为它的镜像树。 void mirror_tree(TreeNode *root) {if(rootNULL) return ;TreeNode *temproot->right;root->rightroot->left;root->lefttemp;mirror_tree(root->right);mirror_tree(root->left);}2、输入两棵二叉树A,B&…...
瑞秋,詹妮弗·安妮斯顿多年来与本·阿弗莱克保持着“调情”友谊 又一个詹妮弗
尽管所有迹象都表明本阿弗莱克和詹妮弗洛佩兹的婚姻即将走向离婚,但他尚未公开评论此事。不过,好莱坞圈内人士已经纷纷将他与另一位名人联系起来。事实上,是另一位詹妮弗。 一位消息人士向媒体透露,詹妮弗安妮斯顿和阿弗莱克一直都很有默契——无论是在银幕上还是在银幕外…...
指纹失效,忘记iPhone屏幕解锁密码怎么应对?
为保证手机的安全及隐私,我们会给手机设置屏幕锁屏密码,通过输入设置密码来解锁手机屏幕锁,但为了给大家提供快速便捷的解锁方式,苹果公司提供了指纹解锁,不仅解锁更便捷了还极大地增强了设备的安全性。但有时我们手指…...
09.XSS跨站脚本攻击(超详细!!!)
1、什么是XSS XSS(跨站脚本攻击):攻击者利用这个漏洞将恶意脚本注入到网页中,当其它用户浏览这些页面时,恶意脚本会在用户的浏览器中执行。XSS攻击允许攻击者在用户的浏览器上执行脚本,从而可能获取用户的…...
讲解人工智能在现代科技中的应用和未来发展趋势-水文
人工智能(Artificial Intelligence,简称AI)是一种模拟人类智能的科技领域,它通过计算机模拟人类的思维、学习、推理和决策能力,以便解决复杂的问题。近年来,人工智能技术的发展取得了惊人的进展,…...
2.2 QT 环境配置
2.2 QT环境配置 QT是一个1991年由QT Company开发的跨平台C图形用户界面应用程序开发框架。它既可以开发GUI程序,也可以用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器&…...
2.类和对象(上)
1. 类的定义 1.1 类定义格式 • class为定义类的关键字,Stack为类的名字,{ }中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量; (类和结构体非常像&#…...
【实际案例】服务器宕机情况分析及处理建议
了解银河麒麟操作系统更多全新产品,请点击访问麒麟软件产品专区:https://product.kylinos.cn 服务器环境以及配置 物理机/虚拟机/云/容器 物理机 外网/私有网络/无网络 私有网络 处理器: Kunpeng 920 内存: 4 TiB BIOS版…...
Linux系统之ncdu命令的基本使用
Linux系统之ncdu命令的基本使用 一、ncdu命令命令介绍1.1 ncdu简介1.2 ncdu特点 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、检查本地环境3.1 检查本地操作系统版本3.2 检查系统内核版本3.3 检查系统镜像源3.4 更新软件列表 四、安装ncdu工具4.1 安装ncdu软件4.2 n…...
STM32L051K8U6-HAL-LED闪烁设计
HAL三步法: 1、配置下载线 2、配置晶振 3、配置时钟 注意:中断优先级(这里防止HAL_Delay卡死,详细请看 http://t.csdnimg.cn/NQhQV) 4、 配置灯引脚属性为输出模式。并设置标签为LED 生成代码:编写while里…...
记一次远程API调用失败
记一次远程API调用失败 最近开发忙,项目紧,系统出现一些忽隐忽现的问题,本地也不能复现,当时也无法理解,就先搁置了,现在回想起来,这里还是明智的。 这个bug很神奇 今天,原本好好的…...
【力扣】746.使用最小花费爬楼梯
题目描述 给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 请你计算并返回达到楼梯顶部的最低花费。 示例…...
06:【stm32】中断一:NVIC的配置
中断 1、中断的简介1.1、什么是中断1.2、为什么需要中断 2、中断的优先级2.1、中断优先级的表示方法 3、NVIC3.1、什么的NVIC3.2、NVIC的内部结构3.3、中断向量表3.4、程序实现①开启中断源②配置NVIC③中断响应函数 1、中断的简介 1.1、什么是中断 正在进行的事务被突发事件打…...
Flutter简介
Flutter是一个由Google开发的开源移动UI框架,它允许开发者使用Dart语言来构建高性能、高保真的iOS和Android应用。Flutter的设计理念是"编写一次,到处运行"(write once, run everywhere),这意味着开发者可以…...
WT2605C蓝牙语音芯片赋能对讲机新体验:无屏操控、音频解码与蓝牙音箱三合一
一、产品概况 对讲机市场是一个技术成熟且具有广泛应用前景的市场。对讲机作为无线通信设备的一种,在许多不同的领域和业务中发挥着重要作用。从技术发展角度来看,对讲机经历了从模拟到数字的转型,以及从简单通信工具向多功能设备的演进。当…...
ctfshow-web入门-sql注入(web191-web195)
目录 1、web191 2、web192 3、web193 4、web194 5、web195 1、web191 过滤了 ascii 使用 ord 代替: import requests import string url "http://a585c278-320a-40e7-841f-109b1e394caa.challenge.ctf.show/api/index.php" out for j in range(1…...
【ARM】v8架构programmer guide(3)_ARMv8的寄存器
目录 4.ARMv8 registers 4.1 AArch64 特殊寄存器 4.1.1 Zero register 4.1.2 Stack pointer (SP) 4.1.3 Program Counter (PC) 4.1.4 Exception Link Register(ELR) 4.1.5 Saved Process Status Register (SPSR) 4.2 Proc…...
SpringIOC整合dbUtil做的增删改查以及转账业务的实现
目录 一、xml方式实现 1.介绍lombok插件 2.功能 3.步骤 3.1 idea安装插件(只做一次) 3.2 添加坐标 3.3 编写注解 4.核心类 4.1 QueryRunner 4.2 query() 查询 4.3 update() 增删改 5.配置文件applicationContext.xml 6.junit测试 6.1使用步骤 6.1.1 坐标 6.1.2…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
MySQL的pymysql操作
本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...
TCP/IP 网络编程 | 服务端 客户端的封装
设计模式 文章目录 设计模式一、socket.h 接口(interface)二、socket.cpp 实现(implementation)三、server.cpp 使用封装(main 函数)四、client.cpp 使用封装(main 函数)五、退出方法…...
