zoukankan      html  css  js  c++  java
  • 基于NIO的同步非阻塞编程完整案例,客户端发送请求,服务端获取数据并返回给客户端数据,客户端获取返回数据

    这块还是挺复杂的,挺难理解,但是多练几遍,多看看研究研究其实也就那样,就是一个Selector轮询的过程,这里想要双向通信,客户端和服务端都需要一个Selector,并一直轮询,

    直接贴代码:

    Server:服务端:

    package cn.hou.socket01._03nio01;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    
    //nio 服务端
    public class Server implements Runnable {
    	//1 多路复用器
    	private Selector selector;
    	//2 建立缓冲区
    	private ByteBuffer readBuf=ByteBuffer.allocate(1024);
    	private ByteBuffer writeBuf=ByteBuffer.allocate(1024);
    	//构造函数
    	public Server(int port){
    		try {
    			//1 打开多路复用器
    			this.selector=Selector.open();
    			//2 打开服务器通道
    			ServerSocketChannel ssc = ServerSocketChannel.open();
    			//3 设置服务器通道为非阻塞方式
    			ssc.configureBlocking(false);
    			//4 绑定ip
    			ssc.bind(new InetSocketAddress(port));
    			//5 把服务器通道注册到多路复用器上,只有非阻塞信道才可以注册选择器.并在注册过程中指出该信道可以进行Accept操作
    			ssc.register(this.selector, SelectionKey.OP_ACCEPT);
    			System.out.println("服务器已经启动.....");
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    	@Override
    	public void run() {
    		while(true){//一直循环
    			try {
    				this.selector.select();//多路复用器开始监听
    				//获取已经注册在多了复用器上的key通道集
    				Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
    				//遍历
    				while (keys.hasNext()) {
    					SelectionKey key = keys.next();//获取key
    					//如果是有效的
    					if(key.isValid()){
    						// 如果为阻塞状态,一般是服务端通道
    						if(key.isAcceptable()){
    							this.accept(key);
    						}
    						// 如果为可读状态,一般是客户端通道
    						if(key.isReadable()){
    							this.read(key);
    						}
    					}
    					//从容器中移除处理过的key
    					keys.remove();
    				}
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    		
    	}
    	//从客户端通道获取数据并进行处理
    	private void read(SelectionKey key) {
    		try {
    			//1 清空缓冲区旧的数据
    			this.readBuf.clear();
    			//2 获取之前注册的socket通道对象
    			SocketChannel sc = (SocketChannel) key.channel();
    			//3 读取数据
    			int count = sc.read(this.readBuf);
    			//4 如果没有数据
    			if(count == -1){
    				key.channel().close();
    				key.cancel();
    				return;
    			}
    			//5 有数据则进行读取 读取之前需要进行复位方法(把position 和limit进行复位)
    			this.readBuf.flip();
    			//6 根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据
    			byte[] bytes = new byte[this.readBuf.remaining()];
    			//7 接收缓冲区数据
    			this.readBuf.get(bytes);
    			//8 打印结果
    			String body = new String(bytes).trim();
    			System.out.println("服务端接受到客户端请求的数据: " + body);
    			//9 告诉客户端已收到数据
    			writeBuf.put("你好,客户端,我已收到数据".getBytes());
    			//对缓冲区进行复位
    			writeBuf.flip();
    			//写出数据到服务端
    			sc.write(writeBuf);
    			//清空缓冲区数据
    			writeBuf.clear();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    	//接受一个客户端socket进行处理
    	private void accept(SelectionKey key) {
    		try {
    			//1 获取服务通道
    			ServerSocketChannel ssc =  (ServerSocketChannel) key.channel();
    			//2 执行阻塞方法,当有客户端请求时,返回客户端通信通道
    			SocketChannel sc = ssc.accept();
    			//3 设置阻塞模式
    			sc.configureBlocking(false);
    			//4 注册到多路复用器上,并设置可读标识
    			sc.register(this.selector, SelectionKey.OP_READ);
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    		
    	}
    	
    	public static void main(String[] args) {
    		//启动服务器
    		new Thread(new Server(9527)).start();
    	}
    	
    
    }
    

      Client客户端:

    package cn.hou.socket01._03nio01;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    
    //nio 客户端
    public class Client{
    	//客户端信道选择器,轮询读取服务端返回数据
    	private Selector selector;
    	//连接信道
    	private SocketChannel sc;
    	public Client(){
    		try {
    			this.sc=SocketChannel.open();//打开信道
    			sc.connect(new InetSocketAddress("127.0.0.1",9527));////连接服务端
    			sc.configureBlocking(false);//设置非阻塞
    			selector = Selector.open();//必须打开
    			//将当前客户端注册到多路复用器上,并设置为可读状态
    			sc.register(this.selector, SelectionKey.OP_READ);
    			//开启线程,一直轮询
    			new Thread(()->{
    				while(true){//一直循环
    					try {
    						this.selector.select();//多路复用器开始监听
    						//获取已经注册在多了复用器上的key通道集
    						Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
    						//遍历
    						while (keys.hasNext()) {
    							SelectionKey key = keys.next();//获取key
    							//如果是有效的
    							if(key.isValid()){
    								// 如果为可读状态,读取服务端返回的数据
    								if(key.isReadable()){
    									this.read(key);
    								}
    							}
    							//从容器中移除处理过的key
    							keys.remove();
    						}
    					} catch (IOException e) {
    						e.printStackTrace();
    					}
    				}
    			}).start();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    	
    	//客户端获取服务端返回的数据
    	private void read(SelectionKey key) {
    		try {
    			//建立写缓冲区
    			ByteBuffer readBuf = ByteBuffer.allocate(1024);
    			//2 获取之前注册的socket通道对象
    			SocketChannel sc = (SocketChannel) key.channel();
    			//3 读取数据
    			int count = sc.read(readBuf);
    			//4 如果没有数据
    			if(count == -1){
    				key.channel().close();
    				key.cancel();
    				return;
    			}
    			//5 有数据则进行读取 读取之前需要进行复位方法(把position 和limit进行复位)
    			readBuf.flip();
    			//6 根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据
    			byte[] bytes = new byte[readBuf.remaining()];
    			//7 接收缓冲区数据
    			readBuf.get(bytes);
    			//8 打印结果
    			String body = new String(bytes).trim();
    			System.out.println("客户端已接受到服务端返回的数据: " + body);
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    
    	public static void main(String[] args) {
    		//建立写缓冲区
    		ByteBuffer writebuf = ByteBuffer.allocate(1024);
    		Client client = new Client();
    		try {
    			while(true){
    				//定义一个字节数组,然后使用系统录入功能:
    				byte[] bytes = new byte[1024];
    				System.in.read(bytes);
    				//把数据放到缓冲区中
    				writebuf.put(bytes);
    				//对缓冲区进行复位
    				writebuf.flip();
    				//写出数据到服务端
    				client.sc.write(writebuf);
    				//清空缓冲区数据
    				writebuf.clear();
    			}
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			if(client.sc != null){
    				try {
    					client.sc.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    		}
    	}
    }
    

      ,先启动服务端,然后再启动客户端:

    效果如下:

    Server:

    Client:

  • 相关阅读:
    html问题记录20180529
    html问题记录20180518
    html问题记录20180515
    Redis持久化--AOF
    Redis持久化--RDB
    Redis事件模型
    两个字符串的编辑距离-动态规划方法
    Reactor事件模型在Redis中的应用
    事件驱动模式--Reactor
    IO多路复用--总结
  • 原文地址:https://www.cnblogs.com/houzheng/p/9460450.html
Copyright © 2011-2022 走看看