TCP-IP学习笔记四:NIO的网络编程-多线程实例
来源:互联网 发布:什么行业招数据分析 编辑:程序博客网 时间:2024/05/17 07:18
TCP/IP学习笔记四:NIO的网络编程-多线程实例
标签(空格分隔): 网络编程 NIO 多线程
NIO的多线程编程
对于单线程的程序来说,我们无法达到并行处理,我们要向达到并行处理,必定会使用多线程,但是我们哪些代码使用子线程呢?我们可以对单线程程序进行分析,在程序中最耗时的操作就是I/O操作(读和写)。找到入口就进行改造程序。
服务器端修改代码:
package com.socket.nio3;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.Iterator;import java.util.Set;/** * NIO 服务器端 * * @author MOTUI * */public class NIOServerSocket { private static Selector selector = null; public static void main(String[] args) throws IOException { // 1.创建ServerSocketChannel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 2.绑定端口 serverSocketChannel.bind(new InetSocketAddress(8989)); // 3.设置为非阻塞 serverSocketChannel.configureBlocking(false); // 4.创建通道选择器 selector = Selector.open(); /* * 5.注册事件类型 * * sel:通道选择器 * ops:事件类型 ==>SelectionKey:包装类,包含事件类型和通道本身。四个常量类型表示四种事件类型 * SelectionKey.OP_ACCEPT 获取报文 SelectionKey.OP_CONNECT 连接 * SelectionKey.OP_READ 读 SelectionKey.OP_WRITE 写 */ serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { System.out.println("服务器端:正在监听8989端口"); // 6.获取可用I/O通道,获得有多少可用的通道 int num = selector.select(); if (num > 0) { // 判断是否存在可用的通道 // 获得所有的keys Set<SelectionKey> selectedKeys = selector.selectedKeys(); // 使用iterator遍历所有的keys Iterator<SelectionKey> iterator = selectedKeys.iterator(); // 迭代遍历当前I/O通道 while (iterator.hasNext()) { // 获得当前key SelectionKey key = iterator.next(); // 调用iterator的remove()方法,并不是移除当前I/O通道,标识当前I/O通道已经处理。 iterator.remove(); // 判断事件类型,做对应的处理 if (key.isAcceptable()) { ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel(); SocketChannel socketChannel = ssChannel.accept(); System.out.println("处理请求:"+ socketChannel.getRemoteAddress()); // 获取客户端的数据 // 设置非阻塞状态 socketChannel.configureBlocking(false); // 注册到selector(通道选择器) socketChannel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { //调用读操作工具类 RequestProcessor.ProcessorRequest(key,selector); } else if (key.isWritable()) { //调用写操作工具类 ResponeProcessor.ProcessorRespone(key); } } } } }}
RequestProcessor:
package com.socket.nio3;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.SocketChannel;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 读操作的工具类 * @author MOTUI * */public class RequestProcessor { //构造线程池 private static ExecutorService executorService = Executors.newFixedThreadPool(10); public static void ProcessorRequest(final SelectionKey key,final Selector selector){ //获得线程并执行 executorService.submit(new Runnable() { @Override public void run() { try { System.out.println("开始读"); SocketChannel readChannel = (SocketChannel) key.channel(); // I/O读数据操作 ByteBuffer buffer = ByteBuffer.allocate(1024); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int len = 0; while (true) { buffer.clear(); len = readChannel.read(buffer); if (len == -1) break; buffer.flip(); while (buffer.hasRemaining()) { baos.write(buffer.get()); } } System.out.println("服务器端接收到的数据:"+ new String(baos.toByteArray())); System.out.println("开始注册------"); //注册写操作 readChannel.register(selector, SelectionKey.OP_WRITE); System.out.println("注册完成-------"); } catch (IOException e) { e.printStackTrace(); } } }); }}
ResponeProcessor:
package com.socket.nio3;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.SocketChannel;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 写操作工具类 * @author MOTUI * */public class ResponeProcessor { //构造线程池 private static ExecutorService executorService = Executors.newFixedThreadPool(10); public static void ProcessorRespone(final SelectionKey key) { //获得线程并执行 executorService.submit(new Runnable() { @Override public void run() { try { System.out.println("开始写"); // 写操作 SocketChannel writeChannel = (SocketChannel) key.channel(); //拿到客户端传递的数据 //ByteArrayOutputStream attachment = (ByteArrayOutputStream)key.attachment(); //System.out.println("客户端发送来的数据:"+new String(attachment.toByteArray())); ByteBuffer buffer = ByteBuffer.allocate(1024); String message = "你好,我好,大家好!!"; buffer.put(message.getBytes()); buffer.flip(); writeChannel.write(buffer); writeChannel.close(); } catch (IOException e) { e.printStackTrace(); } } }); }}
两个工具类分别处理读操作和写操作,分别加入了线程
客户端(未做修改):
package com.socket.nio3;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SocketChannel;/** * NIO 客户端 */public class NIOClientSocket { public static void main(String[] args) throws IOException { //1.创建SocketChannel SocketChannel socketChannel=SocketChannel.open(); //2.连接服务器 socketChannel.connect(new InetSocketAddress("192.168.0.117",8989)); //写数据 String msg="我是客户端~"; ByteBuffer buffer=ByteBuffer.allocate(1024); buffer.put(msg.getBytes()); buffer.flip(); socketChannel.write(buffer); socketChannel.shutdownOutput(); //读数据 ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len = 0; while (true) { buffer.clear(); len = socketChannel.read(buffer); if (len == -1) break; buffer.flip(); while (buffer.hasRemaining()) { bos.write(buffer.get()); } } System.out.println("客户端收到:"+new String(bos.toByteArray())); socketChannel.close(); }
执行结束:
服务器端:正在监听8989端口 处理请求:/192.168.0.117:60194 服务器端:正在监听8989端口 服务器端:正在监听8989端口 服务器端:正在监听8989端口 开始读 开始读 ... 开始写 开始写 开始写 开始写
会出现一个严重的问题:在主线程中,读操作会执行多次。因为在子线程没有执行结束,主线程认为读操作没有结束(在通道选择器中读操作的管道一直存在),会一直开启新的线程去执行读操作,这样就会产生严重的错误。产生这种问题的原因是我们将读操作交给子线程去处理,主线程依然对此通道保留监控,所以会一直执行读操作,我们取消主线程对此通道的监控。将服务器端代码稍作修改(添加:key.cancel();):
else if (key.isReadable()) { //取消读事件的监控 key.cancel(); //调用读操作工具类 RequestProcessor.ProcessorRequest(key,selector);}
执行结果为:
服务器端:正在监听8989端口 处理请求:/192.168.0.117:60378 服务器端:正在监听8989端口 服务器端:正在监听8989端口 开始读 服务器端接收到的数据:我是客户端~ 开始注册------
取消了主线程对通道的监控,但是出现了注册写操作无法执行(无法注册成功)。出现这种问题主要是由于selector.select()和注册操作需要同步。这种方式不能达到目的。换一种解决方式,我们在主线程控制通道的注册会怎样呢?
服务器端:
package com.socket.nio3;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.ArrayList;import java.util.Iterator;import java.util.List;import java.util.Set;/** * NIO 服务器端 * * @author MOTUI * */public class NIOServerSocket { //存储SelectionKey的队列 private static List<SelectionKey> writeQueen = new ArrayList<SelectionKey>(); private static Selector selector = null; //添加SelectionKey到队列 public static void addWriteQueen(SelectionKey key){ synchronized (writeQueen) { writeQueen.add(key); //唤醒主线程 selector.wakeup(); } } public static void main(String[] args) throws IOException { // 1.创建ServerSocketChannel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 2.绑定端口 serverSocketChannel.bind(new InetSocketAddress(8989)); // 3.设置为非阻塞 serverSocketChannel.configureBlocking(false); // 4.创建通道选择器 selector = Selector.open(); /* * 5.注册事件类型 * * sel:通道选择器 * ops:事件类型 ==>SelectionKey:包装类,包含事件类型和通道本身。四个常量类型表示四种事件类型 * SelectionKey.OP_ACCEPT 获取报文 SelectionKey.OP_CONNECT 连接 * SelectionKey.OP_READ 读 SelectionKey.OP_WRITE 写 */ serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { System.out.println("服务器端:正在监听8989端口"); // 6.获取可用I/O通道,获得有多少可用的通道 int num = selector.select(); if (num > 0) { // 判断是否存在可用的通道 // 获得所有的keys Set<SelectionKey> selectedKeys = selector.selectedKeys(); // 使用iterator遍历所有的keys Iterator<SelectionKey> iterator = selectedKeys.iterator(); // 迭代遍历当前I/O通道 while (iterator.hasNext()) { // 获得当前key SelectionKey key = iterator.next(); // 调用iterator的remove()方法,并不是移除当前I/O通道,标识当前I/O通道已经处理。 iterator.remove(); // 判断事件类型,做对应的处理 if (key.isAcceptable()) { ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel(); SocketChannel socketChannel = ssChannel.accept(); System.out.println("处理请求:"+ socketChannel.getRemoteAddress()); // 获取客户端的数据 // 设置非阻塞状态 socketChannel.configureBlocking(false); // 注册到selector(通道选择器) socketChannel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { //取消读事件的监控 key.cancel(); //调用读操作工具类 RequestProcessor.ProcessorRequest(key); } else if (key.isWritable()) { //取消读事件的监控 key.cancel(); //调用写操作工具类 ResponeProcessor.ProcessorRespone(key); } } }else{ synchronized (writeQueen) { while(writeQueen.size() > 0){ SelectionKey key = writeQueen.remove(0); //注册写事件 SocketChannel channel = (SocketChannel) key.channel(); Object attachment = key.attachment(); channel.register(selector, SelectionKey.OP_WRITE,attachment); } } } } }}
RequestProcessor类:
package com.socket.nio3;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.SocketChannel;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 读操作的工具类 * @author MOTUI * */public class RequestProcessor { //构造线程池 private static ExecutorService executorService = Executors.newFixedThreadPool(10); public static void ProcessorRequest(final SelectionKey key){ //获得线程并执行 executorService.submit(new Runnable() { @Override public void run() { try { SocketChannel readChannel = (SocketChannel) key.channel(); // I/O读数据操作 ByteBuffer buffer = ByteBuffer.allocate(1024); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int len = 0; while (true) { buffer.clear(); len = readChannel.read(buffer); if (len == -1) break; buffer.flip(); while (buffer.hasRemaining()) { baos.write(buffer.get()); } } System.out.println("服务器端接收到的数据:"+ new String(baos.toByteArray())); //将数据添加到key中 key.attach(baos); //将注册写操作添加到队列中 NIOServerSocket.addWriteQueen(key); } catch (IOException e) { e.printStackTrace(); } } }); }}
ResponeProcessor类:
package com.socket.nio3;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.SocketChannel;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 写操作工具类 * @author MOTUI * */public class ResponeProcessor { //构造线程池 private static ExecutorService executorService = Executors.newFixedThreadPool(10); public static void ProcessorRespone(final SelectionKey key) { //拿到线程并执行 executorService.submit(new Runnable() { @Override public void run() { try { // 写操作 SocketChannel writeChannel = (SocketChannel) key.channel(); //拿到客户端传递的数据 ByteArrayOutputStream attachment = (ByteArrayOutputStream)key.attachment(); System.out.println("客户端发送来的数据:"+new String(attachment.toByteArray())); ByteBuffer buffer = ByteBuffer.allocate(1024); String message = "你好,我好,大家好!!"; buffer.put(message.getBytes()); buffer.flip(); writeChannel.write(buffer); writeChannel.close(); } catch (IOException e) { e.printStackTrace(); } } }); }}
客户端代码:
package com.socket.nio3;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SocketChannel;/** * NIO 客户端 */public class NIOClientSocket { public static void main(String[] args) throws IOException { //使用线程模拟用户 并发访问 for (int i = 0; i < 2; i++) { new Thread(){ public void run() { try { //1.创建SocketChannel SocketChannel socketChannel=SocketChannel.open(); //2.连接服务器 socketChannel.connect(new InetSocketAddress("192.168.0.117",8989)); //写数据 String msg="我是客户端"+Thread.currentThread().getId(); ByteBuffer buffer=ByteBuffer.allocate(1024); buffer.put(msg.getBytes()); buffer.flip(); socketChannel.write(buffer); socketChannel.shutdownOutput(); //读数据 ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len = 0; while (true) { buffer.clear(); len = socketChannel.read(buffer); if (len == -1) break; buffer.flip(); while (buffer.hasRemaining()) { bos.write(buffer.get()); } } System.out.println("客户端收到:"+new String(bos.toByteArray())); socketChannel.close(); } catch (IOException e) { e.printStackTrace(); } }; }.start(); } }}
服务器执行结果:
服务器端:正在监听8989端口 处理请求:/192.168.0.117:60792 服务器端:正在监听8989端口 处理请求:/192.168.0.117:60791 服务器端:正在监听8989端口 服务器端:正在监听8989端口 服务器端接收到的数据:我是客户端10 服务器端接收到的数据:我是客户端9 服务器端:正在监听8989端口 服务器端:正在监听8989端口 客户端发送来的数据:我是客户端10 客户端发送来的数据:我是客户端9
客户端执行结果:
客户端收到:你好,我好,大家好!! 客户端收到:你好,我好,大家好!!
总结:
对于NIO的多线程网络编程:NIO也是使用的阻塞I/O,使用了通道选择器进行选择就绪的通道进行处理I/O,达到非阻塞的目的。 编程思路: 1.创建ServerSocketChannel 2.绑定端口 3.设置为非阻塞 4.创建通道选择器 5.注册事件类型 6.获取可用I/O通道,获得有多少可用的通道 7.将耗时的读写操作开启子线程去处理 8.将注册写操作交给主线程处理 重点是7、8两步,如何将注册操作交给主线程处理。 执行流程: 1:启动TestServiceSocketChannel(服务器),加载服务器端中的代码;进入到第一个while(true){ 停留到int num = selector.select();查询是否有可用通道 } 2:启动TestClientSocketChannel(客户端),加载客户端中的代码; 3:这时客户端有可用的通道,开始向下执行进入下个while(true)循环,然后将这个iterator.remove();标记移除,以为这个请已经被处理,(防止高并发时进行重复处理), 4:判断当前时间类型并进行处理 a:key.isAcceptable(),key.channeal拿到一个通道,并用通道接收报文请求,然后进行通道设置为非阻塞的,然后再将读操作注入到通道里。执行完毕后,此时跳出当前while(iterator.hasNext()因为此时测试的环境是一个线程,如果是多个线程则进行循环处理) b:因为在步骤1中已经将读取所以进行判断后会进入else if(key.isReadable()){这里边,进入后会将key.cancel();(会将这个key做上标记)调用RequestProcess.request(key);这个类中的方法进行读操作。此时,服务器端的主线程会阻塞。因为此时已经没有要处理的通道或者说是key的时间 c:进入读操作后,执行代码并将从客户端接受到数据,防止到key的附件中key.attach(bos);然后调用服务器端的TestServiceSocketChannel.addWriteQueen(key);方法,将可以存入WriteQueen集合中,存入后会将已经 阻塞的主线程 进行唤醒(selector.wakeup();) d:唤醒主线程后,以为此时已经有了所对象,会进行注册写入事件和从客户端读取到的数据 放入通道,进行写入操作.进行判断if(key.isWritable())取消key的写标记,调用写方法ResponseProcess.response(key);将写入数据传到客户端,释放通道,完成响应。
0 0
- TCP-IP学习笔记四:NIO的网络编程-多线程实例
- TCP-IP学习笔记三:NIO的网络编程-单线程实例
- TCP-IP学习笔记二:NIO的网络编程Buffer简单使用
- TCP/IP学习笔记一:BIO的网络编程-简单实例
- TCP/IP网络编程学习笔记
- TCP/IP网络编程 学习笔记_1 --网络编程入门
- TCP/IP网络协议学习笔记四--ICMP
- TCP/IP网络编程 学习笔记_5 --基于TCP的服务端/客服端
- java TCP/IP Socket编程-----NIO--TCP信道-----笔记11
- TCP/IP网络编程 学习笔记_9 --域名系统(DNS)
- TCP/IP网络编程 学习笔记_11 --多进程服务器端
- TCP/IP网络编程 学习笔记_12 --进程间通信
- linux程序设计(套接字)+TCP/IP网络编程学习笔记
- Linux网络编程学习笔记-TCP/IP基础-1
- TCP/IP 网络编程(四)
- tcp/ip 多线程服务器端的实现(参考tcp/ip网络编程)
- java网络编程学习(四)UDP/IP+BIO/NIO/多播
- java网络编程自学(三)-TCP/IP+NIO
- 恢复和去除时间(Recovery and Removal Time)
- Atitit. Atiposter 发帖机 新特性 poster new feature v7 q39
- 块级元素与行内元素
- Android学习好资料网址
- thinkphp5.0整合七牛云
- TCP-IP学习笔记四:NIO的网络编程-多线程实例
- php压缩和解压缩字符串的方法
- jsp使用jstl、el标签时出现According to TLD or attribute directive in tag file, attribute items does not acc错误
- Activity之间的跳转以及交互
- Spring事务传播机制和数据库隔离级别
- Field 'id' doesn't have a default value问题解决方法
- Atitit qzone qq空间博客自动点赞与评论工具的设计与实现
- cf-500A New Year Transportation
- <九> SD 卡驱动移植