使用XShell的隧道功能实现网络穿透

通常我们开发都是在自己电脑环境下开发,遇到需要与外部网络调试时将非常不方便,比如微信公众号开发,物联网开发等场景;如果可以将服务器的请求转发到自己的电脑上那么这些问题都迎刃而解;这时候我们可以使用xshell的隧道功能,将服务器的某个端口转发到自己电脑上

0x0,安装好Xshell并配置好服务器IP

0x1,配置隧道

如图:类型(方向)选择远程传入,源主机0.0.0.0,目标主机为局域网IP,千万不用使用127.0.0.1否则接收不到;

配置完成后我们链接到服务器用 netstat 命令可以查看

如图,服务器在0.0.0.0上监听了9999端口,若显示的是127.0.0.1:9999则还需要修改 sshd_conf文件

vim /etc/ssh/sshd_config

找到GatewayPorts 去掉注释将 no 改成 yes

GatewayPorts yes

再执行 systemctl restart sshd.service 重启sshd 服务,关闭Xshell再次进入后执行 netstat  命令将显示0.0.0.0:9999 ;

这样所有服务器9999端口的请求都会转发到Xshell所在局域网的192.168.188.125的9999端口上;

如何使用 ZeroBrane Studio 调试嵌入式 Lua

C++调用Lua如何调试lua代码呢,可以使用 mobdebug.lua + ZeroBrane Studio 进行调试

0x00:工具下载

mobdebug.lua 项目地址:https://github.com/pkulchenko/MobDebug

ZeroBrane Studio 项目地址:https://github.com/pkulchenko/ZeroBraneStudio

ZeroBrane Studio 官网地址:https://studio.zerobrane.com/

在官网上找到对应的操作系统版本下载

0x01:实例操作

C++代码

#include "lua.hpp"

int main() {
    lua_State *L =  luaL_newstate();
    luaL_openlibs(L);
    luaopen_debug(L);
    luaL_dofile(L,"a.lua");
    lua_pcall(L,0,0,0);
    lua_close(L);
    return 0;
}

通过C++代码调用a.lua,输出“hello world”

a.lua

require('mobdebug').start()
function hello(l)
    print("hello world")
end
hello(1);

代码很简单,只要在需要调试的lua文件上加上require(‘mobdebug’).start()这行代码。并且将 mobdebug.lua 文件,放到lua代码目录下,如下图:

打开ZeroBrane Studio,将lua代码目录设置成project,在需要调试的地方打上断点。

在Project菜单,选择 Start Debugger Server即可。

然后直接执行C++代码;触发断点会在ZeroBrane中显示,就是这么简单

主要窗口就是 Stack和Watch窗口,一个显示调用栈,一个用来查看当前变量的值信息。

通过调整 require(‘mobdebug’).start(‘192.168.1.110’) 的IP参数OR端口,可实现调试嵌入移动端的Lua

谨记结构体中包含指针变量时候的内存初始化

被这个问题坑过两次,第一次没有记录下来,第二次果断又被坑,赶紧记录下,例如下面的结构体

struct tagUserItem {
    int iUserID;
    std::string strUserName;
}
tagUserItem userItem;

使用 memset(&userItem,0,sizeof(userItem)); 方式将内存为NULL,在IOS和WINDWOS下对strUserName进行赋值都没问题,但是一到NDK编译的安卓系统中运行就报错。在NDK中这种初始化方式会导致 strUserName 指针异常!!! IOS和windows暂时都没问题, 以防再次被坑,赶紧记录下来。!!!

2018年07月17日记录新坑
虽然 IOS和windows暂时都没问题不会报错,但是memset后指针类型的元素 会变成野指针,内存不会被释放,会导致内存溢出。谨记。

swing中给JTable加上水平与垂直滚动条与设置条头

通常使用JAVA都是用来开发web应用,很少用于开发桌面应用,每次都需要百度同样的内容,顾做个记录”何如给JTable加上水平与垂直滚动条与设置表头”

1、JTable需要放在JScrollPane容器中,JScrollPane定义好宽和高。

jTable = new JTable();
jTable.setFillsViewportHeight(true);
jScrollPane.setViewportView(jTable);

如此一番操作后,JTable的row如果超过jScrollPane的高度就会出现,但是会发现水平滚动条是不会出现的,原因是JTable默认是自动适应列宽;若需要水平滚动条首先设置 jTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 关闭自动宽度设置,接着同过如下代码:

jTable.getColumnModel().getColumn(0).setPreferredWidth(80);	
jTable.getColumnModel().getColumn(1).setPreferredWidth(120);	
jTable.getColumnModel().getColumn(2).setPreferredWidth(120);	

手动指定好jTable的列宽,如果操作后,若宽度超出后,就会自动出现滚动条

2、JTable设置表头,代码大致如下,通过DefaultTableModel设置ColumnIdentifiers

jTable.setModel(new DefaultTableModel());
((DefaultTableModel) jTable.getModel()).setColumnIdentifiers(new String[]{"列1","列2","列3"});

其实代码都很简单,但是实在是用的太少,一年半载用不上一次,所以很容易忘记。

Spring-boot事件通知机制用法与原理分析

首先要说明的是Spring-boot的事件通知机制并不是Spring-boot的功能,而是Spring具有的功能。

0x00、使用篇

从用法上讲是很简单的,一般分为三步

一、定义事件源

创建一个事件源类继承ApplicationEvent这个类,这个类构造函数默认有个 source 的入参数,用来方便事件传参数,定义一个事件类的代码类似如下:

import org.springframework.context.ApplicationEvent;

/**
 * Created by 席有芳 on 2018/7/7.
 *
 * @author 席有芳
 */
public class StartedEvent extends ApplicationEvent {
    /**
     * Create a new ApplicationEvent.
     *
     * @param source the object on which the event initially occurred (never {@code null})
     */
    public StartedEvent(Object source) {
        super(source);
    }
}

二、定义事件监听器

首先要注意的是事件监听器是Spring的一个bean,所以一定要加上@Component这个注解或者类似的定义bean的注解,在需要处理事件的方法上加上@EventListener的注解并指定事件源,大致代码如下:

@Component
public class ShowUIEventListener {
  
    @EventListener(StartedEvent.class)
    public void showUI(StartedEvent startedEvent) {
		//具体业务逻辑
    }
}	

一个事件源可以绑定多个事件监听器,只要在不同的bean方法上加上同样的 @EventListener 注解就行,可以通过加上 @Order 注解控制监听器的执行顺序。可以通过 event.getSource() 获取到构造函数时候指定的对象。

三、发布并执行事件

通过 applicationContext.publishEvent(event); 的方式发布一个事件,事件发布完成后会自动触发并执行相关的EventListener 。

使用方法就是这么简单。然后我们来具体分析spring的源码实现。

0x01、原理篇

首先根据publishEvent的方法可以发现 Spring的ApplicationEventPublisher.java这个接口定一个了 publishEvent 相关的方法。追踪源码发现具体实现在 AbstractApplicationContext.java 这个类中。这个类中会通过 ApplicationEventMulticaster 来广播事件,具体代码如下:

源码很清晰,同时也可以发现如果是SpringMVC这种子容器定义的事件也会同步广播到父容器中。
接着看看广播器的源码

主要就两个部分,第一部分根据事件源提取出事件类型,第二步根据事件源与事件类型获取到对应的bean 并执行相关的方法。
getApplicationListeners 这个方法在 AbstractApplicationEventMulticaster 类中实现,具体逻辑大致是 首先去缓存里获取如果获取不到则调用retrieveApplicationListeners方法获取。
getApplicationListeners方法如下:

retrieveApplicationListeners方法如下:

代码很明显,容器的bean中找出满足规则的Listener组成一个列表,并放到缓存里。

Spring-boot启动过程之ApplicationContext

虽然精通Spring和SpringMVC框架,Spring-boot也一直关注,只是项目情况没具体自己从搭框架开始使用,这次终于准备使用Spring-boot搭建个小系统,记录下这个过程中的一些问题与心得;

首先仅仅用maven引入:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <version>2.0.0.RELEASE</version>
</dependency>

使用如下代码启动后 进程很快就结束,而以前的WEB项目能够正常的监听网络端口,将程序hold。

@SpringBootApplication
public class PlazaApplication {

    /**
     * @param args 参数
     */
    public static void main(String[] args) {
        SpringApplication.run(PlazaApplication.class, args);
    }
}

这时在maven中加入如下依赖,再次启动程序后,发现项目变成了WEB项目,观察jar导入情况会发现加入和很多web相关的jar

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
	<version>2.0.0.RELEASE</version>
</dependency>

这时打断点跟踪代码,发现在SpringAppliction.java的createApplicationContext方法创建 ConfigurableApplicationContext 过程中会根据项目类型使用不同的ApplicationContext初始化IOC容器,在deduceWebApplicationType方法中会根据Class判断项目类型

	private WebApplicationType deduceWebApplicationType() {
		if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
				&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : WEB_ENVIRONMENT_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}	

从代码中可以看出Spring-boot根据jar包中是否存在某些类来判断项目类型,项目类型一共有 REACTIVE、SERVLET、NONE 三种。
REACTIVE、SERVLET两种会内置web容器会以web程序进行启动,NONE则不是个web程序执行完成后进程就结束。

所以如果使用spring-boot启动为web项目必须要引入 spring-boot-starter-web 的jar
如果不是个web项目,而是个有界面的桌面项目,启动程序也需要略作修改

    /**
     * @param args 参数
     */
    public static void main(String[] args) {
        SpringApplicationBuilder builder = new SpringApplicationBuilder(PlazaApplication.class);
        builder.headless(false).web(WebApplicationType.NONE).run(args);
    }

headless设置为false很重要,否则初始化 AWT 会抛出java.awt.HeadlessException的异常!

Cocos2d-X定时任务实现参数传递

通常Cocos2d-X实现定时任务通过

void Node::schedule(SEL_SCHEDULE selector, float interval, unsigned int repeat, float delay);
void Node::scheduleOnce(SEL_SCHEDULE selector, float delay);

这两种方式,都是传递 SEL_SCHEDULE 类型的方法指针,查看SEL_SCHEDULE定义可以发现定义如下:

typedef void (Ref::*SEL_SCHEDULE)(float);

这个定义只传递一个 float 参数,如果需要传递参数怎么办呢,其实可以通过如下方式:
getScheduler()->schedule(CC_CALLBACK_1(AAA::run, this, 参数1, 参数2), this, 0.0f, 0, 2.0f, false, “定时器名称”);

void Scheduler::schedule(const ccSchedulerFunc& callback, void *target, float interval, unsigned int repeat, float delay, bool paused, const std::string& key)

定时任务的Node一定要加入到 场景中才会执行哦。!