Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

Java for Web学习笔记(八八):消息和集群(3)序列化Serializable

$
0
0

对象序列化

在集群中,涉及到数据在不同的app,不同的JVM,不同的机器之间的。Java通过对象的序列化实现对象在不同app之间传递。在进一步探讨集群之前,我们需要对序列化进行了解。推荐阅读 Java对象表示方式1:序列化、反序列化的作用

对于要求序列化的对象,static和transient是不进行序列化的。我们看看下面简单的小例子,如何通过自定义的序列化和反序列化来实现transient参数的传递:

public class S1 implements Serializable{
    private static final long serialVersionUID = 1L;

    private String str0;
    private transient String str1; //transient不进行序列化,用于在自定义序列化中进行测试。
    private String str2;

    // getters and setters ... 略

    private void writeObject(java.io.ObjectOutputStream s) throws Exception{
        s.defaultWriteObject();
        //增加写入str1对象
        s.writeObject(str1);
    }

    private void readObject(java.io.ObjectInputStream s) throws Exception{
        /* Read the non-static and non-transient fields of the current class from this stream. This may only be called from the readObject method of the class being deserialized. It will throw the NotActiveException if it is called otherwise. 
        * 这里有些特别,调用defaultReadObject()必须在readObject的方法中,但此方法不作为Override。对于writeObject也是同理 */
        s.defaultReadObject();
        //增加读入str1对象
        str1 = (String) s.readObject(); 
    }
}
/** 用于测试序列化和反序列化 */
public class SerialTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        S1 s = new S1();
        s.setStr0("Hello");
        s.setStr1("my");
        s.setStr2("friend");

        File file = new File("D:" + File.separator + "weitest.txt");
        try(OutputStream os = new FileOutputStream(file);
            ObjectOutputStream oos = new ObjectOutputStream(os);){
            oos.writeObject(s);
        }

        try(InputStream is = new FileInputStream(file);
            ObjectInputStream ois = new ObjectInputStream(is);){
            S1 object = (S1) ois.readObject();
            System.out.println("str0=" + object.getStr0() + ", str1=" + object.getStr1() + ", str2=" + object.getStr2());    
        }
    }
}

ApplicationEvent的序列化

ApplicationEvent是个抽象类,继承了EventObject。我们看看这两个类的源代码

public abstract class ApplicationEvent extends EventObject {
    private static final long serialVersionUID = 7099057708183571937L;

    private final long timestamp;

    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }

    public final long getTimestamp() {
        return this.timestamp;
    }
}
public class EventObject implements java.io.Serializable {
    private static final long serialVersionUID = 5516075349620653480L;

    protected transient Object  source;

    public EventObject(Object source) {
        if (source == null)
            throw new IllegalArgumentException("null source");

        this.source = source;
    }

    public Object getSource() {
        return source;
    }

    public String toString() {
        return getClass().getName() + "[source=" + source + "]";
    }
}

可以看到ApplicationEvent含有long timestamp,transient Object source,而source是我们传递的关键信息,它是transient的,也就是不在序列化的范畴。我们希望将ApplicationEvent的对象通过消息在不同的JVM中传递,必须要将source也序列化,因此:

  1. Object source需要是Serializable source
  2. 通过自定义的序列化和反序列化来实现。
public class ClusterEvent extends ApplicationEvent implements Serializable{    
    private static final long serialVersionUID = 1L;

    //【1】由于EventObject是父类的父类,无法进行修订,增加一个可以序列化的参数Serializable serializableSource来记录source
    private final Serializable serializableSource;

    public ClusterEvent(Serializable source) {
        super(source);
        //1.1)在构造函数中将source记录至可序列化参数serializableSource中
        this.serializableSource = source;
    }

    private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException{
        in.defaultReadObject();
        //1.2)将反序列化后获得的serializableSource赋值给无法序列化的source
        this.source = this.serializableSource;
    } 
}

相关链接: 我的Professional Java for Web Applications相关文章

作者:flowingflying 发表于2017/11/10 22:00:52 原文链接
阅读:3 评论:0 查看评论

Java for Web学习笔记(八九):消息和集群(4)定制发布和订购

$
0
0

SimpleApplicationEventMulticaster

本学习一方面进一步了解Spring对publish/subcribe的支持,另一方面也是为了后面如何利用webSocket在集群中传递事件的小例子作准备。spring将发布的消息称为event,通过消息广播器发布消息并发送到响应的订阅中。消息广播器可以理解为app内部的broker。Spring提供了SimpleApplicationEventMulticaster,实现了ApplicationEventMulticaster接口,将事件广播到已经登记的listener中。先看看javadoc对SimpleApplicationEventMulticaster的说明:

Simple implementation of the ApplicationEventMulticaster interface.

Multicasts all events to all registered listeners, leaving it up to the listeners to ignore events that they are not interested in. Listeners will usually perform corresponding instanceof checks on the passed-in event object.

By default, all listeners are invoked in the calling thread. This allows the danger of a rogue listener blocking the entire application, but adds minimal overhead. Specify an alternative task executor to have listeners executed in different threads, for example from a thread pool.

参考阅读:http://www.baeldung.com/spring-events

实现异步监听事件

在上次学习中,可以很简单地通过在listener的触发方法上加入@Async,使得subscribe实现异步处理,如果事件多,需要在很多的方法上都加上异步的字样,我们可以如javadoc所说,指定一个任务executor来让listener在其他线程中执行。也就是说,我们需要给出自定义的ApplicationEventMulticaster。在Root上下文中:

//【学习1】这个Bean的名字必须是applicationEventMulticaster,如果我们的方法取了其他名字,即非applicationEventMulticaster(),可以采用name的方式指定
@Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster customMulticaster(){    
    SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
    //【学习2】指定一个任务executor来进行后台处理
    eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
    return eventMulticaster;
}

我们发布了某个Event,被两个Listener监听到,相关的log如下,实现了不同

10:47:51.199 [SimpleAsyncTaskExecutor-12] [INFO ] AuthenticationInterestedParty:20 onApplicationEvent() - Authentication event from context /adfsjjflsdjfljsedljfljf received in context /chapter18.
10:47:51.199 [SimpleAsyncTaskExecutor-13] [INFO ] LoginInterestedParty:21 onApplicationEvent() - Login event for context /adfsjjflsdjfljsedljfljf received in context /chapter18.

进一步了解

我们自定义一个类MyMulticaster,继承SimpleApplicationEventMulticaster,来进一步了解。相应地,在配置中,要将applicationEventMulticaster设置为自定义的类:

@Bean
public ApplicationEventMulticaster applicationEventMulticaster(){    
    SimpleApplicationEventMulticaster eventMulticaster = new MyMulticaster();
    eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
    return eventMulticaster;
}

我们重点看看MyMulticaster,重写了三个方法:

public class MyMulticaster extends SimpleApplicationEventMulticaster{
    private static final Logger log = LogManager.getLogger();

    @Override
    public void multicastEvent(ApplicationEvent event) {
        log.traceEntry("【这里是个坑】实际上没有触发到此方法(Spring版本4.3.11.RELEASE),需特别注意。");
        super.multicastEvent(event);
    }

    @Override
    public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
        log.traceEntry("发布时调用,eventType可以为null,此运行在发布所在线程,此处可以用于集群消息发布,添加通过网络发布给其他模块。");
        super.multicastEvent(event, eventType);
    }

    @Override
    protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        log.traceEntry("触发接收事件时调用,如设置了taskExecutor,则异步运行");
        super.invokeListener(listener, event);
    }
}

相关链接: 我的Professional Java for Web Applications相关文章

作者:flowingflying 发表于2017/11/11 10:50:38 原文链接
阅读:97 评论:0 查看评论

Quartz-集成Spring使用XML配置

$
0
0

概述

Spring为创建Quartzde Scheduler、Trigger和JobDetail提供了方便的FactoryBean类,以便能够在Spring容器中享受注入的好处。

此外,Spring还通了一些便利的工具类,用于直接将Spring中的Bean包装成合法的任务。

Spring进一步降低了使用Quartz的难度,能够以更加Spring风格的方式使用Quartz,主要体现在如下两点

  • 为Quartz的重要组件提供更具Bean风格的扩展类

  • 提供创建Scheduler的BeanFactory类,方便在Spring环境下创建对应的组件对象,并结合Spring容器生命周期执行启动和停止的动作


步骤

1 创建JobDteail

Spring通过扩展JobDetail提供了一个更具Bean风格的JobDetailFactoryBean,此外Spring还提供了了一个 MethodInvokingJobDetailFactoryBean,通过这个FactoryBean可以将Spring容器中的Bean的方法包装成Quartz任务,这样我们就不必为Job创建对应的类。


2 创建Trigger

Quartz中另外一个重要的组件就是Trigger,Spring按照相似的思路分别为SimpleTrigger和CronTrigger提供了更具Bean风格的CronTriggerFactoryBean和SimpleTriggerFactoryBean扩展类。


3 创建Scheduler

Quartz的SchedulerFactory是标准的工厂类,不太适合在Spring中使用。 此外为了保证Scheduler能够感知Spring容器的生命周期,在Spring容器启动后,Scheduler自动开始工作,而在Spring容器关闭前,自动关闭Scheduler。为此Spring提供了SchedulerFactoryBean.


示例

这里写图片描述

模拟业务类

配置成一个普通的java类即可

package com.xgj.quartz.quartzWithSpring.xml;


/**
 * 
 * 
 * @ClassName: MyJob
 * 
 * @Description: 不用继承Quartz的Job接口
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年11月10日 下午10:17:26
 */
public class MyJob {

    public void execute() {
        System.out.println("Quartz Spring XML 配置 - MyJob");
    }

}

Spring集成Quartz的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置Job类 -->
    <bean id="myJob" class="com.xgj.quartz.quartzWithSpring.xml.MyJob"></bean>

    <!-- 配置JobDetail -->
    <bean id="springQtzJobMethod" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <!-- 执行目标job -->
        <property name="targetObject" ref="myJob"></property>

        <!-- 要执行的方法 -->
        <property name="targetMethod" value="execute"></property>
    </bean>

    <!-- 配置tirgger触发器 -->
    <bean id="cronTriggerFactoryBean" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <!-- jobDetail -->
        <property name="jobDetail" ref="springQtzJobMethod"></property>

        <!-- cron表达式,执行时间  每5秒执行一次 -->
        <property name="cronExpression" value="0/5 * * * * ?"></property>
    </bean>

    <!-- 配置调度工厂 -->
    <bean id="springJobSchedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="cronTriggerFactoryBean"></ref>
            </list>
        </property>

    </bean>
</beans>

除了使用CronTrigger也可以使用SimpleTrigger配置,示例代码如下

<bean id="simpleTrigger"  class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
    <!--配置具体要执行的jobDetail-->
    <property name="jobDetail" ref="simpleJobDetail" />
    <!--初始延迟时间 1s-->
    <property name="startDelay" value="1000" />
    <!--间隔执行时间每2s执行一次-->
    <property name="repeatInterval" value="2000" />
</bean>

测试类

package com.xgj.quartz.quartzWithSpring.xml;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringQuartzXmlTest {

    public static void main(String[] args) {
        // 启动Spring 容器
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                        "classpath:com/xgj/quartz/quartzWithSpring/xml/application.xml");
        System.out.println("initContext successfully");
    }
}

在调度器中,有一个lazy-init参数,如果lazy-init=’false’,则容器启动时就会执行调度程序;如果lazy-init=’true’,则需要实例化该bean才能执行调度程序;

<bean id="springJobSchedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="true">

运行结果

2017-11-11 02:03:41,399  INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@174580e6: startup date [Sat Nov 11 02:03:41 BOT 2017]; root of context hierarchy
2017-11-11 02:03:41,481  INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/quartz/quartzWithSpring/xml/application.xml]
INFO  StdSchedulerFactory - Using default implementation for ThreadExecutor
INFO  SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
INFO  QuartzScheduler - Quartz Scheduler v.2.2.3 created.
INFO  RAMJobStore - RAMJobStore initialized.
INFO  QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.2.3) 'springJobSchedulerFactoryBean' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

INFO  StdSchedulerFactory - Quartz scheduler 'springJobSchedulerFactoryBean' initialized from an externally provided properties instance.
INFO  StdSchedulerFactory - Quartz scheduler version: 2.2.3
INFO  QuartzScheduler - JobFactory set to: org.springframework.scheduling.quartz.AdaptableJobFactory@7fe0dbb6
2017-11-11 02:03:42,862  INFO [main] (DefaultLifecycleProcessor.java:343) - Starting beans in phase 2147483647
2017-11-11 02:03:42,862  INFO [main] (SchedulerFactoryBean.java:645) - Starting Quartz Scheduler now
INFO  QuartzScheduler - Scheduler springJobSchedulerFactoryBean_$_NON_CLUSTERED started.
initContext successfully
Quartz Spring XML 配置 - MyJob
Quartz Spring XML 配置 - MyJob
Quartz Spring XML 配置 - MyJob
Quartz Spring XML 配置 - MyJob
Quartz Spring XML 配置 - MyJob
.......
.......
.......
.......

示例源码

代码已托管到Github—> https://github.com/yangshangwei/SpringMaster

作者:yangshangwei 发表于2017/11/11 12:05:55 原文链接
阅读:54 评论:0 查看评论

改善深度神经网络:超参数调试、正则化以及优化——超参数调试、Batch正则化和程序框架(2-3)

$
0
0

1.调试处理

关于训练神经网络,要处理的参数的数量,从学习速率 到momentum 同时还有Adam优化算法的参数 and ,也许还需要选择层数 layer_size, 不同层中隐藏单元的数量 layer_units ,有可能还需要选择学习率衰减 learning_rate_decay,还有需要选择mini_batch 的大小。

但是,虽然有这么多的超参数,但是在神经网络中的调试是有优先级的,优先级如下:
> (momentum,0.9) > mini_batch_size = hidden_units_size >layer_size = learning_rate_decay> (0.9), (0.999) ,(10^(-8))

1.在机器学习领域,超参数比较少的情况下,我们之前利用设置网格点的方式来调试超参数。
2.但在深度学习领域,超参数较多的情况下,不是设置规则的网格点,而是随机选择点进行调试。这样做是因为在我们处理问题的时候,是无法知道哪个超参数是更重要的,所以随机的方式去测试超参数点的性能,更为合理,这样可以探究更超参数的潜在价值。

如果在某一区域找到一个效果好的点,将关注点放到点附近的小区域内继续寻找。

这里写图片描述

2.为超参数选择合适的范围

Scale 均匀随机

在上一节中,随机取值并不是在有效值范围内的随机均匀取值,而是选择合适的scale(标尺)用于探究这些超参数。
在超参数选择的过程中,一些超参数是在一个范围内进行均匀随机取值的,如神经元结点的个数,隐藏层的层数等。但是有一些超参数的选择做均匀随机取值是不合适的,如学习率 ,这里需要按照一定的比例在不同的小范围内进行均匀随机取值,假如 在[0.0001,1]之间取值,那么我们如果使用普通的均匀随机取值, 在[0.0001,0.001]之间的取值只有0.1*0.1*0.1=0.1%的概率才会选在这个区间中,这明显是不合理的!

这里写图片描述

所以,为保证在[0.0001,0.001] , [0.001,0.01] , [0.01,0.1] , [0.1,1] 这几个范围内均匀取值,则我们需要使用scale均匀随机。
可以构造一个指数型函数,则我们有:

r=np.random.rand()
learning_rate=10**r

这里我们通过对数求得 r 属于 [-4,0] 这个区间,。因此当根据学习率的范围可以求出r的范围 [a,b],从而学习率 ,

对于momentum中的指数加平均中的,若我们需要它的取值在[0.9,0.999]之间,我们也需要对其进行scale调整,我们需要对其划分为[0.9,0.99] [0.99,0.999] , 其中0.9,0.99,0.999分别对应了其平均10,100,1000个平均数(如果忘记了指数加权平均,请跳转自改善深层神经网络:超参数调试、正则化以及优化——优化算法(2-2) )。则 1- 分别为 0.1,0.01,0.001,这样子我们可以利用对数,得到 ,对r进行均匀取值。

为什么需要对越小的区间给予相同的权重?
对于学习率 而言,学习率越小,虽然迭代速度慢,但是优于找到最小值。对于Momentum 的 来说,如果 =0.999,是平均了前1000个数,而 =0.9995则平均了前2000个数, 越靠近1,灵敏度越大,所以必须对其保持一定的警惕性,才能得到较好的值。

作者:Hansry 发表于2017/11/11 12:09:24 原文链接
阅读:18 评论:0 查看评论

git权威指南总结四:进度保存与恢复

$
0
0

前期准备

在进行一次文件创建时,在关闭电脑前如果我们这次创建完成之后还不想提交,可以先将它保存下来,在后面进行恢复就可以了,这样是不是很方便
首先我们先创建一个文件,制造进度保存环境echo "save file" > save.txt,接下来开始教程讲解吧


进度使用

在前面创建了一个新的文件save.txt之后,假设此时暂时不想要去设置该文件,我们可以先将该进度保存下来,使用命令:git stash即可,接下来详细介绍一下git stash命令的几种常用的语法:
git stash:保存当前的工作进度,该命令会将工作区和暂存区的状态分别进行保存
git stash save “description”:同上,并且设置保存的提示信息
git stash list:显示保存的进度列表,如果用save设置了提示信息的就会显示提示信息
git stash pop:恢复最新保存的工作进度,并将其从进度列表中移除
git stash apply:恢复最新保存的工作进度,同时进度列表中依旧保存该进度,通常用于进度需要循环使用的情况
git stash drop:删除一个存储的进度,默认是删除最新的进度
git stash clear:删除所有存储的进度


作者:qq_27905183 发表于2017/11/11 12:58:02 原文链接
阅读:57 评论:0 查看评论

【LuaJIT版】从零开始在 macOS 上配置 Lua 开发环境

$
0
0

前言

这篇文章针对的是基于 LuaJIT 的环境配置。借助于 LuaJIT,Lua 的执行效率可以进一步提升几十倍。如果你不是很清楚自己是需要 Lua 还是 LuaJIT,那么建议你从 LuaJIT 起步。LuaJIT 对应的是 Lua5.1 的语法,这一点需要注意。

如果你想看基于最新版 Lua 环境的配置文章,请移步:http://yanfeng.life/2017/11/10/Latest-guid-for-lua/

脚本语言,你可能更需要的是 Lua

不同的脚本语言有不同的特性,第一接触的脚本语言,可能会影响自己对整个脚本语言的理解和认知。我以前接触最多的脚本语言是 JavaScript。后果就是:我一度以为脚本语言都是必须和宿主语言运行在不同的进程;脚本本身的语法受环境的影响很大,很难做到统一;如果想统一写法,都需要在应用体积上做出非常大的妥协(嵌入一个通用的 JS 解释器,会使应用体积增大十几 M)。

我在试着做一些努力,去改进因大量使用 JavaScript 引起的一些特定的技术问题。但是偶然间又想起了 Lua。最开始是从玩 WOW 的室友那里听说 Lua 的。因为他说写WOW插件很赚钱,所以曾经认真地搜索过 Lua,现在脑海中有一些残留的片段。

我突然意识到,可能 JavaScript 的大部分限制,在 Lua 或其他脚本语言中并不存在。想要解决那些因为使用 JavaScript 引起的各种问题,可能只需要换一门脚本语言。

当然,此处不考虑通常意义上的使用难度,学习难度,推广成本一类的因素;毕竟我是纯自嗨。但假如,Lua 真的能很好解决我目前遇到的脚本语言无法和宿主语言灵活通信的问题,必将给自己的整体知识体系带来一个新的提升,也必将在自己的日常实践中创造出许多新的可能。

所涉及的各个工具的版本

  • 编程语言: LuaJIT 2.0.5 (对应 Lua 5.1的语法)

  • Lua 模块管理工具:LuaRocks 2.3.0(LuaJIT版)

  • 编辑器:IntelliJ IDEA CE (社区版)

Build #IC-172.4343.14, built on September 26, 2017
JRE: 1.8.0_152-release-915-b12 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
Mac OS X 10.13.1

  • 调试插件:EmmyLua 1.2.0

  • 操作系统:macOS 10.13.1

  • macOS 专用模块管理工具: Homebrew 1.3.6

以下讨论,均以 macOS 为主,其他系统平台仅供参考。

安装 LuaJIT

下载 LuaJIT 源码

下载地址:http://luajit.org/download.html

一般应选择最新的 Stable 稳定版本。下载后解压。

下载 LuaJIT 源码

编译 LuaJIT

编译非常简单,cd 到 LuaJIT 源码解压目录,然后在终端执行:

make && sudo make install

验证 LuaJIT 安装是否成功

打开终端执行:

luajit -v

安装成功后,应该有以下类似输出:

LuaJIT 2.0.5 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/

安装 Lua 模块管理工具:LuaRocks

打开 macOS 终端,输入以下指令:

brew tap mesca/luarocks
brew install luarocks51 --with-luajit

LuaRocks 的安装也有多种方式,这是较为简化高效的一种。需要注意的是,以此种方式安装,LuaRocks 的调用命令,将变为 luarocks-jit

LuaRocks 安装成功

选择一个合适的 Lua 编辑器: IntelliJ IDEA CE

Lua 日常编码,推荐使用 IntelliJ IDEA CE(社区免费版),下载地址:https://www.jetbrains.com/idea/download/

主要是从配置难度,语法高亮,代码提示,代码调试等方面权衡。

IntelliJ IDEA CE 预览

使用 EmmyLua 插件配置调试环境

安装 EmmyLua

Lua 的调试和高亮,主要得益于 EmmyLua。在 IntelliJ IDEA CE 的 Plugins 中,直接搜索安装即可。记得,要先安装此插件,然后再新建工程。安装此插件后,工程模板,会多一个 Lua 模板选项。从 Lua 模板新建工程,会简化许多 Lua 相关的配置。

安装 EmmyLua

安装 luasocket

在安装 EmmyLua 插件之后,还需要安装一个 Lua 库 luasocket,才能进行调试。

打开终端,输入以下指令:

sudo luarocks-jit install luasocket  

修改 Lua 路径配置

Lua 的配置路径要从默认的 lua,改为 luajit 命令的真实路径,一般是 /usr/local/bin/luajit

配置 Lua

开始调试

在 IntelliJ IDEA CE 中新建 Lua 工程,然后新建 Lua 文件 hello.lua,输入代码:

-- defines a factorial function
function fact (n)
    if n == 0 then
        return 1
    else
        return n * fact(n - 1)
    end end
a = 6
print(fact(a))

在编辑区左侧,打上几个断点,然后文件编辑区右键,选择 Debug ‘hello.lua’:

debug 右键菜单

一起简单看下 Debug 断点调试的效果:

debug 效果演示

配置 LuaCheck 静态检查工具

首先在在终端命令行执行:

sudo luarocks-jit install luacheck

然后在设置页,配置 LuaCheck 的完整路径: /usr/local/bin/luacheck

LuaCheck 配置页

现在编辑区右键菜单中选择 “Run LuaCheck”,就可以进行静态检查了。不过初次接触或运行某些示例代码时,不用过于纠结静态检查的警告。

LuaCheck 效果图

注意事项

  • 安装 LuaJIT ,不需要安装 Lua 5.1 了

  • 一定是要在配置好 EmmyLua 插件后,直接基于新出现的 Lua 模板新建工程;否则在修改项目配置上,可能会花费许多时间。

  • 调试时报错 module ‘socket’ not found ,是因为没有安装 luasocket。

  • Lua 基于MIT 开源,如果哪天想自己定义某种新语言来实现特定的产品特性,可以考虑基于 Lua 定制。

  • Lua 支持各种低级硬件嵌入式开发,可移植性非常好,物联网时代可能会有新的可能和增长潜力。

  • 如果感觉 luajit 没有 lua 写着方便,可以试着在 .profile 中配置 alias 命令别名,详见:https://stackoverflow.com/a/8967864

alias lua="/usr/local/bin/luajit"
alias luarocks="/usr/local/bin/luarocks-jit"
  • 命令别名,不能 sudo 执行,如安装 luasocket 时,还是需要 sudo 原始命令:

sudo luarocks-jit install luasocket

  • 如果已经安装过 Lua 5.1,可能需要先移除它:
brew unlink lua@5.1
  • 编码时,光标移动到下一行的快捷键是:

cmd + shift + enter

小结

以上介绍了 macOS 平台,LuaJIT 从安装到配置调试环境的完整过程。接下来,就可以根据自己的节奏和需要,好好领略 Lua 之美了~

参考文档:

作者:sinat_30800357 发表于2017/11/11 16:04:12 原文链接
阅读:0 评论:0 查看评论

SDL系列讲解(十) 按键处理流程

$
0
0

SDL系列讲解(一)  简介
SDL系列讲解(二) 环境搭建
SDL系列讲解(三) 工具安装
SDL是什么,能干什么,为什么我们要学习它?
SDL系列讲解(四) demo讲解
SDL系列讲解(五) 调试c代码
SDL系列讲解(六) SDL_Activity流程
SDL系列讲解(七) SDL_image教程SDL系列讲解(八) SDL_ttf教程
SDL系列讲解(九) 异常退出分析
SDL 按键消息流程时序图
 

代码剖析
   

按键消息处理,路径比较简单,就是我们Android上面的SurfaceView的onKey方法处理,使用 onNativeKeyDown 或者onNativeKeyUp将按键消息传递到c代码中,c代码接收到按键事件,使用TranslateKeycode方法,将android的按键映射成SDL的扫描码(SDL_scancode.h)

,同时使用keyboard->keymap[scancode]拿到扫描码对应的SDL按键值,具体在SDL_default_keymap[SDL_NUM_SCANCODES]存储。
按键值都在这里SDL_keycode.h,然后填充一个SDL_Event联合体,按照key结构体去填写,具体为:
这里type的值为SDL_KEYDOWN 或者 SDL_KEYUP

state的值为SDL_PRESSED 或者 SDL_RELEASED
 repeat 存储是否重复按键
keysym.scancode 存储扫描码,这个取值在SDL_scancode.h,比如 :SDL_SCANCODE_AC_BACK
keysym.sym 存储按键值:这个取值在SDL_keycode.h 比如:SDLK_AC_BACK
keysym.mod 存储特殊键,处理组合键使用
windowID存储focus窗口值

演示代码
   
修改main.c


然后我们安装到手机上测试,查看log可以看到输出结果。


可以看到按键信息。

作者:a332324956 发表于2017/11/11 16:05:13 原文链接
阅读:0 评论:0 查看评论

SDL系列讲解(十一) SDL_QUIT流程

$
0
0

明天会将自己的很早的一部小说发出,一次发出写的所有的六章,如果时间,或者某天突然觉得可以续写了,那么就是它又一次起航了。

工作期间,码了一个记录工作的小说,有兴趣的可以阅读。当然,文采上面,确实逊色了许多,不过能看完,你能发现一些惊喜,因为很多人物是真实存在的。
移步:

http://blog.csdn.net/a332324956/article/category/1886055

近期写的小说,不出意外,会先更新出来,前面的章节,可以温习了。具体点击:

校园江湖(一)厕所被揍
校园江湖(二)表哥出狱

代码GG,不一样的编程人生。

太多散文发在别处,这里摘录下公众号的几篇,可以阅读了。

梦想太远,现实太近(一)
梦想太远,现实太近(二)
梦想太远,现实太近(终结篇)

下来,继续我们 的SDL系列讲解

SDL系列讲解(一)  简介
SDL系列讲解(二) 环境搭建
SDL系列讲解(三) 工具安装
SDL是什么,能干什么,为什么我们要学习它?
SDL系列讲解(四) demo讲解
SDL系列讲解(五) 调试c代码
SDL系列讲解(六) SDL_Activity流程
SDL系列讲解(七) SDL_image教程SDL系列讲解(八) SDL_ttf教程
SDL系列讲解(九) 异常退出分析

SDL系列讲解(十) 按键处理流程

项目合作

SDL_QUIT时序图
   

看下创建窗口的流程图,我们简单说下过程。
原理剖析
   
应用上层调用 this.finish();关闭Activity的时候,走入onDestroy方法,此方法会去调用        SDLActivity.nativeQuit();进入到C代码里面Java_org_libsdl_app_SDLActivity_nativeQuit,这里向系统发出一个退出消息SDL_SendQuit();,以及一个 SDL_SendAppEvent(SDL_APP_TERMINATING)
;消息,让我们去接收,处理退出事件。发送消息,最后走入的是SDL_PushEvent方法,这个是SDL向消息队列扔入一个事件的方法。我们处理的思路是:

使用SDL_PollEvent获取到事件,通过类型过滤出来,如果是SDL_QUIT, 则退出主循环。这里我们看到onDestroy方法,在处理SDLActivity.nativeQuit();后,进行了一个动作:

这个便是等待我们的SDLMain线程退出,起到同步等待的作用。

看完这条线路,我们再看下,如果我们在c代码main运行完毕,应用的退出流程:

SDL_QUIT主动退出时序图

SDL_QUIT主动退出代码剖析

SDLSurface在创建的时候,就会走到surfaceChanged,然后这里创建了我们的SDLMain线程,进行调用我们写的c代码,同时这里使用 sdlThread.join();等待结束,当结束的时候,调用   SDLActivity.handleNativeExit();来完成退出,然后便走到上面的那个流程了,退出SDL。

作者:a332324956 发表于2017/11/11 16:05:47 原文链接
阅读:0 评论:0 查看评论

SDL系列讲解(十二)创建窗口流程

$
0
0


SDL系列讲解(一)  简介
SDL系列讲解(二) 环境搭建
SDL系列讲解(三) 工具安装
SDL是什么,能干什么,为什么我们要学习它?
SDL系列讲解(四) demo讲解
SDL系列讲解(五) 调试c代码
SDL系列讲解(六) SDL_Activity流程
SDL系列讲解(七) SDL_image教程SDL系列讲解(八) SDL_ttf教程
SDL系列讲解(九) 异常退出分析
SDL系列讲解(十) 按键处理流程

SDL系列讲解(十一) SDL_QUIT流程

项目合作

创建窗口时序图

时序图细节

看下创建窗口的流程图,我们简单说下过程。窗口创建,会先判断是否进行了SDL_VideoInit,如果没有,需要初始化。

完成了VideoInit的初始化后,我们使用LoadLibrary将android平台的opengl es动态库进行加载,同时将动态库里面的一些方法进行保存,作为我们适配android的实质方法,完成对接任务。SDL平台调用绘制,用的是SDL的一套标准,但是具体真正具体到每个平台是需要具体平台的实现,这里就是完成这个对接,使得SDL的方法,最终能操作到android平台上。

完成了opengl es的方法赋值之后,我们需要完成真正的窗口创建。在讲解这个之前,我们讲下android的绘制流程。我们知道,android使用activity进行承载界面,我们普通的View,比如button ,Textview这些,都是在ondraw进行绘制,使用传递的一个参数canvas,这个叫画布。是作用在一个绘制表面上的一套封装,使用canvas的一些api,可以简化我们直接绘制的难度,直接使用写好的画线,画圆,画一个弧度等等。当我们在canvas画好之后,系统会根据当前窗口耳朵层叠关系,透明度,缩放等等,进行将多个窗口排序,混合之后,使用驱动操作,将最终的一屏数据,刷入显卡,然后我们便看到了效果。

使用canvas,有个瓶颈便是,性能问题。ondraw属于主线程,于是乎就不能长时间频繁绘制,这样子会导致手机卡顿,出现anr,为了规避这种情况(特别是游戏,用canvas绘制,只能做比如五子棋,简单的连连看游戏,因为不会大量更新界面),大型的游戏开发,都是使用opengl es,android为了配合opengl es的硬件加速渲染,在上层配置了一个SurfaceView,这个是直接申请了一块绘制表面,独立于activity的其他View,自己独占一份,因此我们不需要使用ondraw绘制,这样子我们就可以单独开启一个绘制线程,单独作用在这个绘制表面,而直接操作绘制表面,会让我们开发效率大大降低。于是,我们将这个绘制表面进行封装,提供一套接口,而这套接口是配合硬件加速开发出来,于是我们的绘制速度会大幅度提升。而我们此处的封装,就是使用eglCreateWindowSurface,如此之后
我们就可以使用gl的一系列方法进行绘制,最后使用gl的swap-buffer将数据刷入绘制表面,然后进行和其他窗口混合,最终显示到屏幕。

作者:a332324956 发表于2017/11/11 16:06:21 原文链接
阅读:0 评论:0 查看评论

android SDL系列讲解(十三) 播放音乐库 SDL_mixer教程

$
0
0


项目外包项目信息更新:
qq抢红包,因为评估时间问题,没有对接下来。
一个网页开发项目,已经内部消耗掉了。

机会总是稍纵即逝,有兴趣探讨技术,以及项目事宜,可以联系代码GG微信:

code_gg_boy

SDL系列讲解(一)  简介
SDL系列讲解(二) 环境搭建
SDL系列讲解(三) 工具安装
SDL是什么,能干什么,为什么我们要学习它?
SDL系列讲解(四) demo讲解
SDL系列讲解(五) 调试c代码
SDL系列讲解(六) SDL_Activity流程
SDL系列讲解(七) SDL_image教程SDL系列讲解(八) SDL_ttf教程
SDL系列讲解(九) 异常退出分析
SDL系列讲解(十) 按键处理流程
SDL系列讲解(十一) SDL_QUIT流程

SDL系列讲解(十二)创建窗口流程

项目合作

下周发布一个游戏移植过程,敬请期待。 android学习过程的所有疑惑,可以留言,代码GG知无不言言无不尽,会答疑解惑。

前言

  讲解完了图片,文字,这节我们来看下怎么播放声音。
  SDL默认是可以播放wav以及pcm格式的音乐,但是使用起来比较麻烦,所以官网给我们提供了一个三方库SDL_mixer,用来支援更多的音频格式,比如mp3,midi以及ogg,关于flac无损音乐,后续有个移植过程,但是编译出来使用的时候,发现速度跟不上,有卡顿现象,有兴趣的可以去研究下。在本节的最后,会提供flac的编译方法。

下载SDL_mixer库

来到 http://www.libsdl.org/projects/SDL_mixer/ ,下载这里的SDL2_mixer-2.0.1.zip


下载下来,我们放置到jni目录,改名字为SDL2_mixer。

配置SDL_mixer库

 修改app\src\main\jni下面的Android.mk,新增两行加入
 include src/main/jni/SDL2_mixer/external/libmikmod-3.1.12/Android.mk
include src/main/jni/SDL2_mixer/external/smpeg2-2.0.0/Android.mk
目的在于,将SDL2_mixer里面包含的开源库编译进来。然后开始编译,爆出一个错误


修改成assert(audio->timestamp >= (double*)0); 再次编译。

然后修改android-project/app/src/main/jni/src/Android.mk
LOCAL_SHARED_LIBRARIES := SDL2 SDL2_image SDL2_ttf

 为
LOCAL_SHARED_LIBRARIES := SDL2 SDL2_image SDL2_ttf SDL2_mixer

 将我们的SDL2_mixer包含进来。

验证播放音乐

下载一首歌曲,这里下载薛之谦的《动物世界》,放置在项目的assets目录下。


然后在我们的main.c的main方法里面加入
//初始化mix,MP3格式if (Mix_Init(MIX_INIT_MP3) < 0) {    printf(“Mix_Init: %s\n”, SDL_GetError());    return 1;}//打开设备if (Mix_OpenAudio(22050, MIX_DEFAULT_FORMAT, 2, 4096) < 0) {    printf(“Mix_OpenAudio: %s\n”, SDL_GetError());    return 1;}Mix_Music *music = Mix_LoadMUS(“dongwu.mp3”);Mix_PlayMusic(music, SDL_TRUE);

 在退出的地方,加入

然后编译,运行app,观看效果。然而你所遇到的是个bug,apk异常退出啦。


可以看到,这里是解包的大小引起,本身三方库就是解决编解码的问题,然而编解码出现问题,可以去修复,同时也可以使用其他替代,这里我们用SDL提供的第二个库libmad进行解码mp3格式。
移植libmod 库

第一步,下载git for window
打开 http://rj.baidu.com/soft/detail/30195.html?ald 进行下载即可。
关于Git

Git是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。
Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。
Git 与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库的方式,不必服务器端软件支持。

安装Git

主界面:

我们只需要一直下一步即可,不用管其他的操作,使用默认的即可。
安装后,我们点击电脑左下方的window图标,选择所有应用,选择git目录,选择git Bash

选择点开git Bash之后,界面为:

然后我们使用cd  /d 进入d盘,然后使用 
git clone https://gitorious.org/rowboat/external-libmad.git 将libmad模块抓下来。


等待下载完成,然后我们进入d盘下,将下载好的目录,复制到我们项目的app\src\main\jni\SDL2_mixer\external目录下,并且改名为libmad


然后我们修改app\src\main\jni下面的Android .mk ,加入一行:
include src/main/jni/SDL2_mixer/external/libmad/Android.mk
然后我们修改app\src\main\jni\SDL2_mixer下面的Android.mk

LOCAL_C_INCLUDES := $(LOCAL_PATH)LOCAL_CFLAGS := -DWAV_MUSIC
后面加入:

同时把本文件的
SUPPORT_MP3_SMPEG ?= true 修改成
SUPPORT_MP3_SMPEG ?= false
去掉smpeg的默认解码mp3,使用mad库进行解码。
进入app\src\main\jni\SDL2_mixer\external\libmad 修改这里的Android.mk

具体为:

删除第一行的
ifeq ((strip(BUILD_WITH_GST)),true)
和最后一行的

endif
然后修改:

LOCAL_CFLAGS := -DHAVE_CONFIG_H -DFPM_DEFAULT
修改为:
LOCAL_CFLAGS :=-DHAVE_CONFIG_H -DFPM_ARM -ffast-math -O3
这里主要的是FPM_ARM,让编译arm指令集。

如果编译过程报错:
Error:error: invalid instruction mnemonic ‘smull’
我们需要修改
ndk { abiFilters “armeabi”, “armeabi-v7a”, “x86” }
这里的armeabi 需要去掉,同时我们去掉x86,去掉armeabi是因为低版本的arm没有smull指令,去掉x86是因为我们手机是arm平台,为了我们快速编译apk,就先去掉它。
延伸flac无损解码库编译

不做更详细的讲解,这里将我的操作过程展示一下。因为我们一般使用的mp3 wav格式,当前已经完美支持,所以flac放在有兴趣研究的人,这里作为抛砖一下。
http://downloads.xiph.org/releases/flac/flac-1.3.2.tar.xz 下载flac库。
然后我们需要ubuntu 等linux环境,解压,进入目录,然后使用:
./configure –help 进行查看都有哪些命令,然后进行配置,配置参数里面的CC CXX这些路径,需要自己修改对应自己的真正arm编译链的位置。

./configure \
–prefix=/home/andy/share/flac/cross_install –enable-cross-compile –build=i386-pc-linux-gnu –host=arm-linux –target=arm-linux –enable-ogg –disable-3dnow –disable-oggtest –disable-asm-optimizations –disable-xmms-plugin –disable-sse –disable-cpplibs –disable-shared –disable-id3libtest \CC=/home/user/Android/Sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc \CXX=/home/user/Android/Sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-g++ \AR=/home/user/Android/Sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ar \LD=/home/user/Android/Sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ld \RANLIB=/home/user/Android/Sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ranlib \STRIP=/home/user/Android/Sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-strip \CFLAGS=”-I/home/user/Android/Sdk/ndk-bundle/platforms/android-24/arch-arm/usr/include -I/media/user/big/sdl/android-project/app/src/main/jni/SDL2_mixer/external/libogg-1.3.1/include” \CXXFLAGS=”-I/home/user/Android/Sdk/ndk-bundle/platforms/android-24/arch-arm/usr/include -I/media/user/big/sdl/android-project/app/src/main/jni/SDL2_mixer/external/libogg-1.3.1/include” \LDFLAGS=–sysroot=/home/user/Android/Sdk/ndk-bundle/platforms/android-24/arch-arm 

配置通过后,我们使用make 即可完成编译,如果想看编译的具体参数等信息,可以使用make –just-print 进行只输出命令,不做实质编译,用来学习编译过程,最好不过啦。

编译过后,会出来一个libFLAC-static.a 静态库,然后我们在我们的项目jni目录下创建libs目录加入这个库:libs/libFLAC.a (改成这个名字)

在SDL2_mixer目录下的Android .mk 加入:

编译之后,这是我们可以播放flac格式的音乐了。

问题
flac编译出来,可以播放音乐,但是比较卡,具体此问题,留作疑问。

作者:a332324956 发表于2017/11/11 16:06:56 原文链接
阅读:0 评论:0 查看评论

SDL的几个宽高概念讲解(文中有福利)

$
0
0

圈子朋友公众号福利:

「安卓干货铺」节后送书福利-感谢大家一直以来对我的支持!

SDL系列讲解(一)  简介
SDL系列讲解(二) 环境搭建
SDL系列讲解(三) 工具安装
SDL是什么,能干什么,为什么我们要学习它?
SDL系列讲解(四) demo讲解
SDL系列讲解(五) 调试c代码
SDL系列讲解(六) SDL_Activity流程
SDL系列讲解(七) SDL_image教程SDL系列讲解(八) SDL_ttf教程
SDL系列讲解(九) 异常退出分析
SDL系列讲解(十) 按键处理流程
SDL系列讲解(十一) SDL_QUIT流程
SDL系列讲解(十二)创建窗口流程

android SDL系列讲解(十三) 播放音乐库 SDL_mixer教程

项目合作

首先,说一些sdl的网站地址:http://www.libsdl.org/projects/ 一些项目,比如我们的ttf net 和mixer ,都是在这里进行下载。官方的wiki地址http://wiki.libsdl.org/FAQWindows 另一个wiki地址,比较详细的讲了一些例子,方法描述,算是很好的资料http://sdl.beuc.net/sdl.wiki/FrontPage
SDL里面有几个比较关键的尺寸,我们这节来聊聊这个。了解了这些之后,你就知道如何开发游戏,如何放置一个图片的准确位置。
我们一个个来看下:
SDLSurfaceView
 android上层布局使用的View,和TextView
 ,Button
一致,都属于View级别,这个View的大小,就是我们实际屏幕看到的大小,宽高,就是按照像素计算出来的。getWidth()
 and getHeight()
 去获取,如果在oncreate
里面获取,基本获取的是错误的值,因为View还没有被测量,这时我们可以使用post
来处理,或者监听ViewTreeObserver
也可以做到。
Window
 SDL创建出来的窗口(SDL_CreateWindow
),这个窗口的大小是固定的,和SDLSurfaceView
大小一致,不能调整。因此对应的设置窗口的大小方法,在android上面不起作用,这个需要注意啦。也就是SDL_SetWindowSize
方法是不能用的。
Render
,渲染器大小(SDL_CreateRenderer
),size默认是和Window大小一样,是全屏的,但是我们可以主动修改这个大小,然后配合我们的设计尺寸。比如我们设计的游戏是480×800的尺寸下,那么我们游戏里面的角色,位置等信息,都可以使用这个480×800尺寸下进行计算,比如一个按钮,在100,100这个位置,指的就是在480×800这个尺寸下的位置,如果我们的Window大小是720×1080,那么我们这里的100,100的位置,就会经过投影,从480×800->720×1080,这里就是100/480×720,100/800×1080, 得到在7201080屏幕的显示位置。如此设置之后,我们的开发就只需要围绕480×800去展开,不需关注具体屏幕的大小,render会正确的缩放到实际尺寸,具体我们要做的是:如果我们要在480×800的尺寸下开发游戏,我们这样子操作
SDL_GetRendererOutputSize(renderer,&w,&h);

SDL_RenderSetScale(w/480,h/800);
这个有可能会不按比例缩放,如果需要按比例,需要使用SDL_RenderSetLogicalSize(480,800); 这个方法会将大小不拉伸的情况下,进行等比缩放。剩余部分不填充。
Textrue
, 纹理的大小,可以通过copy surface的大小,这里比如一张图大小200×400,那么创建一个surface,从这个图,那surface的大小就是200×400,然后纹理从surface复制过来,也就是也是200×400,如果这时textrue在贴到render上面的时候,没有指定区域,那么就是将整个的texture投影到render的整个区域,这里就是将200×400缩放到400*800上面去。然后再投影到Window上面,完成整个的显示。

作者:a332324956 发表于2017/11/11 16:07:33 原文链接
阅读:0 评论:0 查看评论

android 游戏移植 (一) (文末有福利) | SDL 西游释厄传调试

$
0
0

image

游戏效果(不是真实画质)

image

有没有被惊艳到?你的内心肯定会说,我靠,画质这么渣,画面却如此熟悉。

对的,就是如此渣渣的画面,却伴随了我们的童年快乐。

下面我们就详细的讲讲这个移植过程,说得更加具体些,就是本身这款游戏的

android

版本并非我移植的,本身模拟器也是有开源项目支持的。西游释厄传有人已经移植

ok了,但是市面上开源的只有

SDL1.3版本,而这个版本有个大问题,就是使用的

framebuffer

实现的,这个实现版本,由于没有使用硬件加速,所以性能大大损失。

因此,在原有项目的基础上,进行移植

SDL2.0

,支持

GPU

硬件加速,如此之后,会发现性能直接上升三倍,此游戏在低

CPU

上,就不会出现卡顿,没法玩的问题了。

下来简单讲下整个游戏的流程:

c语言开发的程序,要在android上面跑起来,主要解决以下问题:

  • c语言与java语言的jni接口

  • c语言的按键,触摸,以及手柄等事件传递

  • c语言的声音播放

  • c语言的绘制,显示

主要就是围绕这些,算法,逻辑等等,可以直接使用c语言已经实现的,可以直接使用。

差异部分就是如何和

android

去对接,将事件传递过来,同时响应,将结果输出到屏幕。

c版本的游戏,当前移植到

android

,都不可避免的使用了

surfaceView

,因为此

View

是在

android

的java端搭出一个框架,让事件可以传递给它,同时它又在独立的自己线程会去执行绘制动作,这样保证了它的绘制,不会影响

android

普通

View

的绘制流程,从而使得c语言的绘制变得可行。

c语言开发这边,便可以拿到

surfaceViw

对应的一个绘制buffer,将这个绘制buffer封装,使用一组接口操作,便成了

OpenGL es

 啦。如此之后,我们可以使用

opeGL es

的标准方法,进行操作这个buffer,从而将内容绘制到屏幕上了。

声音,按键 ,以及触摸,都是通过标准的

JAVA

C C++

语法之间的通道实现,即所谓的

JNI

,用来打通两边的参数传递,方法调用。

SDL

在此款游戏里面,只充当绘制的动作,其余的声音这些,都是通过JNI打通的。同时,游戏模拟器的概念类似虚拟机,也就是我们的apk运行起来,模拟出来我们的16位游戏机当时使用的

CPU

,以及内存地址空间,如此一来,我们将对应的游戏

ROM

,加载起来运行,便可以在此虚拟环境下,正常运行。

俄罗斯方块效果

image

这个是PC上面的效果,还未移植到android,主要是在考虑,到底该不该用贴图来实现更炫的画质,还是就保持绘制线条的方式,敬请期待吧! 再个原因就是:时间真的不充裕,毕竟这个是周末空闲时间来写的。

 下节来分析代码,以及讲解移植过程,想要查看下载代码的,可以先行阅读了。

源码位置:

https://github.com/Cpasjuste/afba

https://github.com/Cpasjuste/libarcade

俄罗斯方块源码:

http://pan.baidu.com/s/1eSH7J6U

推荐阅读:

福利:

送书福利,不忘初心!

image

作者:a332324956 发表于2017/11/11 16:09:55 原文链接
阅读:0 评论:0 查看评论

HDU4738 Caocao's Bridges

$
0
0

双连通分量

题目传送门

明天就考试了QAQ

题目大意:给你一张无向图,问图中权值最小的桥的权值。

据说NOIp会考边双。。。赶紧学一下。

边双裸题啊,Tarjan直接缩,如果有两个及以上的连通块就直接输出-1.

注意当桥的权值为0时答案仍然是1,因为仍然需要派一个人去炸桥。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 1000
using namespace std;
struct edge{
    int next,to,dis;
}ed[MAXN*MAXN*2+5];
int n,m,k,p,num,ans;
int h[MAXN+5],dfn[MAXN+5],low[MAXN+5];
bool f[MAXN+5];
void addedge(int x,int y,int z){
    ed[k].next=h[x]; ed[k].to=y; ed[k].dis=z; h[x]=k++;
}
void Tarjan(int x,int e){
    dfn[x]=low[x]=++p;
    for (int i=h[x];~i;i=ed[i].next)
        if (i!=e){
            int v=ed[i].to;
            if (!dfn[v]){
                Tarjan(v,i^1);
                low[x]=min(low[x],low[v]);
                if (low[v]>dfn[x]) 
                    ans=min(ans,ed[i].dis);
            }
            else low[x]=min(low[x],dfn[v]);
        }
    num++;
}
int main(){
    while (scanf("%d%d",&n,&m)){
        if (n==0&&m==0) break;
        memset(h,-1,sizeof(h)); k=0;
        for (int i=1;i<=m;i++){
            int u,v,d;
            scanf("%d%d%d",&u,&v,&d);
            addedge(u,v,d); addedge(v,u,d);
        }
        p=0; ans=0x7fffffff; num=0;
        memset(dfn,0,sizeof(dfn));
        memset(low,0,sizeof(low));
        Tarjan(1,-1);
        if (num<n) printf("0\n");
        else if (ans==0x7fffffff) printf("-1\n");
        else if (ans==0) printf("1\n");
        else printf("%d\n",ans);
    }
    return 0;
}
作者:a1799342217 发表于2017/11/10 20:32:46 原文链接
阅读:227 评论:0 查看评论

CCF CSP 2015年12月第3题 画图 (模拟+DFS或BFS)

$
0
0

问题描述
试题编号: 201512-3
试题名称: 画图
时间限制: 1.0s
内存限制: 256.0MB
问题描述:
问题描述
  用 ASCII 字符来画图是一件有趣的事情,并形成了一门被称为 ASCII Art 的艺术。例如,下图是用 ASCII 字符画出来的 CSPRO 字样。
  ..____.____..____..____...___..
  ./.___/.___||.._.\|.._.\./._.\.
  |.|...\___.\|.|_).|.|_).|.|.|.|
  |.|___.___).|..__/|.._.<|.|_|.|
  .\____|____/|_|...|_|.\_\\___/.

  本题要求编程实现一个用 ASCII 字符来画图的程序,支持以下两种操作:
  Ÿ 画线:给出两个端点的坐标,画一条连接这两个端点的线段。简便起见题目保证要画的每条线段都是水平或者竖直的。水平线段用字符 - 来画,竖直线段用字符 | 来画。如果一条水平线段和一条竖直线段在某个位置相交,则相交位置用字符 + 代替。
  Ÿ 填充:给出填充的起始位置坐标和需要填充的字符,从起始位置开始,用该字符填充相邻位置,直到遇到画布边缘或已经画好的线段。注意这里的相邻位置只需要考虑上下左右 4 个方向,如下图所示,字符 @ 只和 4 个字符 * 相邻。
  .*.
  *@*
  .*.
输入格式
  第1行有三个整数mnqmn分别表示画布的宽度和高度,以字符为单位。q表示画图操作的个数。
  第2行至第q + 1行,每行是以下两种形式之一:
  Ÿ 0 x1 y1 x2 y2:表示画线段的操作,(x1y1)和(x2y2)分别是线段的两端,满足要么x1 = x2 且y1 ≠ y2,要么 y1 = y2 且 x1 ≠ x2
  Ÿ 1 x y c:表示填充操作,(xy)是起始位置,保证不会落在任何已有的线段上;c 为填充字符,是大小写字母。
  画布的左下角是坐标为 (0, 0) 的位置,向右为x坐标增大的方向,向上为y坐标增大的方向。这q个操作按照数据给出的顺序依次执行。画布最初时所有位置都是字符 .(小数点)。
输出格式
  输出有n行,每行m个字符,表示依次执行这q个操作后得到的画图结果。
样例输入
4 2 3
1 0 0 B
0 1 0 2 0
1 0 0 A
样例输出
AAAA
A--A
样例输入
16 13 9
0 3 1 12 1
0 12 1 12 3
0 12 3 6 3
0 6 3 6 9
0 6 9 12 9
0 12 9 12 11
0 12 11 3 11
0 3 11 3 1
1 4 2 C
样例输出
................
...+--------+...
...|CCCCCCCC|...
...|CC+-----+...
...|CC|.........
...|CC|.........
...|CC|.........
...|CC|.........
...|CC|.........
...|CC+-----+...
...|CCCCCCCC|...
...+--------+...
................
评测用例规模与约定
  所有的评测用例满足:2 ≤ mn ≤ 100,0 ≤ q ≤ 100,0 ≤ x < mx表示输入数据中所有位置的x坐标),0 ≤ y < ny表示输入数据中所有位置的y坐标)。

解题思路:因为只有水平线和垂直线,所以判断线段相交的时候只需要判断这个位置有没有横线或竖线就行了。如果某个点本来是横线,而现在要画一条竖线,那么该点就要变成'+';要注意如果这个点本身就是'+',那么划线之后它仍然是一个'+'。这里有点小坑,如果不注意的话就只有90分。填充操作也很简单,DFS和BFS都可以,注意一下边界就行。还有一个地方是坐标的变换,因为题目给的坐标是以左下角为原点的,而数组在计算机中则并不是这样存的(应该是左上角为(0, 0)),需要进行一个变换。

代码如下:

#include <iostream>

using namespace std;
const int maxn = 105;
const int maxm = 105;
char maze[maxm][maxn];
int m, n, q;

void init() {
    for(int i = 0; i <= n; i++) {
        for(int j = 0; j <= m; j++) {
            maze[i][j] = '.';
        }
    }
}
//将坐标转换为数组下标
void transform(int &x, int &y) {
    int a = x, b= y;
    x = n - b - 1;
    y = a;
    return ;
}

void printLine(int x1, int y1, int x2, int y2) {
    //按照数组下标操作
    if(x1 == x2) {
        if(y1 > y2) swap(y1, y2);
        for(int j = y1; j <= y2; j++) {
            if(maze[x1][j] == '|' || maze[x1][j] == '+') maze[x1][j] = '+';
            else maze[x1][j] = '-';
        }
    }
    if(y1 == y2) {
        if(x2 > x1) swap(x1, x2);
        for(int i = x2; i <= x1; i++) {
            if(maze[i][y1] == '-' || maze[i][y1] == '+') maze[i][y1] = '+';
            else maze[i][y1] = '|';
        }
    }
    return ;
}

void fillArea(int x, int y, char ch) {
    if(x < 0 || x >= n || y < 0 || y >= m || maze[x][y] == ch ||
            maze[x][y] == '+' || maze[x][y] == '|' || maze[x][y] == '-')
        return ;
    maze[x][y] = ch;
    fillArea(x + 1, y, ch);
    fillArea(x, y + 1, ch);
    fillArea(x - 1, y, ch);
    fillArea(x, y - 1, ch);
    return ;
}

void print() {
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m; j++) {
            cout << maze[i][j];
        }
        cout << endl;
    }
}

int main(void) {
    cin >> m >> n >> q;
    int x1, x2, y1, y2, op;
    char ch;
    init();
    while(q--) {
        cin >> op;
        if(op == 0) {
            cin >> x1 >> y1 >> x2 >> y2;
            transform(x1, y1);
            transform(x2, y2);
            printLine(x1, y1, x2, y2);
        } else {
            cin >> x1 >> y1 >>ch;
            transform(x1, y1);
            fillArea(x1, y1, ch);
        }
    }
    print();
    return 0;
}


作者:qq_26658823 发表于2017/11/11 18:44:13 原文链接
阅读:142 评论:0 查看评论

轮播图实战

$
0
0

采用JQ1.8库,比较简结,不过该有的功能还是有的,欢迎在基础上自己添加需要的内容

源文档可以直接到我github上下载

轮播下载源文档地址

效果

这里写图片描述

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>轮播</title>
    <link rel="stylesheet" type="text/css" href="css/index.css">
</head>
<body>
    <div class="container">
        <div class="pic">
            <img src="images/1.jpg" width="1226" height="460" alt="11"/>
            <img src="images/2.jpg" width="1226" height="460" alt="22"/>
            <img src="images/3.jpg" width="1226" height="460" alt="33"/>
        </div>

        <div class="list">
            <ul>
                <li class="active"></li>
                <li></li>
                <li></li>
            </ul>
        </div>

        <div class="tip">
            <img src="images/left.png" alt="tip" id="left_tip"/>
            <img src="images/right.png" alt="tip" id="right_tip"/>
        </div>
    </div>

    <script type="text/javascript" src="js/jquery-1.8.0.min.js"></script>   
    <script type="text/javascript" src="js/index.js"></script>
</body>
</html>

CSS

*{margin: 0;padding: 0;}

.container{width: 1226px;height: 460px;margin: 0 auto;position: relative;}

.container .pic{width: 1226px;height: 460px;overflow: hidden;}
.container .list ul{width: 60px;height: 20px; position: absolute; bottom: 10px;left: 45%;}
.container .list ul li{float: left;width: 10px;height: 10px;border: 2px solid #fff;list-style: none;
                        line-height: 10px; text-align: center;margin-left:3px;  border-radius:5px; }/* 变成圆形 */
.container .list .active{background: #fff;}

.container .tip #left_tip{position: absolute; left: 0px;top: 45%;}
.container .tip #left_tip:hover{background:rgba(0,0,0,0.5);}

.container .tip #right_tip{position: absolute; right: 0px;top: 45%;}
.container .tip #right_tip:hover{background:rgba(0,0,0,0.5);}


Js

$(".container .list ul li").mouseover(function(){//mouseover:当鼠标放到该元素上
    clearInterval(timeplay);//清除时钟//目的是当鼠标放在指定位置时,不再自动轮播

    index=$(this).index();//index() 方法返回指定元素相对于其他指定元素的 index 位置
                            //this<==>.container .list ul li
    $(this).addClass("active").siblings().removeClass("active");//前半句是对元素增加一个active类
                                                                //siblings()指当增加了类时,剩下的其他元素执行后面的函数

    //eq(index)获取序列号
    //代码解析:获取图片的序列号,展示,当执行展示的时候,其他的图片隐藏
    $(".container .pic img").eq(index).show().siblings(".container .pic img").hide();
});
$("container .list ul li").mouseout(function(){//当鼠标移开时,自动轮播继续
    automatic();
});


//////////自动轮播///////////
var time=0;
timeplay=null;//全局变量

automatic();
function automatic(){
    //设置时钟
    //setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式。

    timeplay=setInterval(function(){
        ////setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。
        //由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数

        time++;

        if(time<3){
            //和上面类似,只是这里是自动执行
            $(".container .list ul li").eq(time).addClass("active").siblings().removeClass("active");
            $(".container .pic img").eq(time).show().siblings("img").hide();
        }else{
            time=-1;
        }   

    },1000);
}


////////////左切换///////////////
$(".container .tip #left_tip").click(function(){///////click为点击按钮是发生该事件
    time--;
    if(time>=0){
        $(".container .list ul li").eq(time).addClass("active").siblings().removeClass("active");
        $(".container .pic img").eq(time).show().siblings(".container .pic img").hide();
    }else{
        time=2
        $(".container .list ul li").eq(time).addClass("active").siblings().removeClass("active");
        $(".container .pic img").eq(time).show().siblings(".container .pic img").hide();
    }
});
/////////鼠标放在箭头上停止自动轮播/////////////
$(".container .tip #left_tip").mouseover(function(){
    clearInterval(timeplay);
}).mouseout(function(){
    automatic();
});


///////////右切换/////////////////
$(".container .tip #right_tip").click(function(){
    time++;
    if(time<3){
        $(".container .list ul li").eq(time).addClass("active").siblings().removeClass("active");
        $(".container .pic img").eq(time).show().siblings(".container .pic img").hide();
    }else{
        time=0
        $(".container .list ul li").eq(time).addClass("active").siblings().removeClass("active");
        $(".container .pic img").eq(time).show().siblings(".container .pic img").hide();
    }
});
/////////鼠标放在箭头上停止自动轮播/////////////
$(".container .tip #right_tip").mouseover(function(){
    clearInterval(timeplay);
}).mouseout(function(){
    automatic();
});
作者:w_linux 发表于2017/11/11 19:03:11 原文链接
阅读:87 评论:0 查看评论

Java for Web学习笔记(九十):消息和集群(5)利用websocket实现订阅和发布(上)

$
0
0

集群中的订阅和发布

利用spring framework在本app内的订阅和发布十分简单。当我们系统越来越复杂的时候,我们需要向其他app发布消息。本学习将给出一个通过websocket来实现不同app之间消息的订购和发布。

在小例子中,我们在所有节点之间都建立webSocket连接来实现消息的发布和订阅。这种方式,节点既是publisher,又是subcriber,还是broker。我们利用spring app内可监听不同消息,而无区分地将所有消息直接广播出去。具体步骤如下:

  1. 启动后,开启一个组播socket(224.0.0.4:6780),监听该组播,同时广播其websocket的地址
    • 使用@Service ClusterManager来实现
    • 需要在app启动完毕,可以正常工作时,才广播自己的websocket地址,例子讲通过一个ping的url来测试是否得到正确的回应来判断是否已经正常工作
  2. 集群中的其他app,监听到该广播后,获得新app的websocket地址,与之建立websocket连接
    • 由于是在组播地址中进行广播,所有自己也会收到,需要过滤掉自己发送的自己websocket地址
    • 维护与自己相连的websocket连接,无论自己作为server还是client,通过@Bean ClusterEventMulticaster来实现
    • websocket不属于spring framework,因此需要将其纳入spring框架,才能支持spring的自动注入
  3. 发布消息,如果需要发布到集群,则向所有建立的websocket连接发布
    • 有些消息需要发布到集群,有些消息可能只需要在本app内发布,用ClusterEvent来表示需要发布到集群的消息。
    • 发布消息由@Bean ClusterEventMulticaster来实现,向所有连接发布
    • 接收消息由websocket endpoint来实现
  4. app关闭时,关闭相关连接
    • 关闭组播socket
    • 关闭websocket的连接。

很显然,这是个N*N的websocket连接。在小规模的情况下,可能满足我们的要求。我们也可以有专门的broker,而websocket是节点与该broker之间的连接,这种模式即是WebSocket Application Messaging Protocol,不过小例子不采用专门broker的方式。

ClusterEvent

小例子将Event对象直接在websocket中进行传递,采用java序列化的方式。这种方式简单,但也有限制,也就是所对方也必须在java程序。我们还可以选择JSON或者XML的格式进行传递。我们在之前专门学习了序列化的基本知识,现在可以直接使用。

public class ClusterEvent extends ApplicationEvent implements Serializable{    
    private static final long serialVersionUID = 1L;    
    private final Serializable serializableSource;
    //在上一学习的基础上增加rebroadcasted,用于标识这是一个从外部接收的事件,不需要向集群广播。
    private boolean rebroadcasted;

    public ClusterEvent(Serializable source) {
        super(source);
        this.serializableSource = source;
    }

    public final boolean isRebroadcasted() {
        return rebroadcasted;
    }

    public final void setRebroadcasted() {
        this.rebroadcasted = true;
    }

    private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException{
        in.defaultReadObject();
        this.source = this.serializableSource;
    }
}

我们设置相关的Event和listener

public abstract class AuthenticationEvent extends ClusterEvent{
    private static final long serialVersionUID = 1L;

    public AuthenticationEvent(Serializable source) {
        super(source);
    }    
}

public class LoginEvent extends AuthenticationEvent{
    private static final long serialVersionUID = 1L;

    public LoginEvent(String username) {
        super(username);
    }
}

@Service
public class AuthenticationInterestedParty implements ApplicationListener<AuthenticationEvent>{
    private static final Logger log = LogManager.getLogger();    
    @Inject ServletContext servletContext;

    @Override
    public void onApplicationEvent(AuthenticationEvent event) {
        log.info("Authentication event from context {} received in context {}.",
                event.getSource(), this.servletContext.getContextPath());
    }
}

@Component
public class LoginInterestedParty implements ApplicationListener<LoginEvent>{
    private static final Logger log = LogManager.getLogger();    
    @Inject ServletContext servletContext;

    @Override
    public void onApplicationEvent(LoginEvent event) {
        log.info("Login event for context {} received in context {}.",
                event.getSource(), this.servletContext.getContextPath());
    }
}

ClusterManager:通过组播发布自己的位置

这作为一个Service纳入到spring framework中,我们的自定义ApplicationEventMulticaster为ClusterEventMulticaster,具体的websocket连接在ClusterMessagingEndpoint中实现。Spring在上下文初始化结束后,发布ContextRefreshedEvent事件,我们可以监听这个事件,就如同我们监听前面设置的LoginEvent那样。

@Service public class ClusterManager implements ApplicationListener<ContextRefreshedEvent>{...}

上下文,包括root上下文,web上下文和Rest上下文,按bootstrap的顺序,先是root上下文完成初始化,但此时app尚未能正常启动。我们可以具体检查事件,是否是最后一个上下文启动完毕。小例子中,我们采用另外一个方式,Controller中提供了一个ping接口,如果app正常工作,这个ping接口就可以正常回复200 OK。

用于检测是否正常工作的ping接口

@Controller
public class HomeController {
    @RequestMapping("/ping")
    public ResponseEntity<String> ping()  {
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "text/plain;charset=UTF-8");
        return new ResponseEntity<>("ok", headers, HttpStatus.OK);
    }
}

ClusterManager的代码

@Service
public class ClusterManager implements ApplicationListener<ContextRefreshedEvent>{
    private static final Logger log = LogManager.getLogger();

    //组播是UDP,本例子中组播地址为224.0.0.4,端口为6780
    private static final InetAddress MULTICAST_GROUP;
    private static final int MULTICAST_PORT = 6780;
    static{
        try {
            MULTICAST_GROUP = InetAddress.getByName("224.0.0.4");
        } catch (UnknownHostException e) {
            throw new FatalBeanException("Could not initialize IP addresses.", e);
        }
    }
    
    private boolean  initialized,destroyed = false;
    private MulticastSocket socket;
    private String pingUrl, messagingUrl;
    private Thread listenThread;
 
    @Inject ServletContext servletContext; //获取servlet Context,即可以获得在web.xml中的配置
    //multicaster是我们自定义的ApplicationEventMulticaster,我们将在那里维护和集群其他app的websocket连接。
    @Inject ClusterEventMulticaster multicaster;

    //【1】初始化:创建组播socket,并启动监听
    @PostConstruct
    public void listenForMulticastAnnouncements() throws NumberFormatException, IOException{
        //1.1】初始化设置pingUrl和websocket的Url。这里的host没有自动获取,而是通过配置,主要是多网卡的情况下,例如开发机上同时安装了虚机,可能会指定到其他地址。在稍后的组播设置中,需要指定network interface。所以方便起见,小例子采用了配置的方式。web的端口port也一样采用配置方式。
        String host = servletContext.getInitParameter("host");
        if(host == null)
            host = InetAddress.getLocalHost().getHostAddress();        
        String port = servletContext.getInitParameter("port");
        if(port == null)
            port = "8080";

        this.pingUrl = "http://" + host + ":" + port + this.servletContext.getContextPath() + "/ping";
        this.messagingUrl = "ws://" + host + ":" + port + this.servletContext.getContextPath() + "/services/Messaging/a83teo83hou9883hha9";

         //1.2】这里组播socket的创建,并在线程中开启监听
        this.socket =  new MulticastSocket(MULTICAST_PORT);
        this.socket.setInterface(InetAddress.getByName(host));//需要放在joinGroup()前,用于多网卡时确定使用哪个网卡,如单网卡,无需设置
        this.socket.joinGroup(MULTICAST_GROUP);
        this.listenThread = new Thread(this::listen, "cluster-listener"); //设置监听的线程
        this.listenThread.start();
    }

    //【2】在app正常运行后,通过组播socket,将自己的websocket的URL广播出去
    @Async //确保一定运行在线程中。
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        //2.1】initialized用于确保只执行一次,否则root context初始化完成执行一次,web context初始化完成又执行一次
        if(initialized)
            return;
        initialized = true;

        //【2.2】不断尝试访问自己的/ping接口,不成功,则休眠500ms,再次尝试,总的尝试次数限制为120次,即1分钟内,都尝试失败,就放弃
       try {
            URL url = new URL(this.pingUrl);
            log.info("Attempting to connect to self at {}.", url);

            int tries = 0;
            while(true){
                tries ++;
                //(2.2.1)方位自己的/ping,看看是否正常回复。这里学习一下URLConnection的使用
                URLConnection connection = url.openConnection();
                connection.setConnectTimeout(100);
                try(InputStream stream = connection.getInputStream()){
                    String response = StreamUtils.copyToString(stream,StandardCharsets.UTF_8);
                    if(response != null && response.equals("ok")){  //检查是否已经正常工作
                        //(2.2.2)app正常工作,此处将放置通过组播socket,将自己的websocket的url(messageUrl)广播出去的代码
                        DatagramPacket packet = new DatagramPacket(this.messagingUrl.getBytes(),this.messagingUrl.length(), 
                                                                   MULTICAST_GROUP, MULTICAST_PORT);                         
                        this.socket.send(packet);
                        return;
                    }else{
                        log.warn("Incorrect response: {}", response);
                    }
                }catch(Exception e){
                    if(tries > 120) {
                        log.fatal("Could not connect to self within 60 seconds.",e);
                        return;
                    }
                    Thread.sleep(500L);
                }
            }            
        } catch (Exception e) {
            log.fatal("Could not connect to self.", e);
        }
    }

    //【3】组播socket监听,如果听到由websocket的URL,则连接该URL,建立起websocket的连接。由于是组播,因此也会收到自己广播初期的自己的websocket的URL,需要将此过滤掉
    private void listen(){
        byte[] buffer = new byte[2048];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        while(true){
            try {
                this.socket.receive(packet);
                String url = new String(buffer, 0, packet.getLength()); //获取内容
                if(url.length() == 0)
                    log.warn("Received blank multicast packet.");
                else if(url.equals(this.messagingUrl)) //过滤掉自己的webSocket地址
                    log.info("Ignoring our own multicast packet from {}",packet.getAddress().getHostAddress());
                else
                    //3.1】在自定义的ApplicationEventMulticaster(维护各websocket连接)中,根据url创建一个websocket链接
                    this.multicaster.registerNode(url);
            } catch (IOException e) {
                if(this.destroyed)
                     return;
                log.error(e);
            }
        }
    }

    //【4】app关闭前,应关闭组播socket 
    @PreDestroy
    public void shutDownMulticastConnection() throws IOException {
        this.destroyed = true;
        try{
            this.listenThread.interrupt();
            this.socket.leaveGroup(MULTICAST_GROUP);
        }finally{
            this.socket.close();
        }
    }
}

相关链接: 我的Professional Java for Web Applications相关文章

作者:flowingflying 发表于2017/11/11 19:35:15 原文链接
阅读:97 评论:0 查看评论

Java for Web学习笔记(九一):消息和集群(6)利用websocket实现订阅和发布(下)

$
0
0

ClusterEventMulticaster:自定义的ApplicationEventMulticaster

设置为root上下文的bean

在root上下文的配置文件中:

@Bean
public ApplicationEventMulticaster applicationEventMulticaster(){    
    SimpleApplicationEventMulticaster eventMulticaster = new  ClusterEventMulticaster();
    //ClusterManager也是在监听事件,如果我们没有再次设置后台运行setTaskExecutor(),那么ClusterManager中的onApplicationEvent()就必须带上@Async的标记,否则这里会花费1分钟的时间,在此期间,由于无法往下继续进行web上下文的初始化(需先完成onApplicationEvent()),即/ping无法正常回应,最终导致失败。安全起见,我们仍可以在ClusterManager的onApplicationEvent()上加上@Async,大不了属于线程中的线程。
    eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
    return eventMulticaster;
}

ClusterEventMulticaster代码

public class ClusterEventMulticaster extends SimpleApplicationEventMulticaster{
    private static final Logger log = LogManager.getLogger();
    private final Set<ClusterMessagingEndpoint> endpoints = new HashSet<>(); //管理和维护各个websocket连接     
    @Inject ApplicationContext context; //作为在Root上下文中设置为bean,因此这里将获得的是root上下文

    //【1】自定义消息发布,如果属于ClusterEvent事件,将通过websocket发布给集群(当然排除掉接收的集群事件)
    @Override
    public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
        try{
            super.multicastEvent(event, eventType);
        }finally{
            try{
                if(event instanceof ClusterEvent && !((ClusterEvent)event).isRebroadcasted())
                     publishClusteredEvent((ClusterEvent)event); 
            }catch(Exception e){
                log.error("Failed to broadcast distributable event to cluster.",e);
            }
        }    
    }

    //【2】集群事件收发的相关方法
    //【2.1】通过websocket向集群广播事件
    private void publishClusteredEvent(ClusterEvent event){
        synchronized(endpoints){
            for(ClusterMessagingEndpoint endpoint : this.endpoints){
                endpoint.send(event);
            }
        }
    }

    //【2.2】从websocket中接收到事件,需要向本地发布,以便触发响应的ApplicationEventListener。同时设置了消息中的rebroadcasted标记,表明无需向集群广播词消息 
    protected final void handleReceivedClusteredEvent(ClusterEvent event){
        event.setRebroadcasted();
        this.multicastEvent(event,null); //相当于在本地发布
    }

    //【3】管理socket连接(ClusterMessagingEndpoint)    
    //【3.1】注册websocket连接    
    protected void registerEndpoint(ClusterMessagingEndpoint endpoint){
        synchronized (endpoints) {
            this.endpoints.add(endpoint);
        }        
    }

    //【3.2】注销websocket连接    
    protected void deregisterEndpoint(ClusterMessagingEndpoint endpoint){
        synchronized(endpoints){
            this.endpoints.remove(endpoint);
        }
    }

    //【3.3】程序退出时,关闭所有的websocket连接      
    @PreDestroy
    public void shutdown(){
        synchronized(this.endpoints){
            for(ClusterMessagingEndpoint endpoint : this.endpoints){
                endpoint.close();
            }
        }
    }

    //【3.4】此处更合适的名字是createNode,根据监听的到websocket的url,创建连接(创建ClusterMessagingEndpoint对象),需要注意的WebSocketEndpoint不属于Spring框架,要手动将其纳入,否则无法在WebSocketEndpoint中支持@Inject。这在之前学些过,再次重温。
    protected void registerNode(String endpoint){
        log.info("Connecting to cluster node {}.", endpoint);
        /* A WebSocketContainer is an implementation provided object that provides applications a view 
         * on the container running it. The WebSocketContainer container various configuration parameters 
         * that control default session and buffer properties of the endpoints it contains. It also allows 
         * the developer to deploy websocket client endpoints by initiating a web socket handshake from 
         * the provided endpoint to a supplied URI where the peer endpoint is presumed to reside. 
         * 
         * A WebSocketContainer may be accessed by concurrent threads, so implementations must ensure the 
         * integrity of its mutable attributes in such circumstances.
         * 
         * URI uri = new URI("ws","localhost:8080",path,null,null);
         * Session session = ContainerProvider.getWebSocketContainer().connectToServer(this, uri);
         *  */
        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
        /* ClusterMessagingEndpoint是websocket的cilent+server,websocket不是spring framework。如果要向bean那样可以自动注入等,我们需要使用org.springframework.beans.factory.annotation.Configurable标注对于非Spring管理的bean。*/
        ClusterMessagingEndpoint bean = this.context.getAutowireCapableBeanFactory()
                    .createBean(ClusterMessagingEndpoint.class);
        try {
            log.info("Connect to server {}", endpoint);
            container.connectToServer(bean, new URI(endpoint));
        } catch (DeploymentException | IOException | URISyntaxException e) {
            log.error("Failed to connect to cluster node {}.", endpoint, e);
        }
    }
}

ClusterMessagingEndpoint:websocket的endpoint

//【1】我们关心的是从websocket连接中收到的集群的event,对于websocket的server和client放在一起处理。将事件对象序列化后,直接在websocket的连接中传递,因此codec就是实现序列化和反序列化,由内部静态类Codec来实现
@ServerEndpoint(value = "/services/Messaging/{securityCode}",
    encoders = { ClusterMessagingEndpoint.Codec.class },
    decoders = { ClusterMessagingEndpoint.Codec.class },
    configurator = SpringConfigurator.class)
@ClientEndpoint(
    encoders = { ClusterMessagingEndpoint.Codec.class },
    decoders = { ClusterMessagingEndpoint.Codec.class })
public class ClusterMessagingEndpoint {
    private static final Logger log = LogManager.getLogger();
    private Session session;
    //支持@Inject,对于server,设置configurator为SpringConfigurator.class,每一个新的连接都会创建@ServiceEndpoint的bean。对于client需要作为Spring的bean创建,见之前在ClusterEventMulticaster中的代码。
    @Inject ClusterEventMulticaster multicaster; 

    @OnOpen
    public void open(Session session){
        Map<String, String> parameters = session.getPathParameters();
        if(parameters.containsKey("securityCode") && !"a83teo83hou9883hha9".equals(parameters.get("securityCode"))){
            try {
                log.error("Received connection with illegal code {}.", parameters.get("securityCode"));
                session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "Illegal Code"));
            } catch (IOException e) {
                log.warn("Failed to close illegal connection.", e);
            }
        }else{
            log.info("Successful connection onOpen.");
            this.session = session;
            this.multicaster.registerEndpoint(this); //websocket连接成功建立
        }
    }

    @OnMessage
    public void receive(ClusterEvent message){
        this.multicaster.handleReceivedClusteredEvent(message); //收到集群中的消息
    }

    @OnClose
    public void close(){
        log.info("Cluster node connection closed.");
        this.multicaster.deregisterEndpoint(this); //对方关闭连接
        if(this.session.isOpen()){
            try {
                this.session.close();
            } catch (IOException e) {
                log.warn("Error while closing cluster node connection.", e);
            }
        }
    }

    public void send(ClusterEvent message){
        try {
            session.getBasicRemote().sendObject(message);  //通过websocket连接发布事件(消息)
        } catch (IOException | EncodeException e) {
            log.error("Failed to send message to adjacent node.", e);
        }
    }

    //【2】将事件对象序列化后,直接在websocket的连接中传递,因此codec就是实现序列化和反序列化,由内部静态类Codec来实现
    public static class Codec implements Encoder.BinaryStream<ClusterEvent>,Decoder.BinaryStream<ClusterEvent> {
        @Override
        public void init(EndpointConfig config) {                
        }

        @Override
        public void destroy() {
        }

        @Override
        public ClusterEvent decode(InputStream is) throws DecodeException, IOException {
            try(ObjectInputStream ois = new ObjectInputStream(is);){
                try {
                    return (ClusterEvent)ois.readObject();
                } catch (ClassNotFoundException e) {
                    throw new DecodeException((String)null, "Failed to decode.", e);
                }
            }            
        }

        @Override
        public void encode(ClusterEvent object, OutputStream os) throws EncodeException, IOException {
            try(ObjectOutputStream oos = new ObjectOutputStream(os);){
                oos.writeObject(object);
            }            
        }        
    }
}

相关链接: 我的Professional Java for Web Applications相关文章

作者:flowingflying 发表于2017/11/11 20:26:45 原文链接
阅读:79 评论:0 查看评论

Advanced Programming in UNIX Environment Episode 14

$
0
0
#include "apue.h"
#include <fcntl.h>

int main(int argc, char *argv[])
{
    int val;

    if(argc!=2)
    {
        err_quit("usage: a.out <descriptor#>");
    }

    if((val=fcntl(atoi(argv[1]), F_GETFL,0))<0)
    {
        err_sys("fcntl error for fd %d",atoi(argv[1]));
    }

    switch(val&O_ACCMODE)
    {
        case O_RDONLY:
            printf("read only");
            break;
        case O_WRONLY:
            printf("write only");
            break;
        case O_RDWR:
            printf("read write");
            break;
        default:
            err_dump("unknown access mode");
    }

    if(val&O_APPEND)
    {
        printf(", append");
    }
    if(val&O_NONBLOCK)
    {
        printf(", nonblocking");
    }
    if(val&O_SYNC)
    {
        printf(", synchronous writes");
    }

#if !define(_POSIX_C_SOURCE)&&defined(O_FSYNC)&&(O_FSYNC!=O_SYNC)
    if(val&O_FSYNC)
    {
        printf(", synchronous writes");
    }
#endif

    putchar("\n");
    return 0;
}

对于指定文件描述符打印文件标志

#include "apue.h"
#include <fcntl.h>

void set_fl(int fd, int flags)
{
    int val;

    if((val=fcntl(fd,F_GETFL,0))<0)
    {
        err_sys("fcntl F_GETFL error");
    }

    val|=flags;

    if(fcntl(fd,F_SETFL,val)<0)
    {
        err_sys("fcntl F_SETFL error");
    }
}

对一个文件描述符开启一个或多个文件状态标志
中间一条语句改为:

val&=~flags;

就构成另一个函数,我们成为clr_fl。此语句是当前文件状态标志值val与flags的反码进行逻辑“与”运算。
在开始处加上一句以调用set_fl,则开启了同步写标志。

set_fl(STDOUT_FILENO,O_SYNC);

这就使每次write都要等待,直至数据已写到磁盘上再返回。

作者:myfather103 发表于2017/11/11 21:46:48 原文链接
阅读:64 评论:0 查看评论

leetcode: 31. Next Permutation

$
0
0

Q

Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.

If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order).

The replacement must be in-place, do not allocate extra memory.

Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.

Translation

假设数组中所有数值组成的排列组合是个循环列表,则返回该输入组合的下一组合。

Ideas

nums[k:]的盈满则亏之演。

Example

1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

AC

class Solution(object):
    def nextPermutation(self, nums):
        """
        :type nums: List[int]
        :rtype: void Do not return anything, modify nums in-place instead.
        """
        k, l = -1, 0
        for i in xrange(len(nums) - 1):
            if nums[i] < nums[i + 1]:
                k = i   # 保证了k之后必定是降序的
        if k == -1:
            nums.reverse()  # 全称降序,那还找个毛,直接回到循环列表表首咯 (。・・)ノ
            return
        for i in xrange(k + 1, len(nums)):
            if nums[i] > nums[k]:   # 免得从后面拉出来一个比 nums[k] 还小的,那就搞笑了
                l = i   # 因为k之后必定是降序的,所以最后跑出来的l必定是k之后 比nums[k]大的最小元素 的下标,
        nums[k], nums[l] = nums[l], nums[k]
        nums[k + 1:] = nums[:k:-1]  # 每次对于 nums[k:] 来说,都是一次overflow,所以一定是从最满(降序)变成最底(升序)

if __name__ == "__main__":
    nums = [1, 4, 3, 2]
    Solution().nextPermutation(nums)
    assert nums == [2, 1, 3, 4]


作者:JNingWei 发表于2017/11/11 16:56:19 原文链接
阅读:9 评论:0 查看评论

leetcode: 32. Longest Valid Parentheses

$
0
0

Q

Given a string containing just the characters '(' and ')', find the length of the longest valid (well-formed) parentheses substring.

For "(()", the longest valid parentheses substring is "()", which has length = 2.

Another example is ")()())", where the longest valid parentheses substring is "()()", which has length = 4.

AC

class Solution(object):
    def longestValidParentheses(self, s):
        """
        :type s: str
        :rtype: int
        """
        stack = []
        maxlen = 0
        start = -1
        for i, c in enumerate(s):
            if c=='(':
                if start>=0:
                    stack.append((c, start))
                    start = -1
                else:
                    stack.append((c, i))
            else:
                if stack and stack[-1][0]=="(":
                    length = i-stack[-1][1]+1
                    if length>maxlen:
                        maxlen = length
                    start = stack[-1][1]
                    stack.pop()
                else:
                    start = -1
                    stack.append((c, i))
        return maxlen

if __name__ == "__main__":
    assert Solution().longestValidParentheses("()") == 2
    assert Solution().longestValidParentheses(")()())") == 4


作者:JNingWei 发表于2017/11/11 17:03:29 原文链接
阅读:14 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>