游戏服务器中的Netty应用以及源码剖析

目录
  • 一、Reactor模式和Netty线程模型
    • 1. BIO模型
    • 2. NIO模型
    • 3. Reacor模型
      • ①. 单Reacor单线程模型
      • ②. 单Reactor多线程模型
      • ③. 主从Reactor多线程模型
      • ④. 部分源码分析
  • 二、select/poll和epoll
    • 1.概念
    • 2.jdk提供selector
    • 3.Netty提供的Epoll封装
    • 4.Netty相关类图
    • 5.配置Netty为EpollEventLoop
  • 三、Netty相关参数
    • 1.SO_KEEPALIVE
    • 2.SO_REUSEADDR
    • 3.TCP_NODELAY
    • 4.SO_BACKLOG
    • 5.ALLOCATOR和RCVBUF_ALLOCATOR
  • 四、Netty关闭

一、Reactor模式和Netty线程模型

最近因为工作需要,学习了一段时间Netty的源码,并做了一个简单的分享,研究还不是特别深入,继续努力。因为分享也不涉及公司业务,所以这里也把这次对源码的研究成果分享出来 以下都是在游戏服务器开发中针对Netty使用需要了解知识点以及相关优化

这次分享主要设计以下内容

  • Netty线程模型
  • Netty对TCP相关参数的配置和具体含义
  • Netty对Epoll的封装
  • Netty的优雅关闭

客户端连接数的限制

  • 内存资源
  • CPU资源

端口号资源

cat /proc/sys/net/ipv4/ip_local_port_range

文件描述符资源

  • 系统级:当前系统可打开的最大数量,通过 cat /proc/sys/fs/file-max 查看
  • 用户级:指定用户可打开的最大数量,通过 cat /etc/security/limits.conf 查看
  • 进程级:单个进程可打开的最大数量,通过 cat /proc/sys/fs/nr_open 查看
  • 线程资源 BIO/NIO

1. BIO模型

  • 所有操作都是同步阻塞(accept,read)
  • 客户端连接数与服务器线程数比例是1:1

2. NIO模型

  • 非阻塞IO
  • 通过selector实现可以一个线程管理多个连接
  • 通过selector的事件注册(OP_READ/OP_WRITE/OP_CONNECT/OP_ACCEPT),处理自己感兴趣的事件

客户端连接数与服务器线程数比例是n:1

3. Reacor模型

①. 单Reacor单线程模型

所有IO在同一个NIO线程完成(处理连接,分派请求,编码,解码,逻辑运算,发送)

优点

  • 编码简单
  • 不存在共享资源竞争
  • 并发安全

缺点

  • 单线程处理大量链路时,性能无法支撑,不能合理利用多核处理
  • 线程过载后,处理速度变慢,会导致消息积压
  • 一旦线程挂掉,整个通信层不可用 redis使用的就是reactor单进程模型,redis由于都是内存级操作,所以使用此模式没什么问题

reactor单线程模型图

netty reactor单线程模型图

Netty对应实现方式

// Netty对应实现方式:创建io线程组是,boss和worker,使用同一个线程组,并且线程数为1
EventLoopGroup ioGroup = new NioEventLoopGroup(1);
b.group(ioGroup, ioGroup)
        .channel(NioServerSocketChannel.class)
        .childHandler(initializer);
ChannelFuture f = b.bind(portNumner);
cf = f.sync();
f.get();

②. 单Reactor多线程模型

根据单线程模型,io处理中最耗时的编码,解码,逻辑运算等cpu消耗较多的部分,可提取出来使用多线程实现,并充分利用多核cpu的优势

优点

多线程处理逻辑运算,提高多核CPU利用率

缺点

对于单Reactor来说,大量链接的IO事件处理依然是性能瓶颈

reactor多线程模型图

netty reactor多线程模型图

Netty对应实现方式

// Netty对应实现方式:创建io线程组是,boss和worker,使用同一个线程组,并且线程数为1,把逻辑运算部分投递到用户自定义线程处理
EventLoopGroup ioGroup = new NioEventLoopGroup(1);
b.group(ioGroup, ioGroup)
        .channel(NioServerSocketChannel.class)
        .childHandler(initializer);
ChannelFuture f = b.bind(portNumner);
cf = f.sync();
f.get();

③. 主从Reactor多线程模型

根据多线程模型,可把它的性能瓶颈做进一步优化,即把reactor由单个改为reactor线程池,把原来的reactor分为mainReactor和subReactor

优点

  • 解决单Reactor的性能瓶颈问题(Netty/Nginx采用这种设计)

reactor主从多线程模型图

netty reactor主从多线程模型图

Netty对应实现方式

// Netty对应实现方式:创建io线程组boss和worker,boss线程数为1,work线程数为cpu*2(一般IO密集可设置为2倍cpu核数)
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
b.group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        .childHandler(initializer);
ChannelFuture f = b.bind(portNumner);
cf = f.sync();
f.get();

④. 部分源码分析

  • 创建group实例
// 1.构造参数不传或传0,默认取系统参数配置,没有参数配置,取CPU核数*2
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
private static final int DEFAULT_EVENT_LOOP_THREADS;
static {
    DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
            "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
}
// 2.不同版本的JDK会有不同版本的SelectorProvider实现,Windows下的是WindowsSelectorProvider
public NioEventLoopGroup(int nThreads, Executor executor) {
    //默认selector,最终实现类似:https://github.com/frohoff/jdk8u-jdk/blob/master/src/macosx/classes/sun/nio/ch/DefaultSelectorProvider.java
    //basic flow: 1 java.nio.channels.spi.SelectorProvider 2 META-INF/services 3 default
    this(nThreads, executor, SelectorProvider.provider());
}
// 3.创建nThread个EventExecutor,并封装到选择器chooser,chooser会根据线程数分别有两种实现(GenericEventExecutorChooser和PowerOfTwoEventExecutorChooser,算法不同,但实现逻辑一样,就是均匀的分配线程处理)
EventExecutorChooserFactory.EventExecutorChooser chooser;
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
    // ...
    children[i] = newChild(executor, args);
    // ...
}
chooser = chooserFactory.newChooser(children);
  • 设置group
// 两种方式设置group
// parent和child使用同一个group,调用仍然是分别设置parent和child
@Override
public ServerBootstrap group(EventLoopGroup group) {
    return group(group, group);
}
ServerBootstrap.group(EventLoopGroup parentGroup, EventLoopGroup childGroup){
    // 具体代码略,可直接参考源码
    // 里面实现内容是把parentGroup绑定到this.group,把childGroup绑定到this.childGroup
}
  • Netty启动
// 调用顺序
ServerBootstrap:bind() -> doBind() -> initAndRegister()
private ChannelFuture doBind(final SocketAddress localAddress) {
    final ChannelFuture regFuture = initAndRegister();
    // ...
    doBind0(regFuture, channel, localAddress, promise);
    // ...
}
final ChannelFuture initAndRegister() {
    // 创建ServerSocketChannel
    Channel channel = channelFactory.newChannel();
    // ...
    // 开始register
    ChannelFuture regFuture = config().group().register(channel);
    // register调用顺序
    // next().register(channel) -> (EventLoop) super.next() -> chooser.next()
    // ...
}

由以上源码可得知,bind只在起服调用一次,因此bossGroup仅调用一次regist,也就是仅调用一次next,因此只有一根线程是有用的,其余线程都是废弃的,所以bossGroup线程数设置为1即可

// 启动BossGroup线程并绑定本地SocketAddress
private static void doBind0(
        final ChannelFuture regFuture, final Channel channel,
        final SocketAddress localAddress, final ChannelPromise promise) {
    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            if (regFuture.isSuccess()) {
                channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            } else {
                promise.setFailure(regFuture.cause());
            }
        }
    });
}
  • 客户端连接
// 消息事件读取
NioEventLoop.run() -> processSelectedKeys() -> ... -> ServerBootstrapAcceptor.channelRead
// ServerBootstrapAcceptor.channelRead处理客户端连接事件
// 最后一行的childGroup.register的逻辑和上面的代码调用处一样
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    child.pipeline().addLast(childHandler);
    setChannelOptions(child, childOptions, logger);
    setAttributes(child, childAttrs);
    childGroup.register(child)
}

二、select/poll和epoll

1.概念

  • select(时间复杂度O(n)):用一个fd数组保存所有的socket,然后通过死循环遍历调用操作系统的select方法找到就绪的fd
while(1) {
  nready = select(list);
  // 用户层依然要遍历,只不过少了很多无效的系统调用
  for(fd <-- fdlist) {
    if(fd != -1) {
      // 只读已就绪的文件描述符
      read(fd, buf);
      // 总共只有 nready 个已就绪描述符,不用过多遍历
      if(--nready == 0) break;
    }
  }
}

poll(时间复杂度O(n)):同select,不过把fd数组换成了fd链表,去掉了fd最大连接数(1024个)的数量限制

epoll(时间复杂度O(1)):解决了select/poll的几个缺陷

  • 调用需传入整个fd数组或fd链表,需要拷贝数据到内核
  • 内核层需要遍历检查文件描述符的就绪状态
  • 内核仅返回可读文件描述符个数,用户仍需自己遍历所有fd

epoll是操作系统基于事件关联fd,做了以下优化:

  • 内核中保存一份文件描述符集合,无需用户每次都重新传入,只需告诉内核修改的部分即可。(epoll_ctl)
  • 内核不再通过轮询的方式找到就绪的文件描述符,而是通过异步 IO 事件唤醒。(epoll_wait)
  • 内核仅会将有 IO 事件的文件描述符返回给用户,用户也无需遍历整个文件描述符集合。

epoll仅在Linux系统上支持

2.jdk提供selector

// DefaultSelectorProvider.create方法在不同版本的jdk下有不同实现,创建不同Selector
// Windows版本的jdk,其实现中调用的是native的poll方法
public static SelectorProvider create() {
    return new WindowsSelectorProvider();
}
// Linux版本的jdk
public static SelectorProvider create() {
    String str = (String)AccessController.doPrivileged(new GetPropertyAction("os.name"));
    if (str.equals("SunOS")) {
        return createProvider("sun.nio.ch.DevPollSelectorProvider");
    }
    if (str.equals("Linux")) {
        return createProvider("sun.nio.ch.EPollSelectorProvider");
    }
    return new PollSelectorProvider();
}

3.Netty提供的Epoll封装

netty依然基于epoll做了一层封装,主要做了以下事情:

(1)java的nio默认使用水平触发,Netty的Epoll默认使用边缘触发,且可配置

  • 边缘触发:当状态变化时才会发生io事件。
  • 水平触发:只要满足条件,就触发一个事件(只要有数据没有被获取,内核就不断通知你)

(2)Netty的Epoll提供更多的nio的可配参数。

(3)调用c代码,更少gc,更少synchronized 具体可以参考源码NioEventLoop.run和EpollEventLoop.run进行对比

4.Netty相关类图

线程组类图

channel类图

5.配置Netty为EpollEventLoop

// 创建指定的EventLoopGroup
bossGroup = new EpollEventLoopGroup(1, new DefaultThreadFactory("BOSS_LOOP"));
workerGroup = new EpollEventLoopGroup(32, new DefaultThreadFactory("IO_LOOP"));
b.group(bossGroup, workerGroup)
        // 指定channel的class
        .channel(EpollServerSocketChannel.class)
        .childHandler(initializer);
// 其中channel(clz)方法是通过class来new一个反射ServerSocketChannel创建工厂类
public B channel(Class<? extends C> channelClass) {
    if (channelClass == null) {
        throw new NullPointerException("channelClass");
    }
    return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
final ChannelFuture initAndRegister() {
    // ...
    Channel channel = channelFactory.newChannel();
    // ...
}

三、Netty相关参数

1.SO_KEEPALIVE

childOption(ChannelOption.SO_KEEPALIVE, true)

TCP链路探活

2.SO_REUSEADDR

option(ChannelOption.SO_REUSEADDR, true)

重用处于TIME_WAIT但是未完全关闭的socket地址,让端口释放后可立即被重用。默认关闭,需要手动开启

3.TCP_NODELAY

childOption(ChannelOption.TCP_NODELAY, true)

IP报文格式

TCP报文格式

开启则禁用TCP Negal算法,优点低延时,缺点在大量小数据包的情况下,网络利用率低

关闭则开启TCP Negal算法,优点提高网络利用率(数据缓存到一定量才发送),缺点延时高

Negal算法

  • 如果包长度达到MSS(maximum segment size最大分段长度),则允许发送;
  • 如果该包含有FIN,则允许发送;
  • 设置了TCP_NODELAY选项,则允许发送;
  • 未设置TCP_CORK选项(是否阻塞不完整报文)时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送;
  • 上述条件都未满足,但发生了超时(一般为200ms),则立即发送。

MSS计算规则 MSS的值是在TCP三次握手建立连接的过程中,经通信双方协商确定的 802.3标准里,规定了一个以太帧的数据部分(Payload)的最大长度是1500个字节(MTU)

MSS = MTU - IP首部 - TCP首部
以太网环境下:
  MTU = 1500字节
IP首部 = 32*5/4 = 160bit = 20字节
TCP首部 = 32*5/4 = 160bit = 20字节
最终得出MSS = 1460字节

结论:因为游戏服务器的实时性要求,在网络带宽足够的情况下,建议开启TCP_NODELAY,关闭Negal算法,带宽可以浪费,响应必须及时

注意:需要客户端服务器均关闭Negal算法,否则仍然会有延迟发送,影响传输速度

4.SO_BACKLOG

option(ChannelOption.SO_BACKLOG, 100)

操作系统内核中维护的两个队列

  • syns queue:保存syn到达,但没完成三次握手的半连接
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
  • accpet queue:保存完成三次握手,内核等待accept调用的连接
cat /proc/sys/net/core/somaxconn

netty对于backlog的默认值设置在NetUtil类253行

SOMAXCONN = AccessController.doPrivileged(new PrivilegedAction<Integer>() {
    @Override
    public Integer run() {
        // 1.设置默认值
        int somaxconn = PlatformDependent.isWindows() ? 200 : 128;
        File file = new File("/proc/sys/net/core/somaxconn");
        if (file.exists()) {
            // 2.文件存在,读取操作系统配置
            in = new BufferedReader(new FileReader(file));
            somaxconn = Integer.parseInt(in.readLine());
        } else {
            // 3.文件不存在,从各个参数中读取
            if (SystemPropertyUtil.getBoolean("io.netty.net.somaxconn.trySysctl", false)) {
                tmp = sysctlGetInt("kern.ipc.somaxconn");
                if (tmp == null) {
                    tmp = sysctlGetInt("kern.ipc.soacceptqueue");
                    if (tmp != null) {
                        somaxconn = tmp;
                    }
                } else {
                    somaxconn = tmp;
                }
            }
        }
    }
}

结论:

Linux下/proc/sys/net/core/somaxconn一定存在,所以backlog一定取得它的值,我参考prod机器的参数配置的65535,也就是不设置backlog的情况下,服务器运行缓存65535个全连接

5.ALLOCATOR和RCVBUF_ALLOCATOR

默认分配ByteBuffAllocator赋值如下: ByteBufUtil.java

static {
    //以io.netty.allocator.type为准,没有的话,安卓平台用非池化实现,其他用池化实现
    String allocType = SystemPropertyUtil.get(
            "io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
    allocType = allocType.toLowerCase(Locale.US).trim();
    ByteBufAllocator alloc;
    if ("unpooled".equals(allocType)) {
        alloc = UnpooledByteBufAllocator.DEFAULT;
        logger.debug("-Dio.netty.allocator.type: {}", allocType);
    } else if ("pooled".equals(allocType)) {
        alloc = PooledByteBufAllocator.DEFAULT;
        logger.debug("-Dio.netty.allocator.type: {}", allocType);
    } else {
        //io.netty.allocator.type设置的不是"unpooled"或者"pooled",就用池化实现。
        alloc = PooledByteBufAllocator.DEFAULT;
        logger.debug("-Dio.netty.allocator.type: pooled (unknown: {})", allocType);
    }
    DEFAULT_ALLOCATOR = alloc;
}

RCVBUF_ALLOCATOR默认AdaptiveRecvByteBufAllocator

public class DefaultChannelConfig implements ChannelConfig {
    // ...
    public DefaultChannelConfig(Channel channel) {
        this(channel, new AdaptiveRecvByteBufAllocator());
    }
    // ...
}

四、Netty关闭

/**
 * Shortcut method for {@link #shutdownGracefully(long, long, TimeUnit)} with sensible default values.
 *
 * @return the {@link #terminationFuture()}
 */
Future<?> shutdownGracefully();
/**
 * Signals this executor that the caller wants the executor to be shut down.  Once this method is called,
 * {@link #isShuttingDown()} starts to return {@code true}, and the executor prepares to shut itself down.
 * Unlike {@link #shutdown()}, graceful shutdown ensures that no tasks are submitted for <i>'the quiet period'</i>
 * (usually a couple seconds) before it shuts itself down.  If a task is submitted during the quiet period,
 * it is guaranteed to be accepted and the quiet period will start over.
 *
 * @param quietPeriod the quiet period as described in the documentation
                     静默期:在此期间,仍然可以提交任务
 * @param timeout     the maximum amount of time to wait until the executor is {@linkplain #shutdown()}
 *                    regardless if a task was submitted during the quiet period
                     超时时间:等待所有任务执行完的最大时间
 * @param unit        the unit of {@code quietPeriod} and {@code timeout}
 *
 * @return the {@link #terminationFuture()}
 */
Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit);
// 抽象类中的实现
static final long DEFAULT_SHUTDOWN_QUIET_PERIOD = 2;
static final long DEFAULT_SHUTDOWN_TIMEOUT = 15;
@Override
public Future<?> shutdownGracefully() {
    return shutdownGracefully(DEFAULT_SHUTDOWN_QUIET_PERIOD, DEFAULT_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS);
}
  • 把NIO线程的状态位设置成ST_SHUTTING_DOWN状态,不再处理新的消息(不允许再对外发送消息);
  • 退出前的预处理操作:把发送队列中尚未发送或者正在发送的消息发送完、把已经到期或者在退出超时之前到期的定时任务执行完成、把用户注册到NIO线程的退出Hook任务执行完成;
  • 资源的释放操作:所有Channel的释放、多路复用器的去注册和关闭、所有队列和定时任务的清空取消,最后是NIO线程的退出。

以上就是游戏服务器中的Netty应用以及源码剖析的详细内容,更多关于Netty游戏服务器的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java游戏服务器系列之Netty相关知识总结

    一.简介 Java的底层API逐渐复杂,而开发者面对的开发场景需求也在逐渐增大.如果直接针对底层API进行编程,无疑是耗时耗力的.这时就催生了极多的编程框架,这些框架隐藏了API实现的复杂细节,以最简洁的方式给开发人员提供功能的实现接口.Netty就是一款针对于网络链接的框架,他的出现让服务器开发人员更加的集中关注于更多逻辑的实现,而不为了实现更好更多更稳定的链接而头疼.Netty的核心功能基于NIO实现. 二.Netty的应用场景 几乎适用于所有的长短链接场景,由于Java应用的广泛性,几乎所

  • SpringBoot+netty-socketio实现服务器端消息推送

    首先:因为工作需要,需要对接socket.io框架对接,所以目前只能使用netty-socketio.websocket是不支持对接socket.io框架的. netty-socketio顾名思义他是一个底层基于netty'实现的socket. 在springboot项目中的集成,请看下面的代码 maven依赖 <dependency> <groupId>com.corundumstudio.socketio</groupId> <artifactId>ne

  • Springboot整合Netty实现RPC服务器的示例代码

    一.什么是RPC? RPC(Remote Procedure Call)远程过程调用,是一种进程间的通信方式,其可以做到像调用本地方法那样调用位于远程的计算机的服务.其实现的原理过程如下: 本地的进程通过接口进行本地方法调用. RPC客户端将调用的接口名.接口方法.方法参数等信息利用网络通信发送给RPC服务器. RPC服务器对请求进行解析,根据接口名.接口方法.方法参数等信息找到对应的方法实现,并进行本地方法调用,然后将方法调用结果响应给RPC客户端. 二.实现RPC需要解决那些问题? 1. 约

  • 亲自教你在netty中使用TCP协议请求DNS服务器的详细过程

    目录 简介 DNS传输协议简介 DNS的IP地址 Do53/TCP在netty中的使用 搭建DNS netty client 发送DNS查询消息 DNS查询的消息处理 总结 简介 DNS的全称domain name system,既然是一个系统就有客户端和服务器之分.一般情况来说我们并不需要感知这个DNS客户端的存在,因为我们在浏览器访问某个域名的时候,浏览器作为客户端已经实现了这个工作. 但是有时候我们没有使用浏览器,比如在netty环境中,如何构建一个DNS请求呢? DNS传输协议简介 在R

  • 如何用Netty实现高效的HTTP服务器

    1 概述 HTTP 是基于请求/响应模式的:客户端向服务器发送一个 HTTP 请求,然后服务器将会返回一个 HTTP 响应.Netty 提供了多种编码器和解码器以简化对这个协议的使用.一个HTTP 请求/响应可能由多个数据部分组成,FullHttpRequest 和FullHttpResponse 消息是特殊的子类型,分别代表了完整的请求和响应.所有类型的 HTTP 消息(FullHttpRequest.LastHttpContent 等等)都实现了 HttpObject 接口. (1) Htt

  • spring+netty服务器搭建的方法

    游戏一般是长连接,自定义协议,不用http协议,BIO,NIO,AIO这些我就不说了,自己查资料 我现在用spring+netty搭起简单的游戏服 思路:1自定义协议和协议包:2spring+netty整合:3半包粘包处理,心跳机制等:4请求分发(目前自己搞的都是单例模式) 下个是测试用的,结构如下 首先自定义包头 Header.java package com.test.netty.message; /** * Header.java * 自定义协议包头 * @author janehuang

  • 游戏服务器中的Netty应用以及源码剖析

    目录 一.Reactor模式和Netty线程模型 1. BIO模型 2. NIO模型 3. Reacor模型 ①. 单Reacor单线程模型 ②. 单Reactor多线程模型 ③. 主从Reactor多线程模型 ④. 部分源码分析 二.select/poll和epoll 1.概念 2.jdk提供selector 3.Netty提供的Epoll封装 4.Netty相关类图 5.配置Netty为EpollEventLoop 三.Netty相关参数 1.SO_KEEPALIVE 2.SO_REUSEA

  • Netty分布式获取异线程释放对象源码剖析

    目录 获取异线程释放对象 在介绍之前我们首先看Stack类中的两个属性 我们跟到pop方法中 继续跟到scavengeSome方法中 我们继续分析transfer方法 接着我们我们关注一个细节 我们跟到reclaimSpace方法 章节小结 前文传送门:异线程下回收对象 获取异线程释放对象 上一小节分析了异线程回收对象, 原理是通过与stack关联的WeakOrderQueue进行回收 如果对象经过异线程回收之后, 当前线程需要取出对象进行二次利用, 如果当前stack中为空, 则会通过当前st

  • Java 中的FileReader和FileWriter源码分析_动力节点Java学院整理

    FileReader和FileWriter源码分析 1. FileReader 源码(基于jdk1.7.40) package java.io; public class FileReader extends InputStreamReader { public FileReader(String fileName) throws FileNotFoundException { super(new FileInputStream(fil java io系列21之 InputStreamReade

  • Java中的InputStreamReader和OutputStreamWriter源码分析_动力节点Java学院整理

    InputStreamReader和OutputStreamWriter源码分析 1. InputStreamReader 源码(基于jdk1.7.40) package java.io; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import sun.nio.cs.StreamDecoder; // 将"字节输入流"转换成"字符输入流" public class

  • c# WPF中通过双击编辑DataGrid中Cell的示例(附源码)

    背景 在很多的时候我们需要编辑DataGrid中每一个Cell,编辑后保存数据,原生的WPF中的DataGrid并没有提供这样的功能,今天通过一个具体的例子来实现这一个功能,在这个例子中DataGrid中的数据类型可能是多种多样的,有枚举.浮点类型.布尔类型.DateTime类型,每一种不同的类型需要双击以后呈现不同的效果,本文通过使用Xceed.Wpf.DataGrid这个动态控件库来实现这个功能,当前使用的Dll版本是2.5.0.0,不同的版本可能实现上面有差别,这个在使用的时候需要特别注意

  • Python 写小游戏吃金币+打乒乓+滑雪(附源码)

    目录 1.吃金币 2.打乒乓 3.滑雪 1.吃金币 源码分享: import os import cfg import sys import pygame import random from modules import *     '''游戏初始化''' def initGame():     # 初始化pygame, 设置展示窗口     pygame.init()     screen = pygame.display.set_mode(cfg.SCREENSIZE)     pygam

  • 详解Golang中select的使用与源码分析

    目录 背景 select 流程 背景 golang 中主推 channel 通信.单个 channel 的通信可以通过一个goroutine往 channel 发数据,另外一个从channel取数据进行.这是阻塞的,因为要想顺利执行完这个步骤,需要 channel 准备好才行,准备好的条件如下: 1.发送 缓存有空间(如果是有缓存的 channel) 有等待接收的 goroutine 2.接收 缓存有数据(如果是有缓存的 channel) 有等待发送的 goroutine 对channel实际使

  • 深入理解Python虚拟机中复数(complex)的实现原理及源码剖析

    目录 复数数据结构 复数的操作 复数加法 复数取反 Repr 函数 总结 复数数据结构 在 cpython 当中对于复数的数据结构实现如下所示: typedef struct { double real; double imag; } Py_complex; #define PyObject_HEAD PyObject ob_base; typedef struct { PyObject_HEAD Py_complex cval; } PyComplexObject; typedef struc

  • 深入理解Python虚拟机中整型(int)的实现原理及源码剖析

    目录 数据结构 深入分析 PyLongObject 字段的语意 小整数池 整数的加法实现 总结 数据结构 在 cpython 内部的 int 类型的实现数据结构如下所示: typedef struct _longobject PyLongObject; struct _longobject { PyObject_VAR_HEAD digit ob_digit[1]; }; #define PyObject_VAR_HEAD PyVarObject ob_base; typedef struct

随机推荐