Socket demo 代码

代码逻辑以及核心代码实现

模块逻辑

客户端:

  • Client:主入口函数,启动tcpCLient, 启动udpSearcher,负责从键盘接收输入string, 并用tcp转发;
  • UDPSearcher: 主要负责 searchServer, 启动监听线程,发送广播,接收UDP报文,并解析出server bean。
    监听线程:DatagramSocket receive DatagramPacket
  • TCPClient: 负责发起连接,处理读写。 startWith里面用socketChannel,connect, 读写通过继承的connector里面来实现。

服务端

  • server: 主函数入口, 启动tcpServer,启动UDPProvider,接收键盘输入并转发给所有的注册客户端。
  • UDPProvider:监听指定端口接收UDP报文, 解析UDP报文, 构造回送数据,并回送
  • TCPServer:启动selector,channel, 注册 accept事件,启动客户端监听(start函数); 客户端监听: 拿到selector以及里面所有的key,对于accept事件,通过serverChannel.accept拿到对应的socketChannel,然后对每个key。建立一个Client Handler(处理IO)。异步转发接收到的消息,使用线程池。【里面额外有两个函数,一个是server主函数用的功能broadcast,一个是重写的callback的onNewMessageArrived】

代码整体框架

客户端:

  • Client.java main客户端入口程序
  • UDPSearcher.searchServer广播搜索服务端;
  • TCPClient.startWith(info); 启动客户端业务逻辑。提供收发handler
    在这里插入图片描述
Client.java
import java.io.IOException;
import java.io.BufferedReader; //处理流
import java.io.InputStream; //字节流
import java.io.InputStreamReader; //字符流

public class Client {
    public static void main(String[] args) {
    //UDP搜索
        ServerInfo info = UDPSearcher.searchServer(10000);
        System.out.println("Server:" + info);

        if (info != null) {
            TCPClient tcpClient = null;
      
            try {
            //尝试TCP连接
                tcpClient = TCPClient.startWith(info);
                if (tcpClient == null) {
                    return;
                }

                write(tcpClient);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (tcpClient != null) {
                    tcpClient.exit();
                }
            }
        }
    }
    private static void write(TCPClient tcpClient) throws IOException {
        // 构建键盘输入流
        InputStream in = System.in;
        BufferedReader input = new BufferedReader(new InputStreamReader(in));
        do {
            // 键盘读取一行
            String str = input.readLine();
            // 发送到服务器
            tcpClient.send(str);
            //实现客户端退出
            if ("00bye00".equalsIgnoreCase(str)) {
                break;
            }
        } while (true);
    }

}
UDPSearcher
  • public static ServerInfo searchServer(int timeout);//总的逻辑函数,里面会调用listen, sendBroadcast()等函数
  • private static Listener listen(CountDownLatch receiveLatch) throws InterruptedException;//启动监听线程
  • private static void sendBroadcast() //局域网内发送广播的
import java.io.IOException;
import java.net.DatagramPacket;//UDP协议的数据格式c
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;//栅栏
import java.util.concurrent.TimeUnit;

public class UDPSearcher {
    private static final int LISTEN_PORT = UDPConstants.PORT_CLIENT_RESPONSE;
    /**
    UDP搜索,使用receiveCountLatch使当前线程等待 server回送信息
    打开监听
    发送广播
    使当前线程等待receiveLatch之后再继续执行
    */
    public static ServerInfo searchServer(int timeout) {
        System.out.println("UDPSearcher Started.");

        // 成功收到回送的栅栏
        CountDownLatch receiveLatch = new CountDownLatch(1);
        Listener listener = null;
        try {
            listener = listen(receiveLatch);
            sendBroadcast();
            receiveLatch.await(timeout, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 完成
        System.out.println("UDPSearcher Finished.");
        if (listener == null) {
            return null;
        }
        List<ServerInfo> devices = listener.getServerAndClose();
        if (devices.size() > 0) {
            return devices.get(0);
        }
        return null;
    }

    private static Listener listen(CountDownLatch receiveLatch) throws InterruptedException {
        System.out.println("UDPSearcher start listen.");
        CountDownLatch startDownLatch = new CountDownLatch(1);
        Listener listener = new Listener(LISTEN_PORT, startDownLatch, receiveLatch);
        listener.start();
        startDownLatch.await();
        return listener;
    }

    private static void sendBroadcast() throws IOException {
        System.out.println("UDPSearcher sendBroadcast started.");

        // 作为搜索方,让系统自动分配端口
        DatagramSocket ds = new DatagramSocket();

        // 构建一份请求数据
        ByteBuffer byteBuffer = ByteBuffer.allocate(128);
        // 头部
        byteBuffer.put(UDPConstants.HEADER);
        // CMD命名
        byteBuffer.putShort((short) 1);
        // 回送端口信息
        byteBuffer.putInt(LISTEN_PORT);
        // 直接构建packet
        DatagramPacket requestPacket = new DatagramPacket(byteBuffer.array(),
                byteBuffer.position() + 1);
        // 广播地址
        requestPacket.setAddress(InetAddress.getByName("255.255.255.255"));
        // 设置服务器端口
        requestPacket.setPort(UDPConstants.PORT_SERVER);

        // 发送
        ds.send(requestPacket);
        ds.close();

        // 完成
        System.out.println("UDPSearcher sendBroadcast finished.");
    }
    private static class Listener extends Thread {
        private final int listenPort;
        //有两个Latch一个用来标记监听线程是否已启动,另一个用来标记广播发送后是否收到了回送
        private final CountDownLatch startDownLatch;
        private final CountDownLatch receiveDownLatch;
        private final List<ServerInfo> serverInfoList = new ArrayList<>();
        private final byte[] buffer = new byte[128];
        private final int minLen = UDPConstants.HEADER.length + 2 + 4;
        private boolean done = false;
        private DatagramSocket ds = null;

        private Listener(int listenPort, CountDownLatch startDownLatch, CountDownLatch receiveDownLatch) {
            super();
            this.listenPort = listenPort;
            this.startDownLatch = startDownLatch;
            this.receiveDownLatch = receiveDownLatch;
        }

        @Override
        public void run() {
            super.run();

            // run开始执行,说明监听线程已启动,通知已启动
            startDownLatch.countDown();
            try {
                // 监听回送端口
                ds = new DatagramSocket(listenPort);
                // 构建接收实体
                DatagramPacket receivePack = new DatagramPacket(buffer, buffer.length);

                while (!done) {
                    // 接收
                    ds.receive(receivePack);

                    // 打印接收到的信息与发送者的信息
                    // 发送者的IP地址
                    String ip = receivePack.getAddress().getHostAddress();
                    int port = receivePack.getPort();
                    int dataLen = receivePack.getLength();
                    byte[] data = receivePack.getData();
                    boolean isValid = dataLen >= minLen
                            && ByteUtils.startsWith(data, UDPConstants.HEADER);

                    System.out.println("UDPSearcher receive form ip:" + ip
                            + "\tport:" + port + "\tdataValid:" + isValid);

                    if (!isValid) {
                        // 无效继续
                        continue;
                    }

                    ByteBuffer byteBuffer = ByteBuffer.wrap(buffer, UDPConstants.HEADER.length, dataLen);
                    final short cmd = byteBuffer.getShort();
                    final int serverPort = byteBuffer.getInt();
                    if (cmd != 2 || serverPort <= 0) {
                        System.out.println("UDPSearcher receive cmd:" + cmd + "\tserverPort:" + serverPort);
                        continue;
                    }

                    String sn = new String(buffer, minLen, dataLen - minLen);
                    ServerInfo info = new ServerInfo(serverPort, ip, sn);
                    serverInfoList.add(info);
                    // 成功接收到一份
                    receiveDownLatch.countDown();
                }
            } catch (Exception ignored) {
            } finally {
                close();
            }
            System.out.println("UDPSearcher listener finished.");
        }

        private void close() {
            if (ds != null) {
                ds.close();
                ds = null;
            }
        }

        List<ServerInfo> getServerAndClose() {
            done = true;
            close();
            return serverInfoList;
        }
    }
}
TCPClient

发起连接,处理读写事件
伪异步
startWith 启动, readHandler 处理读
写的话,直接是send函数, client main里面接收键盘输入,然后send发送
改造:
读到直接调用父类的onNewmassageArrived打印即可
在这里插入图片描述
在这里插入图片描述

参数、构造函数

public class TCPClient {
    private final Socket socket;
    private final ReadHandler readHandler;
    private final PrintStream printStream;

    public TCPClient(Socket socket, ReadHandler readHandler) throws IOException {
        this.socket = socket;
        this.readHandler = readHandler;
        this.printStream = new PrintStream(so cket.getOutputStream());
    }

    public void exit() {
        readHandler.exit();
        CloseUtils.close(printStream);
        CloseUtils.close(socket);
    }

启动函数 startWith;负责逻辑调用

public static TCPClient startWith(ServerInfo info) throws IOException {
        Socket socket = new Socket();
        // 超时时间
        socket.setSoTimeout(3000);

        // 连接本地,端口2000;超时时间3000ms
        socket.connect(new InetSocketAddress(Inet4Address.getByName(info.getAddress()), info.getPort()), 3000);

        System.out.println("已发起服务器连接,并进入后续流程~");
        System.out.println("客户端信息:" + socket.getLocalAddress() + " P:" + socket.getLocalPort());
        System.out.println("服务器信息:" + socket.getInetAddress() + " P:" + socket.getPort());

        try {
            //连接建立之后,异步处理读
            ReadHandler readHandler = new ReadHandler(socket.getInputStream());
            readHandler.start();
            //这里返回这个socket, 以便在client main里面接收键盘输入,发送到server
            return new TCPClient(socket, readHandler);
        } catch (Exception e) {
            System.out.println("连接异常");
            CloseUtils.close(socket);
        }

        return null;
    }

异步读 readHandler

static class ReadHandler extends Thread {
        private boolean done = false;
        private final InputStream inputStream;

        ReadHandler(InputStream inputStream) {
            this.inputStream = inputStream;
        }

        @Override
        public void run() {
            super.run();
            try {
                // 得到输入流,用于接收数据
                BufferedReader socketInput = new BufferedReader(new InputStreamReader(inputStream));

                do {
                    String str;
                    try {
                        // 客户端拿到一条数据
                        str = socketInput.readLine();
                    } catch (SocketTimeoutException e) {
                        continue;
                    }
                    if (str == null) {
                        System.out.println("连接已关闭,无法读取数据!");
                        break;
                    }
                    // 打印到屏幕
                    System.out.println(str);
                } while (!done);
            } catch (Exception e) {
                if (!done) {
                    System.out.println("连接异常断开:" + e.getMessage());
                }
            } finally {
                // 连接关闭
                CloseUtils.close(inputStream);
            }
        }

server

Server 主函数入口
public class Server {
    public static void main(String[] args) throws IOException {
    //设置上下文
        IoContext.setup()
                .ioProvider(new IoSelectorProvider())
                .start();

        TCPServer tcpServer = new TCPServer(TCPConstants.PORT_SERVER);
        boolean isSucceed = tcpServer.start();
        if (!isSucceed) {
            System.out.println("Start TCP server failed!");
            return;
        }

        UDPProvider.start(TCPConstants.PORT_SERVER);

        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        String str;
        do {
            str = bufferedReader.readLine();
            tcpServer.broadcast(str);
        } while (!"00bye00".equalsIgnoreCase(str));

        UDPProvider.stop();
        tcpServer.stop();

        IoContext.close();
    }
TCPServer

构造

public class TCPServer implements ClientHandler.ClientHandlerCallback {
    private final int port;
    private ClientListener listener;
    private List<ClientHandler> clientHandlerList = new ArrayList<>();
    private final ExecutorService forwardingThreadPoolExecutor;
    private Selector selector;
    private ServerSocketChannel server;

    public TCPServer(int port) {
        this.port = port;
        // 转发线程池
        this.forwardingThreadPoolExecutor = Executors.newSingleThreadExecutor();
    }

启动函数

public boolean start() {
        try {
            selector = Selector.open();
            ServerSocketChannel server = ServerSocketChannel.open();
            // 设置为非阻塞
            server.configureBlocking(false);
            // 绑定本地端口
            server.socket().bind(new InetSocketAddress(port));
            // 注册客户端连接到达监听
            server.register(selector, SelectionKey.OP_ACCEPT);

            this.server = server;
            System.out.println("服务器信息:" + server.getLocalAddress().toString());

            // 启动客户端监听
            ClientListener listener = this.listener = new ClientListener();
            listener.start();
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

监听
监听所有注册到当前selector上的accept事件。
1 用iterator拿到所有的事件
2 对每一个事件,判断 是否 isAcceptable, 如果是就绪的 则 new ClientHandler 去处理,这个ClientHandler 再去调用connector

private class ClientListener extends Thread {
        private boolean done = false;

        @Override
        public void run() {
            super.run();
            Selector selector = TCPServer.this.selector;
            System.out.println("服务器准备就绪~");
            // 等待客户端连接
            do {
                // 得到客户端
                try {
                    if (selector.select() == 0) {
                        if (done) {
                            break;
                        }
                        continue;
                    }

                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        if (done) {
                            break;
                        }

                        SelectionKey key = iterator.next();
                        iterator.remove();

                        // 检查当前Key的状态是否是我们关注的
                        // 客户端到达状态
                        if (key.isAcceptable()) {
                            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                            // 非阻塞状态拿到客户端连接
                            SocketChannel socketChannel = serverSocketChannel.accept();

                            try {
                                // 客户端构建异步线程
                                ClientHandler clientHandler = new ClientHandler(socketChannel, TCPServer.this);
                                // 添加同步处理
                                synchronized (TCPServer.this) {
                                    clientHandlerList.add(clientHandler);
                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                                System.out.println("客户端连接异常:" + e.getMessage());
                            }
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

            } while (!done);

            System.out.println("服务器已关闭!");
        }

广播发送

public synchronized void broadcast(String str) {
        for (ClientHandler clientHandler : clientHandlerList) {
            clientHandler.send(str);
        }
    }

转发

@Override
    public void onNewMessageArrived(final ClientHandler handler, final String msg) {
        // 异步提交转发任务
        forwardingThreadPoolExecutor.execute(() -> {
            synchronized (TCPServer.this) {
                for (ClientHandler clientHandler : clientHandlerList) {
                    if (clientHandler.equals(handler)) {
                        // 跳过自己
                        continue;
                    }
                    // 对其他客户端发送消息
                    clientHandler.send(msg);
                }
            }
        });
ClientHandler

在这里插入图片描述
这里 onReceiveNewMessage .对应 父类的打印,和tcpServer的转发。
这里的比客户端实现,要多一个 对所有注册在改服务器的客户端一个转发,重写的
在这里插入图片描述

读写业务逻辑

Connector

Connector 其实就是拿到了一个具体的socketChannel 之后要进行下一步的操作;
Sender ,Receiver 是 发送接送的最高抽象;
具体发送接收要做的: 把键盘或者收到的String 分成packet,然后异步发送;反之同理‘
在这里插入图片描述
Sender 和 Receiver的 非阻塞有两个两面组成: 一是 使用了读写 两个selector,通过读写时间注册的方式(IoProvider); 二是任务底层具体的读写操作由线程池来做。

在这里插入图片描述
在这里插入图片描述
packet 缓存,取用,异步发送, 发送成功之后回送。
在这里插入图片描述
Connector 就是标记一个唯一的连接。 拿到socketChannel, 然后对channel进行读写操作;

public class Connector implements Closeable, SocketChannelAdapter.OnChannelStatusChangedListener {
    private UUID key = UUID.randomUUID(); //唯一标志一个连接
    private SocketChannel channel; //连接中得到的
    private Sender sender;// 连接可以做的事
    private Receiver receiver;

setup 启动函数:

/*
       启动的流程:
       拿到: SocketChannelAdapter,启动消息的读取
     */
    public void setup(SocketChannel socketChannel) throws IOException {
        this.channel = socketChannel;

        IoContext context = IoContext.get();
        //对channel操作的调试器??? sender和receiver 集合成一个抽象
        //通过channel对应的 selector 判断是用sender 还是 receiver
        SocketChannelAdapter adapter = new SocketChannelAdapter(channel, context.getIoProvider(), this);
        this.sender = adapter;
        this.receiver = adapter;

        readNextMessage();
    }
/**
     * 开始读取数据
     * 注意监听里面实现的是打印和不停的读
     */
    private void readNextMessage() {
        if (receiver != null) {
            try {
                //这里就是回调了, 传入的是 接口的 某个方法
                receiver.receiveAsync(echoReceiveListener);
            } catch (IOException e) {
                System.out.println("开始接收数据异常:" + e.getMessage());
            }
        }
    }
 private IoArgs.IoArgsEventListener echoReceiveListener = new IoArgs.IoArgsEventListener() {
        @Override
        public void onStarted(IoArgs args) {
        }
        @Override
        //写 buffer 完成之后
        public void onCompleted(IoArgs args) {
            // 打印
            onReceiveNewMessage(args.bufferString());
            // 读取下一条数据
            readNextMessage();
        }
    };
protected void onReceiveNewMessage(String str) {
        System.out.println(key.toString() + ":" + str);
    }

IoContext

提供给io环境的类, 单例模式, 封装ioProvider的
这个类的 对象 拥有一个 ioProvider的 成员。
==这里有个问题? 为什么要设置成 单例模式呢? ==
每一个 channel 只对应一个 io selector??? 多个线程 都要操作 这个 selector

ublic class IoContext {
    //单例
    private static IoContext INSTANCE;
    private final IoProvider ioProvider;

    private IoContext(IoProvider ioProvider) {
        this.ioProvider = ioProvider;
    }

    public IoProvider getIoProvider() {
        return ioProvider;
    }

    public static IoContext get() {
        return INSTANCE;
    }
    //内部类的对象
    public static StartedBoot setup() {
        return new StartedBoot();
    }
   //从这里可以看出来究竟在干嘛,
    public static void close() throws IOException {
        if (INSTANCE != null) {  
            INSTANCE.callClose();
        }
    }
// 关闭的是ioProvider, 所以 ioContext 其实就是在 提供一个provider 的封装。
    private void callClose() throws IOException {
        ioProvider.close();
    }

内部类:

public static class StartedBoot {
        private IoProvider ioProvider;

        private StartedBoot() {

        }

        public StartedBoot ioProvider(IoProvider ioProvider) {
            this.ioProvider = ioProvider;
            return this;
        }

        public IoContext start() {
            INSTANCE = new IoContext(ioProvider);
            return INSTANCE;
        }
    }

IoProvider ⭐️

接口:

/**
 * 观察者模式:
 * 所有的连接都可以通过IoProvider 来注册 读写事件
 * 观察SocketChannel的可读/可写状态,通过channel状态的变化来 改变 读写 channel的行为
 * 若可读,则通过HandleInputCallback 和 HandleOutputCallback 进行回调
 */
public interface IoProvider extends Closeable {
    boolean registerInput(SocketChannel channel, HandleInputCallback callback);

    boolean registerOutput(SocketChannel channel, HandleOutputCallback callback);

    void unRegisterInput(SocketChannel channel);

    void unRegisterOutput(SocketChannel channel);
    
    // 这两个回调 是 实现类中 map 的 value
    //实现类中 每个 selectionKey, 都对应一个这个 回调 对象, 在 socketchannel 中完成 发送或者接收之后, 会回调,返回执行的结果。

    abstract class HandleInputCallback implements Runnable {
        @Override
        public final void run() {
            canProviderInput();
        }

        protected abstract void canProviderInput();
    }

    abstract class HandleOutputCallback implements Runnable {
        private Object attach;

        @Override
        public final void run() {
            canProviderOutput(attach);
        }

        public final void setAttach(Object attach) {
            this.attach = attach;
        }

        protected abstract void canProviderOutput(Object attach);
    }

}

实现类:
外面肯定不会出现直接使用这个类, 对外只能看见一个接口 IoProvider 对象, 但接口的实例其实是它的实现类的实例, 也就是 会调用这个 构造, 也就自然而然的开启了 读写的。。。

import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 这个类 提供 向selector注册 读写事件, 并处理 读写事件的 操作
 * 用法的话: 定义一个这个类的对象, 然后有两个selector成员, 里面有注册的方法, 和读写数据的方法
 * 调用顺序 : startRead()-> handleSelection(这里是异步的,read放入了线程池)
 */
public class IoSelectorProvider implements IoProvider {
    private final AtomicBoolean isClosed = new AtomicBoolean(false);

    /**
     * 555555555555555555
     * readSelector的锁是否处于某个过程
     */
    private final AtomicBoolean inRegInput = new AtomicBoolean(false);
    private final AtomicBoolean inRegOutput = new AtomicBoolean(false);

    private final Selector readSelector;
    private final Selector writeSelector;
   
   // 这里的runnable 对应的是 HandleInputCallback 和HandleOutputCallback
    private final HashMap<SelectionKey, Runnable> inputCallbackMap = new HashMap<>();
    private final HashMap<SelectionKey, Runnable> outputCallbackMap = new HashMap<>();

    private final ExecutorService inputHandlePool;
    private final ExecutorService outputHandlePool;

    public IoSelectorProvider() throws IOException {
        readSelector = Selector.open();
        writeSelector = Selector.open();

        inputHandlePool = Executors.newFixedThreadPool(4,
                new IoProviderThreadFactory("IoProvider-Input-Thread-"));
        outputHandlePool = Executors.newFixedThreadPool(4,
                new IoProviderThreadFactory("IoProvider-Output-Thread-"));

        // 开始输出输入的监听
        //就是通过这里实现了读写监听的 启动, 只要初始化 IoProvider 的对象 , 那么它的实现类的构造函数就会自然而然的被调用。
        startRead();
        startWrite();
    }

读写的非阻塞是通过 selector实现的, 也就是这个 其实就是对 读selector的操作

/**
     * 111111111111111111
     * 开始 读
     */
    private void startRead() {
        Thread thread = new Thread("Clink IoSelectorProvider ReadSelector Thread") {
            @Override
            public void run() {
                while (!isClosed.get()) {
                    try {
                        // 这里有对readSelector的读操作
                        if (readSelector.select() == 0) {
                            waitSelection(inRegInput);
                            continue;
                        }
                        /**
                        为什么一定要有取消注册?
                        因为 这里 可以看到对channel的读是 用一个线程池来处理的,可以理解为立即就有结果,也就是数组可以很快遍历,没有阻塞
                        那么如果存在某个 channel 没有完成读操作, 那么下一次循环readSelector.select() == 0 仍然不是0, 也就是没完成的话
                        这个channel的读,会仍然注册在selector 上, 在这次的循环中,仍然会被加入 线程池。。
                         如果有的任务执行缓慢, 那么会在线程池大量堆积。。
                        这当然是要避免的。,所以对于,已经提交到线程池的,要及时取消注册。
                         */

                        Set<SelectionKey> selectionKeys = readSelector.selectedKeys();
                        for (SelectionKey selectionKey : selectionKeys) {
                            if (selectionKey.isValid()) {
                                //处理 当前 事件的 ,需要声明 当前 究竟是读事件,还是写事件,这很明显是 把可能融于的直接抽象成了一个
                                //将 执行结果的 返回 存到map中
                                // 异步 ,用的线程池
                                handleSelection(selectionKey, SelectionKey.OP_READ, inputCallbackMap, inputHandlePool);
                            }
                        }
                        selectionKeys.clear();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }


        };
        thread.setPriority(Thread.MAX_PRIORITY);
        thread.start();
    }
private void startWrite() {
        Thread thread = new Thread("Clink IoSelectorProvider WriteSelector Thread") {
            @Override
            public void run() {
                while (!isClosed.get()) {
                    try {
                        if (writeSelector.select() == 0) {
                            //registerSelection有一个唤醒,所以这里要等待上一次的注册监听完成之后,才能继续进行下一次
                            waitSelection(inRegOutput);
                            continue;
                        }

                        Set<SelectionKey> selectionKeys = writeSelector.selectedKeys();
                        for (SelectionKey selectionKey : selectionKeys) {
                            if (selectionKey.isValid()) {
                                handleSelection(selectionKey, SelectionKey.OP_WRITE, outputCallbackMap, outputHandlePool);
                            }
                        }
                        selectionKeys.clear();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.setPriority(Thread.MAX_PRIORITY);
        thread.start();
    }
 /**
     * 22222222222222222222222222
     * 如何 处理 所有的 注册到 selector上的事件

     */
    private static void handleSelection(SelectionKey key, int keyOps,
                                        HashMap<SelectionKey, Runnable> map,
                                        ExecutorService pool) {
        // 重点,!!!!!!!!!!!!!!!!!!!!!
        // 为什么要先取消注册??? 看1的注释
        // 取消继续对keyOps的监听
        //selector只有注册操作, 看3,没有取消注册,所以这里要自己实现
        //各种readyOps(),其实都是int值, 注册事件其实就是叠加ops的int值,要取消事件, 就把值去掉key.readyOps() & ~keyOps
        key.interestOps(key.readyOps() & ~keyOps);

        Runnable runnable = null;
        //拿到当前 key 对应的runnable,然后加入线程池进行异步调度
        try {
            runnable = map.get(key);
        } catch (Exception ignored) {

        }

        if (runnable != null && !pool.isShutdown()) {
            // 异步调度
            pool.execute(runnable);
        }
    }

因为select是不断循环执行的, 若 select 返回0, 有两种可能:

  • 一是当前确实没有就绪的事件,那么此时就继续从头扫到尾;
  • 还有一种可能就是,select没扫描完,被wakeup了(此时就是注册那里的代码了),这种情况的标志就是locker = true;这里 处理的就是 select扫描了0个就绪的,就被注册那里 wakeup了, 然后就没有了锁。 所以这里select返回0 之后, 判断锁的状态, 如果是 没有锁, 那就wait等待,直到在registerSelection那里 notify, 也就是修改完了, 再继续select。

这部分代码就是处理的这个情况;(也可以理解什么是wakeup了, 就是从不停的扫描的状态中停止, 去注册啥的)

那么还有一种很显然的情况没有处理, 就是 select 不等于0, 但被wakeup了, 此时正确的做法是, select停止等待, 等到notify了, 再把上一次扫描到的两个消费掉, 再继续新一次的扫描。


    private static void waitSelection(final AtomicBoolean locker) {
        //noinspection SynchronizationOnLocalVariableOrMethodParameter
        synchronized (locker) {
            if (locker.get()) {
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

重写接口中的 注册 取消注册等方法

/**
     * 3333333333333333333
     * 在 SocketChannelAdapter 里面调用
     */
    @Override
    public boolean registerInput(SocketChannel channel, HandleInputCallback callback) {
        // 注册了 channel, 则 当前 channel只要可读,就要进行回调。
        /*
        这里必须有取消注册的过程, 原因可以看1 的注释
         */

        return registerSelection(channel, readSelector, SelectionKey.OP_READ, inRegInput,
                inputCallbackMap, callback) != null;
    }

    @Override
    public boolean registerOutput(SocketChannel channel, HandleOutputCallback callback) {
        return registerSelection(channel, writeSelector, SelectionKey.OP_WRITE, inRegOutput,
                outputCallbackMap, callback) != null;
    }

这个一定得记住, 背下来 ⭐️ ⭐️ ⭐️

/**
     * 444444444444444444444
     * 1 有对readSelector 的读操作,
     * 这里也有,这里的是 添加内容, 相当于写, 所以要加锁, 锁住readSelector,或者锁住操作readSelector的代码
     * 用原子布尔值当锁, 见5
     *
     */
    private static SelectionKey registerSelection(SocketChannel channel, Selector selector,
                                                  int registerOps, AtomicBoolean locker,
                                                  HashMap<SelectionKey, Runnable> map,
                                                  Runnable runnable) {

        //noinspection SynchronizationOnLocalVariableOrMethodParameter
        synchronized (locker) {
            // 设置锁定状态
            locker.set(true);

            try {
                // 唤醒当前的selector,让selector不处于select()状态
                //先唤醒,避免注册无效, 可以说是二次select?????不懂
                selector.wakeup();

                SelectionKey key = null;
                if (channel.isRegistered()) {
                    // 查询是否已经注册过
                    key = channel.keyFor(selector);
                    if (key != null) {
                        key.interestOps(key.readyOps() | registerOps);
                    }
                }

                if (key == null) {
                    // 注册selector得到Key
                    key = channel.register(selector, registerOps);
                    // 注册回调
                    map.put(key, runnable);
                }

                return key;
            } catch (ClosedChannelException e) {
                return null;
            } finally {
                // 解除锁定状态
                locker.set(false);
                try {
                    // 通知
                    locker.notify();
                } catch (Exception ignored) {
                }
            }
        }
    }
 

取消注册:

/**
     * 77777777777777
     * @param channel
     */
    @Override
    public void unRegisterInput(SocketChannel channel) {
        unRegisterSelection(channel, readSelector, inputCallbackMap);
    }

    @Override
    public void unRegisterOutput(SocketChannel channel) {
        unRegisterSelection(channel, writeSelector, outputCallbackMap);
    }
 /**
     * 66666666666666666666666
     * @param channel
     * @param selector
     * @param map
     */
    private static void unRegisterSelection(SocketChannel channel, Selector selector,
                                            Map<SelectionKey, Runnable> map) {
        if (channel.isRegistered()) {
            SelectionKey key = channel.keyFor(selector);
            if (key != null) {
                // 取消监听的方法
                key.cancel();
                map.remove(key);
                selector.wakeup();
            }
        }
    }

关闭各种资源:

/**
     * 8888888888888888888
     */
    @Override
    public void close() {
        if (isClosed.compareAndSet(false, true)) {
            inputHandlePool.shutdown();
            outputHandlePool.shutdown();

            inputCallbackMap.clear();
            outputCallbackMap.clear();

            readSelector.wakeup();
            writeSelector.wakeup();

            CloseUtils.close(readSelector, writeSelector);
        }
    }

Ioargs

/**
 * 封装ByteBuffer
 * 实现 channel buffer 和真实要使用的数据的相互转换
 */
public class IoArgs {
    private byte[] byteBuffer = new byte[256];
    private ByteBuffer buffer = ByteBuffer.wrap(byteBuffer);

    public int read(SocketChannel channel) throws IOException {
        buffer.clear();
        return channel.read(buffer);
    }

    public int write(SocketChannel channel) throws IOException {
        return channel.write(buffer);
    }

    public String bufferString() {
        // 丢弃换行符
        return new String(byteBuffer, 0, buffer.position() - 1);
    }
   // 接口,用来监听 IoArgs的状态, 也就是比如 buffer 到 channel, 开始了吗,完成了吗?
    //完成了,应该 回调,也就是这部分会在 调用这块 逻辑 的那部分代码那里使用
   // 完成和开始 的回调
    public interface IoArgsEventListener {
        void onStarted(IoArgs args);

        void onCompleted(IoArgs args);
    }
}

Sender、Receiver、SocketChannelAdapter

/**
 * 异步发送数据, 数据封装到IoArgs args, 发送状态通过IoArgsEventListener来回调
 */

public interface Sender extends Closeable {
    boolean sendAsync(IoArgs args, IoArgs.IoArgsEventListener listener) throws IOException;
}


public interface Receiver extends Closeable {
    boolean receiveAsync(IoArgs.IoArgsEventListener listener) throws IOException;
}

SocketChannelAdapter

import net.qiujuer.library.clink.core.IoArgs;
import net.qiujuer.library.clink.core.IoProvider;
import net.qiujuer.library.clink.core.Receiver;
import net.qiujuer.library.clink.core.Sender;
import net.qiujuer.library.clink.utils.CloseUtils;

import java.io.IOException;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 实现 Sender, Receiver 这两个端口, 实现具体的输入输出调用,也就是调用 ioProvider 的注册读写
 */
public class SocketChannelAdapter implements Sender, Receiver, Cloneable {
    private final AtomicBoolean isClosed = new AtomicBoolean(false);
    private final SocketChannel channel;
    private final IoProvider ioProvider;
    //三个回调; channel关闭回调
    private final OnChannelStatusChangedListener listener;
    //有输入输出的回调用
    private IoArgs.IoArgsEventListener receiveIoEventListener;
    private IoArgs.IoArgsEventListener sendIoEventListener;

    public SocketChannelAdapter(SocketChannel channel, IoProvider ioProvider,
                                OnChannelStatusChangedListener listener) throws IOException {
        this.channel = channel;
        this.ioProvider = ioProvider;
        this.listener = listener;

        channel.configureBlocking(false);
    }

重写接口函数

@Override
    public boolean receiveAsync(IoArgs.IoArgsEventListener listener) throws IOException {
        if (isClosed.get()) {
            throw new IOException("Current channel is closed!");
        }

        receiveIoEventListener = listener;
        //ioProvider.registerInput 是注册读写事件的,真正的buffer操作 在 inputCallback中,它实现了runnable, 并被加入了线程池。 在 startRead()中, 线程池异步执行。
        return ioProvider.registerInput(channel, inputCallback);
    }

    @Override
    public boolean sendAsync(IoArgs args, IoArgs.IoArgsEventListener listener) throws IOException {
        if (isClosed.get()) {
            throw new IOException("Current channel is closed!");
        }

        sendIoEventListener = listener;
        // 当前发送的数据附加到回调中
        outputCallback.setAttach(args);
        return ioProvider.registerOutput(channel, outputCallback);
    }

HandlerInputCallback 实现了 runnable, 实现了对于 channel 到buffer的操作。对于每一个注册到selector的key ,都会有一个这样的线程, 然后用线程池来执行。

private final IoProvider.HandleInputCallback inputCallback = new IoProvider.HandleInputCallback() {
        @Override
        protected void canProviderInput() {
            if (isClosed.get()) {
                return;
            }

            IoArgs args = new IoArgs();
            IoArgs.IoArgsEventListener listener = SocketChannelAdapter.this.receiveIoEventListener;

            if (listener != null) {
                listener.onStarted(args);
            }

            try {
                // 具体的读取操作
                if (args.read(channel) > 0 && listener != null) {
                    // 读取完成回调
                    listener.onCompleted(args);
                } else {
                    throw new IOException("Cannot read any data!");
                }
            } catch (IOException ignored) {
                CloseUtils.close(SocketChannelAdapter.this);
            }


        }
    };
    private final IoProvider.HandleOutputCallback outputCallback = new IoProvider.HandleOutputCallback() {
        @Override
        protected void canProviderOutput(Object attach) {
            if (isClosed.get()) {
                return;
            }
            // TODO
            sendIoEventListener.onCompleted(null);
        }
    };
@Override
    public void close() throws IOException {
        if (isClosed.compareAndSet(false, true)) {
            // 解除注册回调
            ioProvider.unRegisterInput(channel);
            ioProvider.unRegisterOutput(channel);
            // 关闭
            CloseUtils.close(channel);
            // 回调当前Channel已关闭
            listener.onChannelClosed(channel);
        }
    }

Packet 消息分片

Packet

对Packet本身而言,只有type 和 length, 也就是不涉及具体数据, 只有类型长度信息;
更细致的分类是 sendPacket 和receivePacket;
sendPacket 里面有 byte[] 来存放 具体的内容, 和一个标志位标记是否取消
receivePacket 只有save方法, 将包中的内容存下来。
在这里插入图片描述
在这里插入图片描述
以String类型为例,StringSendPacket 实现String转为要发送的包的类型; StringReceivePacket 收到的Packet转String;
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

SendDispatcher

在这里插入图片描述
在这里插入图片描述
之前的IoProvider ,SocketChannelAdapter 是注册和执行的调度, 这个是 包的缓存,接收,发送的调度。

在这里插入图片描述

注意这里本来的流程是 connector 里面的sender 和receiver 对象 调用实现类SocketChannelAdapter 里面的sendAsync 和receiveAsync方法, 然后去注册, 在handlerselection里面提交到线程池, 然后在HandleInputCallback中执行。。 接下来就是要把这个过程 换成packet。。

在这里插入图片描述
在这里插入图片描述

AsyncSendDispatcher

在这里插入图片描述
在这里插入图片描述
到这里都还和ioArgs 无关,后面就是如何转成IOArgs
position表示的是 当前的下标, total是packet总长度
在这里插入图片描述
在这里插入图片描述
IoArgs 是 对buffer操作的, 之前只有buffer 和channel, 现在要加一条,packet 和buffer的交互。
这里有个问题。。 Packet 是按照http2的那种吗? 没有看见划分啊,只看到了转成一整个包

修改IoArgs中 读写函数, 改为重载的 readFrom, writeTo方法, 参数分别为 byte数组,也就是Packet,和 channel, 有两个方面的对buffer的操作。
在这里插入图片描述
从channel读数据 写入到 buffer中,写之后,要flip才能是翻转成了读模式。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
继续AsyncSendDispatcher
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

原来的Connector 拥有 socketChannelAdapter 作为sender 和 receiver, 现在要改造 发送接收的调度
start -> registerReceive -> receiver. receiveAsync(ioArgs).
在这里插入图片描述

回调逻辑

HandleInputCallback & HandleOutputCallback

  1. 在接口 IoProvider 定义的抽象类
abstract class HandleInputCallback implements Runnable {
        @Override
        public final void run() {
            canProviderInput();
        }

        protected abstract void canProviderInput();
    }

    abstract class HandleOutputCallback implements Runnable {
        private Object attach;

        @Override
        public final void run() {
            canProviderOutput(attach);
        }

        public final void setAttach(Object attach) {
            this.attach = attach;
        }

        protected abstract void canProviderOutput(Object attach);
    }

  1. 在 实现类 IoSelectorProvider 的registerInput 方法中, 为 每个 selectionKey 都建立一个 回调对象。
public boolean registerInput(SocketChannel channel, HandleInputCallback callback) {
        // 注册了 channel, 则 当前 channel只要可读,就要进行回调。
        /*
        这里必须有取消注册的过程, 原因可以看1 的注释
         */

        return registerSelection(channel, readSelector, SelectionKey.OP_READ, inRegInput,
                inputCallbackMap, callback) != null;
    }
  1. 在 接口Sender /Receiver 的实现类 Socket ChannelAdapter 中具体实现业务逻辑, 也就是 发送接收中 实现了回调;
 /**
     * 回调到底是什么
     * 
     */
    private final IoProvider.HandleInputCallback inputCallback = new IoProvider.HandleInputCallback() {
        @Override
        protected void canProviderInput() {
            if (isClosed.get()) {
                return;
            }

            IoArgs args = new IoArgs();
            IoArgs.IoArgsEventListener listener = SocketChannelAdapter.this.receiveIoEventListener;

            if (listener != null) {
                listener.onStarted(args);
            }

            try {
                // 具体的读取操作
                if (args.read(channel) > 0 && listener != null) {
                    // 读取完成回调
                    listener.onCompleted(args);
                } else {
                    throw new IOException("Cannot read any data!");
                }
            } catch (IOException ignored) {
                CloseUtils.close(SocketChannelAdapter.this);
            }


        }
    };


    private final IoProvider.HandleOutputCallback outputCallback = new IoProvider.HandleOutputCallback() {
        @Override
        protected void canProviderOutput(Object attach) {
            if (isClosed.get()) {
                return;
            }
            // TODO
            sendIoEventListener.onCompleted(null);
        }
    };

    //channel关闭的时候的回调
    public interface OnChannelStatusChangedListener {
        void onChannelClosed(SocketChannel channel);
    }
}

//channel关闭的时候的回调

定义在 socketChannelChanged类中的接口

public interface OnChannelStatusChangedListener {
    void onChannelClosed(SocketChannel channel);
}

在socketChannelChanged中使用

IoArgs 操作buffer的回调

定义在IoArgs类

// 接口,用来监听 IoArgs的状态, 也就是比如 buffer 到 channel, 开始了吗,完成了吗?
    //完成了,应该 回调,也就是这部分会在 调用这块 逻辑 的那部分代码那里使用
   // 完成和开始 的回调
    public interface IoArgsEventListener {
        void onStarted(IoArgs args);

        void onCompleted(IoArgs args);
    }

实现在Connector

private IoArgs.IoArgsEventListener echoReceiveListener = new IoArgs.IoArgsEventListener() {
        @Override
        public void onStarted(IoArgs args) {

        }

        @Override
        public void onCompleted(IoArgs args) {
            // 打印
            onReceiveNewMessage(args.bufferString());
            // 读取下一条数据
            readNextMessage();
        }
    };

使用在 SocketChannelSelector的 IoProvider.HandleInputCallback中

private final IoProvider.HandleInputCallback inputCallback = new IoProvider.HandleInputCallback() {
        @Override
        protected void canProviderInput() {
            if (isClosed.get()) {
                return;
            }

            IoArgs args = new IoArgs();
            IoArgs.IoArgsEventListener listener = SocketChannelAdapter.this.receiveIoEventListener;
            //

            if (listener != null) {
                listener.onStarted(args);
            }

            try {
                // 具体的读取操作
                if (args.read(channel) > 0 && listener != null) {
                    // 读取完成回调
                    listener.onCompleted(args);
                } else {
                    throw new IOException("Cannot read any data!");
                }
            } catch (IOException ignored) {
                CloseUtils.close(SocketChannelAdapter.this);
            }


        }
    };

各部分多线程的具体使用

性能分析

Bug 以及困难收获

IoSelectorProvider selector、channel、SelectionKey 之间的并发锁死等待bug

debug模式下, 运行clientTest, 发现 服务端的线程 处于 monitor状态。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

因为select是不断循环执行的, 若 select 返回0, 有两种可能:

  • 一是当前确实没有就绪的事件,那么此时就继续从头扫到尾;
  • 还有一种可能就是,select没扫描完,被wakeup了(此时就是注册那里的代码了),这种情况的标志就是locker = true;这里 处理的就是 select扫描了0个就绪的,就被注册那里 wakeup了, 然后就没有了锁。 所以这里select返回0 之后, 判断锁的状态, 如果是 没有锁, 那就wait等待,直到在registerSelection那里 notify, 也就是修改完了, 再继续select。

这部分代码就是处理的这个情况;(也可以理解什么是wakeup了, 就是从不停的扫描的状态中停止, 去注册啥的)

那么还有一种很显然的情况没有处理, 就是 select 不等于0, 但被wakeup了, 此时正确的做法是, select停止等待, 等到notify了, 再把上一次扫描到的两个消费掉, 再继续新一次的扫描。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
改进:也就是抽取代码解耦
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

关闭的异常捕获

close() 自带wakeup
在这里插入图片描述

空指针Bug

扩展:了解Netty

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值