栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【网络编程学习之旅】一文学习网络编程之NIO

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【网络编程学习之旅】一文学习网络编程之NIO

一文学习网络编程之NIO

前置知识:

Java基础知识;BIO网络编程知识;多线程编程知识;

一、网络编程模型 1.编程模型

模型:对事务共性的抽象;

编程模型:对编程共性的抽象;

实际的问题 具体的解决方案 抽象出一套

依靠这个模型来解决这一类问题;

2.BIO网络模型

阻塞式 I/O 模型

如果客户端没有发起请求,服务的会一直存在并等待连接 弹性伸缩能力差

每一个对服务端的连接就需要开启一个线程连接数很可能超过服务器所能负载的最大线程数 多线程耗资源

创建,销毁,维护大量线程以及线程切换都非常消耗系统资源 3.NIO网络模型

非阻塞 IO 模型

服务器端提供一个单线程的 selector 来统一管理所有客户端接入的连接并负责监听每个连接所关心的事件 弹性伸缩能力加强

服务器端一个线程处理所有客户端的连接请求客户端的个数与服务器端的线程数呈 M 比 1 的关系 单线程节省资源

避免了线程的频繁创建和销毁同时也避免了多个线程之间上下文的切换,提高了执行效率 二、BIO下的TCP编程通信

1.服务器端server

server.java

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;


public class Server {
    
    public void start() throws IOException {
        //1.服务器端监听建立连接请求
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务端即将启动,等待客户端的连接...");
        //2.客户端发起建立连接请求
        int count = 0;
        Socket socket  = null;
        while(true){//循环监听客户端的连接
            //调用accept()方法监听,等待客户端连接以获取socket实例
            socket = serverSocket.accept();
            //3.服务器端启动新线程
            //4.线程响应客户端
            //5.等待客户单再次请求
            //创建新线程
            Thread thread = new Thread(new ServerThread(socket));
            thread.start();
            count++;
            System.out.println("服务器端被连接过的次数:"+count);
            InetAddress address = socket.getInetAddress();
            System.out.println("当前客户端IP为:"+address.getHostAddress());
        }


    }

    public static void main(String[] args) throws IOException {
        Server server = new Server();
        server.start();
    }
}

ServerThread.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;

public class ServerThread implements Runnable{

    Socket socket = null;
    public ServerThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        InputStream is = null;
        InputStreamReader isr = null;
        BufferedReader br = null;
        OutputStream os = null;
        PrintWriter pw = null;
        //与客户端建立通信,获取输入流,读取客户端提供的信息
        try {
            is = socket.getInputStream();
            isr = new InputStreamReader(is,"UTF-8");
            br = new BufferedReader(isr);
            String data = null;
            while((data = br.readLine())!=null){
                System.out.println("我是服务器,客户端提交的信息为:"+data);
                socket.shutdownInput();//关闭输入流

                //获取输出流,响应客户端的请求
                os = socket.getOutputStream();
                pw = new PrintWriter(os);
                pw.write("服务器端响应成功");
                pw.flush();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            //关闭资源和相关socket
            try{
                if(pw!=null){
                    pw.close();
                }
                if(os!=null){
                    os.close();
                }
                if(is!=null){
                    is.close();
                }
                if(isr!=null){
                    isr.close();
                }
                if(br!=null){
                    br.close();
                }
                if(socket!=null){
                    socket.close();
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}

2.客户端client
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;


public class Client {
    public static void main(String[] args) {
        try{
            Socket socket = new Socket("localhost",8888);
            OutputStream os = socket.getOutputStream();
            PrintWriter pw = new PrintWriter(os);
            pw.write("用户名:jinxueling;密码:123");
            pw.flush();
            socket.shutdownOutput();

            InputStream is = socket.getInputStream();
            InputStreamReader isr = new InputStreamReader(is,"UTF-8");
            BufferedReader br = new BufferedReader(isr);
            String data = null;
            while((data = br.readLine())!=null){
                System.out.println("我是客户端,服务器端响应的数据为:"+data);
            }
            socket.close();
        }catch (UnknownHostException e) {
            e.printStackTrace();
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}
三、NIO下的聊天室编程 1NIO网络编程详解

NIO 核心

Channel:通道Buffer:缓冲区Selector:选择器或多路复用器 1.1. Channel

特点:

双向性非阻塞性操作唯一性

实现:

文件类:FileChannelUDP 类:DatagramChannelTCP 类:ServerSocketChannel / SocketChannel

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.bind(new InetSocketAddress(8000));
 
SocketChannel socketChannel = serverSocketChannel.accept();
SocketChannel socketChannel = SocketChannel.open(
        new InetSocketAddress("127.0.0.1", 8000));
1.2 Buffer

作用:读写 Channel 中数据

本质:一块内存区域

属性:

Capacity:容量Position:位置Limit:上限Mark:标记

用法:

ByteBuffer.allocate(10);

ByteBuffer.put("abc".getBytes(Charset.forName("UTF-8")));

ByteBuffer.flip();

ByteBuffer.get();

ByteBuffer.mark();

ByteBuffer.get();
ByteBuffer.reset();

ByteBuffer.clear();

1.3 Selector

作用:I/O 事件就绪选择

地位:NIO 网络编程的基础之一

Selector selector = Selector.open();

serverSocketChannel.register(selector, SelectionKey.OP_READ);

int selectNum = selector.select();

Set selectionKeys = selector.selectedKeys();

SelectionKey 提供四种就绪状态常量:

连接就绪:connect接受就绪:accept读就绪:read写就绪:write 1.4 NIO编程步骤

第一步:创建 Selector第二步:创建 ServerSocketChannel,并绑定监听端口第三步:将 Channel 设置为非阻塞模式第四步:将 Channel 注册到 Selector 上,监听连接事件第五步:循环调用 Selector 的 select 方法,检测就绪情况第六步:调用 selectedKeys 方法获取就绪 channel 集合第七步:判断就绪事件种类,调用业务处理方法第八步:根据业务需要决定是否再次注册监听事件,重复执行第三步操作 2.NIO网络编程聊天室实战 2.1 服务器端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;


public class NioServer {
    
    public void start() throws IOException {
        //1.创建selector
        Selector selector = Selector.open();
        //2.通过serversocketchannel创建channel通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //3.为channel绑定监听端口
        serverSocketChannel.bind(new InetSocketAddress(8000));
        //4.设置channel为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //5.将channel注册到selector上监听连接事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器启动成功");
        //6.循环等待新接入的连接
        for (;;){
            
            //获取可用的channel数量
            int readChannels = selector.select();
            if (readChannels==0){
                continue;
            }
            //获取可用的channel地址集合
            Set selectionKeySet = selector.selectedKeys();
            Iterator iterator = selectionKeySet.iterator();
            while (iterator.hasNext()){
                //selectionKey实例
                SelectionKey  selectionKey= (SelectionKey) iterator.next();
                //移除set中当前selectionKey实例
                iterator.remove();
                //7.根据对应状态,调用对应方法处理业务逻辑
                if (selectionKey.isAcceptable()){
                    //如果是接入事件
                    acceptHandler(serverSocketChannel,selector);
                }
                if (selectionKey.isReadable()){
                    //如果是可读事件
                    readHandler(selectionKey,selector);
                }
            }
        }
        
    }

    
    private void acceptHandler(ServerSocketChannel serverSocketChannel,
                               Selector selector) throws IOException {
        //如果是接入事件,创建socketchannel
        SocketChannel socketChannel = serverSocketChannel.accept();
        //将socketchannel设置为非阻塞工作模式
        socketChannel.configureBlocking(false);
        //将channel注册到selector上,监听可读事件
        socketChannel.register(selector,SelectionKey.OP_READ);
        //回复客户端提示信息
        socketChannel.write(StandardCharsets.UTF_8.encode("你与聊天室李其他人都不是朋友关系,请注意隐私安全"));
    }

    
    private void readHandler(SelectionKey selectionKey,Selector selector) throws IOException {
        //从selectionkey中获取到已经就绪的channel
        SocketChannel socketChannel  = (SocketChannel) selectionKey.channel();
        //创建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //循环读取客户端请求信息
        StringBuilder request = new StringBuilder();
        while (socketChannel.read(byteBuffer)>0){
            //切换buffer为读模式
            byteBuffer.flip();
            //读取buffer中的内容
            request.append(StandardCharsets.UTF_8.decode(byteBuffer));
        }

        //将channel再次注册到selector上,监听其它的可读事件
        socketChannel.register(selector,SelectionKey.OP_READ);
        //将客户端发送的请求信息,广播到其它客户端
        if (request.length()>0){
            boardCast(selector,socketChannel,request.toString());
        }
    }

    
    private void boardCast(Selector selector, SocketChannel sourceChannel, String request) {
        //获取到所有已接入的客户端channel
        Set selectionKeySet = selector.keys();
        //循环向所有channel广播信息
        selectionKeySet.forEach(selectionKey -> {
            Channel targetChannel= selectionKey.channel();
            //剔除发消息的客户端
            if (targetChannel instanceof SocketChannel && targetChannel != sourceChannel){
                try {
                    //将消息发送到targetChannel客户端
                    ((SocketChannel) targetChannel).write(StandardCharsets.UTF_8.encode(request));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public static void main(String[] args) throws IOException {
        NioServer nioServer = new NioServer();
        nioServer.start();
    }
}

2.2 客户端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;


public class NioClient {
    
    public void start(String nickname) throws IOException {
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8000));
        System.out.println("客户端启动成功");
        
        Selector selector = Selector.open();
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ);
        new Thread(new NioClientHandler(selector)).start();

        
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()){
            String request = scanner.nextLine();
            if (request!=null&&request.length()>0){
                socketChannel.write(StandardCharsets.UTF_8.encode(nickname+" : " + request));
            }
        }
    }
}

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.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;


public class NioClientHandler implements Runnable{
    private final Selector selector;

    public NioClientHandler(Selector selector) {
        this.selector = selector;
    }

    @Override
    public void run() {
        try {
            for (;;){
                int readChannels = selector.select();
                if (readChannels==0){
                    continue;
                }
                Set selectionKeySet = selector.selectedKeys();
                Iterator iterator = selectionKeySet.iterator();
                while(iterator.hasNext()){
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    if (selectionKey.isReadable()){
                        readHander(selectionKey,selector);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    private void readHander(SelectionKey selectionKey,Selector selector) throws IOException {
       SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        StringBuilder response = new StringBuilder();
        while(socketChannel.read(byteBuffer)>0){
            byteBuffer.flip();
            response.append(StandardCharsets.UTF_8.decode(byteBuffer));
        }
        socketChannel.register(selector,SelectionKey.OP_READ);
        if (response.length()>0){
            System.out.println(response);
        }
    }
}

3.NIO网络编程缺陷

麻烦

NIO 类库和 API 繁杂 心累

可靠性能力补齐,工作量和难度都非常大 有坑

Selector 空轮询,导致 CPU 占用率 100%

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/710104.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号