NIO和BIO编程

一、网络通信编程基本常识

1、什么是Socket?

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,一般由操作系统提供。

2、短连接

短连接是指socket建立连接之后传输数据确定接收完后关闭连接

3、长连接

长连接是指建立socket连接后不管是否使用都保持连接

4、什么时候用长连接,短连接?

http1.1网上都开始向长连接演化,web网站成千上万的客户端使用短连接会更节省资源消耗,短连接频繁的通信会造成socket错误,而socket频繁的创建也会耗费系统资源

二、Java原生网络编程

1、原生JDK网络编程BIO

面向流的阻塞IO,

服务端(这种来一个客户端就创建一个线程的方式,如果客户端连接数量过多,内存资源消耗过多,造成系统崩溃)

java 复制代码
public class Server {

	public static void main(String[] args) throws IOException {
		// 创建ServerSocket接收客户端连接对象
		ServerSocket serverSocket = new ServerSocket();
		// 绑定服务器监听端口
		serverSocket.bind(new InetSocketAddress(8888));
		System.out.println("start server...");
		while (true) {
			new Thread(new ServerTask(serverSocket.accept())).start();
		}
	}

	private static class ServerTask implements Runnable {

		private Socket socket = null;
		public ServerTask(Socket socket) {
			this.socket = socket;
		}

		@Override
		public void run() {
			try(
				//实例化与客户端通信的输入输出流
				ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
				ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())
			){
				//接收客户端的输出,也就是服务器的输入
				String userName = inputStream.readUTF();
				System.out.println("Accept client message:"+userName);

				//服务器的输出,也就是客户端的输入
				outputStream.writeUTF("Hello,"+userName);
				outputStream.flush();
			}catch(Exception e){
				e.printStackTrace();
			}finally {
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

// 单线程实现服务端连接(单线程服务端无法同时支持多个连接同时处理)
class ServerSingle {

    public static void main(String[] args) throws IOException {
        //服务端启动必备
        ServerSocket serverSocket = new ServerSocket();
        //表示服务端在哪个端口上监听
        serverSocket.bind(new InetSocketAddress(8888));
        System.out.println("Start Server ....");
        int connectCount = 0;
        try {
            while(true){
                Socket socket = serverSocket.accept();

                System.out.println("accept client socket ....total =" + ( ++connectCount));
                //实例化与客户端通信的输入输出流
                try(ObjectInputStream inputStream =
                            new ObjectInputStream(socket.getInputStream());
                    ObjectOutputStream outputStream =
                            new ObjectOutputStream(socket.getOutputStream())){

                    //接收客户端的输出,也就是服务器的输入
                    String userName = inputStream.readUTF();
                    System.out.println("Accept client message:"+userName);

                    //服务器的输出,也就是客户端的输入
                    outputStream.writeUTF("Hello,"+userName);
                    outputStream.flush();
                }catch(Exception e){
                    e.printStackTrace();
                }finally {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } finally {
            serverSocket.close();
        }

    }
}

// 线程池的方式启动服务端(受线程数量影响,如果传输的数据过大 比如文件,占用时间过长的话,其它连接只能等待)
class ServerPool {

    private static ExecutorService executorService
            = Executors.newFixedThreadPool(
            Runtime.getRuntime().availableProcessors());

    public static void main(String[] args) throws IOException {
        //服务端启动必备
        ServerSocket serverSocket = new ServerSocket();
        //表示服务端在哪个端口上监听
        serverSocket.bind(new InetSocketAddress(10001));
        System.out.println("Start Server ....");
        try{
            while(true){
                executorService.execute(new ServerTask(serverSocket.accept()));
            }
        }finally {
            serverSocket.close();
        }
    }

    //每个和客户端的通信都会打包成一个任务,交个一个线程来执行
    private static class ServerTask implements Runnable{

        private Socket socket = null;
        public ServerTask(Socket socket){
            this.socket = socket;
        }

        @Override
        public void run() {
            //实例化与客户端通信的输入输出流
            try(ObjectInputStream inputStream =
                        new ObjectInputStream(socket.getInputStream());
                ObjectOutputStream outputStream =
                        new ObjectOutputStream(socket.getOutputStream())){

                //接收客户端的输出,也就是服务器的输入
                String userName = inputStream.readUTF();
                System.out.println("Accept client message:"+userName);

                //服务器的输出,也就是客户端的输入
                outputStream.writeUTF("Hello,"+userName);
                outputStream.flush();
            }catch(Exception e){
                e.printStackTrace();
            }finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

客户端

java 复制代码
public class Client {

    public static void main(String[] args) throws IOException {
        Socket socket = null;
        ObjectOutputStream objectOutputStream = null;
        ObjectInputStream objectInputStream = null;

        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8888);

        try {
            socket = new Socket();
            socket.connect(inetSocketAddress);
            System.out.println("connect server success");

            objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            objectInputStream = new ObjectInputStream(socket.getInputStream());
            System.out.println("ready send message");

            objectOutputStream.writeUTF("gaorufeng");
            objectOutputStream.flush();

            // 接收并打印服务器传输的数据
            System.out.println(objectInputStream.readUTF());

        } finally {
            if (socket!=null) socket.close();
            if (objectOutputStream!=null) objectOutputStream.close();
            if (objectInputStream!=null) objectInputStream.close();
        }
    }
}

BIO阻塞的两个地方:

1、服务端执行serverSocket.accept()方法后会进入阻塞,直到有客户端请求进来

2、服务器如果是单线程运行(一个线程处理多个客户端请求会进入阻塞),如果数据还没传输完毕,其它客户端socket是无法与服务端建立tcp连接的,虽然socket显示连接成功,但是tcp连接还没建立

BIO适合少量连接的使用场景

BIO多线程通信模型:

2、原生JDK网络编程NIO

面向缓冲的非阻塞IO

1)NIO的Reactor模式

BIO和NIO最大的区别就是线程阻塞和不阻塞,NIO就是基于Reactor模式实现的,所谓Reactor模式,就是往一个组件注册感兴趣的事件并监听,触发对应的事件进行处理,而不是阻塞

2)NIO三大核心组件
  1. Selector选择器:Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器(Selectors),然后使用一个单独的线程来操作这个选择器,进而"选择"通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道(应用程序将向 Selector 对象注册需要它关注的 Channel,以及具体的某一个 Channel会对哪些 IO 事件感兴趣。Selector 中也会维护一个"已经注册的Channel"的容器)。
  2. Channel管道:被建立的一个应用程序和操作系统交互事件、传递内容的渠道(注意是连接到操作系统)。那么既然是和操作系统进行内容的传递,那么说明应用程序可以通过通道读取数据,也可以通过通道向操作系统写数据,而且可以同时进行读写。
  3. buffer缓冲区:JDK NIO是面向缓冲的。Buffer就是这个缓冲,用于和 NIO 通道进行交互。 数据是从通道读入缓冲区,从缓冲区写入到通道中的。以写为例,应用程序都是将数据写入 缓冲,再通过通道把缓冲的数据发送出去,读也是一样,数据总是先从通道读到缓冲,应用程序再读缓冲的数据。
java 复制代码
public class NioSelectorServer {

    public static void main(String[] args) throws IOException, InterruptedException {

        // 创建NIO ServerSocketChannel
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(9000));
        // 设置ServerSocketChannel为非阻塞
        serverSocket.configureBlocking(false);
        // 打开Selector处理Channel,即创建epoll
        Selector selector = Selector.open();
        // 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
        serverSocket.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务启动成功");

        while (true) {
            // 阻塞等待需要处理的事件发生
            selector.select();

            // 获取selector中注册的全部事件的 SelectionKey 实例
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();

            // 遍历SelectionKey对事件进行处理
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // 如果是OP_ACCEPT事件,则进行连接获取和事件注册
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = server.accept();
                    socketChannel.configureBlocking(false);
                    // 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("客户端连接成功");
                } else if (key.isReadable()) {  // 如果是OP_READ事件,则进行读取和打印
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                    int len = socketChannel.read(byteBuffer);
                    // 如果有数据,把数据打印出来
                    if (len > 0) {
                        System.out.println("接收到消息:" + new String(byteBuffer.array()));
                    } else if (len == -1) { // 如果客户端断开连接,关闭Socket
                        System.out.println("客户端断开连接");
                        socketChannel.close();
                    }
                }
                //从事件集合里删除本次处理的key,防止下次select重复处理
                iterator.remove();
            }
        }
    }
}
相关推荐
学传打活7 天前
【边打字.边学昆仑正义文化】_进阶篇5 宇宙星系的运转(续3)
微信公众平台·1024程序员节·汉字·昆仑正义文化
学传打活12 天前
【边打字.边学昆仑正义文化】_进阶篇5 宇宙星系的运转(续1)
微信公众平台·1024程序员节·汉字·昆仑正义文化
fangzhanpeng16820 天前
快速掌握C#语言基础知识点(1.基础数据类型)
开发语言·c#·1024程序员节
无己心22 天前
面向多平台 AI 搜索引擎的适配层架构设计
前端·人工智能·搜索引擎·ai·1024程序员节
u0103055271 个月前
AI驱动的安卓UI自动生成功能实现
人工智能·1024程序员节
u0103055271 个月前
长株潭智能体赋能腾讯元器链接共享
1024程序员节
云贝贝贝1 个月前
OceanBase认证体系介绍与备考指南
运维·服务器·oceanbase·1024程序员节
u0103055271 个月前
Java图像处理实战指南
人工智能·1024程序员节
u0103055271 个月前
Java AWT鼠标事件全解析
人工智能·1024程序员节
u0103055271 个月前
Java实用类应用精讲
人工智能·1024程序员节