我们可以了解到 mina是个异步通信框架,一般使用场景是服务端开发,长连接、异步通信使用mina是及其方便的。不多说,看例子。

本次mina 使用的例子是使用maven构建的,过程中需要用到的jar包如下:

<!-- mina -->
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-integration-beans</artifactId>
<version>2.0.16</version>
</dependency>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>2.0.16</version>
</dependency>

导入jar包,pom文件会报错,具体错误如下:

Missing artifact org.apache.mina:mina-core:bundle:2.0.16 pom.xml
1
原因是因为缺少maven-bundle-plugin导入即可

<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
</plugin>
</plugins>
</build>

本次mina机制的消息通讯接口规范主要为:
包头2个字节,包长2个字节,协议类型2个字节,数据包标识码8个字节,报文正文内容,校验码4个字节,包尾2个字节
mina 与spring 结合后,使用更加方便。
spring配置文件如下:

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

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="java.net.SocketAddress"
value="org.apache.mina.integration.beans.InetSocketAddressEditor"></entry>
</map>
</property>
</bean>

<bean id="ioAcceptor" class="org.apache.mina.transport.socket.nio.NioSocketAcceptor"
init-method="bind" destroy-method="unbind">
<!--端口号 -->
<property name="defaultLocalAddress" value=":8888"></property>
<!--绑定自己实现的handler -->
<property name="handler" ref="serverHandler"></property>
<!--声明过滤器的集合 -->
<property name="filterChainBuilder" ref="filterChainBuilder"></property>
<property name="reuseAddress" value="true" />
</bean>

<bean id="filterChainBuilder"
class="org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder">
<property name="filters">
<map>
<!--mina自带的线程池filter -->
<entry key="executor" value-ref="executorFilter"></entry>
<entry key="mdcInjectionFilter" value-ref="mdcInjectionFilter" />
<!--自己实现的编解码器filter -->
<entry key="codecFilter" value-ref="codecFilter" />
<!--日志的filter -->
<entry key="loggingFilter" value-ref="loggingFilter" />
<!--心跳filter -->
<entry key="keepAliveFilter" value-ref="keepAliveFilter" />
</map>
</property>
</bean>

<!-- executorFilter多线程处理 -->
<bean id="executorFilter" class="org.apache.mina.filter.executor.ExecutorFilter" />
<bean id="mdcInjectionFilter" class="org.apache.mina.filter.logging.MdcInjectionFilter">
<constructor-arg value="remoteAddress" />
</bean>

<!--日志 -->
<bean id="loggingFilter" class="org.apache.mina.filter.logging.LoggingFilter" />

<!--编解码 -->
<bean id="codecFilter" class="org.apache.mina.filter.codec.ProtocolCodecFilter">
<constructor-arg>
<!--构造函数的参数传入自己实现的对象 -->
<bean class="com.onion.mina.server.NSMinaCodeFactory"></bean>
</constructor-arg>
</bean>

<!--心跳检测filter -->
<bean id="keepAliveFilter" class="org.apache.mina.filter.keepalive.KeepAliveFilter">
<!--构造函数的第一个参数传入自己实现的工厂 -->
<constructor-arg>
<bean class="com.onion.mina.server.NSMinaKeepAliveMessageFactory"></bean>
</constructor-arg>
<!--第二个参数需要的是IdleStatus对象,value值设置为读写空闲 -->
<constructor-arg type="org.apache.mina.core.session.IdleStatus"
value="BOTH_IDLE">
</constructor-arg>
<!--心跳频率,不设置则默认5 -->
<property name="requestInterval" value="1500" />
<!--心跳超时时间,不设置则默认30s -->
<property name="requestTimeout" value="30" />
<!--默认false,比如在心跳频率为5s时,实际上每5s会触发一次KeepAliveFilter中的session_idle事件,
该事件中开始发送心跳包。当此参数设置为false时,对于session_idle事件不再传递给其他filter,如果设置为true,
则会传递给其他filter,例如handler中的session_idle事件,此时也会被触发-->
<property name="forwardEvent" value="true" />
</bean>

<!--自己实现的handler-->
<bean id="serverHandler" class="com.onion.mina.server.NSMinaHandler" />
</beans>

mina 核心包括 IOhandler处理器,编码工厂(包含编码器,解码器)等核心。

服务端代码如下:

编码工厂 (NSMinaCodeFactory)如下所示:

public class NSMinaCodeFactory implements ProtocolCodecFactory {
private final NSProtocalEncoder encoder;
private final NSProtocalDecoder decoder;

public NSMinaCodeFactory() {
this(Charset.forName("utf-8"));
}

public NSMinaCodeFactory(Charset charset) {
encoder = new NSProtocalEncoder();
decoder = new NSProtocalDecoder();
}

public ProtocolDecoder getDecoder(IoSession arg0) throws Exception {
// TODO Auto-generated method stub
return decoder;
}

public ProtocolEncoder getEncoder(IoSession arg0) throws Exception {
// TODO Auto-generated method stub
return encoder;
}
}

编码器——负责将需要发送给客户端的数据进行编码,然后发送给客户端

public class NSProtocalEncoder extends ProtocolEncoderAdapter {
private static final Logger logger = Logger.getLogger(NSProtocalEncoder.class);

@SuppressWarnings("unused")
private final Charset charset = Charset.forName("GBK");

/**
* 在此处实现包的编码工作,并把它写入输出流中
*/
public void encode(IoSession session, Object message,
ProtocolEncoderOutput out) throws Exception {
// TODO Auto-generated method stub
if(message instanceof BaseMessageForClient){
BaseMessageForClient clientmessage = (BaseMessageForClient)message;
byte[] packhead_arr = clientmessage.getPackHead().getBytes(charset);//包头2个字节
byte[] length_arr = ByteTools.intToByteArray(clientmessage.getLength()+19, 2);//包长
byte[] funcid_arr = ByteTools.intToByteArray(clientmessage.getFuncid(), 1);//协议类型
byte[] packetIdCode_arr = ByteTools.longToByteArray(clientmessage.getPacketIdCode(), 8);//数据包标识码
byte[] content_arr = clientmessage.getContent().getBytes(charset);//内容
byte[] checkcode_arr = ByteTools.longToByteArray(clientmessage.getCheckCode(), 4);//校验码
byte[] packtail_arr = clientmessage.getPackTail().getBytes();//包尾
IoBuffer buffer = IoBuffer.allocate(packhead_arr.length + length_arr.length + funcid_arr.length + packetIdCode_arr.length+ content_arr.length + checkcode_arr.length + packtail_arr.length);
buffer.setAutoExpand(true);
buffer.put(packhead_arr);
buffer.put(length_arr);
buffer.put(funcid_arr);
buffer.put(packetIdCode_arr);
buffer.put(content_arr);
buffer.put(checkcode_arr);
buffer.put(packtail_arr);
buffer.flip();
out.write(buffer);
out.flush();

buffer.free();
}else{
String value = (String)message;
logger.warn("encode message:" + message);
IoBuffer buffer = IoBuffer.allocate(value.getBytes().length);
buffer.setAutoExpand(true);
if(value != null){
buffer.put(value.trim().getBytes());
}
buffer.flip();
out.write(buffer);
out.flush();

buffer.free();

}
}

}

解码器——负责将客户端发送过来的数据进行解码变换为对象,传输给IoHandler处理器进行处理。本解码器包含了断包问题解决。

public class NSProtocalDecoder implements ProtocolDecoder {

private static final Logger logger = Logger.getLogger(NSProtocalDecoder.class);
private final AttributeKey context = new AttributeKey(getClass(), "context");
private final Charset charset = Charset.forName("GBK");
private final String PACK_HEAD = "$$"; //包头
private final String PACK_TAIL = "\r\n"; //包尾

// 请求报文的最大长度 100k
private int maxPackLength = 102400;

public int getMaxPackLength() {
return maxPackLength;
}

public void setMaxPackLength(int maxPackLength) {
if (maxPackLength <= 0) {
throw new IllegalArgumentException("请求报文最大长度:" + maxPackLength);
}
this.maxPackLength = maxPackLength;
}

private Context getContext(IoSession session) {
Context ctx;
ctx = (Context) session.getAttribute(context);
if (ctx == null) {
ctx = new Context();
session.setAttribute(context, ctx);
}
return ctx;
}

public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)
throws Exception {
Long start = System.currentTimeMillis();
// 报文前缀长度 包头2个字节,包长2个字节,协议类型2个字节,数据包标识码8个字节,校验码4个字节,包尾2个字节
final int packHeadLength = 19;
// 先获取上次的处理上下文,其中可能有未处理完的数据
Context ctx = getContext(session);
// 先把当前buffer中的数据追加到Context的buffer当中
ctx.append(in);
// 把position指向0位置,把limit指向原来的position位置
IoBuffer buf = ctx.getBuffer();
buf.flip();
// 然后按数据包的协议进行读取
while (buf.remaining() >= packHeadLength) {
logger.debug("test 长度1:" + buf.remaining());
buf.mark();
// 读取包头 2个字节
String packhead = new String(new byte[]{buf.get(),buf.get()});
logger.debug("包头:" + packhead);
if(PACK_HEAD.equals(packhead)){
//读取包的长度 2个字节 报文的长度,不包含包头和包尾
byte[] length_byte = new byte[]{buf.get(),buf.get()};
byte[] length_byte_arr = new byte[]{0,0,0,0};
length_byte_arr[2] = length_byte[0];
length_byte_arr[3] = length_byte[1];
int length = ByteTools.byteArrayToInt(length_byte_arr);
logger.debug("长度:" + length);
logger.debug("test 长度1:" + buf.remaining());
// 检查读取是否正常,不正常的话清空buffer
if (length < 0 || length > maxPackLength) {
logger.debug("报文长度[" + length + "] 超过最大长度:" + maxPackLength
+ "或者小于0,清空buffer");
buf.clear();
break;
//packHeadLength - 2 :减去包尾的长度,
//length - 2 <= buf.remaining() :代表length-本身长度占用的两个字节-包头长度
}else if(length >= packHeadLength && length - 4 <= buf.remaining()){
//读取协议类型2个字节
byte[] funcid_byte = new byte[]{buf.get()};
byte[] funcid_byte_arr = new byte[]{0,0,0,0};
//funcid_byte_arr[2] = funcid_byte[0];
funcid_byte_arr[3] = funcid_byte[0];
int funcid = ByteTools.byteArrayToInt(funcid_byte_arr);
logger.warn("协议类型:" + funcid);
//读取数据包标识码8个字节
byte[] packetIdCode_byte = new byte[]{buf.get(),buf.get(),buf.get(),buf.get(),buf.get(),buf.get(),buf.get(),buf.get()};
long packetIdCode = ByteTools.byteArrayToLong(packetIdCode_byte);
logger.debug("数据包标识码:" + packetIdCode);

//读取报文正文内容
int oldLimit = buf.limit();
logger.debug("limit:" + (buf.position() + length));
//当前读取的位置 + 总长度 - 前面读取的字节长度 - 校验码
buf.limit(buf.position() + length - 19);
String content = buf.getString(ctx.getDecoder());
buf.limit(oldLimit);
logger.debug("报文正文内容:" + content);
CRC32 crc = new CRC32();
crc.update(content.getBytes("GBK"));

//读取校验码 4个字节
byte[] checkcode_byte = new byte[]{buf.get(),buf.get(),buf.get(),buf.get()};
byte[] checkcode_byte_arr = new byte[]{0,0,0,0,0,0,0,0};
checkcode_byte_arr[4] = checkcode_byte[0];
checkcode_byte_arr[5] = checkcode_byte[1];
checkcode_byte_arr[6] = checkcode_byte[2];
checkcode_byte_arr[7] = checkcode_byte[3];
long checkcode = ByteTools.byteArrayToLong(checkcode_byte_arr);
logger.debug("校验码:" + checkcode);
//验证校验码
if(checkcode != crc.getValue()){
// 如果消息包不完整,将指针重新移动消息头的起始位置
buf.reset();
break;
}
//读取包尾 2个字节
String packtail = new String(new byte[]{buf.get(),buf.get()});
logger.debug("包尾:" + packtail);
if(!PACK_TAIL.equals(packtail)){
// 如果消息包不完整,将指针重新移动消息头的起始位置
buf.reset();
break;
}
BaseMessageForServer message = new BaseMessageForServer();
message.setLength(length);
message.setCheckCode(checkcode);
message.setFuncid(funcid);
message.setPacketIdCode(packetIdCode);
message.setContent(content);
out.write(message);
}else{
// 如果消息包不完整,将指针重新移动消息头的起始位置
buf.reset();
break;
}
}else{
// 如果消息包不完整,将指针重新移动消息头的起始位置
buf.reset();
break;
}
}
if (buf.hasRemaining()) {
// 将数据移到buffer的最前面
IoBuffer temp = IoBuffer.allocate(maxPackLength).setAutoExpand(true);
temp.put(buf);
temp.flip();
buf.clear();
buf.put(temp);
} else {// 如果数据已经处理完毕,进行清空
buf.clear();
}
}

public void finishDecode(IoSession session, ProtocolDecoderOutput out)
throws Exception {
// TODO Auto-generated method stub

}

public void dispose(IoSession session) throws Exception {
// TODO Auto-generated method stub

}

// 记录上下文,因为数据触发没有规模,很可能只收到数据包的一半
// 所以,需要上下文拼起来才能完整的处理
private class Context {
private final CharsetDecoder decoder;
private IoBuffer buf;
private int matchCount = 0;
private int overflowPosition = 0;

private Context() {
decoder = charset.newDecoder();
buf = IoBuffer.allocate(3000).setAutoExpand(true);
}

public CharsetDecoder getDecoder() {
return decoder;
}

public IoBuffer getBuffer() {
return buf;
}

@SuppressWarnings("unused")
public int getOverflowPosition() {
return overflowPosition;
}

@SuppressWarnings("unused")
public int getMatchCount() {
return matchCount;
}

@SuppressWarnings("unused")
public void setMatchCount(int matchCount) {
this.matchCount = matchCount;
}

@SuppressWarnings("unused")
public void reset() {
overflowPosition = 0;
matchCount = 0;
decoder.reset();
}

public void append(IoBuffer in) {
getBuffer().put(in);
}
}

}

NSMinaHandler——处理器,处理业务数据。继承IoHandlerAdapter 接口,主要重写messageReceived 方法

public class NSMinaHandler extends IoHandlerAdapter {

private final Logger logger = Logger.getLogger(NSMinaHandler.class);

public static ConcurrentHashMap<Long, IoSession> sessionHashMap = new ConcurrentHashMap<Long, IoSession>();

@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
session.closeOnFlush();
logger.error("session occured exception, so close it."
+ cause.getMessage());
}

@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
BaseMessageForServer basemessage = (BaseMessageForServer) message;
logger.debug("客户端"
+ ((InetSocketAddress) session.getRemoteAddress()).getAddress()
.getHostAddress() + "连接成功!");
session.setAttribute("type", message);
String remoteAddress = ((InetSocketAddress) session.getRemoteAddress())
.getAddress().getHostAddress();
session.setAttribute("ip", remoteAddress);
// 组装消息内容,返回给客户端
BaseMessageForClient messageForClient = new BaseMessageForClient();
messageForClient.setFuncid(2);
if (basemessage.getContent().indexOf("hello") > 0) {
// 内容
messageForClient.setContent("hello,我收到您的消息了! ");
} else {
// 内容
messageForClient.setContent("恭喜,您已经入门! ");
}
// 校验码生成
CRC32 crc32 = new CRC32();
crc32.update(messageForClient.getContent().getBytes());
// crc校验码
messageForClient.setCheckCode(crc32.getValue());
// 长度
messageForClient
.setLength(messageForClient.getContent().getBytes().length);

// 数据包标识码
messageForClient.setPacketIdCode(basemessage.getPacketIdCode());
session.write(messageForClient);
}

@Override
public void messageSent(IoSession session, Object message) throws Exception {
logger.debug("messageSent:" + message);
}

@Override
public void sessionCreated(IoSession session) throws Exception {
logger.debug("remote client [" + session.getRemoteAddress().toString()
+ "] connected.");
Long time = System.currentTimeMillis();
session.setAttribute("id", time);
sessionHashMap.put(time, session);
}

@Override
public void sessionClosed(IoSession session) throws Exception {
logger.debug("sessionClosed");
session.closeOnFlush();
sessionHashMap.remove(session.getAttribute("id"));
}

@Override
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
logger.debug("session idle, so disconnecting......");
session.closeOnFlush();
logger.warn("disconnected");
}

@Override
public void sessionOpened(IoSession session) throws Exception {
logger.debug("sessionOpened.");
}

}

还有个就是心跳工厂:

public class NSMinaKeepAliveMessageFactory implements KeepAliveMessageFactory {

private final Logger logger = Logger
.getLogger(NSMinaKeepAliveMessageFactory.class);

private BaseMessageForServer basemessage;
/** 心跳包内容 */

private static long packetIdCode = 0;

/**
* 判断是否心跳请求包 是的话返回true
*/
public boolean isRequest(IoSession session, Object message) {
// TODO Auto-generated method stub
if (message instanceof BaseMessageForServer) {
basemessage = (BaseMessageForServer) message;
// 心跳包方法协议类型
if (basemessage.getFuncid() == 3) {
// 为3,代表是一个心跳包,
packetIdCode = basemessage.getPacketIdCode();

return true;
} else {
return false;
}
} else {
return false;
}
}

/**
* 由于被动型心跳机制,没有请求当然也就不关注反馈 因此直接返回false
*/
public boolean isResponse(IoSession session, Object message) {
// TODO Auto-generated method stub
return false;
}

/**
* 被动型心跳机制无请求 因此直接返回nul
*/
public Object getRequest(IoSession session) {
// TODO Auto-generated method stub
return null;
}

/**
* 根据心跳请求request 反回一个心跳反馈消息
*/
public Object getResponse(IoSession session, Object request) {
// 组装消息内容,返回给客户端
BaseMessageForClient messageForClient = new BaseMessageForClient();
messageForClient.setFuncid(4);
// 内容
messageForClient.setContent("2222");
// 校验码生成
CRC32 crc32 = new CRC32();
crc32.update(messageForClient.getContent().getBytes());
// crc校验码
messageForClient.setCheckCode(crc32.getValue());
// 长度
messageForClient
.setLength(messageForClient.getContent().getBytes().length);

// 数据包标识码
messageForClient.setPacketIdCode(packetIdCode);
return messageForClient;
}

}

到此服务端代码结束。其实服务端与客户端都存在相似之处。编码,解码器都是一样的。客户端启动程序如下:

public class ClientTest {

public static void main(String[] args) {
NioSocketConnector connector = new NioSocketConnector();
//添加过滤器
connector.getFilterChain().addLast("logger", new LoggingFilter());
//设置编码,解码过滤器
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ByteArrayCodecFactory()));
//connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("utf-8"))));//设置编码过滤器
connector.setHandler(new ClientHandler());//设置事件处理器
ConnectFuture cf = connector.connect(new InetSocketAddress("127.0.0.1",8888)); //建立连接
cf.awaitUninterruptibly(); //等待连接创建完成
BaseMessageForServer message = new BaseMessageForServer();
String content = "hello world!";
CRC32 crc = new CRC32();
try {
crc.update(content.getBytes("GBK"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
message.setFuncid(5);
message.setPacketIdCode(10000);
message.setContent(content);
message.setCheckCode(crc.getValue());
message.setLength(content.getBytes().length);
cf.getSession().write(message);
}
}

到此mina基本的已完成。
---------------------
原文:https://blog.csdn.net/u012151597/article/details/78847234

Apache Mina -2的更多相关文章

  1. Apache MiNa 实现多人聊天室

    Apache MiNa 实现多人聊天室 开发环境: System:Windows JavaSDK:1.6 IDE:eclipse.MyEclipse 6.6 开发依赖库: Jdk1.4+.mina-c ...

  2. Apache Mina(一)

    原文链接:http://www.cnblogs.com/xuekyo/archive/2013/03/06/2945826.html Apache Mina是一个能够帮助用户开发高性能和高伸缩性网络应 ...

  3. Apache MINA(一)

    Apache MINA is a network application framework which helps users develop high performance and high s ...

  4. Apache Mina 入门实例

    这个教程是介绍使用Mina搭建基础示例.这个教程内容是以创建一个时间服务器. 以下是这个教程需要准备的东西: MINA 2.0.7 Core JDK 1.5 或更高 SLF4J 1.3.0 或更高 L ...

  5. Apache Mina原理及典型例子分析

    Apache Mina ,一个高性能 Java 异步并发网络通讯框架.利用 Mina 可以高效地完成以下任务: TCP/IP 和 UDP/IP 通讯 串口通讯 VM 间的管道通讯 SSL/TLS JX ...

  6. Apache Mina 2.x 框架+源码分析

    源码下载 http://www.apache.org/dyn/closer.cgi/mina/mina/2.0.9/apache-mina-2.0.9-src.tar.gz 整体架构 核心过程(IoA ...

  7. Apache Mina开发手冊之四

    Apache Mina开发手冊之四 作者:chszs,转载需注明. 博客主页:http://blog.csdn.net/chszs 一.Mina开发的主要步骤 1.创建一个实现了IoService接口 ...

  8. 网络通信框架Apache MINA

    Apache MINA(Multipurpose Infrastructure for Network Applications) 是 Apache 组织一个较新的项目,它为开发高性能和高可用性的网络 ...

  9. Apache Mina(二)

    在mina的源码,整个框架最核心的几个包是 : org.apache.mina.core.service :IoService.IoProcessor.IoHandler.IoAcceptor.IoC ...

  10. Apache Mina入门实例

    一.mina是啥 ApacheMINA是一个网络应用程序框架,用来帮助用户简单地开发高性能和高可扩展性的网络应用程序.它提供了一个通过Java NIO在不同的传输例如TCP/IP和UDP/IP上抽象的 ...

随机推荐

  1. Linq小整理

    Linq(Language Integrated Query)中文翻译为语言集成查询 (1)源起 .net的设计者在类库中定义了一系列的扩展方法 来方便用户操作集合对象 这些扩展方法构成了LINQ的查 ...

  2. ubuntu 18.04安装docker以及docker内配置neo4j

    如题 切换到root用户下 apt install docker.io 等啊等,很快,就好了.. 如图 即可使用 如果出现Cannot connect to the Docker daemon at ...

  3. JSF-受管Bean与EL表达式

    受管Bean与EL表达式 1)编写Bean:①有一个不带形参的构造方法 ②getXxx.setXxx ③一般要实现io.Serializable接口 2)声明受管Bean:①bean名称为外界访问其属 ...

  4. Ubuntu16.04下安装Hive

    上一篇博客我们已经说过了要如何安装Hadoop,别忘记了我们的目的是安装Hive.所以这篇博客,我就来介绍一下如何安装Hive. 一.环境准备 (1)Vmware (2)  Ubuntu 16.04 ...

  5. dw cs6 trial

    试用版: https://helpx.adobe.com/x-productkb/policy-pricing/cs6-product-downloads.html English/Japanese: ...

  6. 小白突破百度翻译反爬机制,33行Python代码实现汉译英小工具!

    表弟17岁就没读书了,在我家呆了差不多一年吧. 呆的前几个月,每天上网打游戏,我又不好怎么在言语上管教他,就琢磨着看他要不要跟我学习Python编程.他开始问我Python编程什么?我打开了我给学生上 ...

  7. C++ 文件流的详解

    部分内容转载:http://blog.csdn.net/kingstar158/article/details/6859379 感谢追求执着,原本想自己写,却发现了这么明白的文章. C++文件流操作是 ...

  8. Android自动化框架介绍

    随着Android应用得越来越广,越来越多的公司推出了自己移动应用测试平台.例如,百度的MTC.东软易测云.Testin云测试平台…….由于自己所在项目组就是做终端测试工具的,故抽空了解了下几种常见的 ...

  9. CSS学习笔记二:css 画立体图形

    继上一次学了如何去运用css画平面图形,这一次学如何去画正方体,从2D向着3D学习,虽然有点满,但总是一个过程,一点一点积累,然后记录起来. Transfrom3D 在这一次中运用到了一下几种属性: ...

  10. PAT1040:Longest Symmetric String

    1040. Longest Symmetric String (25) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, ...