java IO,伪异步IO以及NIO网络编程 简单实现源码以及区别

        JAVA网络编程有三种方式,IO也就是BIO,BIO的伪异步方式,和NIO,原理都是通过socket(套接字进行通信)

        套接字:就是ip+port ip就是计算机的地址 在java中默认是本地ip 127.0.0.1,port是端口号,每一个应用程序都有自己的端口号。每一台电脑的ip都不一样,每一台电脑上不能同时存在两个端口相同的程序,这样就可以确保网络编程通信的准确性,在其他编程语言中也是一样。

        首先我们先通过看几个deme 来实现网络编程,再来比较几种方式的不同。

         IO:

        server服务端,首先创建一个serverSocket 其实也就是socket,java中在服务端喜欢用serversocket来表示服务端的socket,ip是默认的端口号是9876,io方式中采用阻塞的方式,当客户端有连接进来后才会结束阻塞返回一个socket也就是客户端的socket,并启动一个线程来监听这个socket,接着会继续阻塞等待下一个客户端socket的到来

public class IOServer {
	
	//端口
	private static final int port = 9876;

	public static void main(String[] args) {
		ServerSocket serverSocket = null;
		try {
			serverSocket = new ServerSocket(port);
			
			//阻塞
			Socket socket = serverSocket.accept();
			
			//创建一个新的线程来监听
			new Thread(new IOServerHandler(socket) ).start();;
			
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				serverSocket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

在来看一下IOServerHandler的代码,因为这个类是通过一个线程来监听的 所以一定要实现runnable接口,io是读写分离的,inputStream是读的流,outputstream是写的流,读到的和写出的都是通过byte数组的来读写的,数组的长度决定一次读的大小,当读到的返回值是-1时,表示没有数据可读,切记inputStream的read方法是阻塞的当读完数据的时候如果没有新的数据可读,程序不会往下走会阻塞到这里 直到读到新的数据,使用完之后记得关闭所有的流

public class IOServerHandler implements Runnable{
	
	private Socket socket; 
	
	public IOServerHandler(Socket socket){
		this.socket = socket;
	}
	
	public void run() {
		InputStream in = null;
		OutputStream out = null;
		try {
			in = socket.getInputStream();
			out = socket.getOutputStream();
			
			//读取客户端的 消息
			
			byte[] temp = new byte[1024];
			String pri = "";
			int length = 0;
			while(true){
				length = in.read(temp);
				if(length == -1){
					break;
				}
				pri = new String(temp,0,length);
				System.out.println(pri);
				String wri = "服务器收到信息。。";
				out.write(wri.getBytes());
			}
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			try {
				out.flush();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			
			try {
				out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
}

再来看客户端代码:客户端想要和服务端进行通信 一定要知道服务端的ip和端口号22,并创建连接,当new了一个socket之后,这个socket就会进入服务端的accept处,将这个socket返回到服务端,建立连接。流的方式和服务端得使用是一样的 读写分离

public class Client {
	
	private static final int port = 9876;
	
	private static final String host = "127.0.0.1";
	
	public static void main(String[] args) {
		Socket socket = null;
		InputStream in = null;
		OutputStream out = null;
		try {
			socket = new Socket(host,port);
			
			in = socket.getInputStream();
			out = socket.getOutputStream();
			
			out.write(new String("连接到服务器。。。").getBytes());
			
			byte[] temp = new byte[1024];
			int length = 0;
			String pri = "";
			while(true){
				length = in.read(temp);
				if(length == -1){
					break;
				}
				pri = new String(temp,0,length);
				System.out.println(pri);
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			try {
				in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			try {
				out.flush();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			try {
				out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
}

看完服务端和客户端的 代码之后,大家想必明白当有一个客户端进行连接的时候会创建一个新的线程,除非旧的连接断开线程才会结束,这样来说如果你的电脑最多只能承受1000个线程,那么你的服务器最多只能承载1000个用户。

   伪异步的io网络编程是通过线程池的方式来管理线程,并可设置队列等待的线程数,相对的可以增加服务器的承载力,我们来看一下服务端代码:

         和上面一样是创建一个serversocket 也是阻塞之后返回一个socket连接,不同的是这里并没有直接new一个线程,而是通过线程池来管理连接

public class PoolIOServer {
	
	private static final int port = 9876;
	
	public static void main(String[] args) {
		ServerSocket serverSocket = null;
		try {
			serverSocket = new ServerSocket(port);
			Socket socket = null;
			ServerHandlerExecutePool executePool = new ServerHandlerExecutePool(50,100);
			while(true){
				socket = serverSocket.accept();
				executePool.execute(new IOServerHandler(socket));
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally{
			try {
				serverSocket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

看一下线程池的代码:使用工厂模式来创建线程,方便对线程的管理

Runtime.getRuntime().availableProcessors() 是coreThreadSize表示的是核心的线程数,也就是我们cup的最大线程数,比如说电脑配置的4核8线程,maxPoolSize是我们设置得到最大线程数,120L是表示120秒的等待时间,当一个线程120秒没有活跃会自动断开给其他客户端腾出位置,secnds是秒的意思就是120的单位,queuesieze队列的大小,排队的大小

public class ServerHandlerExecutePool {

	private ExecutorService executor;
	
	public ServerHandlerExecutePool(int maxPoolSize,int queueSize){
		
		//Runtime.getRuntime().availableProcessors()本地方法 cpu最大的线程数
		//maxPoolSize 最大线程数
		//120L保持活跃的时间
		//TimeUnit.SECONDS 单位
		//queueSize 队列的容量
		this.executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), maxPoolSize, 120L, 
				TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize),new ExecuteThreadFactory("executePool"));
		
	}
	
	public void execute(Runnable task){
		executor.execute(task);
	}
}

        可以显而易见相比较与第一种方式用线程池的方式管理 可以略微的提高性能

       在看一下nio 的方式;首先我们 要了解nio的三个基础概念:

      buffer(缓冲区)中定义了大量的put get  flip 方法,position mark等参数,大大方便了对读写内容的操作

      channel(通道)不存在inputstream和outputStream,直接通过channel就可以实现两端的通信

      selector是选择器,每个channel会都会注册的奥selector中得到一个key会不断轮询找到可以进行通信的channel进行调用

      通过channel提升了传输效率,通过selector可以大大利用起闲置的线程,能承载1000线程的电脑不在是支持1000个玩家,而是能支持1000个线程去并发。看一下服务器代码:

        

public class NioServer implements Runnable{
	//buffer:缓冲区 本质是一个数组
	private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
	
	private ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
	//多路复用器
	private Selector selector;
	
	public NioServer(int port){
		try{
			//1.打开多路复用器
			this.selector = Selector.open();
			//2.打开服务器端的通道
			ServerSocketChannel ssc = ServerSocketChannel.open();
			//3.设置通道的阻塞模式
			ssc.configureBlocking(false);
			//4.绑定地址
			ssc.bind(new InetSocketAddress(port));
			//5.把服务器通道注册到多路复用器 监听的状态
			ssc.register(this.selector,SelectionKey.OP_ACCEPT);
		}catch(IOException e){
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		new Thread(new NioServer(9876)).start();;
	}

	public void run() {
		while(true){
			try {
				//1.必须让多路复用器 开始监听
				this.selector.select();
				//2.返回多路复用器所有注册的key selectedKeys()返回值是set
				Iterator<SelectionKey> it = this.selector.selectedKeys().iterator();
				//3.遍历获取的key
				while(it.hasNext()){
					//4.接收key的值
					SelectionKey key = it.next();
					//5.从容器中移除已经被选中的key  缓解压力删掉没有意义的key
					it.remove();
					//6.验证操作 判断key是否有效 true是有效
					if(key.isValid()){
						//7.如果 为阻塞状态
						if(key.isAcceptable()){
							this.accept(key);
						}
						//8.如果是可读状态
						if(key.isReadable()){
							this.read(key);
						}
						//9.如果是可写状态
						if(key.isWritable()){
							this.write(key);
						}
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void write(SelectionKey key) {
		
	}

	private void read(SelectionKey key) {
		try{
			//1.对缓冲区进行清空
			this.readBuffer.clear();
			//2.获取之前注册的socketchannel通道对象
			SocketChannel sc = (SocketChannel)key.channel();
			//3.从通道里获取数据放入缓冲区
			int index = sc.read(this.readBuffer);
			
			if(index == -1){
				key.channel().close();
				key.cancel();
				return;
			}
		}catch(IOException e){
			e.printStackTrace();
		}
		//读取readbuffer数据 然后打印到控制台
		//4.由于sc通道里的数据到readbuffer容器中 ,所以readbuffer里面的position一定发生了变化 要进行复位
		this.readBuffer.flip();
		byte[] bytes = new byte[this.readBuffer.remaining()];
		this.readBuffer.get(bytes);
		
		String body = new String(bytes).trim();
		System.out.println("服务器读取数据:"+body);
	}

	/**
	 * 监听阻塞状态方法执行
	 * @param key
	 */
	private void accept(SelectionKey key) {
		
		try {
			//1.由于目前是server端 ,那么一定是server端启动并且处于阻塞状态  所哟煀阻塞状态的key 一定是:serversocketChannel
			ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
			//2.通过调用accept方法  返回一个具体的客户端连接句柄
			SocketChannel sc = serverSocketChannel.accept();
			//3.设置客户端通道为非阻塞
			sc.configureBlocking(false);
			//4.设置当前获取的客户端连接状态为可读状态位
			sc.register(this.selector,SelectionKey.OP_READ);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

通过上面的内容我们做出如下总结:

1.io是面向流 nio是面向缓冲

2.io是阻塞 nio是非阻塞

3.io 一个线程管理一个连接 nio一个线程管理多个连接(一个线程管理一个selector,一个selector管理所有通道),使nio可容纳的客户端更多

4.io是读写分离的 nio读写可以使用一个buffer 一个channel使nio传输速度更快

5.nio唯一的劣势,难度相比于io要大很多,但是过netty的架构来简化代码

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值