《Java网络编程》中chargenServer与chargenClient的NIO执行过程

xiaoxiao2021-02-28  130

《Java网络编程》中chargenServer与chargenClient的执行过程

1. chargenServer

 

package jnp4.nio.SocketIO; import java.nio.*; import java.nio.channels.*; import java.net.*; import java.util.*; import java.io.IOException; /** * 选择器支持一个线程查询一组socket,找出哪些socket已经准备就绪可以读/写数据,然后顺序地处理这些准备好的socket。 */ public class ChargenServer { public static int DEFAULT_PORT = 2036; public static void main(String[] args) { int port; try { port = Integer.parseInt(args[0]); } catch (RuntimeException ex) { port = DEFAULT_PORT; } System.out.println("Listening for connections on port " + port); byte[] rotation = new byte[95 * 2]; //ASCII 文本行有74个ASCII字符长( 72个可打印字符,后面是回车/换行对) //此数据在初始化之后只用于读取,所以可以重用于多个通道 // space对应32 ~对应126 可显示字符就是从32到126 一共是126-32+1=95个字符 for (byte i = ' '; i <= '~'; i++) { rotation[i - ' '] = i; rotation[i - ' ' + 95] = i; } ServerSocketChannel serverSocketChannel; Selector selector; try { //调用静态工厂方法ServerSocketChannel . open ()创建一个新的ServerSocketChannel对象。 serverSocketChannel = ServerSocketChannel.open(); ServerSocket ss = serverSocketChannel.socket(); /*开始时,这个通道并没有具体监听任何端口。 要把它绑定到一个端口,可以用socket()方法获取其ServerSocket对等端( peer )对象, 然后使用bind ()方法绑定到这个对等端*/ InetSocketAddress address = new InetSocketAddress(port); ss.bind(address); /*你可能还希望ServerSocketζhannel也处于非阻塞模式。默认情况下,这个accept ()方邑 会阻塞,直到有一个人站连接为止,这与ServerSocket的accept ()方法类似。为了改变 这一点,只需在调用accept ()之前调用configureBlocking(false):*/ serverSocketChannel.configureBlocking(false); /*可以创建一个Selector ,允许程序迭代处理所有准备好的连接。 要构造一个新的Selector ,只需调用Selector.open ()静态工厂方法:*/ selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); } catch (IOException ex) { ex.printStackTrace(); return; } /*为了检查是否有可操作的数据,可以调用选择器的select ()方法。对于长时间运行的服 务器,这一般要放在一个无限循环中:*/ while (true) { try { //选择一组keys,其相应的通道准备好进行I/O操作 //该方法执行阻塞选择操作。 //只有在至少选择一个通道之后,才会返回此选择器的唤醒方法,或者当前线程中断,以先到者为准。 selector.select(); } catch (IOException ex) { ex.printStackTrace(); break; } /*假定选择器确实找到了一个就绪的通道,其selectedKeys()方法会返回一个java.util.Set, 其中对应各个就绪通道分别包含一个SelectionKey对象。否则它会返回一个空集。 在两种情况下,都可以通过一个java.util.Iterator循环处理*/ Set<SelectionKey> readyKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = readyKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); /*Removes from the underlying collection the last element returned by this iterator (optional operation). This method can be called only once per call to next().*/ /*通过从集合中删除键,这就告诉选择器这个键已经处理过,这样Selector就不需要在每次调用select ()时再将这个键返回给我们了。 再次调用select ()时,如果这个通道再次就绪, Selector就会把该通道再增加到就绪集合中。*/ iterator.remove(); try { //就绪的通道是服务器通道,程序就会接受一个新Socket通道,将其添加到选择器。 if (key.isAcceptable()) { //返回创建此key的通道 这里是服务器socket通道 ServerSocketChannel server = (ServerSocketChannel) key.channel(); /*服务器Socket通道现在在端口19监听入站连接。要接受连接,可以调用accept (), 它会返回一个SocketChannel对象:*/ SocketChannel clientSocketChannel = server.accept(); System.out.println("Accepted connection from " + clientSocketChannel); //在服务器端,你肯定希望客户端通道处子非阻塞模式,以允许服务器处理多个并发连接 clientSocketChannel.configureBlocking(false); //使用每个通道的register()方法向监视这个通道的选择器进行注册。 //在注册时,要使用SelectionKey类提供的命名常量指定所关注的操作。 SelectionKey clientSelectionKey = clientSocketChannel.register(selector, SelectionKey. OP_WRITE); //服务器SocketChannel建立缓冲器... ByteBuffer buffer = ByteBuffer.allocate(74); buffer.put(rotation, 0, 72); buffer.put((byte) '\r'); buffer.put((byte) '\n'); //position = 1, limit = 74 buffer.flip(); //把buffer附加到通道的SelectionKey的attachment object中: clientSelectionKey.attach(buffer); //如果就绪的通道是客户端Socket通道, //程序就会向客户端Socket通道写入之前附加到其clientSelectionKey中attachment成员变量中的数据。 //如果没有通道就绪,选择器就会等待。一个线程(主线程)可以同时处理多个连接。 } else if (key.isWritable()) { /*向客户端SocketChannel写入数据很简单。首先获取键的附件,将它转换为ByteBuffer , 调用has Remaining ()检查缓冲器中是否还剩余未写的数据。 如果有,就写入到客户端通道。 否则,用rotation数组中的下一行数据重新填充缓冲区,并写入到客户端通道。*/ //返回创建此key的通道 这里是客户端socket通道 SocketChannel clientSocketChannel = (SocketChannel) key.channel(); //获取键的附件,将它转换为ByteBuffer ByteBuffer buffer = (ByteBuffer) key.attachment(); //如果buffer没有内容了,已经排空了(即已经写到ClientSocketChannel了,position==limit // 当下一次循环时,会进入到此if语句中) if (!buffer.hasRemaining()) { // position = 1 buffer.rewind(); // 确定最后一行从哪里开始 //以回车/换行作为行分隔符的72字符循环文本行(其中包含95个可打印ASCII字符) //要写入客户端SocketChannel循环文本行 /*要确定从哪里获取下一行数据,这个算法依赖于以ASCII字符顺序存储在rotation数组中的字符。 buffer.get ()从缓冲区中读取第一个数据字节。这个数字要减去空格字符(32),因为空格是rotation数组中的第一个字符。 由此可以知道缓冲区当前从数组的哪个索引开始。要加l来得到下一行的开始索引,并重新填充缓冲区。*/ // Get the old first character int first = buffer.get(); // 执行完这条语句 position = 2 // reset position = 1 buffer.rewind(); // Find the new first characters position in rotation int position = first - ' ' + 1; // copy the data from rotation into the buffer //以用现有的子数组填充一个ByteBuffer buffer.put(rotation, position, 72); // Store a line break at the end of the buffer buffer.put((byte) '\r'); buffer.put((byte) '\n'); // Prepare the buffer for writing // limit = 74 position = 1 buffer.flip(); } //将缓冲区内容写到ClientSocketChannel clientSocketChannel.write(buffer); } /*在chargen协议中,服务器永远不会关闭连接。它等待客户端中断Socket。 当Socket中断时,会抛出一个异常。取消这个键,并关闭对应的通道:*/ } catch (IOException ex) { key.cancel(); try { key.channel().close(); } catch (IOException cex) { } } } } } }

 

2. chargenClient

 

package jnp4.nio.SocketIO; import java.nio.*; import java.nio.channels.*; import java.net.*; import java.io.IOException; public class ChargenClient { public static int DEFAULT_PORT = 19; /** * 将服务器所发送连续的字符序列显示到system.out上 * * 只有当你希望客户端 有更多的功能时,即除了将所有输入复制到输出之外还要做其他一些工作,才会真正 体现出新特性。 * * @param args */ public static void main(String[] args) { int port = Integer.parseInt("2036"); try { /*要调用静态工厂方法SocketChannel.open()来创建一个新的java.nio.channels.SocketChannel对象。 这个方法的参数是一个java.net.SocketAddress对象,指示要连接的主机和端口*/ SocketAddress address = new InetSocketAddress("localhost", port); SocketChannel clientChannel = SocketChannel.open(address); //通道以阻塞模式打开 /*利用通道,你可以直接写入通道本身。不是写入字节数组,而是要写入ByteBuffer对象。 你已经很清楚,文本行有74个ASCil字符长( 72个可打印字符,后面是回车/换行对), 所以要使用静态方法allocate ()创建一个容量为74字节的ByteBuffer*/ ByteBuffer buffer = ByteBuffer.allocate(74); //利用Channels工具类(确切地讲是该工具类的newChannel ()方法), // 将OutputStream将System.out封装在一个通道中: WritableByteChannel out = Channels.newChannel(System.out); //将这个ByteBuffer对象传递给通道的read() /*在非阻塞模式下,即使没有任何可用的数据, read()也会立即返回。 这就允许程序在试图读取前做其他操作。它不必等待慢速的网络连接*/ while (clientChannel.read(buffer) != -1) { //回绕( flip )缓冲区,使得输出通道会从所读取数据的开头而不是末尾开始写入 buffer.flip(); out.write(buffer); /*请空将把缓冲区重置回初始状态(这实际上有点过于简化。老数据仍然存在,还没有被覆 盖,但很快就会被从掘读取的新数据覆盖)*/ buffer.clear(); } } catch (IOException ex) { ex.printStackTrace(); } } }

 

3. 交互过程

TCP是全双工的,双向传递

 

转载请注明原文地址: https://www.6miu.com/read-28387.html

最新回复(0)