一、Mina中的线程池模型

前面介绍了Mina总体的层次结构,那么在Mina里面是怎么使用Java NIO和进行线程调度的呢?这是提高IO处理性能的关键所在。Mina的线程调度原理主要如下图所示:

Acceptor与Connector线程

在服务器端,bind一个端口后,会创建一个Acceptor线程来负责监听工作。这个线程的工作只有一个,调用Java NIO接口在该端口上select connect事件,获取新建的连接后,封装成IoSession,交由后面的Processor线程处理。在客户端,也有一个类似的,叫Connector的线程与之相对应。这两类线程的数量只有1个,外界无法控制这两类线程的数量。

TCP实现的代码可以参考org.apache.mina.core.polling.AbstractPollingIoAcceptor的内部类Acceptor和org.apache.mina.core.polling.AbstractPollingIoConnector的内部类Connector。

Processor线程

Processor线程主要负责具体的IO读写操作和执行后面的IoFilterChain和IoHandler逻辑。Processor线程的数量N默认是CPU数量+1,可以通过配置参数来控制其数量。前面进来的IoSession会被分配到这N个Processor线程中。默认的SimpleIoProcessorPool的策略是session id绝对值对N取模来分配。

每个Porcessor线程中都维护着一个selector,对它维护的IoSession集合进行select,然后对select的结果进行遍历,逐一处理。像前面提到的,读取数据,以事件的形式通知后面IoFilterChain;以及对写请求队列的flush操作,都是在这类线程中来做的。

通过将session均分到多个Processor线程里进行处理,可以充分利用多核的处理能力,减轻select操作的压力。默认的Processor的线程数量设置可以满足大部分情况下的需求,但进一步的优化则需要根据实际环境进行测试。

线程模型

线程模型原理

从单一的Processor线程内部来看,IO请求的处理流程是单线程顺序处理的。前面也提到过,当Process线程select了一批就绪的IO请求后,会在线程内部逐一对这些IO请求进行处理。处理的流程包括IoFilter和IoHandler里的逻辑。当前面的IO请求处理完毕后,才会取下一个IO请求进行处理。也就是说,如果IoFilter或IoHandler中有比较耗时的操作的话(如:读取数据库等),Processor线程将会被阻塞住,后续的请求将得不到处理。这样的情况在高并发的服务器下显然是不能容忍的。于是,Mina通过在处理流程中引入线程池来解决这个问题。

那么线程池应该加在什么地方呢?正如前面所提到过的:IoFilterChain是Mina的扩展点。没错,Mina里是通过IoFilter的形式来为处理流程添加线程池的。Mina的线程模型主要有一下这几种形式:

第一种模型是单线程模型,也是Mina默认线程模型。也就是Processor包办了从底层IO到上层的IoHandler逻辑的所有执行工作。这种模型比较适合于处理逻辑简单,能快速返回的情况。

第二种模型则是在IoFilterChain中加入了Thread Pool Filter。此时的处理流程变为Processor线程读取完数据后,执行IoFilterChain的逻辑。当执行到Thread Pool Filter的时候,该Filter会将后续的处理流程封装到一个Runnable对象中,并交由Filter自身的线程池来执行,而Processor线程则能立即返回来处理下一个IO请求。这样如果后面的IoFilter或IoHandler中有阻塞操作,只会引起Filter线程池里的线程阻塞,而不会阻塞住Processor线程,从而提高了服务器的处理能力。Mina提供了Thread Pool Filter的一个实现:ExecutorFilter。

当然,也没有限制说chain中只能添加一个ExecutorFilter,开发者也可以在chain中加入多个ExecutorFilter来构成第三种情况,但一般情况下可能没有这个必要。

请求的处理顺序

在处理流程中加入线程池,可以较好的提高服务器的吞吐量,但也带来了新的问题:请求的处理顺序问题。在单线程的模型下,可以保证IO请求是挨个顺序地处理的。加入线程池之后,同一个IoSession的多个IO请求可能被ExecutorFilter并行的处理,这对于一些对请求处理顺序有要求的程序来说是不希望看到的。比如:数据库服务器处理同一个会话里的prepare,execute,commit请求希望是能按顺序逐一执行的。

Mina里默认的实现是有保证同一个IoSession中IO请求的顺序的。具体的实现是,ExecutorFilter默认采用了Mina提供的OrderedThreadPoolExecutor作为内置线程池。后者并不会立即执行加入进来的Runnable对象,而是会先从Runnable对象里获取关联的IoSession(这里有个down cast成IoEvent的操作),并将Runnable对象加入到session的任务列表中。OrderedThreadPoolExecutor会按session里任务列表的顺序来处理请求,从而保证了请求的执行顺序。

对于没有顺序要请求的情况,可以为ExecutorFilter指定一个Executor来替换掉默认的OrderedThreadPoolExecutor,让同一个session的多个请求能被并行地处理,来进一步提高吞吐量。

read  in  data: 
IO读入(IoProcessor)---日志记录、解码、threadPool(IoFilter)---业务逻辑处理(IoHandler) 
write  out  data: 
业务逻辑处理(IoHandler)---日志记录、编码、threadPool(IoFilter)---IO写出(IoProcessor) 
由以上可以看出,IO读入和IO写出的过程是相反的。

二、mina框架中的线程池

Mina中的线程池使用主要有四个地方:

1、IoAcceptor线程池。(一个端口一个线程)不可配置

2、IoConnector线程池。(一个端口一个线程)不可配置

3、IoProcessor线程池。可配置

4、过滤器类ExecutorFilter线程池。可配置(可以配置线程,也可以不配置,如果不配置则业务逻辑处理将共用IoProcessor线程)

2.1、IoAcceptor/IoConnector线程

2.1.1、线程类型及配置:

  线程类型newCachedThreadPool。

   IoAcceptor用于监听客户端的连接,每监听一个端口建立一个线程。IoConnector用于与服务端建立连接,每连接一个服务端就建立一个线程。这两种线程都是通过线程池建立的,我们可以在构建对象的时候就指定线程池类型:

  1. public NioSocketAcceptor(Executor executor, IoProcessor<NioSession> processor) {}
  2. public NioSocketConnector(Executor executor, IoProcessor<NioSession> processor) {}

2.1.2、线程创建:

此类线程池的构造在源代码中为(AbstractIoService第168行):

由此可见默认的线程池类型为newCachedThreadPool,这是一个可根据需要创建新线程的线程池,在以前构造的线程可用时可以重用它们。

2.1.3、提交任务作业方法:

对于IoAcceptor提交任务的方法链:AbstractIoAccepter.bind(Iterable<? extends SocketAddress> localAddresses)-->AbstractPollingAccepter.bindInternal(List<? extends SocketAddress> localAddresses)-->AbstractPollingAccepter.startupAcceptor()-->AbstractIoService.executeWorker()

AbstractIoService.executeWorker():

  1. protected final void executeWorker(Runnable worker, String suffix) {
  2. String actualThreadName = threadName;
  3. if (suffix != null) {
  4. actualThreadName = actualThreadName + '-' + suffix;
  5. }
  6. // 向线程池中提交任务。
  7. executor.execute(new NamePreservingRunnable(worker, actualThreadName));
  8. }

对于IoAcceptor的任务提交调用是在bind和unbind方法实现中的,看下bind最终调用,在类AbstractPollingIoAcceptor的startupAcceptor方法中:

AbstractPollingIoAcceptor

  1. private void startupAcceptor() throws InterruptedException {
  2. // If the acceptor is not ready, clear the queues
  3. // TODO : they should already be clean : do we have to do that ?
  4. if (!selectable) {
  5. registerQueue.clear();
  6. cancelQueue.clear();
  7. }
  8.  
  9. // start the acceptor if not already started
  10. Acceptor acceptor = acceptorRef.get();
  11.  
  12. if (acceptor == null) {
  13. lock.acquire();
  14. acceptor = new Acceptor();
  15.  
  16. if (acceptorRef.compareAndSet(null, acceptor)) {
  17. executeWorker(acceptor);
  18. } else {
  19. lock.release();
  20. }
  21. }
  22. }

AbstractPollingIoAcceptor.bindInternal()和

  1. protected final Set<SocketAddress> bindInternal(List<? extends SocketAddress> localAddresses) throws Exception {
  2. // Create a bind request as a Future operation. When the selector
  3. // have handled the registration, it will signal this future.
  4. AcceptorOperationFuture request = new AcceptorOperationFuture(localAddresses);
  5.  
  6. // adds the Registration request to the queue for the Workers
  7. // to handle
  8. registerQueue.add(request);
  9.  
  10. // creates the Acceptor instance and has the local
  11. // executor kick it off.
  12. startupAcceptor();
  13.  
  14. // As we just started the acceptor, we have to unblock the select()
  15. // in order to process the bind request we just have added to the
  16. // registerQueue.
  17. try {
  18. lock.acquire();
  19.  
  20. // Wait a bit to give a chance to the Acceptor thread to do the select()
  21. Thread.sleep(10);
  22. wakeup();
  23. } finally {
  24. lock.release();
  25. }
  26.  
  27. // Now, we wait until this request is completed.
  28. request.awaitUninterruptibly();
  29.  
  30. if (request.getException() != null) {
  31. throw request.getException();
  32. }
  33.  
  34. // Update the local addresses.
  35. // setLocalAddresses() shouldn't be called from the worker thread
  36. // because of deadlock.
  37. Set<SocketAddress> newLocalAddresses = new HashSet<SocketAddress>();
  38.  
  39. for (H handle : boundHandles.values()) {
  40. newLocalAddresses.add(localAddress(handle));
  41. }
  42.  
  43. return newLocalAddresses;
  44. }

AbstractPollingIoAcceptor.unbind0(List<? extends SocketAddress> localAddresses)

  1. protected final void unbind0(List<? extends SocketAddress> localAddresses) throws Exception {
  2. AcceptorOperationFuture future = new AcceptorOperationFuture(localAddresses);
  3.  
  4. cancelQueue.add(future);
  5. startupAcceptor();
  6. wakeup();
  7.  
  8. future.awaitUninterruptibly();
  9. if (future.getException() != null) {
  10. throw future.getException();
  11. }

AbstractIoAccepter.bind()和AbstractIoAccepter.unbind()

  1. public final void bind(Iterable<? extends SocketAddress> localAddresses) throws IOException {
  2. if (isDisposing()) {
  3. throw new IllegalStateException("Already disposed.");
  4. }
  5.  
  6. if (localAddresses == null) {
  7. throw new IllegalArgumentException("localAddresses");
  8. }
  9.  
  10. List<SocketAddress> localAddressesCopy = new ArrayList<SocketAddress>();
  11.  
  12. for (SocketAddress a : localAddresses) {
  13. checkAddressType(a);
  14. localAddressesCopy.add(a);
  15. }
  16.  
  17. if (localAddressesCopy.isEmpty()) {
  18. throw new IllegalArgumentException("localAddresses is empty.");
  19. }
  20.  
  21. boolean activate = false;
  22. synchronized (bindLock) {
  23. synchronized (boundAddresses) {
  24. if (boundAddresses.isEmpty()) {
  25. activate = true;
  26. }
  27. }
  28.  
  29. if (getHandler() == null) {
  30. throw new IllegalStateException("handler is not set.");
  31. }
  32.  
  33. try {
  34. Set<SocketAddress> addresses = bindInternal(localAddressesCopy);
  35.  
  36. synchronized (boundAddresses) {
  37. boundAddresses.addAll(addresses);
  38. }
  39. } catch (IOException e) {
  40. throw e;
  41. } catch (RuntimeException e) {
  42. throw e;
  43. } catch (Throwable e) {
  44. throw new RuntimeIoException("Failed to bind to: " + getLocalAddresses(), e);
  45. }
  46. }
  47.  
  48. if (activate) {
  49. getListeners().fireServiceActivated();
  50. }
  51. }

再看下IoConnector,它最终是在方法connect时会提交任务,看下AbstractPollingIoConnector类的startupWorker方法:

  1. if (connector == null) {
  2. connector = new Connector();
  3.  
  4. if (connectorRef.compareAndSet(null, connector)) {
  5. //提交执行任务
  6. executeWorker(connector);
  7. }
  8. }

对于IoAcceptor和IoConnector线程池的线程池大小,一般来说一个对象里面只有一个线程池,一个线程池里面一般有一个线程,当然如果你的连接或者监听比较多时可能会自动增加线程,这个就看线程池自己分配了。

2.2、IoProcessor线程

2.2.1、线程类型及配置:

  对于一个IoAcceptor或IoConnector线程对应一个IoProcessor线程用于IO的处理,这个IoProcessor线程从IoProcessor线程池中取出。IoProcessor线程池的大小默认为机器的CPU核数+1,例如双核机器的IoProcessor的线程池大小默认为3,我们可以更改线程池的大小:

看下面的构造方法,可知使用的是newCachedThreadPool。

2.2.2、线程创建:

如上就把IoProcessor线程池的大小改为9个。 
IoProcessor线程池的默认大小在源代码中的定义(SimpleIoProcessorPool第82行):

IoProcessor线程池的构造在源代码中为(SimpleIoProcessorPool第144行):

  1. public SimpleIoProcessorPool(Class<? extends IoProcessor<S>> processorType,
  2. Executor executor, int size) {
  3. //省略部分代码
  4. if (createdExecutor) {
  5. this.executor = Executors.newCachedThreadPool();
  6. } else {
  7. this.executor = executor;
  8. }
  9. }

2.2.3、提交任务作业方法:

提交任务的方法链:SimpleIoProcessorPool.add(S session)-->AbstractPollingIoProcessor.add(S session)/remove(S session)/dispose()-->AbstractPollingIoProcessor.startupProcessor()。x下面是一些关键方法的源码:
AbstractPollingIoProcessor.startupProcessor()
  1. private void startupProcessor() {
  2. Processor processor = processorRef.get();
  3.  
  4. if (processor == null) {
  5. processor = new Processor();
  6.  
  7. if (processorRef.compareAndSet(null, processor)) {
  8. executor.execute(new NamePreservingRunnable(processor, threadName));
  9. }
  10. }
  11.  
  12. // Just stop the select() and start it again, so that the processor
  13. // can be activated immediately.
  14. wakeup();
  15. }

AbstractPollingIoProcessor.add(S session)

  1. public final void add(S session) {
  2. if (disposed || disposing) {
  3. throw new IllegalStateException("Already disposed.");
  4. }
  5.  
  6. // Adds the session to the newSession queue and starts the worker
  7. newSessions.add(session);
  8. startupProcessor();
  9. }
SimpleIoProcessorPool.add(S session)
  1. public final void add(S session) {
  2. getProcessor(session).add(session);
  3. }

2.3、IoHandler线程

  ExecutorFilter类中的线程池。这是一个可选的线程池,是加在过滤器当中的。我们一般选择加在过滤器的最后面,这样Handler里面的业务处理就可以在线程池里面进行处理了。它的默认大小是16。

2.3.1、线程类型及配置:

线程类型:默认是OrderedThreadPoolExecutor(OrderedThreadPoolExecutor extends ThreadPoolExecutor的自定义线程池)

2.3.2、线程创建:

  当我们在过滤器链中没有添加“threadPool”过滤器,则业务逻辑处理和IoProcessor使用同一个线程。如果设置了“threadPool”过滤器,则使用设置的线程池产生线程进行业务逻辑处理,过滤器的配置如下:

  1. public ExecutorFilter() {
  2. // Create a new default Executor
  3. Executor executor = createDefaultExecutor(BASE_THREAD_NUMBER, DEFAULT_MAX_POOL_SIZE, DEFAULT_KEEPALIVE_TIME,
  4. TimeUnit.SECONDS, Executors.defaultThreadFactory(), null);
  5.  
  6. // Initialize the filter
  7. init(executor, MANAGEABLE_EXECUTOR);
  8. }

默认的池大小是16,默认的Executor的创建方式:

  1. private Executor createDefaultExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
  2. ThreadFactory threadFactory, IoEventQueueHandler queueHandler) {
  3. // Create a new Executor
  4. Executor executor = new OrderedThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit,
  5. threadFactory, queueHandler);
  6.  
  7. return executor;
  8. }

也可以自定义不同类型的线程池及池大小,如下:

如上配置之后,IO处理和业务逻辑处理将会使用各自的线程池产生线程使用。如果你的应用每次处理请求的时间较长而又希望应用能够有较好的响应性,那么最好是把处理业务逻辑的任务放到一个新的线程中去执行,而不是在 mina 框架创建的线程中去执行。

2.3.3、提交任务作业方法:

提交任务的方法链:DefaultIoFilterChain.callNextMessageReceived(Entry entry, IoSession session, Object message)
-->ExecutorFilter.messageReceived(NextFilter nextFilter, IoSession session, Object message)

-->ExecutorFilter.fireEvent(IoFilterEvent event)

  1. protected void fireEvent(IoFilterEvent event) {
  2. executor.execute(event);
  3. }
ExecutorFilter.messageReceived(NextFilter nextFilter, IoSession session, Object message)
  1. public final void messageReceived(NextFilter nextFilter, IoSession session, Object message) {
  2. if (eventTypes.contains(IoEventType.MESSAGE_RECEIVED)) {
  3. IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.MESSAGE_RECEIVED, session, message);
  4. fireEvent(event);
  5. } else {
  6. nextFilter.messageReceived(session, message);
  7. }
  8. }

DefaultIoFilterChain.callNextMessageReceived(Entry entry, IoSession session, Object message)

  1. private void callNextMessageReceived(Entry entry, IoSession session, Object message) {
  2. try {
  3. IoFilter filter = entry.getFilter();
  4. NextFilter nextFilter = entry.getNextFilter();
  5. filter.messageReceived(nextFilter, session, message);
  6. } catch (Throwable e) {
  7. fireExceptionCaught(e);
  8. }
  9. }

2.4各种线程的产生

  • 当 IoAcceptor/IoConnector实例创建的时候,同时一个关联在IoAcceptor/IoConnector上的IoProcessor线程池也被创建。
  • 当IoAcceptor/IoConnector建立套接字(IoAcceptor 的bind()或者是IoConnector 的connect()方法被调用)时,从线程池中取出一个线程,监听套接字端口。
  • 当 IoAcceptor/IoConnector监听到套接字上有连接请求时,建立IoSession 对象,从IoProcessor池中取出一个IoProcessor线程执行IO处理。
  • 如若过滤器中配置了“threadPool”过滤器,则使用此线程池建立线程执行业务逻辑(IoHandler)处理,否则使用IoProcessor线程处理业务逻辑。

3线程查看 
举个例子通过jdk自带工具jvisualvm来查看线程:

  1. public class MinaTest {
  2. protected static Logger logger = LoggerFactory.getLogger(MinaTest.class);
  3. private static int PORT = 9999;
  4. public static void main(String[] args) {
  5. try {
  6. // 创建一个非阻塞的server端的Socket
  7. IoAcceptor acceptor = new NioSocketAcceptor();
  8. // 设置过滤器
  9. acceptor.getFilterChain().addLast("logger", new LoggingFilter());
  10. acceptor.getFilterChain().addLast("codec",
  11. new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));
  12. acceptor.getFilterChain().addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool()));
  13. // 设置读取数据的缓冲区大小
  14. acceptor.getSessionConfig().setReadBufferSize(2048);
  15. // 读写通道10秒内无操作进入空闲状态
  16. acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
  17. // 绑定逻辑处理器
  18. acceptor.setHandler(new MinaServerHandler());
  19. // 绑定端口
  20. acceptor.bind(new InetSocketAddress(PORT));
  21. logger.info("服务端启动成功... 端口号为:" + PORT);
  22. } catch (Exception e) {
  23. logger.error("服务端启动异常....", e);
  24. e.printStackTrace();
  25. }
  26. }
  27. }
  1. public class MinaServerHandler extends IoHandlerAdapter {
  2. protected static Logger logger = LoggerFactory.getLogger(MinaServerHandler.class);
  3. public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
  4. logger.error("服务端发送异常...", cause);
  5. }
  6. public void messageReceived(IoSession session, Object message) throws Exception {
  7. String msg = message.toString();
  8. //如果是quit就关闭session退出
  9. if ("quit".equals(msg)) {
  10. session.close();
  11. }
  12. Date date = new Date();
  13. session.write(date.toString());
  14. }
  15. public void sessionCreated(IoSession session) throws Exception {
  16. logger.info("服务端与客户端创建连接...");
  17. }
  18. }
  • 运行MinaTest类,启动服务器端。
  • 开始->运行->cmd进入控制台 窗口。
  • 输入:telnet 127.0.0.1 9999
  • 再重复2、3步骤2次。
  • 对jvisualvm的线程视图截图如下:

    • 通过以上步骤我们可以看出我们打开了一个服务器端口,并用3个客户端进行连接,下面我们通过以上总结的知识来分析一下服务端产生的线程:
    • NioSccketAcceptor为服务器端监听端口9999通过线程池创建的一个线程。
    • NioProcessor-1、NioProcessor-2、NioProcessor-3为IoProcessor线程池创建的线程,用来IO处理。
    • pool-3-thread-1、pool-3-thread-2、pool-3-thread-3为过滤器配置的线程池创建的线程,用来业务逻辑处理。
 

参考:

http://www.iteye.com/topic/1124736

http://www.iteye.com/topic/1112123

spring-- 事务--9的更多相关文章

  1. spring事务概念理解

    1.数据并发问题 脏读 A事务读取B事务尚未提交的更新数据,并在此数据的基础上操作.如果B事务回滚,则A事务读取的数据就是错误的.即读取了脏数据或者错误数据. 不可重复组 A事务先后读取了B事务提交[ ...

  2. 【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】

    一.JDBC编程特点 静态代码+动态变量=JDBC编程. 静态代码:比如所有的数据库连接池 都实现了DataSource接口,都实现了Connection接口. 动态变量:用户名.密码.连接的数据库. ...

  3. Spring事务

    1.@Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.@Transactional 的 ...

  4. spring事务管理器设计思想(二)

    上文见<spring事务管理器设计思想(一)> 对于第二个问题,涉及到事务的传播级别,定义如下: PROPAGATION_REQUIRED-- 如果当前没有事务,就新建一个事务.这是最常见 ...

  5. spring事务管理器设计思想(一)

    在最近做的一个项目里面,涉及到多数据源的操作,比较特殊的是,这多个数据库的表结构完全相同,由于我们使用的ibatis框架作为持久化层,为了防止每一个数据源都配置一套规则,所以重新实现了数据源,根据线程 ...

  6. Spring事务管理的三种方式

    一 .第一种:全注解声明式事务 Xml代码 复制代码 收藏代码 .<?xml version="1.0" encoding="UTF-8"?> .& ...

  7. spring 事务传播特性 和隔离级别

    事务的几种传播特性1. PROPAGATION_REQUIRED: 如果存在一个事务,则支持当前事务.如果没有事务则开启2. PROPAGATION_SUPPORTS: 如果存在一个事务,支持当前事务 ...

  8. Spring事务管理

    Spring是SSH中的管理员,负责管理其它框架,协调各个部分的工作.今天一起学习一下Spring的事务管理.Spring的事务管理分为声明式跟编程式.声明式就是在Spring的配置文件中进行相关配置 ...

  9. Spring事务传播属性

    Spring 对事务控制的支持统一在 TransactionDefinition 类中描述,该类有以下几个重要的接口方法: int getPropagationBehavior():事务的传播行为 i ...

  10. Spring事务属性的介绍

    Spring声明式事务让我们从复杂的事务处理中得到解脱.使得我们再也无需要去处理获得连接.关闭连接.事务提交和回滚等这些操作.再也无需要我们在与事务相关的方法中处理大量的try-catch-final ...

随机推荐

  1. Chocolatey:Windows软件包管理器

    Chocolatey 2016-08-03 https://chocolatey.org/ Chocolatey是一个Windows软件包管理器,就像Nuget或者npm,或者说类似Linux上的ap ...

  2. lnmp 虚拟主机配置及重写

    lnmp安装与调试,请看 http://www.cnblogs.com/lin3615/p/4376224.html 虚拟主机的配置编辑nginx配置文件 nginx.conf此为主配置文件 vim ...

  3. appcan 跨窗口处理方法 appcan.window.evaluateScript({name,scriptContent,type})使用解读

    appcan.window.evaluateScript({ name,/*主窗口名称,此窗口要先用appcan.window.open打开了,才能找到,此方法才会有效*/ scriptContent ...

  4. python(二)基础

    1.lambda表达式(一般格式为冒号前边是参数冒号后边是返回值) 单参数: 多参数: 我们如果实现一个普通的filter过滤函数过滤掉偶数: 而我们如果用lambda表达式就很简便: 2.递归 普通 ...

  5. php中的preg系列函数

    mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int ...

  6. VC6.0生成的exe文件图标

    以下是我网上收到的方法 我都试过 成功不了 具体说下我遇到的问题 VC6.0生成的exe文件图标是用Icon下几个图标中value值最小的,顺序为IDR_MAINFRAME.IDR_ICONTETYP ...

  7. C#.NET连接mysql方法

    C#访问MySQL数据库的方法 (1)首先需要下载C#访问MySQL数据库的ADO.NET驱动程序 下载地址为: http://dev.mysql.com/downloads/connector/ne ...

  8. 导出Excel文件

    /// <summary> /// 类说明:Assistant /// 更新网站:[url=http://www.sufeinet.com/thread-655-1-1.html]http ...

  9. sjtu1364 countcountcount

    Description 我有一个元素个数为\(n\)的整数数组\(a\)和\(Q\)个问题,每个问题有\(x,y\)两个参数,我要数有多少个整数\(K\)满足\(K\)在\(a[x]-a[y]\)中出 ...

  10. [转载]在C#中使用官方驱动操作MongoDB

    在C#中使用官方驱动操作MongoDB 8.1)下载安装 想要在C#中使用MongoDB,首先得要有个MongoDB支持的C#版的驱动.C#版的驱动有很多种,如官方提供的,samus. 实现思路大都类 ...