整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

java-socket长连接demo体验

java-socket长连接demo体验
作者:DavidDing
来源:https://zhuanlan.zhihu.com/p/56135195

、前言

最近公司在预研设备app端与服务端的交互方案,主要方案有:

  • 服务端和app端通过阿里iot套件实现消息的收发;
  • 服务端通过极光推送主动给app端推消息,app通过rest接口与服务端进行交互;
  • 服务端与app通过mqtt消息队列来实现彼此的消息交互;
  • 服务端与app通过原生socket长连接交互。

虽然上面的一些成熟方案肯定更利于上生产环境,但它们通讯基础也都是socket长连接,所以本人主要是预研了一下socket长连接的交互,写了个简单demo,采用了BIO的多线程方案,集成了springboot,实现了自定义简单协议,心跳机制,socket客户端身份强制验证,socket客户端断线获知等功能,并暴露了一些接口,可通过接口简单实现客户端与服务端的socket交互。

Github源码:

https://github.com/DavidDingXu/springboot-socket-demo

二、IO通讯模型

1. IO通讯模型简介

IO通讯模型主要包括阻塞式同步IO(BIO),非阻塞式同步IO,多路复用IO以及异步IO。

该部分内容总结自专栏文章:

https://blog.csdn.net/yinwenjie/column/info/sys-communication/3

1.1 阻塞式同步IO

BIO就是:blocking IO。最容易理解、最容易实现的IO工作方式,应用程序向操作系统请求网络IO操作,这时应用程序会一直等待;另一方面,操作系统收到请求后,也会等待,直到网络上有数据传到监听端口;操作系统在收集数据后,会把数据发送给应用程序;最后应用程序受到数据,并解除等待状态。



BIO通讯示意图

1.2 非阻塞式同步IO

这种模式下,应用程序的线程不再一直等待操作系统的IO状态,而是在等待一段时间后,就解除阻塞。如果没有得到想要的结果,则再次进行相同的操作。这样的工作方式,暴增了应用程序的线程可以不会一直阻塞,而是可以进行一些其他工作。



非阻塞式IO示意图

1.3 多路复用IO(阻塞+非阻塞)



多路复用IO示意图

目前流程的多路复用IO实现主要包括四种:select、poll、epoll、kqueue。下表是他们的一些重要特性的比较:



1.4 异步IO

异步IO则是采用“订阅-通知”模式:即应用程序向操作系统注册IO监听,然后继续做自己的事情。当操作系统发生IO事件,并且准备好数据后,在主动通知应用程序,触发相应的函数。



异步IO示意图

和同步IO一样,异步IO也是由操作系统进行支持的。微软的windows系统提供了一种异步IO技术:IOCP(I/O Completion Port,I/O完成端口);

Linux下由于没有这种异步IO技术,所以使用的是epoll(上文介绍过的一种多路复用IO技术的实现)对异步IO进行模拟。

2. Java对IO模型的支持

  • Java对阻塞式同步IO的支持主要是java.net包中的Socket套接字实现;
  • Java中非阻塞同步IO模式通过设置serverSocket.setSoTimeout(100);即可实现;
  • Java 1.4中引入了NIO框架(java.nio包)可以构建多路复用、同步非阻塞IO程序;
  • Java 7中对NIO进行了进一步改进,即NIO2,引入了异步非阻塞IO方式。

由于是要实现socket长连接的demo,主要关注其一些实现注意点及方案,所以本demo采用了BIO的多线程方案,该方案代码比较简单、直观,引入了多线程技术后,IO的处理吞吐量也大大提高了。下面是BIO多线程方案server端的简单实现:

public static void main(String[] args) throws Exception{
 ServerSocket serverSocket=new ServerSocket(83);
 try {
 while(true) {
 Socket socket=null;
 socket=serverSocket.accept();
 //这边获得socket连接后开启一个线程监听处理数据
 SocketServerThread socketServerThread=new SocketServerThread(socket);
 new Thread(socketServerThread).start();
 }
 } catch(Exception e) {
 log.error("Socket accept failed. Exception:{}", e.getMessage());
 } finally {
 if(serverSocket !=null) {
 serverSocket.close();
 }
 }
 }
}
@slf4j
class SocketServerThread implements Runnable {
 private Socket socket;
 public SocketServerThread (Socket socket) {
 this.socket=socket;
 }
 @Override
 public void run() {
 InputStream in=null;
 OutputStream out=null;
 try {
 in=socket.getInputStream();
 out=socket.getOutputStream();
 Integer sourcePort=socket.getPort();
 int maxLen=2048;
 byte[] contextBytes=new byte[maxLen];
 int realLen;
 StringBuffer message=new StringBuffer();
 BIORead:while(true) {
 try {
 while((realLen=in.read(contextBytes, 0, maxLen)) !=-1) {
 message.append(new String(contextBytes , 0 , realLen));
 /*
 * 我们假设读取到“over”关键字,
 * 表示客户端的所有信息在经过若干次传送后,完成
 * */
 if(message.indexOf("over") !=-1) {
 break BIORead;
 }
 }
 }
 //下面打印信息
 log.info("服务器(收到来自于端口:" + sourcePort + "的信息:" + message);
 //下面开始发送信息
 out.write("回发响应信息!".getBytes());
 //关闭
 out.close();
 in.close();
 this.socket.close();
 } catch(Exception e) {
 log.error("Socket read failed. Exception:{}", e.getMessage());
 }
 }
}

三、注意点及实现方案

1. TCP粘包/拆包

1.1 问题说明

假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下4种情况。 1. 服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包; 2. 服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包; 3. 服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包; 4. 服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包。如果此时服务端TCP接收滑窗非常小,而数据包D1和D2比较大,很有可能会发生第五种可能,即服务端分多次才能将D1和D2包接收完全,期间发生多次拆包。

1.2 解决思路

由于底层的TCP无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,可以归纳如下: 1. 消息定长,例如每个报文的大小为固定长度200字节,如果不够,空位补空格; 2. 在包尾增加回车换行符进行分割,例如FTP协议; 3. 将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度)的字段,通常设计思路为消息头的第一个字段使用int32来表示消息的总长度; 4. 更复杂的应用层协议。

1.3 demo方案

作为socket长连接的demo,使用了上述的解决思路2,即在包尾增加回车换行符进行数据的分割,同时整体数据使用约定的Json体进行作为消息的传输格式。

使用换行符进行数据分割,可如下进行数据的单行读取:

BufferedReader reader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
String message;
while ((message=reader.readLine()) !=null) {
//....
}

可如下进行数据的单行写入:

PrintWriter writer=new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
writer.println(message);

Json消息格式如下:

(1) 服务端接收消息实体类

@Data
public class ServerReceiveDto implements Serializable {
 private static final long serialVersionUID=6600253865619639317L;
 /**
 * 功能码 0 心跳 1 登陆 2 登出 3 发送消息
 */
 private Integer functionCode;
 /**
 * 用户id
 */
 private String userId;
 /**
 * 这边假设是string的消息体
 */
 private String message;
}

(2) 服务端发送消息实体类

@Data
public class ServerSendDto implements Serializable {
 private static final long serialVersionUID=-7453297551797390215L;
 /**
 * 状态码 20000 成功,否则有errorMessage
 */
 private Integer statusCode;
 private String message;
 /**
 * 功能码
 */
 private Integer functionCode;
 /**
 * 错误消息
 */
 private String errorMessage;
}

(3) 客户端发送消息实体类

@Data
public class ClientSendDto implements Serializable {
 private static final long serialVersionUID=97085384412852967L;
 /**
 * 功能码 0 心跳 1 登陆 2 登出 3 发送消息
 */
 private Integer functionCode;
 /**
 * 用户id
 */
 private String userId;
 /**
 * 这边假设是string的消息体
 */
 private String message;
}

2. 客户端或服务端掉线检测功能

2.1 实现思路

通过自定义心跳包来实现掉线检测功能,具体思路如下:

客户端连接上服务端后,在服务端会维护一个在线客户端列表。客户端每隔一段时间,向服务端发送一个心跳包,服务端受收到包以后,会更新客户端最近一次在线时间。一旦服务端超过规定时间没有接收到客户端发来的包,则视为掉线。

2.2 代码实现

维护一个客户端map,其中key代表用户的唯一id(用户唯一id的身份验证下面会说明),value代表用户对应的一个实体

/**
 * 存储当前由用户信息活跃的的socket线程
 */
private ConcurrentMap<String, Connection> existSocketMap=new ConcurrentHashMap<>();

其中Connection对象包含的信息如下:

@Slf4j
@Data
public class Connection {
 /**
 * 当前的socket连接实例
 */
 private Socket socket;
 /**
 * 当前连接线程
 */
 private ConnectionThread connectionThread;
 /**
 * 当前连接是否登陆
 */
 private boolean isLogin;
 /**
 * 存储当前的user信息
 */
 private String userId;
 /**
 * 创建时间
 */
 private Date createTime;
 /**
 * 最后一次更新时间,用于判断心跳
 */
 private Date lastOnTime;
}

主要关注其中的lastOnTime字段,每次服务端接收到标识是心跳数据,会更新当前的lastOnTime字段,代码如下:

if (functionCode.equals(FunctionCodeEnum.HEART.getValue())) {
 //心跳类型
 connection.setLastOnTime(new Date());
 //发送同样的心跳数据给客户端
 ServerSendDto dto=new ServerSendDto();
 dto.setFunctionCode(FunctionCodeEnum.HEART.getValue());
 connection.println(JSONObject.toJSONString(dto));
}

额外会有一个监测进程,以一定频率来监测上述维护的map中的每一个Connection对象,如果当前时间与lastOnTime的时间间隔超过自定义的长度,则自动将其对应的socket连接关闭,代码如下:

Date now=new Date();
Date lastOnTime=connectionThread.getConnection().getLastOnTime();
long heartDuration=now.getTime() - lastOnTime.getTime();
if (heartDuration > SocketConstant.HEART_RATE) {
 //心跳超时,关闭当前线程
 log.error("心跳超时");
 connectionThread.stopRunning();
}

在上面代码中,服务端收到标识是心跳数据的时候,除了更新该socket对应的lastOnTime,还会同样同样心跳类型的数据给客户端,客户端收到标识是心跳数据的时候也会更新自己的lastOnTime字段,同时也有一个心跳监测线程在监测当前的socket连接心跳是否超时

3. 客户端身份获知、强制身份验证

3.1 实现思路

通过代码socket=serverSocket.accept()获得的一个socket连接我们仅仅只能知道其客户端的ip以及端口号,并不能获知这个socket连接对应的到底是哪一个客户端,因此必须得先获得客户端的身份并且验证通过其身份才能让其正常连接。

具体的实现思路是:

自定义一个登陆处理接口,当server端受到标识是用户登陆的时候(此时会携带用户信息或者token,此处简化为用户id),调用用户的登陆验证,验证通过的话则将该socket连接与用户信息绑定,设置其为已登录,并且封装对应的对象放入前面提的客户端map中,由此可获得具体用户对应的哪一个socket连接。

为了实现socket连接的强制验证,在监测线程中,也会判断当前用户多长时间内没有实现登录态,若超时则认为该socket连接为非法连接,主动关闭该socket连接。

3.2 代码实现

自定义登陆处理接口,这边简单以userId来判断是否允许登陆:

public interface LoginHandler {
 /**
 * client登陆的处理函数
 *
 * @param userId 用户id
 *
 * @return 是否验证通过
 */
 boolean canLogin(String userId);
}

收到客户端发来的数据时候的处理:

if (functionCode.equals(FunctionCodeEnum.LOGIN.getValue())) {
 //登陆,身份验证
 String userId=receiveDto.getUserId();
 if (socketServer.getLoginHandler().canLogin(userId)) {
 //设置用户对象已登录状态
 connection.setLogin(true);
 connection.setUserId(userId);
 if (socketServer.getExistSocketMap().containsKey(userId)) {
 //存在已登录的用户,发送登出指令并主动关闭该socket
 Connection existConnection=socketServer.getExistSocketMap().get(userId);
 ServerSendDto dto=new ServerSendDto();
 dto.setStatusCode(999);
 dto.setFunctionCode(FunctionCodeEnum.MESSAGE.getValue());
 dto.setErrorMessage("force logout");
 existConnection.println(JSONObject.toJSONString(dto));
 existConnection.getConnectionThread().stopRunning();
 log.error("用户被客户端重入踢出,userId:{}", userId);
 }
 //添加到已登录map中
 socketServer.getExistSocketMap().put(userId, connection);
}

监测线程判断用户是否完成身份验证:

if (!connectionThread.getConnection().isLogin()) {
 //还没有用户登陆成功
 Date createTime=connectionThread.getConnection().getCreateTime();
 long loginDuration=now.getTime() - createTime.getTime();
 if (loginDuration > SocketConstant.LOGIN_DELAY) {
 //身份验证超时
 log.error("身份验证超时");
 connectionThread.stopRunning();
 }
}

4. socket异常处理与垃圾线程回收

4.1 实现思路

socket在读取数据或者发送数据的时候会出现各种异常,比如客户端的socket已断开连接(正常断开或物理连接断开等),但是服务端还在发送数据或者还在接受数据的过程中,此时socket会抛出相关异常,对于该异常的处理需要将自身的socket连接关闭,避免资源的浪费,同时由于是多线程方案,还需将该socket对应的线程正常清理。

4.2 代码实现

下面以server端发送数据为例,该代码中加入了重试机制:

public void println(String message) {
 int count=0;
 PrintWriter writer;
 do {
 try {
 writer=new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
 writer.println(message);
 break;
 } catch (IOException e) {
 count++;
 if (count >=RETRY_COUNT) {
 //重试多次失败,说明client端socket异常
 this.connectionThread.stopRunning();
 }
 }
 try {
 Thread.sleep(2 * 1000);
 } catch (InterruptedException e1) {
 log.error("Connection.println.IOException interrupt,userId:{}", userId);
 }
 } while (count < 3);
}

上述调用的this.connectionThread.stopRunning()代码如下:

public void stopRunning() {
 //设置线程对象状态,便于线程清理
 isRunning=false;
 try {
 //异常情况需要将该socket资源释放
 socket.close();
 } catch (IOException e) {
 log.error("ConnectionThread.stopRunning failed.exception:{}", e);
 }
}

上述代码中设置了线程对象的状态,下述代码在监测线程中执行,将没有运行的线程给清理掉

/**
 * 存储只要有socket处理的线程
 */
private List<ConnectionThread> existConnectionThreadList=Collections.synchronizedList(new ArrayList<>());
/**
 * 中间list,用于遍历的时候删除
 */
private List<ConnectionThread> noConnectionThreadList=Collections.synchronizedList(new ArrayList<>());
//...
//删除list中没有用的thread引用
existConnectionThreadList.forEach(connectionThread -> {
 if (!connectionThread.isRunning()) {
 noConnectionThreadList.add(connectionThread);
 }
});
noConnectionThreadList.forEach(connectionThread -> {
 existConnectionThreadList.remove(connectionThread);
 if (connectionThread.getConnection().isLogin()) {
 //说明用户已经身份验证成功了,需要删除map
 this.existSocketMap.remove(connectionThread.getConnection().getUserId());
 }
});
noConnectionThreadList.clear();

四、项目结构

由于使用了springboot框架来实现该demo,所以项目结构如下:

整体项目结构图

socket工具包目录如下:

socket工具包目录

pom文件主要添加了springboot的相关依赖,以及json工具和lombok工具等,依赖如下:

<parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.0.3.RELEASE</version>
 <relativePath/>
</parent>
<dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 </dependency>
 <dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 </dependency>
 <dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>fastjson</artifactId>
 <version>1.2.36</version>
 </dependency>
</dependencies>

自己写的socket工具包的使用方式如下:

@Configuration
@Slf4j
public class SocketServerConfig {
@Bean
public SocketServer socketServer() {
 SocketServer socketServer=new SocketServer(60000);
 socketServer.setLoginHandler(userId -> {
 log.info("处理socket用户身份验证,userId:{}", userId);
 //用户名中包含了dingxu则允许登陆
 return userId.contains("dingxu");
 });
 socketServer.setMessageHandler((connection, receiveDto) -> log
 .info("处理socket消息,userId:{},receiveDto:{}", connection.getUserId(),
 JSONObject.toJSONString(receiveDto)));
 socketServer.start();
 return socketServer;
}
}

该demo中主要提供了以下几个接口进行测试:

  • 服务端:获得当前用户列表,发送一个消息客户端:开始一个socket客户端,发送一个消息,关闭一个socket客户端,查看已开启的客户端

具体的postman文件也放已在项目中,具体可点此链接获得

demo中还提供了一个简单压测函数,如下:

@Slf4j
public class SocketClientTest {
 public static void main(String[] args) {
 ExecutorService clientService=Executors.newCachedThreadPool();
 String userId="dingxu";
 for (int i=0; i < 1000; i++) {
 int index=i;
 clientService.execute(() -> {
 try {
 SocketClient client;
 client=new SocketClient(InetAddress.getByName("127.0.0.1"), 60000);
 //登陆
 ClientSendDto dto=new ClientSendDto();
 dto.setFunctionCode(FunctionCodeEnum.LOGIN.getValue());
 dto.setUserId(userId + index);
 client.println(JSONObject.toJSONString(dto));
 ScheduledExecutorService clientHeartExecutor=Executors.newSingleThreadScheduledExecutor(
 r -> new Thread(r, "socket_client+heart_" + r.hashCode()));
 clientHeartExecutor.scheduleWithFixedDelay(() -> {
 try {
 ClientSendDto heartDto=new ClientSendDto();
 heartDto.setFunctionCode(FunctionCodeEnum.HEART.getValue());
 client.println(JSONObject.toJSONString(heartDto));
 } catch (Exception e) {
 log.error("客户端异常,userId:{},exception:{}", userId, e.getMessage());
 client.close();
 }
 }, 0, 5, TimeUnit.SECONDS);
 while (true){
 }
 } catch (Exception e) {
 log.error(e.getMessage());
 }
 });
 }
 }
}

源码地址如下,仅供学习参考

github.com/DavidDingXu/springboot-socket-demo

五、参考

本样式对齐文本text-align属性用于指定文本块的对齐方式,可选值包括: 1)start:内容对齐开始边界,默认; 2)end:内容对齐结束边界; 3)left:内容左对齐; 4)right:内容右对齐; 5)center:内容居中对齐; 6)justify:内容两端对齐。当text-align属性使用了justify值时,可以使用text-justify属性指定文本添加空白的方式,这个属性...
了解了包的概念,就可以系统的介绍Java中的访问控制级别。在Java中,针对类、成员方法和属性提供了四种访问级别,分别是private、default、protected和public。 权限访问修饰符(权限从大到小依次往右排) public(公共) protected(受保护) default(缺省) private(私有) 同一个类 √ √...
Rust 提供了代码封装的机制。可以通过crate (等同于Java中的package)创建相对独立的module模块,模块中封装了可以重复使用的功能函数。当创建了自己的 lib 库或者要使用第三方的库的时候(这些库就是一些事先写好的crate)需要将这些库中的module 模块引用到当前的环境中。Rust提供了以下几种引用方式:一、使用 extern crate在使用这些Module的文件中,通过...
填空题: 他______牺牲生命_______出卖组织? 据数据统计,不同年代的同学回复的最多的是….. 60后,他宁可牺牲生命,也不出卖组织。 70后,他害怕牺牲生命,所以出卖组织。 80后,他与其牺牲生命,不如出卖组织。 90后,他即使牺牲生命,也要出卖组织。 00后,他白白牺牲了生命,忘了出卖组织。 上边的案例,引发了大家对”自我与企业关系的思考”. 能力与欲望...
历届试题 国王的烦恼 时间限制:1.0s 内存限制:256.0MB 问题描述 C国由n个小岛组成,为了方便小岛之间联络,C国在小岛间建立了m座大桥,每座大桥连接两座小岛。两个小岛间可能存在多座桥连接。然而,由于海水冲刷,有一些大桥面临着不能使用的危险。 如果两个小岛间的所有大桥都不能使用,则这两座小岛就不能直接到达了。然而,只要这两座...
go test命令参数问题在使用go test对go代码进行单元测试的时候,遇到关于命令参数的问题,google了一下,没有找到很好的说明,其实就是一些细节而已。问题是这样的,在进行单元测试的时候,我希望输入一些命令行参数来控制程序的运行。 参考go官方文档,只需要在go test后面加上-args和参数就可以了 例如 go test -args -classpath E:\testcase...
阿里云OSS-使用经验总结,存储,账号-权限,分页,缩略图,账号切换最近项目中,需要使用云存储,最后选择了阿里云-对象存储服务OSS。总的来说,比较简单,但是仍然遇到了几个问题,需要总结下。1.OSS总的使用介绍 https://help.aliyun.com/document_detail/oss/sdk/java-sdk/manage_object.html?spm=5176.docoss/...
WEB应用图片的格式,以及各自的特点和优化(一) by FungLeo前言12年前我入行三天.用table布局做了一个非常粗糙的网页.我说了一句话,”网页就是表格加文字加图片,图片分两种,插入图片和背景图片”.这句话在今天看来,当然是一个笑话.但是当时我说出这句话的时候,当时的那些前辈都非常认可我的总结,并且认为我很有从事网络发展的潜力啊.哎,要不是他们的鼓励,说不定我早转行了……扯远了.说回正题,...
1. 单表数据的导出针对单表数据的导出操作,MongoDB 提供了 mongoexport 命令。mongoexport 既可以将数据导出为 CSV 格式的文件,也可以导出 JSON 格式的文件。这两者之间的区别是:JSON 是 mongoexport 默认的导出格式,不需要指定,而要导出 CSV 格式的话需要明确指定;导出 CSV 格式必须显式指定各属性名,而导出 JSON 格式不需要。由此可见...
商业智能对于中小企业来说,由于其高昂的费用和运行维护技术水平要求高,往往难以承受,商业智能SAAS系统平台+模块的创新模式的出现能帮助中小企业走上商业智能之路。...
stack.sh给出了一个非常好的例子,关于学习openstack创建 1.检查devstack文件,检查bash4.2以上,检查用户,不能是root2.准备环境,导入函数3.检查local.conf和localrc是否都存在,如果存在使用localrc4.检查是否已经运行devstack5.代理设置和禁用无效服务6.配置sudo7.配置distro库8.配置目标目录,创建目标目录9.配置主机、日...
博客地址:http://blog.csdn.net/FoxDave本文介绍如何利用SharePoint客户端对象模型(.NET)逐级获取Office 365网站中List的内容,仅仅是示例,没有讲究太多东西。代码如下:ClientContext ctx=new ClientContext(""); ctx.Credentials=new SharePointOn...
题外话Atom,风风雨雨走过一年多了.,目前最新版本是V1.7.0 .社区还是相当活跃;体验也改善了很多;但是性能上还是欠缺;今天我再来介绍自己常用的一款插件git-control插件介绍 官方介绍页面 作者: jacogr Github地址 我的介绍 就是命令行的GUI版本,,有些类似sourcetree,但是不如它强大,日用满足使用在编辑器下加载git版本的工作目录;工具默认启用快捷键...
安装devstack后,如果没有设置参数,执行openstack命令是不成功的。1.登录到horizon页面,使用admin登入,进入project->compute-> Access&Security -> API Access,记录下Service Endpoint。或选择download OpenStack RC File按钮,下载demo-openrc.sh文件2.将demo-openrc...
对于这样的问题,看到第一眼就是暴力破解,所以也就递归找到所有情况,再筛选出合格的小明被劫持到X赌城,被迫与其他3人玩牌。 一副扑克牌(去掉大小王牌,共52张),均匀发给4个人,每个人13张。 这时,小明脑子里突然冒出一个问题: 如果不考虑花色,只考虑点数,也不考虑自己得到的牌的先后顺序,自己手里能拿到的初始牌型组合一共有多少种呢?思路: 首先无论怎么取,手牌为13张的时候结束.也就是递归结束标...
网红和粉丝经济,是最近几年流行起来的概念。 截至目前,有一些初步的认识,整理成文。 粉丝,最早是明星的跟随者比较多。 我的理解是,对于一个人物、动物、运动等,有着共同的兴趣,从而建立多个人和一个人之间的关系,比如粉丝和明星。 粉丝经济,大获成功的标志是,雷军和小米科技。在创业早期,就把粉丝经济和社交传播结合在一起,低成本地实现了全网营销。从此以后,各大手机厂商等很多领域的企业,都...
关于android端apk退出方式的设计,现在大体只有下面几种:1,有退出和取消按钮;2,一定时间内两次返回为退出;3,一次返回就是退出。首先可以看到这两个用按钮的,退出都在左侧,设计者肯定没有看过十年前雅虎研究院出的web端设计指导,下一步的操作一定是在右侧,而返回上一步的操作是在左侧。但是到了移动端应该考虑用户是左手还是右手使用,也就是说,如果是左手使用,这个位置设计没有问题,反之就不用说了。...
写在最前:本文主要描述在网站的不同的并发访问量级下,Mysql架构的演变可扩展性架构的可扩展性往往和并发是息息相关,没有并发的增长,也就没有必要做高可扩展性的架构,这里对可扩展性进行简单介绍一下,常用的扩展手段有以下两种Scale-up : 纵向扩展,通过替换为更好的机器和资源来实现伸缩,提升服务能力Scale-out : 横向扩展, 通过加节点(机器)来实现伸缩,提升服务能力对于互联网的高并...
angular.js中,指令是最基础的也是最重要的工具之一。angular.js指令指的是以ng为前缀的HTML属性。在之前的ng-app、ng-model等,都属于指令。 angular.js中的基本指令包括如下内容: · 1.ng-app/ng-model ng-app指令用于声明angular,js的作用范围,ng-model用于声明模型。这些在之前都已经进行过详细介绍。 2.ng-...
java编码 当你的字节序列是某种编码时,这个时候想把字节序列变成 字符串,也需要用这种编码方式,否则会出现乱码 文本文件就是字节序列 可以是任意编码的序列,如果在中文机器上直接创建文本文件,那么该文本文件 只认识ANSI编码 案例: public class Bianma { public static void main(Strin...
调试JDK源码-一步一步看HashMap怎么Hash和扩容调试JDK源码-ConcurrentHashMap实现原理调试JDK源码-HashSet实现原理调试JDK源码-调试JDK源码-Hashtable实现原理以及线程安全的原因 ConcurrentHashMap线程安全的总结是我从源码分析出来的:ConcurrentHashMap所谓线程安全是哈希冲突的时候新增的节点是线程安全的,而 Conc...
对于后台系统的搜索进行UI自动化,主要是比对页面查询结果是否与预期一致(即数据库查询结果) search.py# -*- coding:utf8 -*- import HTMLTestRunner import time import unittest import public from selenium import webdriver class Search(unittest.TestCa...
ajax 的全称是Asynchronous(异步的意思) JavaScript and XML,是一种创建交互式网页应用的网页开发技术 ajax技术的流行得益于google的大力推广,正是由于google产品对ajax技术的广泛应用,使得ajax流行起来了。 Ajax其核心有JavaScript、XMLHTTPRequest、DOM对象组成,通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用JavaScript来操作DOM而更新页面。这其中最关键的一步就是从服务器获得请...
一、SpringMVChttp://blog.csdn.net/evankaka/article/details/45501811Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC也是要简...
概念: 优化策略:字段选择性 选择性较低索引 可能带来的性能问题索引选择性=索引列唯一值/表记录数;选择性越高索引检索价值越高,消耗系统资源越少;选择性越低索引检索价值越低,消耗系统资源越多;查询条件含有多个字段时,不要在选择性很低字段上创建索引可通过创建组合索引来增强低字段选择性和避免选择性很低字段创建索引带来副作用;尽量减少possible_keys,正确索引会提高sql查询速度,过多索引...
一. 什么是Spark? Spark是UC Berkeley AMP lab所开源的类Hadoop MapReduce的通用的并行计算框架,Spark基于map reduce算法实现的分布式计算,拥有Hadoop MapReduce所具有的优点;但不同于MapReduce的是Job中间输出和结果可以保存在内存中,从而不再需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需...
相比之前的增改查,删除就显得简单的多了。 这里的request的type为delete,删除成功的status为204,404则是要删除的记录不存在 var id='BAD90A95-7FEA-E511-9414-ADA183AB6249'; $.ajax({ async: false, type: "DELETE ", co...
关于JPush极光推送是国内的服务厂商提供的一站式push服务(同时支持iOS、android),后面也加入了即时通讯的能力供app使用。致力于打造简单、可靠、价格有竞争力的服务(简单功能全免费,高级版才收费),让应用开发商可以聚焦业务开发,push相关的技术实现全部通过极光推送来解决,仅需调用极光推送的api即可。正因为如此,开发者小伙伴们对其的评价相当不错。笔者的app新增了从服务器往移动客户端...
Mapreduce初析 Mapreduce是一个计算框架,既然是做计算的框架,那么表现形式就是有个输入(input),mapreduce操作这个输入(input),通过本身定义好的计算模型,得到一个输出(output),这个输出就是我们所需要的结果。 重点就是这个计算模型的运行规则。在运行一个mapreduce计算任务时候,任务过程被分为两个阶段:map阶段...
Jquery对象常用的方法:$(”p”).addClass(css中定义的样式类型); 给某个元素添加样式 $(”img”).attr({src:”test.jpg”,alt:”test Image”}); 给某个元素添加属性/值,参数是map $(”img”).attr(”src”,”test.jpg”); 给某个元素添加属性/值 $(”img”).attr(”title”, function(...

隆汇6月29日丨极光(JG.US)盘前涨逾9%,报1.19美元。近日,极光的核心产品极光推送(JPush)顺利通过亚马逊云科技的多项测试及审核,正式上线亚马逊云科技Marketplace。当前,亚马逊云科技Marketplace上的用户,通过其中国区网站,即可直接购买和体验极光推送(JPush)。

本文源自格隆汇