开源的许可证GPL、LGPL、BSD、Apache 2.0的通俗解释

软件开发者要开源软件,不单单是开放源代码就可以了,选择一种许可证很重要,一个许可证之于软件就相当于价值观之于普通人,代表了这个软件的基本品性。一个错误的许可证选择可能会直接导致整个项目的失败。

各种开源的许可证主要的限制还是在redistribution(发布),所以个人/商业公司开发的软件包含了GPL的代码,只要你不发布,是可以任意使用的。

下面是几个开源许可证的区别:

GPL
GPL软件的使用者有权力得到软件的代码,只要使用了GPL,在发布(redistribution)时,整个项目也必须是GPL的,即主程序和静态链接的库(linux的.a和Windows的.lib)必须是GPL的,动态链接库(Linux的.so,Windows的.dll)必须是GPL兼容的。所谓GPL兼容,也就是GPL软件中可以使用的库,这些许可证必须比GPL弱(如LGPL,BSD),而不能是某个商业许可证。正因如此,GPL是带有很强的传染性,只要你的软件使用了GPL的代码,那么就请以GPL开放源代码吧,并且你的项目中也不能有任何和GPL不兼容的库。

LGPL
GPL 带有很强的传染性,那么如果一个库使用GPL发布,那么使用这个库的所有软件也必须使用GPL发布,这对不想开放源代码的商业软件来讲是致命的打击——你可以不使用其他的库,但最基本的libc是无论如何绕不开的,如果libc是以GPL发布,就相当于所有软件必须以GPL发布了。所以,LGPL(Lesser GPL)诞生了。

LGPL定义为,在以LGPL发布的库的基础上开发新的库的时候,新的库必须以LGPL发布,但是如果仅仅是动态链接,那么则不受任何限制。这样商业软件就可以随意的使用LGPL的库了。因此,LGPL也具有传染性,但限制在其基础上开发的库上,而并不限制使用它的程序本身——它的传染性远小于GPL。

BSD、Apache 2.0
相对GPL/LGPL的开放源代码,BSD,Apache 2.0就宽松许多——商业软件可以任意的使用BSD,Apache 2.0发布的软件代码,而不需要开放源代码,只需要提及代码的原出处就可以了。BSD和Apache 2.0提及的方式稍有不同,具体可以参考协议的详细内容。它们是GPL兼容的

 

看看下面选择开源许可证的案例:

andorid 使用宽松的Apache 2.0发布,因为Google作为一个商业公司,并不想失去商业软件的支持,它希望团结一切可以团结的力量加入的Android的开发中来,壮大自己的阵营,使用Apache 2.0就无可厚非了。而Google本身,并没有丧失对Android的控制权,不会担心另外一个公司拿走了Android的代码开发出一个闭源 Android的对手。因为,只要Android不断的出新版,社区不停的跟进,并且不停的修改API,其他基于Android开发的公司不得不把自己的Patch提回到主干上,否则,必然将耗费大量人力物力在维护自己的Patch上(钱这方面你斗得过Google?),得不偿失。而且,闭源之后,与整个社区为敌,作为一个定位软件平台的项目,会流失大量应用软件开发者,以小博大,任何一个商业公司都不会干这种胜算不高的蠢事。
再看以GPL发布的Linux为什么比以BSD发布的FreeBSD成功。其实正是因为GPL的传染性。当一个开发人员在Linux基础上开发一个新功能之后, 不得不以GPL开放源代码,贡献回Linux,这样Linux本身才能越来也越壮大而且留住了相当的开发人员,形成了一个 优秀软件->很多使用者和贡献者->贡献->更优秀的软件->更多的使用者和贡献者… 的良性循环。

 

Spring boot整合Redis做数据缓存

redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。

Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。

redis的官网地址,非常好记,是redis.io。(特意查了一下,域名后缀io属于国家域名,是british Indian Ocean territory,即英属印度洋领地)目前,Vmware在资助着redis项目的开发和维护。

下面是官方的bench-mark数据:
测试完成了50个并发执行100000个请求。
设置和获取的值是一个256字节字符串。
Linux box是运行Linux 2.6,这是X3320 Xeon 2.5 ghz。
文本执行使用loopback接口(127.0.0.1)。
结果:读的速度是110000次/s,写的速度是81000次/s

redis提供五种数据类型:string,hash,list,set及zset(sorted set)。
string(字符串)
string是最简单的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value,其上支持的操作与Memcached的操作类似。但它的功能更丰富。
redis采用结构sdshdr和sds封装了字符串,字符串相关的操作实现在源文件sds.h/sds.c中。

list(双向链表)
list是一个链表结构,主要功能是push、pop、获取一个范围的所有值等等。操作中key理解为链表的名字。

dict(hash表)
set是集合,和我们数学中的集合概念相似,对集合的操作有添加删除元素,有对多个集合求交并差等操作。操作中key理解为集合的名字。

dict中table为dictEntry指针的数组,数组中每个成员为hash值相同元素的单向链表。set是在dict的基础上实现的,指定了key的比较函数为dictEncObjKeyCompare,若key相等则不再插入。

zset(排序set)
zset是set的一个升级版本,他在set的基础上增加了一个顺序属性,这一属性在添加修改元素的时候可以指定,每次指定后,zset会自动重新按新的值调整顺序。可以理解了有两列的mysql表,一列存value,一列存顺序。操作中key理解为zset的名字。

zset利用dict维护key -> value的映射关系,用zsl(zskiplist)保存value的有序关系。zsl实际是叉数不稳定的多叉树,每条链上的元素从根节点到叶子节点保持升序排序。

tomcat配置SSL环境

生成安全证书:

1.java环境:因为SUN公司提供了制作证书的工具keytool。在JDK 1.4以后的版本中都包含了这一工具,它的位置为<JAVA_HOME>\bin\keytool.exe。

2.创建证书的命令:

Cmd代码
  1. keytool -genkeypair -alias “tomcat” -keyalg “RSA” -keystore “f:\tomcat.keystore”

参数的意思如下:

1505117109-4382-e190-3065-9e30-7633e955be16

这里密码我输的是tomcat,名字与姓氏为域名,其它的根据具体情况输入f38ff7b2-cfa7-3b70-b2ff-fa3588b462e7

 

以上命令将生产一对非对称密钥和自我签名的证书f:\tomcat.keystore.

将证书保存到你要存放的地方,我的保存在D:\Tools\Web\ssl\tomcat.keystore

注意:“名字与姓氏”应该是域名,输成了姓名,和真正运行的时候域名不符,会出问题

—————————————————————————————————————————

配置tomcat:

定位到tomcat的安装目录,找到conf下的server.xml文件

找到如下已经被注释的代码:

Xml代码
<!– <Connector port=”8443″ protocol=”org.apache.coyote.http11.Http11NioProtocol”
maxThreads=”150″ SSLEnabled=”true” scheme=”https” secure=”true” keystoreFile=”/root/shangdao.keystore” keystorePass=”111111″
clientAuth=”false” sslProtocol=”TLS” /> –>

去掉注释,修改为:

Xml代码
<Connector port=”8443″ protocol=”org.apache.coyote.http11.Http11NioProtocol”
maxThreads=”150″ SSLEnabled=”true” scheme=”https” secure=”true” keystoreFile=”/root/shangdao.keystore” keystorePass=”111111″
clientAuth=”false” sslProtocol=”TLS” />

这里,密码和证书的位置根据个人的具体环境而设置,属性参数如下所述:

属性 描述
clientAuth 如果设为true,表示Tomcat要求所有的SSL客户出示安全证书,对SSL客户进行身份验证
keystoreFile 指定keystore文件的存放位置,可以指定绝对路径,也可以指定相对于<CATALINA_HOME>(Tomcat安装目录)环境变量 的相对路径。如果此项没有设定,默认情况下,Tomcat将从当前操作系统用户的用户目录下读取名为“.keystore”的文件。
keystorePass 指定keystore的密码,如果此项没有设定,在默认情况下,Tomcat将使用“changeit”作为默认密码。
sslProtocol 指定套接字(Socket)使用的加密/解密协议,默认值为TLS,用户不应该修改这个默认值。
ciphers 指定套接字可用的用于加密的密码清单,多个密码间以逗号(,)分隔。如果此项没有设定,在默认情况下,套接字可以使用任意一个可用的密码。

访问支持ssl的web站点:

启动tomcat,在浏览器中输入:https://localhost:8443/。

QQ截图20170911161055

Java使用websocket实现聊天室简单功能

最近有个项目需要实现视频流的信息交换处理,但是之前一直都没有写过有关的代码,所以就想到了websocket接口。

java创建一个socket非常简单,繁琐的可能是日常的业务信息处理,下面看下一段代码:

import model.User;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * Created by alan.luo on 2017/9/10.
 */

@ServerEndpoint(value = "/websocket2")
public class TestWebSocket {

    @OnOpen
    public void onOpen(Session session, EndpointConfig endpointConfig) {
        sendMsg(session,"Welcome back.");

        //push list.
        User user = new User();
        user.setId(Integer.parseInt(session.getId()));

        Map<String,List<String>> map = session.getRequestParameterMap();
        user.setUserName(map.get("userName").get(0));
        user.setSession(session);

        //has client come in,so server must be save the session to the application.class.
        Application.getInstance().putSession(user);
    }

    @OnMessage
    public void onMessage(Session session,String message){
        System.out.println("++++++++++:"+message);

        //if has user push message to the server.then server will each all the client and send message too.
        for (User user:Application.getInstance().getUsers()){
            sendMsg(user.getSession(),user.getUserName() + " say:"+message);
        }

    }

    @OnClose
    public void onClose(Session session, CloseReason closeReason){
        List<User> list = Application.getInstance().getUsers();
        //pop list.
        for (int i = 0;i<list.size();i++){
            if (Integer.parseInt(session.getId()) == list.get(i).getId()){
                list.remove(i);
                Application.getInstance().setUsers(list);
                break;
            }
        }
    }

    @OnError
    public void onError(Session session, Throwable thr) {
        System.out.println("+++++++++onError"+thr.getMessage());
    }

    /**
     * send message to session.
     * @param session
     * @param message
     */
    protected void sendMsg(Session session,String message){
        System.out.println(message);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            session.getBasicRemote().sendText(message+" >"+sdf.format(new Date()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

当有客户端进入,则把客户端保存到application这个实例中,application是自定义的一个类,实现了单例,所以保存进去是最合适的,当有消息进入时,server将从application读取session list 并且遍历全部session and send the message。

Application实例的代码:

import model.User;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by alan.luo on 2017/9/10.
 */
public class Application {

    private List<User> users;
    private static Application app;

    public Application(){
        users = new ArrayList<>();
    }

    public static Application getInstance(){
        if (app == null){
            app = new Application();
        }
        return app;
    }

    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }

    public void putSession(User s){
        this.users.add(s);
    }

}

User实体类的代码:

package model;

import javax.websocket.Session;

/**
 * Created by alan.luo on 2017/9/6.
 */
public class User {
    private int id;

    private String userName;

    private String userPassword;

    private Session session;

    public Session getSession() {
        return session;
    }

    public void setSession(Session session) {
        this.session = session;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPassword() {
        return userPassword;
    }

    public void setUserPassword(String userPasswd) {
        this.userPassword = userPasswd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", userPassword='" + userPassword + '\'' +
                '}';
    }
}

可能会有错误,那是因为需要配置一下web.xml

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/websocket2</url-pattern>
</servlet-mapping>

 

剩下的就是javascript客户端的代码了:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <p><input type="text" id="userName" value="" placeholder="输入名字进入聊天室"></p>
</div>
<div>
    <textarea rows="16" cols="40" id="content"></textarea>
</div>
<div>
    <input type="text" id="message" value="" placeholder="输入内容"/>
    <button type="button" id="send">send</button>
</div>
<div>

    <button type="button" id="connect">connect</button>
    <button type="button" id="destroy">destroy</button>
</div>
<script>

        var connect = document.getElementById("connect");
        var content = document.getElementById("content");
        var message = document.getElementById("message");
        var destroy = document.getElementById("destroy");
        var send = document.getElementById("send");
        var userName = document.getElementById("userName");
        var socket = null;

        connect.addEventListener("click",function () {
            console.log("connect")
            if(userName.value.length <= 2){
                return false;
            }
            socket = new WebSocket("ws://localhost:8080/websocket2?userName="+userName.value);

            socket.onopen = function (p1) {
                console.log("onopen",p1)
            }

            socket.onclose = function (p1) {
                console.log("onclose",p1)
            }
            socket.onerror = function (p1) {
                console.log("onerror",p1)
            }
            socket.onmessage = function (p1) {
                console.log("onmessage",p1)
                content.innerHTML = (content.innerHTML + p1.data + "\n")
            }
        });
        destroy.addEventListener("click",function () {
            console.log("destroy")
            socket.close();
        });
        send.addEventListener("click",function () {
            console.log("send")
            socket.send(message.value);
            message.value = "";
        });


</script>

</body>
</html>

最后上传一张效果图吧:

QQ截图20170910223850

解决spring mvc跨域的问题

java开发网站是很繁琐的事情,特别是写前端,每次运行都需要进行编译,所以需要配置跨域访问:

服务端

首先要创建一个CrossDomainFilter类,类继承了Servlet Filter接口,如下代码:

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Created by alan.luo on 2017/9/5.
 */
public class CrossDomainFilter extends Compact implements Filter {


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {

        try {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            HttpServletResponse httpResponse = (HttpServletResponse) response;

            // 跨域
            String origin = httpRequest.getHeader("Origin");
            if (origin == null) {
                httpResponse.addHeader("Access-Control-Allow-Origin", "http://localhost/");

            } else {
                httpResponse.addHeader("Access-Control-Allow-Origin", origin);
            }
            httpResponse.addHeader("Access-Control-Allow-Headers", "Origin, x-requested-with, Content-Type, Accept,X-Cookie");
            httpResponse.addHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.addHeader("Access-Control-Allow-Methods", "GET,POST,PUT,OPTIONS,DELETE");
            if ( httpRequest.getMethod().equals("OPTIONS") ) {
                httpResponse.setStatus(HttpServletResponse.SC_OK);
                return;
            }
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void destroy() {
    }
}

类方法主要是配置response的响应信息,告诉客户端,可以传递什么及使用什么方法过来。

接下来就是配置xml文件,打开web.xml文件,在过滤代码中加入以下代码:

<!-- 允许跨域 -->
<filter>
    <filter-name>CrossDomainFilter</filter-name>
    <filter-class>com.test.com.CrossDomainFilter</filter-class>
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CrossDomainFilter</filter-name>
    <url-pattern>/*</url-pattern><!-- 配置允许跨域访问的的url-pattern -->
</filter-mapping>

到这里服务器就已经配置完毕了。

客户端

客户端直接使用ajax发起请求就可以:

  /**
   * 发起ajax请求
   * @param url 请求地址
   * @param data 请求参数(object)对象
   * @param fnScuss 成功回调的函数
   * @param fnError 失败回调的函数
   * @returns {{get: get, post: post}}
   */
  ajax: function (url, data, fnScuss, fnError) {
      
      var _cacheKey = app.md5(url+JSON.stringify(data));
      var _cache = null;
      var _isCanCache = app.isCanCache(url);
      url = this.constant.DOMAIN + url;

      var http = function (method,header) {
          if (method == null){
              method = "POST";
          }
          if(header == null){
              header = new Object();
          }
     
          $.ajax({
              url: url,
              data: data,
              dataType: "json",
              type: method,
              headers: header,
      beforeSend: function(xhr) {
         xhr.withCredentials = true;
      },
      crossDomain:true,
              success: function (e) {

                  if (typeof fnScuss == "function") {
                      //储存缓存
                      if (e.code == "1" && _isCanCache == true){
                          //app.cache.set(_cacheKey,e)
                      }
                      fnScuss(e);
                  }
              },
              error: function (e) {
                  if (typeof fnError == "function") {
                      fnError(e);
                  }
              }

          })
      };

      return {
          get: function () {
              //缓存判断
              if (_isCanCache == true){
                  _cache = app.cache.get(_cacheKey)
                  if (_cache != null && typeof fnScuss == "function"){
                      fnScuss(_cache);
                      return true;
                  }
              }

              http("GET");
          },
          post: function (isJson) {
              //缓存判断
              if (_isCanCache == true){
                  _cache = app.cache.get(_cacheKey)
                  if (_cache != null && typeof fnScuss == "function"){
                      fnScuss(_cache);
                      return true;
                  }
              }
              var header = new Object();
              if (isJson == true){
                  header['Content-Type'] = "application/json";
              }
              http("POST",header);
          },
          delete:function () {
              http("DELETE");
          },
          put:function () {
              http("PUT");
          }
      }

  },

 

app.event("#getToken", "click", function () {
    app.ajax("/project/api/getToken", {type:"1"}, function (e) {
        app.log(e);
        if (e.code == 1) {
            
        }
    }).post();
});
12320