java网络编程—NIO与Netty(二)
来源:互联网 发布:重生君颜网络剧在线看 编辑:程序博客网 时间:2024/06/06 17:48
相关文章
java网络编程—NIO与Netty(四)
java网络编程—NIO与Netty(三)
java网络编程—NIO与Netty(二)
java网络编程—NIO与Netty(一)
java网络编程—基石:五种IO模型及原理(多路复用\epoll)
继续接着总结NIO
Java NIO:transferFrom与transferTo
两个channel间的通信,不需要通过buffer直接进行数据交换。
示例:
/** * @author zhangsh */public class NIOTransferData { public static void main(String[] args) throws IOException { transferData(); } public static void transferData() throws IOException { RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); FileChannel fromChannel = fromFile.getChannel(); RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw"); FileChannel toChannel = toFile.getChannel(); long position = 0; long count = fromChannel.size(); toChannel.transferFrom(fromChannel, position, count); RandomAccessFile toFileEnd = new RandomAccessFile("toFileEnd.txt", "rw"); FileChannel toChannelEnd = toFileEnd.getChannel(); toChannel.transferTo(position, toChannel.size(), toChannelEnd); }}
Java NIO:Selector
java NIO通过使用Selector可以单线程的管理多个(channel)通道的读写。多个通道可以注册在一个Selector上,一个持有这个Selector的线程可以通过Selector去管理多个通道,每个通道都是某种类型的连接
Selector的创建
Selector selector = Selector.open();
为了将Channel和Selector配合使用,必须将channel注册到selector上。通过SelectableChannel.register()方法来实现,如下:
channel.configureBlocking(false); SelectionKey key = channel.register(selector,Selectionkey.OP_READ);
与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式
注意register()方法的第二个参数。这是一个“interest集合——感兴趣的事件集合,也就是这个selector准备监听的集合。注意区分 感兴趣的时间 与 准备好的事件的区别。
SelectionKey
关键属性:
interest集合
selectionKey.interestOps()
interestOps()是你注册的感兴趣的事件,通过如下方法获取,并判断(详见代码见最后实例)
如上边所说,register第二个参数是“interest set”,指定了channel监听的事件类型:Connect: SelectionKey.OP_CONNECT
一个成功连接了Server的channel,注册为ConnectAccept :SelectionKey.OP_ACCEPT
一个接受连接请求的 serverSocketChannel,被注册为Accept状态(注意该事件只用于服务端)Read :SelectionKey.OP_READ
一个有数据并可被读取的channel,注册为Read状态Write SelectionKey.OP_WRITE
一个可写入数据的channel,注册为Write状态
//如要你要注册多种事件,使用"|"操作int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
interestOps: channel上的关注的事件,通过&运算可以得到相应的判断
int interestSet = selectionKey.interestOps();boolean isInterestedInAccept = (interestSet &SelectionKey.OP_ACCEPT)!=0;boolean isInterestedInConnect = (interestSet &SelectionKey.OP_CONNECT)!=0;boolean isInterestedInRead = (interestSet & SelectionKey.OP_READ)!=0;boolean isInterestedInWrite = (interestSet &SelectionKey.OP_WRITE)!=0;
为什么这么判断?
比如,public static final int OP_WRITE = 1 << 2; 也就是说OP_WRITE =0010(2进制);
当你的interestSet 也是SelectionKey.OP_WRITE(1<<2)时,按位与操作后的结果还是本身 10,
否则其他的事件类型会得到0的结果。
- ready集合
与interest集合类似,但是这个ready集合中的事件表示客户端与服务端已经成功建立了连接。
同样是4种类型:
Connect: SelectionKey.OP_CONNECT
一个成功连接了Server的channel,注册为Connect
Accept :SelectionKey.OP_ACCEPT
一个接受连接请求的 serverSocketChannel,被注册为Accept状态(注意该事件只用于服务端)
Read :SelectionKey.OP_READ
一个有数据并可被读取的channel,注册为Read状态
Write SelectionKey.OP_WRITE
一个可写入数据的channel,注册为Write状态
//源码也是使用位操作判断的selectionKey.isAcceptable()//这个用在服务端selectionKey.isConnectable()selectionKey.isReadable()selectionKey.isWritable()
代码实践,请仔细阅读注释:
Java NIO 中所讲述的 Selector 的使用流程:1 通过 Selector.open() 打开一个 Selector.2 将 Channel 注册到 Selector 中, 并设置需要监听的事件(interest set)3 不断重复: 调用 select() 方法 调用 selector.selectedKeys() 获取 selected keys 迭代每个 selected key: 1) 从 selected key 中获取 对应的 Channel 和附加信息(如果有的话) 2) 判断是哪些 IO 事件已经就绪了, 然后处理它们. 如果是 OP_ACCEPT 事件, 则调用 "SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept()" 获取 SocketChannel, 并将它设置为 非阻塞的, 然后将这个 Channel 注册到 Selector 中. 3) 根据需要更改 selected key 的监听事件. 4) 将已经处理过的 key 从 selected keys 集合中删除.
服务端:
public class NIOServer { /* 标识数字 */ private int flag = 0; /* 缓冲区大小 */ private int BLOCK = 4096; /* 接受数据缓冲区 */ private ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);// 4KB /* 发送数据缓冲区 */ private ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);// 4KB private Selector selector; ServerSocketChannel serverSocketChannelTemp; public NIOServer(int port) throws IOException { // ServerSocketChannel用来在服务端监听Socket连接 // 在这个ServerSocketChannel建立之后(open静态方法建立),创建ServerSocket相应TCP请求 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); // 在这个ServerSocketChannel // 创建ServerSocket相应TCP请求。一个ServerSocketChannel上的ServerSocket是单例对象 System.out.println("serverSocketChannel" + serverSocketChannel); ServerSocket serverSocket = serverSocketChannel.socket(); // 然后在这个ServerSocket上绑定ip+port serverSocket.bind(new InetSocketAddress(port)); serverSocketChannelTemp = serverSocketChannel; // 最后,把那个ServerSocketChannel对象注册到selector上 // -----------------------------------------------------------Selector-------------------------------------------------------------- // 可以从selector中获取多个注册了的channel,一个selector可以管理多个channel, // 因此消除了创建多个线程去处理多个请求的做法。 // 另外,selector中对channel的管理都是非阻塞的,所以FileChannel这种阻塞的channel不能使用selector selector = Selector.open(); // register第二个参数是“interest set”,指定了channel监听的事件类型 /** * <pre> * -Connect SelectionKey.OP_CONNECT 一个成功连接了Server的channel,注册为Connect * * -Accept SelectionKey.OP_ACCEPT 一个接受连接请求的 serverSocketChannel,被注册为Accept状态 * * -Read SelectionKey.OP_READ 一个有数据并可被读取的channel,注册为Read状态 * * -Write SelectionKey.OP_WRITE 一个可写入数据的channel,注册为Write状态 * * 如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来 * int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; * </pre> */ // 每次在一个selector上注册一个channel,就会产生一个SelectionKey对象, // 需要说一下,SelectionKey对象的如下属性 // /** * 1)interestOps: channel上的关注的事件,通过&运算可以得到相应的判断 * * <pre> * int interestSet = selectionKey.interestOps(); * * boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT)==SelectionKey.OP_ACCEPT; * * boolean isInterestedInConnect = (interestSet & SelectionKey.OP_CONNECT)==SelectionKey.OP_CONNECT; * * boolean isInterestedInRead = (interestSet & SelectionKey.OP_READ)==SelectionKey.OP_READ; * * boolean isInterestedInWrite = (interestSet & SelectionKey.OP_WRITE)==SelectionKey.OP_WRITE; * * 2)readyOps 是channel准备好了的事件类型;注意与interestOps并不一样! * Selector.select()就是检查是否有注册的兴趣事件中已经准备好了的事件! * 可以通过如下方式判断: * selectionKey.isAcceptable(); 一个server socket channel准备好接收新进入的连接 * selectionKey.isConnectable(); 某个channel成功连接到另一个服务器 * selectionKey.isReadable(); 一个有数据可读的通道 * selectionKey.isWritable();等待写数据的通道 * </pre> */ SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT) " + selectionKey); System.out.println("Server Start----:" + port + " selector " + selector); } // 监听 private void listen() throws IOException { while (true) { /** * <pre> * select 返回在这个selector上注册过的兴趣事件 (interestsSet)对应的channel. * 比如你在这个selector上注册过,Accept事件, * 那么select的含义就是去选择已经准备好的,accept事件对应的channel。 * * int select():阻塞方法,直到至少返回一个你注册过的兴趣事件对应的channel. * int select(long timeout):与select()类似,不同之处在于设定了阻塞超时时间 * int selectNow():与select()类似,只是不会产生阻塞,立即返回 * * </pre> */ // It returns only after at least one channel is selected, this // selector's wakeup method is invoked, or the current thread is // interrupted, whichever comes first. if (selector.select() <= 0) {// Selector.select()就是检查是否有注册的兴趣事件中已经准备好了的事件. //注意.这里返回的是处于ready状态的事件对应的channel数量 continue; } // 执行完selector.select(),会暗示你是否有准备好的channel, // 接着执行 Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 遍历获取准备好的channel // 每次在一个selector上注册一个channel,就会产生一个SelectionKey对象 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); int i = 0; while (iterator.hasNext()) { ++i; SelectionKey selectionKey = iterator.next(); iterator.remove();// 必须手动remove这个使用过的key handleKey(selectionKey); } System.out.println("iterator size" + i); } } /** * 通过selectKey可以获取到对应的channel和selector (selectionKey.selector()) * @param selectionKey * @throws IOException */ private void handleKey(SelectionKey selectionKey) throws IOException { // 接受请求 ServerSocketChannel server = null; SocketChannel client = null; String receiveText; String sendText; int count = 0; // 测试此键的通道是否已准备好接新的Socket connection。 if (selectionKey.isAcceptable()) { server = (ServerSocketChannel) selectionKey.channel(); // 可阻塞模式:若为阻塞方法,只用于ServerSocketChannel,去监听建立的连接。 // 如果有连接过来,就返回这个新连接的channel // 如非阻塞模式:若没有连接建立,会返回null System.out.println("serverSocketChannel:" + server); System.err.println(serverSocketChannelTemp == server);// 可以看到还是server端之前自己注册的那个serverSocketChannel // 通过 ServerSocketChannel.accept() 方法监听新进来的连接。当 // accept()方法返回的时候,它返回一个包含新进来的连接的 SocketChannel。 // 通常不会仅仅只监听一个连接,在while循环中调用 accept()方法 client = server.accept(); client.configureBlocking(false); // 配置为非阻塞 System.out.println("clientSocketChannel:" + client); // 注册到selector,等待连接 client.register(selector, SelectionKey.OP_READ); } else if (selectionKey.isConnectable()) { // a connection was established with a remote server. System.out.println(" this is connectable !"); } else if (selectionKey.isReadable()) { // 返回为之创建此键的通道。 client = (SocketChannel) selectionKey.channel(); System.err.println(client.toString());// 可以看到还是server端之前自己注册的那个serverSocketChannel // 将缓冲区清空以备下次读取 receivebuffer.clear(); // 读取服务器发送来的数据到缓冲区中 count = client.read(receivebuffer); if (count > 0) { receiveText = new String(receivebuffer.array(), 0, count); System.out.println("服务器端接受客户端数据--:" + receiveText); client.register(selector, SelectionKey.OP_WRITE);// 客户端消息获取后,读取掉。接着注册一个可写事件,用来向客户端发送消息 } } else if (selectionKey.isWritable()) { // 将缓冲区清空以备下次写入 sendbuffer.clear(); // 返回为之创建此键的通道。 client = (SocketChannel) selectionKey.channel(); sendText = "message from server--" + flag++; // 向缓冲区中输入数据 sendbuffer.put(sendText.getBytes()); // 将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位 sendbuffer.flip(); // 输出到通道 client.write(sendbuffer); System.out.println("服务器端向客户端发送数据--:" + sendText); client.register(selector, SelectionKey.OP_READ);// 向客户端发送消息后,注册一个可读事件,当客户端再次发送消息时,这个事件将ready } } public static void main(String[] args) throws IOException { int port = 8989; NIOServer server = new NIOServer(port); server.listen(); }}
客户端:
public class NIOClient { /* 标识数字 */ private static int flag = 0; /* 缓冲区大小 */ private static int BLOCK = 4096; /* 接受数据缓冲区 */ private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);// 4KB; /* 发送数据缓冲区 */ private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK); /* 服务器端地址 */ private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress("localhost", 8989); public static void main(String[] args) throws IOException { /** * SocketChannel创建的两种方式: * * 1.客户端 : SocketChannel.open(); socketChannel.connect(SERVER_ADDRESS); * * * 2.服务端: SocketChannel client =serverSocketChannel.accept(); * 当有连接建立,accept方法返回建立连接的SocketChannel */ SocketChannel socketChannel = SocketChannel.open(); // You can set a SocketChannel into non-blocking mode. When you do so, // you can call connect(), read() and write() in asynchronous mode. socketChannel.configureBlocking(false); // 打开选择器 Selector selector = Selector.open(); // 注册连接服务端socket动作 socketChannel.register(selector, SelectionKey.OP_CONNECT); // 异步连接操作,如 connect() read() write() // 即使没有建立连接也会立刻返回,使用socketChannel.finishConnect()检查连接建立是否成功,未成功会抛出异常 socketChannel.connect(SERVER_ADDRESS); // 分配缓冲区大小内存 Set<SelectionKey> selectionKeys; Iterator<SelectionKey> iterator; SelectionKey selectionKey; SocketChannel client; String receiveText; String sendText; int count = 0; while (true) { // 选择一组键,其相应的通道已为 I/O 操作准备就绪。 // 此方法执行处于阻塞模式的选择操作。 // This method performs a blocking selection operation. It returns // only after at least one channel is selected, this selector's // wakeup method is invoked, or the current thread is interrupted, // whichever comes first. System.out.println(selector.select()); // 返回此选择器的已选择键集。 selectionKeys = selector.selectedKeys(); // System.out.println(selectionKeys.size()); iterator = selectionKeys.iterator(); while (iterator.hasNext()) { selectionKey = iterator.next(); if (selectionKey.isConnectable()) { System.out.println(" this is connectable !"); //通过selectKey可以获取到对应的channel和selector //selectionKey.selector() client = (SocketChannel) selectionKey.channel(); System.err.println(client == socketChannel); // 异步连接操作,如 connect() read() write() // 即使没有建立连接也会立刻返回,使用socketChannel.finishConnect()检查连接建立是否成功,未成功会抛出异常 if (client.isConnectionPending() && client.finishConnect()) { System.out.println("完成连接!"); sendbuffer.clear(); sendbuffer.put("Hello,Server".getBytes()); sendbuffer.flip(); client.write(sendbuffer); } client.register(selector, SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { client = (SocketChannel) selectionKey.channel(); // 将缓冲区清空以备下次读取 receivebuffer.clear(); // 异步连接操作 read(),即使socketChannel中没有可读内容,也会立刻返回 count = client.read(receivebuffer); if (count > 0) { receiveText = new String(receivebuffer.array(), 0, count); System.out.println("客户端接受服务器端数据--:" + receiveText); client.register(selector, SelectionKey.OP_WRITE);// 读取后接着注册一个可写事件,为了向服务端发消息 } } else if (selectionKey.isWritable()) { sendbuffer.clear(); client = (SocketChannel) selectionKey.channel(); sendText = "message from client--" + (flag++); sendbuffer.put(sendText.getBytes()); // 将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位 sendbuffer.flip(); while (sendbuffer.hasRemaining()) {// 无法保证一次全部写完,所以使用循环方式 client.write(sendbuffer); // 异步连接操作 // write(),什么都没写入也会返回,所以循环使用 } System.out.println("客户端向服务器端发送数据--:" + sendText); client.register(selector, SelectionKey.OP_READ);// 向服务端发送消息后,注册一个可读事件,当服务端再次返回消息时,这个事件将ready } } selectionKeys.clear(); } }}
SocketChannel
Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel:
打开一个SocketChannel并连接到互联网上的某台服务器。上边示例中,Client使用的方式。
一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。上边示例中,Server使用的方式,ServerSocketChannel.accept()
非阻塞模式
阻塞模式中,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
就是说channel可以使用异步的方式调用connect()、write()、read()等方法
connect()
socketChannel.configureBlocking(false);socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));while(! socketChannel.finishConnect() ){ //wait, or do something else...}
write()
非阻塞模式下,write()方法在尚未写出(指数据未到达tcp写缓冲区)任何内容时可能就返回了。所以需要在循环中调用write()。前面已经有例子了,这里就不赘述了。
read()
非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的int返回值,它会告诉你读取了多少字节。
serverSocketChannel.accept()
非阻塞的监听连接到当前服务器上的Socket连接。如果没有则返回null.
为什么说JAVA NIO提供了基于Selector的异步网络I/O?
第一节介绍了NIO是同步非阻塞的。那么我们为什么经常说的”NIO异步网络模型“
NIO异步网络模型指的编程模型上的异步,通过reactor模型将具体IO操作放入线程池异步化。
下一节开始介绍Netty。
- java网络编程—NIO与Netty(二)
- java网络编程—NIO与Netty(三)
- java网络编程—NIO与Netty(四) ByteBuffer数据处理与零拷贝
- Java NIO 与 Netty
- java网络编程之NIO(二)
- Netty框架(二)————NIO编程
- Java NIO框架Netty教程(二)
- Java 网络编程nio
- Java NIO 网络编程
- java NIO 网络编程
- Java NIO网络编程
- java-netty介绍-NIO非阻塞编程
- Netty学习 netty nio编程
- Java中的NIO与Netty框架
- 二、java网络io编程(BIO、NIO)
- Java 网络IO编程总结二(BIO、NIO、AIO)
- Netty学习-Java网络编程
- netty(三) NIO编程
- POJ 3522 Slim Span(生成树+克鲁斯卡尔)
- HTTPS和HTTP的区别
- AngularJS 表达式
- 有关http的那些事
- 怎么在Linux里面安装软件
- java网络编程—NIO与Netty(二)
- service类的方法上使用@Transactional
- mysql的基础,增删改查操作
- String,StringBuffer与StringBuilder的区别
- 修改tomcat端口号(tomcat端口号与其他应用冲突)
- Redis学习之redis.conf 配置项说明
- JAX-RS (REST Web Services) 2.0 requires Java 1.6 or newer.
- 01trie树 HDU 5536Chip Factory
- Js_Dom(1)__Dom基础<对象和document>