一个NIO的C/S模型

在学习NIO的过程中看了很多博客和其他人的demo,现在算是对NIO有了一些了解,在此记录

本demo中只有服务端是NIO,客户端不是,而且客户端是简单的new线程而不是使用线程池,在生产实践中并不使用

与此同时,本demo涉及JDBC连接,但因为博文重点为C/S因此没有放上来

以下为客户端代码

package com.hsq.client;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;

public class Client2 {
    //https://www.cnblogs.com/jing99/p/12000371.html
//    public static void client() throws IOException {
    public static void main(String[] args) throws IOException {



        SocketChannel channel = SocketChannel.open();
        InetSocketAddress socketAddress = new InetSocketAddress("localhost", 8080);
        boolean connect = channel.connect(socketAddress);
        if (connect) {
            System.out.println("客户端2启动");
        }

        String uname = "老王";
        String upass = "1";

        while (true) {
            for (int i = 0; i < 5; i++) {
                Thread thread = new Thread();
                thread.start();
//                System.out.println("第" + i + "个客户端2的线程");
                String post = uname + "/" + upass;
                byte[] bytes1 = post.getBytes(StandardCharsets.UTF_8);
                if (bytes1 != null && bytes1.length > 0) {
                    ByteBuffer writeBuffer = ByteBuffer.allocate(bytes1.length);
                    writeBuffer.put(bytes1);
                    writeBuffer.flip();
                    channel.write(writeBuffer);
                }

                ByteBuffer readbuffer = ByteBuffer.allocate(1024);
                int readbytes = channel.read(readbuffer);
                if (readbytes > 0) {
                    readbuffer.flip();
                    byte[] bytes = new byte[readbuffer.remaining()];
                    readbuffer.get(bytes);
                    String reply = new String(bytes);
                    System.out.println("服务器回复 :" + reply);
                }
            }
        }
//        socket.close();

    }
}

以下为服务端代码

package com.hsq.server;


import com.hsq.bean.User;
import com.hsq.jdbc.SqlOperation;

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;
import java.util.Set;

public class LoginServer implements Runnable {

    private ServerSocketChannel serverSocketChannel;
    private Selector selector;
    private volatile boolean stop = false;

    public static void main(String[] args) throws IOException {
        int port = 8081;
        LoginServer loginServer = new LoginServer(port);
        new Thread(loginServer, "nioserver-001").start();
    }


    public LoginServer(int port) {
            try {
                serverSocketChannel = ServerSocketChannel.open();
                //获得一个serverChannel
                selector = Selector.open();
                ////创建选择器  获得一个多路复用器
                serverSocketChannel.configureBlocking(false);
                //设置为非阻塞模式 如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式
                serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);
                //绑定一个端口和等待队列长度
                serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
                //把selector注册到channel,关注链接事件
            } catch (IOException e) {
                e.printStackTrace();
                System.exit(1);
            }
        }


    public void stop() {
        this.stop = true;
    }


    @Override
    public void run() {
        while (!stop) {
            try {
                //无论是否有读写事件发生,selector每隔1s被唤醒一次。如果一定时间内没有事件,就需要做些其他的事情,就可以使用带超时的
                int client = selector.select(1000);
                System.out.println("1:" + client);
                // 阻塞,只有当至少一个注册的事件发生的时候才会继续.
                // int client = selector.select(); 不设置超时时间为线程阻塞,但是IO上支持多个文件描述符就绪
                if (client == 0) {
                    continue;
                }
                System.out.println("2:" + client);
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectionKeys.iterator();
                SelectionKey key = null;
                while (it.hasNext()) {
                    key = it.next();
                    it.remove();
                    try {
                        //处理事件
                        handle(key);
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            } catch (Throwable e) {
                e.printStackTrace();
            } finally {

            }
        }

        if (selector != null) {
            // selector关闭后会自动释放里面管理的资源
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void handle(SelectionKey key) throws IOException {
        if (key.isValid()) {
            //连接事件
            if (key.isAcceptable()) {
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                // 通过ServerSocketChannel的accept创建SocketChannel实例
                // 完成该操作意味着完成TCP三次握手,TCP物理链路正式建立
                SocketChannel sc = ssc.accept();
                //3次握手
                sc.configureBlocking(false);
                sc.register(selector, SelectionKey.OP_READ);
                //连接建立后关注读事件F
            }

            //读事件
            if (key.isReadable()) {
                SocketChannel socketChannel = (SocketChannel) key.channel();
//                ByteBuffer readbuffer = ByteBuffer.allocate(1024);//写 0 1024  1024
                // 申请直接内存,也就是堆外内存
                ByteBuffer readbuffer = ByteBuffer.allocateDirect(1024);
                // 读取请求码流,返回读取到的字节数
                int readBytes = socketChannel.read(readbuffer);
                // 读取到字节,对字节进行编解码
                if (readBytes > 0) {
                    // 将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作
                    readbuffer.flip();
                    //读写模式反转
                    // 将缓冲区可读字节数组复制到新建的数组中
                    byte[] bytes = new byte[readbuffer.remaining()];
                    readbuffer.get(bytes);
                    String body = new String(bytes);
//                    String body = "登录成功";
//                    res(socketChannel, body);
                    // 这下服务器回复的也是字符串了,且按/分割
                    String[] arr = body.split("/");
                    String name = arr[0];
                    String password = arr[1];
                    //调用查询方法
                    User user = new User(name, password);
                    try {
                        int type = SqlOperation.loginModele(user);
                        String response;
                        if (type >= 1 && type <= 3) {
                            response = "登录成功";
                        } else {
                            response = "登录失败";
                        }
                        System.out.println(response);
                        res(socketChannel, response);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else if (readBytes < 0) {
                    // 链路已经关闭 释放资源
                    key.cancel();
                    socketChannel.close();
                } else {
                    // 没有读到字节忽略
                }
            }

        }
    }

    private void res(SocketChannel channel, String response) throws IOException {
        if (response != null && response.length() > 0) {
            byte[] bytes = response.getBytes();
            ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
            writeBuffer.put(bytes);
            writeBuffer.flip();
            channel.write(writeBuffer);
            System.out.println("res end");
        }
    }


}


上一篇:Pset_MaterialOptical


下一篇:IO模型之NIO代码及其实践详解