一、NIO非阻塞式网络通信

  1.阻塞与非阻塞的概念

 传统的 IO 流都是阻塞式的。也就是说,当一个线程调用 read() 或 write()
时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不
能执行其他任务。因此,在完成网络通信进行 IO 操作时,由于线程会
阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,
当服务器端需要处理大量客户端时,性能急剧下降。
 Java NIO 是非阻塞模式的。当线程从某通道进行读写数据时,若没有数
据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时
间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入
和输出通道。因此,NIO 可以让服务器端使用一个或有限几个线程来同
时处理连接到服务器端的所有客户端。

  2.选择器

  大致的图解如下:

  

  // 通道注册到选择器上之后,选择器将会对通道进行监控,直到通道传输完全准备就绪了,才分配给服务端一个或多个线程进行处理

  3.使用NIO完成非阻塞式网络通信

  非阻塞核心有三个:(只能用于网络通信)

    通道(channel)  缓冲区(buffer)  选择器(selector)

  NIO完成的网络通信:

    // 客户端
@Test
public void client() {
SocketChannel sChannel = null;
FileChannel inChannel = null;
try {
// 获取通道(采用1.7之后的open方式)
sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
// 分配缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 读取本地文件(使用FileChannel),发送到服务端
inChannel = FileChannel.open(Paths.get("D:\\test\\1.jpg"), StandardOpenOption.READ);
while ((inChannel.read(byteBuffer)) != -1) {
// 通过socketChannel写入数据
byteBuffer.flip();// 切换读模式
sChannel.write(byteBuffer);
byteBuffer.clear();// 清空缓冲区
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 务必进行通道的关闭
if (inChannel != null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (sChannel != null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} }
// 服务端
@Test
public void server() {
ServerSocketChannel ssChannel = null;
FileChannel outChannel = null;
SocketChannel sChannel = null;
try {
// 开启通道
ssChannel = ServerSocketChannel.open();
// 绑定端口
ssChannel.bind(new InetSocketAddress(9898));
// 获取客户端连接的通道
sChannel = ssChannel.accept();
outChannel = FileChannel.open(Paths.get("D:\\test\\2.jpg"), StandardOpenOption.CREATE, StandardOpenOption.WRITE) ;
// 接收客户端数据,保存到本地
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while ((sChannel.read(byteBuffer)) != -1){
// 通道读写操作
byteBuffer.flip();
outChannel.write(byteBuffer);
byteBuffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outChannel != null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (sChannel != null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ssChannel != null) {
try {
ssChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

  阻塞式NIO网络通信:

    @Test
public void client() {
SocketChannel sChannel = null;
FileChannel inChannel = null;
try {
// 方式与阻塞式基本类似
sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
inChannel = FileChannel.open(Paths.get("D:\\test\\2.jpg"), StandardOpenOption.READ);
while ((inChannel.read(byteBuffer)) != -1) {
byteBuffer.flip();
sChannel.write(byteBuffer);
byteBuffer.clear();
}
// 接收服务端的反馈
int len;
while ((len = sChannel.read(byteBuffer)) != -1) {
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(), 0, len));
byteBuffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 流的关闭
if (inChannel != null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (sChannel != null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void server() {
ServerSocketChannel ssChannel = null;
SocketChannel sChannel = null;
FileChannel outChannel = null;
try {
ssChannel = ServerSocketChannel.open();
// 绑定客户端连接
ssChannel.bind(new InetSocketAddress(9898));
// 获取连接
sChannel = ssChannel.accept();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
outChannel = FileChannel.open(Paths.get("D:\\test\\3.jpg"), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
while ((sChannel.read(byteBuffer)) != -1) {
// 保存到本地
byteBuffer.flip();
outChannel.write(byteBuffer);
byteBuffer.clear();
}
// 发送反馈
String str = "服务端接收成功!";
byteBuffer.put(str.getBytes());
byteBuffer.flip();
sChannel.write(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outChannel != null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (sChannel != null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ssChannel != null) {
try {
ssChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

  结果:无法接收有效结束的反馈,不知道客户端是否发送完成

  

  目前可以通过显式的将客户端接收反馈之前发送状态设置为shutdowm:

sChannel.shutdownOutput();
 @Test
public void client() {
SocketChannel sChannel = null;
FileChannel inChannel = null;
try {
// 方式与阻塞式基本类似
sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
inChannel = FileChannel.open(Paths.get("D:\\test\\2.jpg"), StandardOpenOption.READ);
while ((inChannel.read(byteBuffer)) != -1) {
byteBuffer.flip();
sChannel.write(byteBuffer);
byteBuffer.clear();
}
sChannel.shutdownOutput();
// 接收服务端的反馈
int len;
while ((len = sChannel.read(byteBuffer)) != -1) {
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(), 0, len));
byteBuffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 流的关闭
if (inChannel != null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (sChannel != null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void server() {
ServerSocketChannel ssChannel = null;
SocketChannel sChannel = null;
FileChannel outChannel = null;
try {
ssChannel = ServerSocketChannel.open();
// 绑定客户端连接
ssChannel.bind(new InetSocketAddress(9898));
// 获取连接
sChannel = ssChannel.accept();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
outChannel = FileChannel.open(Paths.get("D:\\test\\3.jpg"), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
while ((sChannel.read(byteBuffer)) != -1) {
// 保存到本地
byteBuffer.flip();
outChannel.write(byteBuffer);
byteBuffer.clear();
}
// 发送反馈
String str = "服务端接收成功!";
byteBuffer.put(str.getBytes());
byteBuffer.flip();
sChannel.write(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outChannel != null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (sChannel != null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ssChannel != null) {
try {
ssChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

  非阻塞式网络通信

  // 客户端
@Test
public void client() {
SocketChannel sChannel = null;
try {
// 开启通道
sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
// 切换成非阻塞模式
sChannel.configureBlocking(false);
// 分配缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 发送数据给服务端(发送时间),数据是存放在缓冲区中
byteBuffer.put(LocalDateTime.now().toString().getBytes());
// 将缓冲区传送给服务端
byteBuffer.flip();
sChannel.write(byteBuffer);
byteBuffer.clear();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (sChannel != null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 服务端
@Test
public void server() {
ServerSocketChannel ssChannel = null;
SocketChannel sChannel = null;
try {
// 开启通道
ssChannel = ServerSocketChannel.open();
// 切换非阻塞模式
ssChannel.configureBlocking(false);
// 绑定连接
ssChannel.bind(new InetSocketAddress(9898));
// 获取选择器,同样的open方法
Selector selector = Selector.open();
// 将通道注册到选择器上,后一个参数表示选择键,也就是监控通道的什么状态
ssChannel.register(selector, SelectionKey.OP_ACCEPT);// 监听接收
// 通过选择器轮巡式选择已经“准备就绪“的事件
while (selector.select() > 0) {
// 获取当前选择器中所有注册的选择键
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
// 迭代器迭代
while (iterator.hasNext()) {
// 获取准备就绪的事件
SelectionKey selectionKey = iterator.next();
// 判断就绪的事件
if (selectionKey.isAcceptable()) {
// 若接收就绪,则获取客户端连接
sChannel = ssChannel.accept();
// 切换为非阻塞模式
sChannel.configureBlocking(false);
// 将该通道注册到选择器上,监控读就绪状态
sChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 获取选择器上读就绪状态的通道(需要强转)
sChannel = (SocketChannel) selectionKey.channel();
// 读取数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int len;
while ((len = sChannel.read(byteBuffer)) > 0) {
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(), 0, len));
byteBuffer.clear();
}
}
// 取消选择键(防止出现传输完成了应该重置而还显示完成的情况)
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}

  结果:可以轮巡接收客户端返回的时间

  

  // UDP的DataGramChannel类似,暂不展开

package com.atguigu.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner; import org.junit.Test; public class TestNonBlockingNIO2 { @Test
public void send() throws IOException{
DatagramChannel dc = DatagramChannel.open(); dc.configureBlocking(false); ByteBuffer buf = ByteBuffer.allocate(1024); Scanner scan = new Scanner(System.in); while(scan.hasNext()){
String str = scan.next();
buf.put((new Date().toString() + ":\n" + str).getBytes());
buf.flip();
dc.send(buf, new InetSocketAddress("127.0.0.1", 9898));
buf.clear();
} dc.close();
} @Test
public void receive() throws IOException{
DatagramChannel dc = DatagramChannel.open(); dc.configureBlocking(false); dc.bind(new InetSocketAddress(9898)); Selector selector = Selector.open(); dc.register(selector, SelectionKey.OP_READ); while(selector.select() > 0){
Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while(it.hasNext()){
SelectionKey sk = it.next(); if(sk.isReadable()){
ByteBuffer buf = ByteBuffer.allocate(1024); dc.receive(buf);
buf.flip();
System.out.println(new String(buf.array(), 0, buf.limit()));
buf.clear();
}
} it.remove();
}
} }

  注册选择器时的选择键:多个请使用或 | 连接

 读 : SelectionKey.OP_READ (1)
 写 : SelectionKey.OP_WRITE (4)
 连接 : SelectionKey.OP_CONNECT (8)
 接收 : SelectionKey.OP_ACCEPT (16)

  4.管道

  

  示例:

  @Test
public void test1() {
Pipe pipe = null;
Pipe.SinkChannel sinkChannel = null;
Pipe.SourceChannel sourceChannel = null;
try {
// 获取管道,经典的open方法获取
pipe = Pipe.open();
// 将缓冲区数据写入管道
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("通过单向管道发送数据".getBytes());
sinkChannel = pipe.sink();
byteBuffer.flip();
sinkChannel.write(byteBuffer); // 读取缓冲区中的数据
sourceChannel = pipe.source();
int len;
byteBuffer.flip();
len = sourceChannel.read(byteBuffer);
System.out.println(new String(byteBuffer.array(), 0, len));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (sinkChannel != null) {
try {
sinkChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (sourceChannel != null) {
try {
sourceChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} }

  //实际使用时可以两个线程公用一个pipe进行数据的传输

二、NIO2——Path Paths Files

  JDK7新增Path接口,Paths工具类,Files工具类。 这些接口和工具类对NIO中的功能进行了高度封装,大大简化了文件系统的IO编程。

  在传统java.io中, 文件和目录都被抽象成File对象, 即 File file = new File(".");

  NIO.2中则引入接口Path代表与平台无关的路径,文件和目录都用Path对象表示。比起传统File类,它更高效,更方便。

  作为与平台无关的系统访问支持;它的出现,就是为了替代 java.io.File

  Paths工具类——提供获取Path的两个静态方法:

   

 @Test
public void test1() {
Path path1 = Paths.get("D:\\test\\nio");
Path path2 = Paths.get("D:\\test", "nio2", "hello.txt");
System.out.println(path1);
System.out.println(path2);
}

  

  //实现了 toString方法,直接输出即可看到根据当前平台产生的路径

  Path接口

    getNameCount()——返回路径中的节点数量

  @Test
public void test1() {
Path path1 = Paths.get("D:\\test\\nio");
Path path2 = Paths.get("D:\\test", "nio2", "hello.txt");
System.out.println(path1);
System.out.println(path2);
System.out.println(path1.getNameCount());
System.out.println(path2.getNameCount());
}

  

    getRoot()——返回根节点,win中是盘符 C D,Linux中是 /,返回的新的Path只包含根节点

 @Test
public void test1() {
Path path1 = Paths.get("D:\\test\\nio");
Path path2 = Paths.get("D:\\test", "nio2", "hello.txt");
System.out.println(path1);
System.out.println(path2);
System.out.println(path1.getRoot());
System.out.println(path2.getRoot());
}

  

    toAbsolutePath()——返回绝对路径,若Path初始化时为绝对路径,则返回原路径;若是相对路径,则返回与上级路径合成的绝对路径

 @Test
public void test1() {
Path path1 = Paths.get("person.txt");
Path path2 = Paths.get("D:\\test", "nio2", "hello.txt");
System.out.println(path1);
System.out.println(path2);
System.out.println(path1.toAbsolutePath());
System.out.println(path2.toAbsolutePath());
}

    

  更多常见方法:

 boolean endsWith(String path) : 判断是否以 path 路径结束
 boolean startsWith(String path) : 判断是否以 path 路径开始
 boolean isAbsolute() : 判断是否是绝对路径
 Path getFileName() : 返回与调用 Path 对象关联的文件名
 Path getName(int idx) : 返回的指定索引位置 idx 的路径名称
 int getNameCount() : 返回Path 根目录后面元素的数量
 Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
 Path getRoot() :返回调用 Path 对象的根路径
 Path resolve(Path p) :将相对路径解析为绝对路径
 Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
 String toString() : 返回调用 Path 对象的字符串表示形式

  深入讲解参见:http://blog.csdn.net/lirx_tech/article/details/51416672

  Files工具类——完善的OS文件系统支持

    copy——复制文件

@Test
public void test1() {
Path src = Paths.get("D:\\test\\1.jpg");
Path dst = Paths.get("D:\\test", "2.jpg");
try {
Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
} }
}

    BufferedWriter——写入字符串

 @Test
public void test1() {
Path src = Paths.get("D:\\test\\hello.txt");
BufferedWriter writer = null;
try {
writer = Files.newBufferedWriter(src, StandardCharsets.UTF_8, StandardOpenOption.APPEND);
writer.write("Hello Files!");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} }

  其它诸如返回文件的大小,删除一个文件,以及与File的转换等等请参见以下随笔、常用方法列表或API:

  几个示例:http://www.cnblogs.com/digdeep/p/4478734.html

  常用方法:

 Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
 Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
 Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
 void delete(Path path) : 删除一个文件
 Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
 long size(Path path) : 返回 path 指定文件的大小
 Files常用方法:用于判断
 boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
 boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
 boolean isExecutable(Path path) : 判断是否是可执行文件
 boolean isHidden(Path path) : 判断是否是隐藏文件
 boolean isReadable(Path path) : 判断文件是否可读
 boolean isWritable(Path path) : 判断文件是否可写
 boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
 public static <A extends BasicFileAttributes> A readAttributes(Path path,Class<A> type,LinkOption...
options) : 获取与 path 指定的文件相关联的属性。
 Files常用方法:用于操作内容
 SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,
how 指定打开方式。
 DirectoryStream newDirectoryStream(Path path) : 打开 path 指定的目录
 InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
 OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象

Java基础——NIO(二)非阻塞式网络通信与NIO2新增类库的更多相关文章

  1. JAVA NIO学习三:NIO 的非阻塞式网络通信

    紧接着上一章,我们继续来研究NIO,上一章中我们讲了NIO 中最常见的操作即文件通道的操作,但实际上NIO的主要用途还是在于网络通信,那么这个时候就会涉及到选择器,这一章我们就会对其进行讲解操作. 一 ...

  2. 4.NIO的非阻塞式网络通信

    /*阻塞 和 非阻塞 是对于 网络通信而言的*/ /*原先IO通信在进行一些读写操作 或者 等待 客户机连接 这种,是阻塞的,必须要等到有数据被处理,当前线程才被释放*/ /*NIO 通信 是将这个阻 ...

  3. NIO 的非阻塞式网络通信

    1.阻塞与非阻塞   ①  传统的 IO 流都是阻塞式的.也就是说,当一个线程调用 read() 或 write()时, 该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务. 因 ...

  4. JAVA NIO学习记录2-非阻塞式网络通信

    一.阻塞与非阻塞 传统的IO 流都是阻塞式的.也就是说,当一个线程调用read() 或write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务.因此,在完成网络通信 ...

  5. Java IO(3)非阻塞式输入输出(NIO)

    在上篇<Java IO(2)阻塞式输入输出(BIO)>的末尾谈到了什么是阻塞式输入输出,通过Socket编程对其有了大致了解.现在再重新回顾梳理一下,对于只有一个“客户端”和一个“服务器端 ...

  6. JAVA基础知识之网络编程——-基于NIO的非阻塞Socket通信

    阻塞IO与非阻塞IO 通常情况下的Socket都是阻塞式的, 程序的输入输出都会让当前线程进入阻塞状态, 因此服务器需要为每一个客户端都创建一个线程. 从JAVA1.4开始引入了NIO API, NI ...

  7. Java基础知识强化之多线程笔记07:同步、异步、阻塞式、非阻塞式 的联系与区别

    1. 同步: 所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回.但是一旦调用返回,就必须先得到返回值了. 换句话话说,调用者主动等待这个"调用"的结果. 对于 ...

  8. NIO非阻塞式编程

    /** * NIO非阻塞式编程<p> * 服务端和客户端各自维护一个管理通道的对象,我们称之为selector,该对象能检测一个或多个通道 (channel) 上的事件. * 我们以服务端 ...

  9. Socket-IO 系列(三)基于 NIO 的同步非阻塞式编程

    Socket-IO 系列(三)基于 NIO 的同步非阻塞式编程 缓冲区(Buffer) 用于存储数据 通道(Channel) 用于传输数据 多路复用器(Selector) 用于轮询 Channel 状 ...

随机推荐

  1. 全新Chrome Devtool Performance使用指南

    运行时性能表现(runtime performance)指的是当你的页面在浏览器运行时的性能表现,而不是在下载页面的时候的表现.这篇指南将会告诉你怎么用Chrome DevToos Performan ...

  2. POJ-2992 Divisors---组合数求因子数目

    题目链接: https://cn.vjudge.net/problem/POJ-2992 题目大意: 给出组合数Cnk,求出其因子个数,其中n,k不大于431,组合数的值在long long范围内 解 ...

  3. 学会WCF之试错法——客户端调用基础

    1当客户端调用未返回结果时,服务不可用(网络连接中断,服务关闭,服务崩溃等) 客户端抛出异常 异常类型:CommunicationException InnerException: Message: ...

  4. PAT——1015. 德才论

    宋代史学家司马光在<资治通鉴>中有一段著名的“德才论”:“是故才德全尽谓之圣人,才德兼亡谓之愚人,德胜才谓之君子,才胜德谓之小人.凡取人之术,苟不得圣人,君子而与之,与其得小人,不若得愚人 ...

  5. 转-四种方案解决ScrollView嵌套ListView问题

    本人网上用的ID是泡面或安卓泡面,学习一年半之前开始从事Android应用开发,这是我写的第一篇Android技术文章,转载请注明出处和作者,有写的不好的地方还请帮忙指出,谢谢. 在工作中,曾多次碰到 ...

  6. React-Navigation web前端架构

    React-Navigation 前端架构 准备 /*安装组件*/ npm install --save react-navigation npm install --save react-nativ ...

  7. 在js中获取request域中的内容

    1.可以使用小脚本<%%>实现: var pro_id=<%request.getPro_id()%>; 2.使用隐藏域实现: <input type="hid ...

  8. SQLserver高级编程

    1.数据库设计 数据库设计的重要性: 减少冗余,提高性能.易维护 数据库设计的步骤: 1.收集信息.标识对象.标识属性.标识关系(一对一.一对多.多对一.多对多) E-R图: 属性:定义实体的性质.实 ...

  9. DIAView组态软件笔记

    1.为了节省成本,我们往往会在PLC将多个开关量整合到同一个word中,这样关联的变量可以从原有的16个变成现在的一个.这样做带来的麻烦就是需要我们在脚本中自己来解析出数据,通过对2求余(mod 2) ...

  10. Selenium基本用法以及元素定位

    一.Selenium 简介 Selenium是ThroughtWorks公司一个强大的开源Web功能测试工具系列,提供一套测试函数,用于支持Web自动化测试.函数非常灵活,能够完成界面元素定位.窗口跳 ...